轻松快捷开发 MetaTrader 程序的函数库(第八部分):订单和持仓修改事件·进阶篇
(2/3)· 当 StopLimit 激活事件已就位,如何扩展函数库捕获已有订单持仓的每一次改动
很多开发者在扩展交易事件库时,只盯住开单和下单,却忽略了订单与持仓在生命周期内的属性修改。这类改动若不被捕获,策略对盘口状态的认知就会滞后于真实账户。
◍ 账户交易事件的位标志枚举
在 MT5 的底层交易事件监听里,账户级动作被拆成一套 2 的幂次位标志,写进 ENUM_TRADE_EVENT_FLAGS 枚举,方便用按位或做组合订阅。 从 0 到 16384 一共 15 个常量:0 是无事件,1 挂单放入、2 挂单删除、4 价格触发挂单、8 开仓、16 仓位变更、32 反手、64 平仓、128 余额操作、256 部分成交、512 被对向仓位吃单。 高亮的三项值得单独看:1024 是挂单 Placement price 修改,8192 是 Order modification(普通挂单改参),16384 是 Position modification(已开仓位改 SL/TP 或手数)。外汇与贵金属杠杆高,这类改仓事件若被 EA 监听不到位,可能在不经意间放大回撤。 开 MT5 敲 ENUM_TRADE_EVENT_FLAGS 看一眼,你会发现 1024/8192/16384 这三个值正好覆盖「价改—单改—仓改」三层,做事件驱动型 EA 时建议先按位过滤再处理。
enum ENUM_TRADE_EVENT_FLAGS { TRADE_EVENT_FLAG_NO_EVENT = class="num">0, class=class="str">"cmt">// No event TRADE_EVENT_FLAG_ORDER_PLASED = class="num">1, class=class="str">"cmt">// Pending order placed TRADE_EVENT_FLAG_ORDER_REMOVED = class="num">2, class=class="str">"cmt">// Pending order removed TRADE_EVENT_FLAG_ORDER_ACTIVATED = class="num">4, class=class="str">"cmt">// Pending order activated by price TRADE_EVENT_FLAG_POSITION_OPENED = class="num">8, class=class="str">"cmt">// Position opened TRADE_EVENT_FLAG_POSITION_CHANGED= class="num">16, class=class="str">"cmt">// Position changed TRADE_EVENT_FLAG_POSITION_REVERSE= class="num">32, class=class="str">"cmt">// Position reversed TRADE_EVENT_FLAG_POSITION_CLOSED = class="num">64, class=class="str">"cmt">// Position closed TRADE_EVENT_FLAG_ACCOUNT_BALANCE = class="num">128, class=class="str">"cmt">// Balance operation(clarified by a deal type) TRADE_EVENT_FLAG_PARTIAL = class="num">256, class=class="str">"cmt">// Partial execution TRADE_EVENT_FLAG_BY_POS = class="num">512, class=class="str">"cmt">// Executed by opposite position TRADE_EVENT_FLAG_PRICE = class="num">1024, class=class="str">"cmt">// Placement price modification TRADE_EVENT_FLAG_SL = class="num">2048, class=class="str">"cmt">// Executed by StopLoss TRADE_EVENT_FLAG_TP = class="num">4096, class=class="str">"cmt">// Executed by TakeProfit TRADE_EVENT_FLAG_ORDER_MODIFY = class="num">8192, class=class="str">"cmt">// Order modification TRADE_EVENT_FLAG_POSITION_MODIFY = class="num">16384, class=class="str">"cmt">// Position modification };
交易事件枚举的成员排布逻辑
在 MT5 的自定义交易事件处理里,用一组枚举常量来标记「发生了什么类型的账户动作」。前几个是基础事件:0 表示无事件,接着是挂单的放置与移除,这两个不依赖成交类型。 中间一段枚举成员刻意和 ENUM_DEAL_TYPE 对齐,注释里写明顺序不能改、不能增删——比如 TRADE_EVENT_ACCOUNT_CREDIT 直接等于 DEAL_TYPE_CREDIT(数值 3),TRADING_EVENT_TAX 等于 DEAL_TAX,后面的余额类事件则按 DEAL_TAX+1、DEAL_TAX+2 递推。这样写是为了让事件 ID 和底层成交类型保持映射,省去运行时转换。 尾部那段属于「可自由调整区」,注释允许改顺序、加删常量。例如挂单被价格激活写成 DEAL_TAX+3,部分激活则顺延枚举值由编译器自动分配。实盘跑 EA 时,若你自行扩展了事件类型,只动这一区就不会破坏和成交类型的硬绑定。 外汇与贵金属杠杆高,事件监听逻辑若漏掉余额类扣费(如每日佣金 DEAL_TAX+1 区之后),可能让风控信号延迟,账户浮盈计算出现偏差的概率会上升。
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 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
「逐笔成交事件枚举里的高频钩子」
在 MT5 的 CTrade/HistoryDeal 事件体系里,成交动作被拆成了几十个细粒度枚举。上面这段列出的是其中与持仓生命周期直接相关的子集:从整仓建仓 TRADE_EVENT_POSITION_OPENED,到部分建仓 TRADE_EVENT_POSITION_OPENED_PARTIAL,再到整仓平仓、被反向单吃掉、被 SL/TP 触发等,合计覆盖了 19 个持仓类事件常量。 黄色高亮的 7 个 TRADE_EVENT_MODIFY_ORDER_* 系列,是挂单改价事件,而不是持仓事件。它们只在你用 OrderModify 改 pending order 的 price / SL / TP 组合时触发,回测里若想统计「改单后胜率偏移」,必须单独监听这组,不能和持仓平仓事件混算。 实盘写 EA 时,建议把持仓类事件和挂单改价类事件分两个 switch 分支处理。比如用 HistoryDealGetInteger(deal_ticket, DEAL_ENTRY) 配合事件枚举,能在成交回执里精确区分「部分平仓被 TP 命中」(TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) 与「整仓被 TP 命中」(TRADE_EVENT_POSITION_CLOSED_BY_TP),外汇与贵金属波动跳空频繁,这种区分对测算真实滑点有概率层面的价值,但相关策略仍属高风险。
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 a market 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 TRADE_EVENT_MODIFY_ORDER_PRICE, class=class="str">"cmt">// Changing order price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS, class=class="str">"cmt">// Changing order and StopLoss price TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT, class=class="str">"cmt">// Changing order and TakeProfit price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, class=class="str">"cmt">// Changing order, StopLoss and TakeProfit price TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT, class=class="str">"cmt">// Changing order&class="macro">#x27;s StopLoss and TakeProfit price TRADE_EVENT_MODIFY_ORDER_STOP_LOSS, class=class="str">"cmt">// Changing order&class="macro">#x27;s StopLoss
◍ 改单与事件状态的枚举划分
在 MT5 的订单事件体系里,挂单止盈改动和持仓止损止盈改动走的是不同枚举值。TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT 只管挂单 TP 调整,而 TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT 等四个值覆盖持仓 SL/TP 的各类变更,写监听器时不能混用。 事件状态枚举 ENUM_EVENT_STATUS 把动作归为六类:MARKET_POSITION(开仓/加仓/部分平仓/反转)、MARKET_PENDING(挂单放置)、HISTORY_PENDING(挂单撤销)、HISTORY_POSITION(持仓平仓)、BALANCE(出入金及 DEAL 类操作)、以及 EVENT_STATUS_MODIFY(订单或持仓的修改事件)。 修改类事件单独用 EVENT_STATUS_MODIFY 标记,意味着你在 OnTradeTransaction 里若只筛这个状态,就能精准捕获所有 SL/TP 重设,而不会和开平仓噪音混在一起。外汇与贵金属杠杆高,修改止损本身不预示方向,只是风险参数的重排。 事件原因枚举 ENUM_EVENT_REASON 里,净仓模式下的反转有四种细分:完整反转、部分执行反转、挂单激活反转、挂单部分执行反转。它们都集中在枚举前半段,后续才接 EVENT_REASON_ACTIVATED_PENDING 这类挂单激活原因。
enum ENUM_TRADE_EVENT { TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT, class=class="str">"cmt">// Changing order&class="macro">#x27;s TakeProfit TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT, class=class="str">"cmt">// Changing position&class="macro">#x27;s StopLoss and TakeProfit TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, class=class="str">"cmt">// Changing position&class="macro">#x27;s StopLoss TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, class=class="str">"cmt">// Changing position&class="macro">#x27;s TakeProfit }; enum ENUM_EVENT_STATUS { EVENT_STATUS_MARKET_POSITION, class=class="str">"cmt">// Market position event(opening, partial opening, partial closing, adding volume, reversal) EVENT_STATUS_MARKET_PENDING, class=class="str">"cmt">// Market pending order event(placing) EVENT_STATUS_HISTORY_PENDING, class=class="str">"cmt">// Historical pending order event(removal) EVENT_STATUS_HISTORY_POSITION, class=class="str">"cmt">// Historical position event(closing) EVENT_STATUS_BALANCE, class=class="str">"cmt">// Balance operation event(accruing balance, withdrawing funds and events from the ENUM_DEAL_TYPE enumeration) EVENT_STATUS_MODIFY class=class="str">"cmt">// Order/position modification event }; enum ENUM_EVENT_REASON { EVENT_REASON_REVERSE, class=class="str">"cmt">// Position reversal(netting) EVENT_REASON_REVERSE_PARTIALLY, class=class="str">"cmt">// Position reversal by partial request execution(netting) EVENT_REASON_REVERSE_BY_PENDING, class=class="str">"cmt">// Position reversal by pending order activation(netting) EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY, class=class="str">"cmt">// Position reversal in case of a pending order partial execution(netting) EVENT_REASON_ACTIVATED_PENDING, class=class="str">"cmt">// Pending order activation };
交易事件触发原因的枚举清单
在 MT5 的订单与成交事件回调里,系统会用一组 ENUM_EVENT_REASON 常量告诉你这笔动作到底因何发生。上面这段枚举覆盖了挂单部分激活、StopLimit 触发、改单、撤单、过期,以及净仓模式下加仓、止盈止损平仓、反向持仓平仓等几乎所有情形。 注意 EVENT_REASON_MODIFY 这种被高亮标记的常量——它专门对应订单或仓位的参数修改事件,如果你在 OnTradeTransaction 里只监听激活和成交,就会漏掉交易者手动挪止损带来的仓位状态变化。 对于外汇和贵金属这类高波动品种,部分成交常量(如 EVENT_REASON_DONE_PARTIALLY、EVENT_REASON_DONE_SL_PARTIALLY)出现频率并不低,尤其在流动性稀薄的新闻秒里,一次 2 手市价单可能拆成 3 次部分成交。读懂这些 reason 才能把复盘脚本写准。 下面把原文枚举原样列出,方便你直接贴进 MT5 头文件比对:
EVENT_REASON_ACTIVATED_PENDING_PARTIALLY, class=class="str">"cmt">// Pending order partial activation EVENT_REASON_STOPLIMIT_TRIGGERED, class=class="str">"cmt">// StopLimit order activation EVENT_REASON_MODIFY, class=class="str">"cmt">// Modification EVENT_REASON_CANCEL, class=class="str">"cmt">// Cancelation EVENT_REASON_EXPIRED, class=class="str">"cmt">// Order expiration EVENT_REASON_DONE, class=class="str">"cmt">// Request executed in full EVENT_REASON_DONE_PARTIALLY, class=class="str">"cmt">// Request executed partially EVENT_REASON_VOLUME_ADD, class=class="str">"cmt">// Add volume to a position(netting) EVENT_REASON_VOLUME_ADD_PARTIALLY, class=class="str">"cmt">// Add volume to a position by a partial request execution(netting) EVENT_REASON_VOLUME_ADD_BY_PENDING, class=class="str">"cmt">// Add volume to a position when a pending order is activated(netting) EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY, class=class="str">"cmt">// Add volume to a position when a pending order is partially executed(netting) EVENT_REASON_DONE_SL, class=class="str">"cmt">// Closing by StopLoss EVENT_REASON_DONE_SL_PARTIALLY, class=class="str">"cmt">// Partial closing by StopLoss EVENT_REASON_DONE_TP, class=class="str">"cmt">// Closing by TakeProfit EVENT_REASON_DONE_TP_PARTIALLY, class=class="str">"cmt">// Partial closing by TakeProfit EVENT_REASON_DONE_BY_POS, class=class="str">"cmt">// Closing by an opposite position EVENT_REASON_DONE_PARTIALLY_BY_POS, class=class="str">"cmt">// Partial closing by an opposite position EVENT_REASON_DONE_BY_POS_PARTIALLY, class=class="str">"cmt">// Partial closing by an opposite position EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY, class=class="str">"cmt">// Closing an opposite position by a partial volume class=class="str">"cmt">//--- Constants related to DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration EVENT_REASON_BALANCE_REFILL, class=class="str">"cmt">// Refilling the balance EVENT_REASON_BALANCE_WITHDRAWAL, class=class="str">"cmt">// Withdrawing funds from the account
「账户事件原因枚举与属性偏移量」
在 MT5 的交易事件体系里,TRADE_EVENT_ACCOUNT_CREDIT 这类账户事件的原因常量,并不是从 0 开始排的。它们相对 ENUM_DEAL_TYPE 整体做了 +13 的偏移,而代码里用宏 REASON_EVENT_SHIFT 定义为 EVENT_REASON_ACCOUNT_CREDIT - 3,实际就是把首枚账户原因常量锚到正确的枚举位上。 下面这段枚举列出了信用入账、附加收费、分红、税务等 16 个具体原因。比如 EVENT_REASON_ACCOUNT_CREDIT 对应信用注入,EVENT_REASON_DIVIDENT 对应股息计提,EVENT_REASON_TAX 对应税务扣除;日终和月终的佣金也分别有 DAILY 与 MONTHLY 两种细分。复制进 MT5 头文件即可直接参与事件过滤。 事件自身的浮点属性另由 ENUM_EVENT_PROP_DOUBLE 承载,起点接在 EVENT_PROP_INTEGER_TOTAL 之后。EVENT_PROP_PRICE_EVENT 记录事件触发时的成交价,EVENT_PROP_PRICE_OPEN / CLOSE / SL 则分别指向开仓、平仓与止损价。写 EA 监听账户变动时,用对偏移宏才不会把信用到账误判成普通成交。
class=class="str">"cmt">//--- List of constants is relevant to TRADE_EVENT_ACCOUNT_CREDIT from the ENUM_TRADE_EVENT enumeration and shifted to +class="num">13 relative to ENUM_DEAL_TYPE(EVENT_REASON_ACCOUNT_CREDIT-class="num">3) EVENT_REASON_ACCOUNT_CREDIT, class=class="str">"cmt">// Accruing credit EVENT_REASON_ACCOUNT_CHARGE, class=class="str">"cmt">// Additional charges EVENT_REASON_ACCOUNT_CORRECTION, class=class="str">"cmt">// Correcting entry EVENT_REASON_ACCOUNT_BONUS, class=class="str">"cmt">// Accruing bonuses EVENT_REASON_ACCOUNT_COMISSION, class=class="str">"cmt">// Additional commissions EVENT_REASON_ACCOUNT_COMISSION_DAILY, class=class="str">"cmt">// Commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_MONTHLY, class=class="str">"cmt">// Commission charged at the end of a trading month EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY, class=class="str">"cmt">// Agent commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY, class=class="str">"cmt">// Agent commission charged at the end of a month EVENT_REASON_ACCOUNT_INTEREST, class=class="str">"cmt">// Accruing interest on free funds EVENT_REASON_BUY_CANCELLED, class=class="str">"cmt">// Canceled buy deal EVENT_REASON_SELL_CANCELLED, class=class="str">"cmt">// Canceled sell deal EVENT_REASON_DIVIDENT, class=class="str">"cmt">// Accruing dividends EVENT_REASON_DIVIDENT_FRANKED, class=class="str">"cmt">// Accruing franked dividends EVENT_REASON_TAX class=class="str">"cmt">// Tax }; class="macro">#define REASON_EVENT_SHIFT(EVENT_REASON_ACCOUNT_CREDIT-class="num">3) class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Event&class="macro">#x27;s real properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_EVENT_PROP_DOUBLE { EVENT_PROP_PRICE_EVENT = EVENT_PROP_INTEGER_TOTAL, class=class="str">"cmt">// Price an event occurred at EVENT_PROP_PRICE_OPEN, class=class="str">"cmt">// Order/deal/position open price EVENT_PROP_PRICE_CLOSE, class=class="str">"cmt">// Order/deal/position close price EVENT_PROP_PRICE_SL class=class="str">"cmt">// StopLoss order/deal/position price };