让新闻交易轻松上手(第五部分):执行交易(2)·进阶篇
用代码卡住交易时段的开盘瞬间
做价格行为交易,很多人亏在「时段混淆」——把悉尼盘流动性当伦敦盘做,止损被毛刺洗掉。MT5 里可以用 CSessions 类直接问「现在是不是刚开盘」,而不是自己手算时区。 下面这段逻辑先抓当前品种、当前星期几的可交易时段,最多扫 10 个 session 槽位。若某个 session 的结束小时是 0 或 24(跨午夜),就把 dateto 钉到当天 23:59,避免边界漏判。 两个数组 DateFrom / DateTo 动态扩容,把每段起止时间存下来。只对最早一段(下标 0)叠加 offsethour / offsetmin 偏移,相当于「开盘后延迟 N 小时 M 分钟才判定为开始」,实盘常用于跳过开盘跳空最疯的前几分钟。 最后用 TimeIsInRange 拿服务器时间比对,命中就返回 true;若当天根本没交易时段(如周末部分品种),函数也返回 true,调用方需自己兜底。外汇和贵金属杠杆高,时段判断错配可能放大滑点风险,参数 offset 建议先在策略测试器跑一周 tick 数据再上真仓。
class="type">bool CSessions::isSessionStart(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">0) { class=class="str">"cmt">//--- Declarations class="type">class="kw">datetime datefrom,dateto,DateFrom[],DateTo[]; class=class="str">"cmt">//--- Find all session times for(class="type">int i=class="num">0; i<class="num">10; i++) { class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto)) { class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24) { class=class="str">"cmt">//--- Adjust the date to one minute before midnight dateto = Time(TimeTradeServer(),class="num">23,class="num">59); } class=class="str">"cmt">//--- Re-adjust DateFrom Array size ArrayResize(DateFrom,class="type">int(ArraySize(DateFrom))+class="num">1,class="type">int(ArraySize(DateFrom))+class="num">2); class=class="str">"cmt">//--- Assign the last array index datefrom value DateFrom[class="type">int(ArraySize(DateFrom))-class="num">1] = datefrom; class=class="str">"cmt">//--- Re-adjust DateTo Array size ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2); class=class="str">"cmt">//--- Assign the last array index dateto value DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto; } } class=class="str">"cmt">//--- Check if there are session times if(DateFrom.Size()>class="num">0) { class=class="str">"cmt">/* Adjust DateFrom index zero date as the first index date will be the earliest date from the whole array, we add the offset to this date only*/ DateFrom[class="num">0] = TimePlusOffset(DateFrom[class="num">0],MinutesS(offsetmin)); DateFrom[class="num">0] = TimePlusOffset(DateFrom[class="num">0],HoursS(offsethour)); class=class="str">"cmt">//--- Iterate through the whole array for(class="type">uint i=class="num">0; i<DateFrom.Size(); i++) { class=class="str">"cmt">//--- Check if the current time is within the trading session if(TimeIsInRange(Time(Today(ReturnHour(DateFrom[i]),ReturnMinute(DateFrom[i]))) ,Time(Today(ReturnHour(DateTo[i]),ReturnMinute(DateTo[i]))))) { class="kw">return true; } } } else { class=class="str">"cmt">//--- If there are no trading session times class="kw">return true; }
「用收盘前偏移量掐准交易时段终点」
CSessions::isSessionEnd 这个函数用来判断当前品种的交易时段是否已经收尾。它默认带两个偏移参数:offsethour=0、offsetmin=45,意思是比实际收盘时间提前 45 分钟就判定为「时段结束」,方便在流动性干涸前撤单或锁仓。 函数先循环 10 次调用 SymbolInfoSessionTrade,把当天所有交易时段结束时间塞进 DateTo 数组。若某个时段结束时刻是 00 或 24 点(跨日平仓),代码会把它硬改成当天 23:59,避免跨日比较出错。 拿到最晚的 lastdate 后,用 Today() 配 ReturnHour / ReturnMinute 拼出今日收盘 datetime,再依次减去 offsetmin 分钟和 offsethour 小时。最后只要服务器时间 TimeTradeServer() 还小于这个 sessionend,就返回 false(没结束);否则返回 true。 外汇与贵金属杠杆高、滑点可能在收盘前后放大,用这套逻辑自动停用 EA 时,建议先在策略测试器用历史数据跑一周验证偏移量是否贴合你 broker 的真实休市节奏。
class="type">bool CSessions::isSessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45) { class=class="str">"cmt">//--- Declarations class="type">class="kw">datetime datefrom,dateto,DateTo[],lastdate=class="num">0,sessionend; class=class="str">"cmt">//--- Find all session times for(class="type">int i=class="num">0; i<class="num">10; i++) { class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto)) { class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24) { class=class="str">"cmt">//--- Adjust the date to one minute before midnight dateto = Time(TimeTradeServer(),class="num">23,class="num">59); } class=class="str">"cmt">//--- Re-adjust DateTo Array size ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2); class=class="str">"cmt">//--- Assign the last array index dateto value DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto; } } class=class="str">"cmt">//--- Check if there are session times if(DateTo.Size()>class="num">0) { class=class="str">"cmt">//--- Assign lastdate a class="kw">default value lastdate = DateTo[class="num">0]; class=class="str">"cmt">//--- Iterate through the whole array for(class="type">uint i=class="num">0; i<DateTo.Size(); i++) { class=class="str">"cmt">//--- Check for the latest date in the array if(DateTo[i]>lastdate) { lastdate = DateTo[i]; } } } else { class=class="str">"cmt">//--- If there are no trading session times class="kw">return false; } class=class="str">"cmt">/* get the current time and modify the hour and minute time to the lastdate variable and assign the new class="type">class="kw">datetime to sessionend variable*/ sessionend = Time(Today(ReturnHour(lastdate),ReturnMinute(lastdate))); class=class="str">"cmt">//--- Re-adjust the sessionend dates with the minute and hour offsets sessionend = TimeMinusOffset(sessionend,MinutesS(offsetmin)); sessionend = TimeMinusOffset(sessionend,HoursS(offsethour)); class=class="str">"cmt">//--- Check if sessionend date is more than the current time if(TimeTradeServer()<sessionend) { class="kw">return false; } class="kw">return true; }
◍ 抓取交易时段收尾时间的写法
在 MT5 里做跨时段策略,经常要拿到「当天最后一个可交易时段」的结束点。上面这段类方法用 SymbolInfoSessionTrade 按星期几循环取前 10 个交易时段,把每个 dateto 塞进动态数组再挑最大值,思路直接但够用。 注意一个坑:若交易所返回的结束小时是 00 或 24(即跨到午夜),代码会把 dateto 强行改成当天 23:59。这意味着你拿到的「收盘」其实是服务器时间的夜里 11 点 59 分,而不是真正的 00:00 交割点,回测时价差和跳空可能算偏。 方法默认带 offsethour=0、offsetmin=45 两个偏移量,最后用 TimeMinusOffset 把结束时间往前推 45 分钟。实战中若你想在伦敦段收盘前 45 分钟撤单,直接传默认参数即可;要盯纽约 17:00 收盘就改 offsethour。外汇与贵金属杠杆高,时段切换前后流动性骤变,参数别拍脑袋调。 下面逐行拆关键逻辑: // 循环 0~9 取当日各交易时段 if(SymbolInfoSessionTrade(...)) 成功才写数组 // 遇午夜结束则改 23:59 dateto = Time(TimeTradeServer(),23,59); // 数组扩容并追加 ArrayResize(DateTo, Size+1, Size+2); // 挑最晚结束时间 if(DateTo[i]>lastdate) lastdate=DateTo[i]; // 无时段直接返回 0 if(DateTo.Size()==0) return 0; // 按偏移回退时间 sessionend = TimeMinusOffset(sessionend, MinutesS(45));
class="type">class="kw">datetime CSessions::SessionEnd(class="type">int offsethour=class="num">0,class="type">int offsetmin=class="num">45) { class=class="str">"cmt">//--- Declarations class="type">class="kw">datetime datefrom,dateto,DateTo[],lastdate=class="num">0,sessionend; class=class="str">"cmt">//--- Find all session times for(class="type">int i=class="num">0; i<class="num">10; i++) { class=class="str">"cmt">//--- Get the session dates for the current symbol and Day of week if(SymbolInfoSessionTrade(Symbol(),DayOfWeek(TimeTradeServer()),i,datefrom,dateto)) { class=class="str">"cmt">//--- Check if the end date&class="macro">#x27;s hour is at midnight if(ReturnHour(dateto)==class="num">00||ReturnHour(dateto)==class="num">24) { class=class="str">"cmt">//--- Adjust the date to one minute before midnight dateto = Time(TimeTradeServer(),class="num">23,class="num">59); } class=class="str">"cmt">//--- Re-adjust DateTo Array size ArrayResize(DateTo,class="type">int(ArraySize(DateTo))+class="num">1,class="type">int(ArraySize(DateTo))+class="num">2); class=class="str">"cmt">//--- Assign the last array index dateto value DateTo[class="type">int(ArraySize(DateTo))-class="num">1] = dateto; } } class=class="str">"cmt">//--- Check if there are session times if(DateTo.Size()>class="num">0) { class=class="str">"cmt">//--- Assign lastdate a class="kw">default value lastdate = DateTo[class="num">0]; class=class="str">"cmt">//--- Iterate through the whole array for(class="type">uint i=class="num">0; i<DateTo.Size(); i++) { class=class="str">"cmt">//--- Check for the latest date in the array if(DateTo[i]>lastdate) { lastdate = DateTo[i]; } } } else { class=class="str">"cmt">//--- If there are no trading session times class="kw">return class="num">0; } class=class="str">"cmt">/* get the current time and modify the hour and minute time to the lastdate variable and assign the new class="type">class="kw">datetime to sessionend variable*/ sessionend = Time(Today(ReturnHour(lastdate),ReturnMinute(lastdate))); class=class="str">"cmt">//--- Re-adjust the sessionend dates with the minute and hour offsets sessionend = TimeMinusOffset(sessionend,MinutesS(offsetmin)); sessionend = TimeMinusOffset(sessionend,HoursS(offsethour)); class=class="str">"cmt">//--- class="kw">return sessionend date class="kw">return sessionend; }
跨式挂单的冗余清理与滑点修正
CTradeManagement 继承自 CRiskManagement,核心管两件事:挂单执行后的反向单清理,以及因滑点导致的 SL/TP 偏移回正。内部用 CTrade 做执行、CSymbolProperties 读品种属性、CTimeManagement 和 CSessions 管时间窗口,mySL/myTP/myDeviation/myOpenPrice 存挂单参数,OrderSettings 内部类封装开仓价止盈止损的 Set/Get。 以 EURUSD 跨式为例:在 1.13118 挂买损、1.12911 挂卖损,买损先触发成持仓 [#2 买入 0.01 于 1.13134],FundamentalMode 随即取消剩余卖损 [#3 卖损 0.01 于 1.12911]。逻辑是遍历 PositionsTotal 与 OrdersTotal,用 COMMENT_COMMON、magic、symbol 三方匹配,持仓为买则删卖损、持仓为卖则删买损,避免双向都成交。 NFP 行情里滑点更狠:买损预期 1.13118,实际 1.13134,差 16 点((1.13134-1.13118)/0.00001)。SlippageReduction 按此偏移重写 SL/TP,原设 SL=1.13018、TP=1.13718(1:6 风险回报),调整后整体平移 16 点保持 ROI。函数逐仓比对 PositionGetDouble(POSITION_SL/TP) 与预期,不等就 Trade.PositionModify,改前过一遍 Valid_Trade。 Valid_Trade 对买入仓要求 Price>SL 且 Price<TP(或 0),且距止损水平不小于品种 StopLevel;Valid_Order 对 BUY_STOP 额外要求 Price>Ask、偏差≥StopLevel。SetBuyStop 收 SL/TP 点数,算价后 NormalizePrice 对齐小数位再派给订单对象。外汇与贵金属杠杆高、事件波动大,挂单清理与滑点修正只降低冗余与偏移概率,不消除跳空风险,参数请在 MT5 策略测试器用真实点差回测。
「挂单与市价单的止损止盈落地细节」
在 MT5 里用 EA 下买入止损,开仓价必须高于当前 Ask 一定缓冲:用 CSymbol.Ask() 取卖出价,再叠 myDeviation * CSymbol.Point() 的点数偏移,最后 NormalizeDouble(myOpenPrice, CSymbol.Digits()) 把小数位对齐到品种精度,否则订单会被终端拒掉。 止损和止盈的标准化逻辑对称但方向相反:买入止损的 SL 在开仓价之下,用 myOpenPrice - SL*Point();TP 在开仓价之上,用 myOpenPrice + TP*Point()。两者都要过一遍 CSymbol.NormalizePrice(),避免浮点误差导致价格落在最小止损距离之外。 市价买入函数 SetBuyTrade 不处理挂单,它直接吃外部传进来的 OP 开仓价,并用 DoubleToString 转字符串再转 double 来强制精度一致——这一招在黄金 XAUUSD 上尤其管用,其 Digits() 为 2 但点值计算常出尾差。 OpenBuyStop 的下单流程里有个容易忽略的防重机制:先用 OpenOrder 和 OpenedDeal 查同 magic+注释的挂单或成交,存在就跳过;再 Valid_Order 校验品种规则(最小止损水平、点值),最后从 Volume() 往 LotsMin() 递减手数循环,确保不超过 Account.LimitOrders()。 OpenStops 同时挂买卖止损时,手数超限会拆单:i 从 Volume() 起,每次 i -= CSymbol.LotsMax(),直到总仓位数加挂单数小于账户限制。订单过期统一设为 CTS.SessionEnd(),即当天交易时段结束。 CloseTrades 只平注释里含 COMMENT_COMMON 且品种匹配的仓:PositionGetTicket 取 ticket,StringFind 查注释子串,命中才 Trade.PositionClose。外汇和贵金属杠杆高,这类批量平仓若在高波动时段触发,滑点可能扩大已有亏损。
◍ 挂单成交后的持仓跟踪逻辑
新闻策略的 EA 在打出自营挂单后,流程并不会终止于开仓。图表里的 ASCII 框图给出了清晰链路:先 Set Stop Loss & Take Profit,再进入 Monitor Open Position,当 If conditions met 时跳到 Adjust SL/TP or Close Trade。这条链路决定了滑点行情下仓位是否被活着带出。 下面这段 MQL5 类骨架把上述流程落到了对象层。CTradeManagement 继承 CRiskManagement,私有成员里 Trade 负责下单、CSymbol 取品种属性、CTime 管时间窗、CTS 管会话,mySL/myTP/myOpenPrice 分别缓存止损、止盈与挂单触发价。 三个布尔方法 OpenTrade / OpenedDeal / OpenOrder 分别用 ENUM_POSITION_TYPE、ENUM_DEAL_TYPE、ENUM_ORDER_TYPE 配合 Magic 与 COMMENT 去检索当前是否有对应持仓、成交或挂单。实战中你可以在 OnTick 里轮询 OpenTrade,一旦发现条件反转就调用 Trade.PositionModify 去移动止损——外汇与贵金属在新闻秒内的点差扩张可能让修改失败,需把 myDeviation 留足余量。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| NewsTrading | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade\Trade.mqh> class="macro">#include <Trade\OrderInfo.mqh> class="macro">#include <Trade\SymbolInfo.mqh> class="macro">#include "RiskManagement.mqh" class="macro">#include "TimeManagement.mqh" class="macro">#include "Sessions.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|TradeManagement class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CTradeManagement:CRiskManagement { class="kw">private: CTrade Trade;class=class="str">"cmt">//Trade class object CSymbolProperties CSymbol;class=class="str">"cmt">//SymbolProperties class object CTimeManagement CTime;class=class="str">"cmt">//TimeManagement class object CSessions CTS;class=class="str">"cmt">//Sessions class object class="type">bool TradeResult;class=class="str">"cmt">//boolean to store trade result class="type">class="kw">double mySL;class=class="str">"cmt">//class="type">class="kw">double variable to store Stoploss class="type">class="kw">double myTP;class=class="str">"cmt">//class="type">class="kw">double variable to store Takeprofit class="type">uint myDeviation;class=class="str">"cmt">//store price deviation for stop orders class="type">class="kw">double myOpenPrice;class=class="str">"cmt">//store open price for stop orders class=class="str">"cmt">//--- Will retrieve if there are any open trades class="type">bool OpenTrade(class="type">ENUM_POSITION_TYPE Type,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will retrieve if there are any deals class="type">bool OpenedDeal(ENUM_DEAL_TYPE Type,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//--- Will retrieve if there are any open orders class="type">bool OpenOrder(ENUM_ORDER_TYPE Type,class="type">ulong Magic,class="type">class="kw">string COMMENT=NULL); class=class="str">"cmt">//-- Check if trade is valid