轻松快捷开发 MetaTrader 程序的函数库(第七部分): StopLimit 订单激活事件,为修改订单和持仓事件准备功能(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库(第七部分): StopLimit 订单激活事件,为修改订单和持仓事件准备功能(基础篇)

第 1/3 篇

◍ 给 StopLimit 挂单接上激活事件

在 MT5 里 StopLimit 挂单比较特殊:价格先触到 Limit 偏移位,单子才转成待成交的 Stop 单,这个过程原生事件并不好直接捕获。Artyom Trishkin 在 2019-07-15 发布的库第七部分,就是给这类订单的激活补一套事件钩子,方便后续统一处理改单和持仓变动。 原文给出的示例在发布当天拿到 2586 次查看、11 条互动,说明这类底层事件封装在实际跟单/风控脚本里需求很实。把它接进自己的 EA 前,建议先在策略测试器用 EURUSD 的 1 分钟历史跑一遍,确认激活回调触发时机和官方文档描述一致。 外汇与贵金属杠杆高、滑点不可控,这类事件库只解决‘何时知道单子变了’,不解决‘变了就该赚’。实盘接之前务必用模拟账户验证回调频率,避免高频改单把点差成本堆起来。

补齐 StopLimit 激活的跟踪缺口

前面几轮跨平台函数库的开发,已经把对冲/净持账户下的订单与持仓数据访问封装好了,下单、撤单、挂单触发、开平仓这些事件都能被程序捕获。 但有一个盲区一直没补:已挂的 StopLimit 单子被激活、以及场内订单和持仓被修改这两类动作,函数库还不会主动抛出消息。 本篇要落地的,是 StopLimit 挂单激活事件的跟踪——它触发后会转成限价挂单。库层捕获到这次转换,就把消息推给上层程序,后续策略就能拿来做条件判断。外汇与贵金属杠杆高,这类事件跟踪只解决数据可见性,不预示任何方向。

「用双列表比对抓住 StopLimit 激活瞬间」

账户历史里查不到 StopLimit 挂单被激活的记录,想靠历史流水直接抓事件行不通。只能自己维护一份“在场订单+持仓”的快照,持续比对属性,直到同一 ticket 的订单类型被改掉——这正是最可靠的激活信号。 具体做法是建两张表:一张存当前状态,一张存上一次刷新的“过去状态”。每次 tick 把两者逐属性比较,只要监控项出现差异,就生成一个“已变化对象”塞进变化列表。若一次报价里多个对象同时变,先攒齐列表再统一处理,否则只拿到封包最后一个变更,漏掉同帧其他修改。 光靠之前第三部分算的哈希不够用。订单属性哈希要补上触发价、止损、止盈三个价格:去掉小数点,六位报价扩成七位 ulong,例如 1.12345 变成 1123450,三个价相加进哈希。这样价格微调也能被捕获。 在 Defines.mqh 里加 ENUM_CHANGE_TYPE_FLAGS,其中 CHANGE_TYPE_ORDER_TYPE 专指 StopLimit 激活导致的订单类型变化;另给 ENUM_TRADE_EVENT 补八个新事件、ENUM_EVENT_REASON 补 StopLimit 激活常量。OrderControl.mqh 中新建受控订单类,用 SymbolInfoTick() 取毫秒级激活时间(TimeCurrent() 不含毫秒)。CMarketCollection 的 Refresh() 改为先加在场对象、再查受控列表:无则 AddToListControl() 建快照,有则 OnChangeEvent() 比差异。CEventsCollection 收到非空变化列表后循环处理,用 CArrayObj 的 Detach() 按索引取对象并删指针,防重复触发。 目前只实装 CHANGE_TYPE_ORDER_TYPE 的跟踪,其余标志留作扩展。开 MT5 把这段逻辑挂到 EA 的 OnTradeTransaction 之前做预筛,能验证同帧多单变更是否不再丢事件。外汇与贵金属挂单触发受点差和熔断影响,实际激活时机可能偏离预期,属高风险操作。

◍ 把 StopLimit 激活伪装成普通挂单事件

在 CEventsCollection 类里,StopLimit 挂单触发后会产生一个新事件。用 Detach() 拿到对象指针处理完就直接删,不用留引用,类改进到此收口。 要让改动真正跑起来,得从在场订单和持仓集合类取“已变化订单列表”,把它的 size 写进 CEngine 的 TradeEventsControl()。Refresh() 被调用时额外比一下这个 size,再把修改过的列表塞进事件集合的 Refresh() 处理。 StopLimit 激活后实际放的是限价挂单,所以我们把这件事“修饰”成放置挂单事件,原因常量用 EVENT_REASON_STOPLIMIT_TRIGGERED,它早定义在 Defines.mqh 的 ENUM_EVENT_REASON 里。EventOrderPlased 类只需加一个该原因的分支,就能在日志打印并推给控制程序。 下面这段枚举是订单变化标记的底层定义,CHANGE_TYPE_FLAG_ORDER 对应值 16,代表订单属性变动,刷新事件时靠这些位标判断哪类改了。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| List of flags of possible order and position change options      |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_CHANGE_TYPE_FLAGS
  {
   CHANGE_TYPE_FLAG_NO_CHANGE     =  class="num">0,                class=class="str">"cmt">// No changes
   CHANGE_TYPE_FLAG_TYPE          =  class="num">1,                class=class="str">"cmt">// Order type change
   CHANGE_TYPE_FLAG_PRICE         =  class="num">2,                class=class="str">"cmt">// Price change
   CHANGE_TYPE_FLAG_STOP          =  class="num">4,                class=class="str">"cmt">// StopLoss change
   CHANGE_TYPE_FLAG_TAKE          =  class="num">8,                class=class="str">"cmt">// TakeProfit change
   CHANGE_TYPE_FLAG_ORDER         =  class="num">16                class=class="str">"cmt">// Order properties change flag
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Possible order and position change options                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_CHANGE_TYPE
  {
   CHANGE_TYPE_NO_CHANGE,                                        class=class="str">"cmt">// No changes
   CHANGE_TYPE_ORDER_TYPE,                                       class=class="str">"cmt">// Order type change
   CHANGE_TYPE_ORDER_PRICE,                                      class=class="str">"cmt">// Order price change
   CHANGE_TYPE_ORDER_PRICE_STOP_LOSS,                            class=class="str">"cmt">// Order and StopLoss price change 
   CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT,                          class=class="str">"cmt">// Order and TakeProfit price change
   CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT,                class=class="str">"cmt">// Order, StopLoss and TakeProfit price change
   CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT,                      class=class="str">"cmt">// StopLoss and TakeProfit change
   CHANGE_TYPE_ORDER_STOP_LOSS,                                  class=class="str">"cmt">// Order&class="macro">#x27;s StopLoss change
   CHANGE_TYPE_ORDER_TAKE_PROFIT                                 class=class="str">"cmt">// Order&class="macro">#x27;s TakeProfit change
  };

改仓与账户事件的枚举定义

在 MT5 的自定义交易监控 EA 里,先把「能改什么」和「账户会发生什么」固化成枚举,后续事件分发才不会写一堆魔法数字。下面这段定义了持仓止损止盈的修改类型,以及账户侧可能出现的交易事件清单。 修改类型分三档:同时改 SL/TP、只改 SL、只改 TP。账户事件枚举里,TRADE_EVENT_ACCOUNT_CREDIT 被显式赋值为 DEAL_TYPE_CREDIT(对应数值 3),说明它与底层成交类型常量强绑定,顺序和取值都不允许擅自增删。 其余成员覆盖了挂单的放置与移除、信用/赠金/分红计入、日终与月终佣金扣除,以及被取消的买卖成交。写监听器时,遍历这个枚举就能把账户流水按事件归类,外汇与贵金属品种还需留意隔夜利息和佣金对净值的侵蚀风险。

MQL5 / C++
  CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT,              class=class="str">"cmt">// Change position&class="macro">#x27;s StopLoss and TakeProfit
  CHANGE_TYPE_POSITION_STOP_LOSS,                            class=class="str">"cmt">// Change position&class="macro">#x27;s StopLoss
  CHANGE_TYPE_POSITION_TAKE_PROFIT,                          class=class="str">"cmt">// Change position&class="macro">#x27;s TakeProfit
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| List of possible trading events on the account                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
  TRADE_EVENT_NO_EVENT = class="num">0,                                  class=class="str">"cmt">// No trading event
  TRADE_EVENT_PENDING_ORDER_PLASED,                          class=class="str">"cmt">// Pending order placed
  TRADE_EVENT_PENDING_ORDER_REMOVED,                         class=class="str">"cmt">// Pending order removed
class=class="str">"cmt">//--- enumeration members matching the ENUM_DEAL_TYPE enumeration members
class=class="str">"cmt">//--- (constant order below should not be changed, no constants should be added/deleted)
  TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,             class=class="str">"cmt">// Accruing credit(class="num">3)
  TRADE_EVENT_ACCOUNT_CHARGE,                                class=class="str">"cmt">// Additional charges
  TRADE_EVENT_ACCOUNT_CORRECTION,                            class=class="str">"cmt">// Correcting entry
  TRADE_EVENT_ACCOUNT_BONUS,                                 class=class="str">"cmt">// Accruing bonuses
  TRADE_EVENT_ACCOUNT_COMISSION,                             class=class="str">"cmt">// Additional commissions
  TRADE_EVENT_ACCOUNT_COMISSION_DAILY,                       class=class="str">"cmt">// Commission charged at the end of a trading day
  TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY,                     class=class="str">"cmt">// Commission charged at the end of a trading month
  TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY,                 class=class="str">"cmt">// Agent commission charged at the end of a trading day
  TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY,               class=class="str">"cmt">// Agent commission charged at the end of a month
  TRADE_EVENT_ACCOUNT_INTEREST,                              class=class="str">"cmt">// Accrued interest on free funds
  TRADE_EVENT_BUY_CANCELLED,                                 class=class="str">"cmt">// Canceled buy deal
  TRADE_EVENT_SELL_CANCELLED,                                class=class="str">"cmt">// Canceled sell deal
  TRADE_EVENT_DIVIDENT,                                      class=class="str">"cmt">// Accruing dividends
  TRADE_EVENT_DIVIDENT_FRANKED,                              class=class="str">"cmt">// Accruing franked dividends

「把账户税务与净值翻转都编成事件常量」

在自建交易事件映射时,把 DEAL_TAX 作为基准锚点是个省事的做法:TRADE_EVENT_TAX 直接等于 DEAL_TAX,后续余额类、挂单类、仓位类事件全部用 DEAL_TAX+N 递推,例如账户入金是 DEAL_TAX+1、出金是 DEAL_TAX+2、挂单被价触激活是 DEAL_TAX+3。 净仓账户(netting)下的反转与加仓被拆得很细:市价反转、挂单反转、部分市价反转、部分挂单反转,以及对应的加仓变体,各自独占一个常量。这样在 OnTradeTransaction 里用 switch 分流时,不用再去查 ENUM_DEAL_TYPE 和 ENUM_ORDER_TYPE 的组合,直接比对自定义事件号就行。 需要注意,注释里明确写了这些常量顺序可改、可增删,只有前三个(税务、入金、出金)相对固定。StopLimit 被触发也单独给了 TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER,回测或实盘监控里若想抓 StopLimit 转活跃这一刻,认这个常量即可。外汇与贵金属杠杆高,这类事件监听仅用于风控统计,不代表任何方向判断。

MQL5 / C++
TRADE_EVENT_TAX                                 = DEAL_TAX,     class=class="str">"cmt">// Tax
class=class="str">"cmt">//--- constants related to the DEAL_TYPE_BALANCE deal type from the DEAL_TYPE_BALANCE enumeration
  TRADE_EVENT_ACCOUNT_BALANCE_REFILL      = DEAL_TAX+class="num">1,    class=class="str">"cmt">// Replenishing account balance
  TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+class="num">2,    class=class="str">"cmt">// Withdrawing funds from an account
class=class="str">"cmt">//--- Remaining possible trading events
class=class="str">"cmt">//--- (constant order below can be changed, constants can be added/deleted)
  TRADE_EVENT_PENDING_ORDER_ACTIVATED       = DEAL_TAX+class="num">3,    class=class="str">"cmt">// Pending order activated by price
  TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL,             class=class="str">"cmt">// Pending order partially activated by price
  TRADE_EVENT_POSITION_OPENED,                             class=class="str">"cmt">// Position opened
  TRADE_EVENT_POSITION_OPENED_PARTIAL,                     class=class="str">"cmt">// Position opened partially
  TRADE_EVENT_POSITION_CLOSED,                             class=class="str">"cmt">// Position closed
  TRADE_EVENT_POSITION_CLOSED_BY_POS,                      class=class="str">"cmt">// Position closed partially
  TRADE_EVENT_POSITION_CLOSED_BY_SL,                       class=class="str">"cmt">// Position closed by StopLoss
  TRADE_EVENT_POSITION_CLOSED_BY_TP,                       class=class="str">"cmt">// Position closed by TakeProfit
  TRADE_EVENT_POSITION_REVERSED_BY_MARKET,                 class=class="str">"cmt">// Position reversal by a new deal(netting)
  TRADE_EVENT_POSITION_REVERSED_BY_PENDING,                class=class="str">"cmt">// Position reversal by activating a pending order(netting)
  TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL,         class=class="str">"cmt">// Position reversal by partial market order execution(netting)
  TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL,        class=class="str">"cmt">// Position reversal by partial pending order activation(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET,               class=class="str">"cmt">// Added volume to a position by a new deal(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL,       class=class="str">"cmt">// Added volume to a position by partial activation of an order(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING,              class=class="str">"cmt">// Added volume to a position by activating a pending order(netting)
  TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL,      class=class="str">"cmt">// Added volume to a position by partial activation of a pending order(netting)
  TRADE_EVENT_POSITION_CLOSED_PARTIAL,                     class=class="str">"cmt">// Position closed partially
  TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS,              class=class="str">"cmt">// Position closed partially by an opposite one
  TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL,               class=class="str">"cmt">// Position closed partially by StopLoss
  TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP,               class=class="str">"cmt">// Position closed partially by TakeProfit
  TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER,                  class=class="str">"cmt">// StopLimit order activation

常见问题

用两个订单列表在每次刷新时做比对,找出状态从挂单变为激活的那一笔,手动抛出激活事件即可。
在比对出激活瞬间后,将其事件类型映射为你已有的普通挂单事件枚举,后续走同一处理分支就行。
小布可自动识别这类订单状态变化并推送提醒,你打开对应品种页就能直接看到激活与改仓标注。
定义独立的事件枚举常量如税务扣除、净值翻转,在账户事件回调里匹配数值变化后抛出对应事件。
先定义清晰的改仓与持仓枚举,再用独立列表记录修改前后字段差异,就能把两者事件彻底拆开。