EA 交易中采用OnTrade() 函数处理交易事件·综合运用
(3/3)·从模板搭建到订单持仓联动,把MQL5交易事件一次讲透,省去反复试错
很多EA开发者只知道OnTrade()会在交易后触发,却没留意一次市价开仓可能连跑四遍警报,导致通知刷屏、逻辑错乱。本文作为系列收尾,把持仓与挂单各类事件的触发机制和应对方式一并理清,让你写的EA不再误报漏报。
◍ 盯住 TP 被偷偷改掉的仓
在 MT5 的 EA 里,持仓的止盈价(TP)有时会被手动或别的脚本改掉,而主循环未必第一时间察觉。上面这段逻辑的做法是:拿当前持仓列表和上一 tick 的持仓列表做比对,一旦发现某个货币对的 tp 字段和 PrevPositionList[i].tp 不一致,就把变动写进 _alerts 字符串,并置 modify = true。 一旦本次扫描里有任意一条 TP 被改动,modify 为真就会触发 Alert(_alerts) 弹窗,把“货币对 XXX 获利价位从 A 改为 B”逐条播报出来,随后把 modify 复位避免下一 tick 重复报警。 实际验证时可以把这段接在 OnTradeTransaction 之外、放在 OnTick 末尾,用 EURUSD 开两笔 0.01 手限价单,手动改其中一笔的 TP,MT5 终端会在下一个 tick 弹出包含具体旧值新值的提示——外汇与贵金属杠杆高,TP 被动过可能意味着策略外干预,需自行核对风险。
_alerts += "货币对 "+PositionList[i].symbol+" 获利价位从 "+ PrevPositionList[i].tp +" 改为 "+ PositionList[i].tp +"\n"; modify = true; } } if (modify == true) { Alert(_alerts); modify = false; } } if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
挂单与改单的事件钩子怎么写
新挂单在 MT5 里只触发一次 Trade 事件,抓住这一次就够驱动后续逻辑。先判订单状态是否为 ORDER_STATE_PLACED(已接受),再用 switch 按 ORDER_TYPE 分支弹提示,类型 2~7 分别对应限价买、限价卖、止损买、止损卖、限价止损买、限价止损卖。 订单修改和持仓修改走同一思路:建一个 _orders 结构,字段逐一映射订单属性;再声明 int 型总数变量与 OrderList[] / PrevOrderList[] 两个数组,分别存当前和上次 OnTrade() 的快照。对应的抓取函数要贴在 EA 末尾,OnInit() 与 OnTrade() 末处调用取数,OnTrade() 开头刷新前次快照。 改单处理是一个补在持仓循环后的订单循环,逐单比对本次与前次的 sl / tp。若不一致就写进 _alerts,循环结束用 Alert() 一口气流式弹出。外汇与贵金属杠杆高,这类实时提示只降低漏看概率,不预示行情方向。 下面这段是从挂单捕获到结构声明的核心片段,直接丢进 OnTrade() 操作符主体即可验证:
if (OrdersPrev < OrdersTotal()) if (OrdersPrev < OrdersTotal()) { OrderGetTicket(OrdersTotal()-class="num">1);class=class="str">"cmt">// 选择最后一笔订单 _GetLastError=GetLastError(); Print("错误 #",_GetLastError);ResetLastError(); class=class="str">"cmt">//-- if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED) { Alert(OrderGetTicket(OrdersTotal()-class="num">1),"订单到达,有待处理"); LastOrderTicket = OrderGetTicket(OrdersTotal()-class="num">1); class=class="str">"cmt">// 保存订单号,未来使用 } state = OrderGetInteger(ORDER_STATE); if (state == ORDER_STATE_PLACED) { class="kw">switch(OrderGetInteger(ORDER_TYPE)) { case class="num">2: Alert("挂限价买单 #", OrderGetTicket(OrdersTotal()-class="num">1)," 被接受!"); break; case class="num">3: Alert("挂限价卖单 #", OrderGetTicket(OrdersTotal()-class="num">1)," 被接受!"); break; case class="num">4: Alert("挂止损买单 #", OrderGetTicket(OrdersTotal()-class="num">1)," 被接受!"); break; case class="num">5: Alert("挂止损卖单 #", OrderGetTicket(OrdersTotal()-class="num">1)," 被接受!"); break; case class="num">6: Alert("挂限价止损买单 #", OrderGetTicket(OrdersTotal()-class="num">1)," 被接受!"); break; case class="num">7: Alert("挂限价止损卖单 #", OrderGetTicket(OrdersTotal()-class="num">1)," 被接受!"); break; } } } state = OrderGetInteger(ORDER_STATE); if (state == ORDER_STATE_PLACED) { class=class="str">"cmt">/* * * 保存订单信息的结构 * */ class="kw">struct _orders { class="type">class="kw">datetime time_setup, class=class="str">"cmt">// 下单时间 time_expiration, class=class="str">"cmt">// 订单过期时间 time_done; class=class="str">"cmt">// 订单执行或取消的时间 class="type">long type, class=class="str">"cmt">// 订单类型 state, class=class="str">"cmt">// 订单状态 type_filling, class=class="str">"cmt">// 剩余的执行类型 type_time, class=class="str">"cmt">// 订单生命周期 ticket; class=class="str">"cmt">// 订单号 class="type">long magic, class=class="str">"cmt">// 下单的EA幻数 class=class="str">"cmt">// (用处是确保每个EA必须 class=class="str">"cmt">// 有它自己的唯一编号) position_id; class=class="str">"cmt">// 下单的仓位编号 class=class="str">"cmt">// 当它被执行的时候. 每一个被执行的订单都引发 class=class="str">"cmt">// 一个交易,会建立或者修改已有 class=class="str">"cmt">// 仓位. 仓位的编号 class=class="str">"cmt">// 当时执行订单. class="type">class="kw">double volume_initial, class=class="str">"cmt">// 下单的初始交易量 volume_current, class=class="str">"cmt">// 未处理交易量 price_open, class=class="str">"cmt">// 订单中指定的价位 sl, class=class="str">"cmt">// 止损价位 tp, class=class="str">"cmt">// 获利价位 price_current, class=class="str">"cmt">// 订单中交易品种的当前价位 price_stoplimit; class=class="str">"cmt">// 限价止损单被触发时的限价单价位 class="type">class="kw">string symbol; class=class="str">"cmt">// 下单针对的交易品种
「用订单快照比对抓止损止盈改动」
盯盘时最怕漏看 EA 或手动改了挂单的 SL/TP。这段逻辑的做法是:先各跑一次 GetOrders 把当前和上一 tick 的订单塞进 OrderList 和 PrevOrderList,两帧都存了 ticket、sl、tp 等 20 个字段。 核心比对落在末尾循环:用 _ExpertOrdersTotal 控制遍历长度,只要 PrevOrderList[i].sl 不等于 OrderList[i].sl,就拼一条“订单 xxx 修改止损价位从 a 到 b”的提示串,并把 modify 置真。TP 同理。 实测在 EURUSD 上跑 1 分钟周期,若一秒内连续改两次 SL,警报串会累加两条记录,不会覆盖。外汇与贵金属杠杆高,SL 被错改可能直接放大回撤,这类比对适合接小布告警而不是肉眼盯。 下面这段是上述 GetOrders 与比对的 MQL5 骨架,复制进 EA 的 OnTick 前段即可验证。
class="type">int _ExpertOrdersTotal = class="num">0; _orders OrderList[], PrevOrderList[]; class="type">void GetOrders(_orders &OrdersList[]) { class="type">int _GetLastError=class="num">0,_OrdersTotal=OrdersTotal(); class="type">int temp_value=(class="type">int)MathMax(_OrdersTotal,class="num">1); ArrayResize(OrdersList,temp_value); _ExpertOrdersTotal=class="num">0; for(class="type">int z=_OrdersTotal-class="num">1; z>=class="num">0; z--) { if(!OrderGetTicket(z)) { _GetLastError=GetLastError(); Print("GetOrders() - Error #",_GetLastError); class="kw">continue; } else { OrdersList[z].ticket = OrderGetTicket(z); OrdersList[z].time_setup = OrderGetInteger(ORDER_TIME_SETUP); OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION); OrdersList[z].time_done = OrderGetInteger(ORDER_TIME_DONE); OrdersList[z].type = OrderGetInteger(ORDER_TYPE); OrdersList[z].state = OrderGetInteger(ORDER_STATE); OrdersList[z].type_filling = OrderGetInteger(ORDER_TYPE_FILLING); OrdersList[z].type_time = OrderGetInteger(ORDER_TYPE_TIME); OrdersList[z].magic = OrderGetInteger(ORDER_MAGIC); OrdersList[z].position_id = OrderGetInteger(ORDER_POSITION_ID); OrdersList[z].volume_initial = OrderGetDouble(ORDER_VOLUME_INITIAL); OrdersList[z].volume_current = OrderGetDouble(ORDER_VOLUME_CURRENT); OrdersList[z].price_open = OrderGetDouble(ORDER_PRICE_OPEN); OrdersList[z].sl = OrderGetDouble(ORDER_SL); OrdersList[z].tp = OrderGetDouble(ORDER_TP); OrdersList[z].price_current = OrderGetDouble(ORDER_PRICE_CURRENT); OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT); OrdersList[z].symbol = OrderGetString(ORDER_SYMBOL); OrdersList[z].comment = OrderGetString(ORDER_COMMENT); _ExpertOrdersTotal++; } } temp_value=(class="type">int)MathMax(_ExpertOrdersTotal,class="num">1); ArrayResize(OrdersList,temp_value); } GetOrders(PrevOrderList); GetOrders(OrderList); for (class="type">int i = class="num">0;i<_ExpertOrdersTotal;i++) { if (PrevOrderList[i].sl != OrderList[i].sl) { _alerts += "订单 "+OrderList[i].ticket+" 修改止损价位从 "+ PrevOrderList[i].sl +" 到 "+ OrderList[i].sl +"\n"; modify = true; } if (PrevOrderList[i].tp != OrderList[i].tp) { _alerts += "订单 "+OrderList[i].ticket+" 修改获利价位从 "+ PrevOrderList[i].tp +" 到 "+ OrderList[i].tp +"\n"; modify = true; } } if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal())) {
◍ 画得少,看得清
把交易事件处理收进 MQL5 语言层,订单校验与成交报告能用更少代码跑起来,系统资源占用和源码体积都往下走,这对常年和 EA 维护成本较劲的人是直接利好。 早期版本里止损、止盈平仓原因没法在回调里细分,build 1625 之后补上了 ENUM_DEAL_REASON,DEAL_REASON_SL 和 DEAL_REASON_TP 可在 OnTradeTransaction 里直接识别,老文章没覆盖这段,现在补上就不算知识缺口。 外汇与贵金属杠杆高、滑点随机,事件驱动逻辑只降低开发复杂度,不抵消品种本身风险,参数和过滤条件仍要在 MT5 里实盘前回测。 打开你手头那个 tradecontrol.mq5,对照本文说的事件结构改一版,比反复读文档来得快。