轻松快捷开发 MetaTrader 程序的函数库(第八部分):订单和持仓修改事件(基础篇)
抓到订单和持仓的修改瞬间
在 MT5 里做自动策略,最容易被忽略的就是「订单和持仓被改」这件事——改止盈止损、部分平仓、挂单改价,这些动作不会触发新订单事件,但实盘里天天发生。 官方测试框架在 2019-07-31 发布的示例库第八部分,专门补上了订单和持仓修改事件的捕获接口,让 EA 能在 OnTradeTransaction 里区分出 TRADE_TRANSACTION_ORDER_UPDATE 与 TRADE_TRANSACTION_POSITION_VOLUME 这类动作。 把这套函数库接进你的 EA,你就能在 MT5 策略测试器里复现:一笔 0.1 手的黄金多单,止损从 1900 改到 1895 时,EA 日志立刻打印修改事件而非开仓事件。外汇和贵金属杠杆高,修改触发逻辑写错可能重复下单,接入后务必用历史数据回放验证。
◍ 给修改订单和持仓补上事件类
上篇已经把 StopLimit 订单激活事件跑通了,那套事件定义框架本身是开放的,后续要加别的事件类型基本就是派生新类、填搜索逻辑。 目前我们只覆盖了新订单的下单与开仓事件,MT5 里订单和持仓的修改动作(比如调止损、改挂单价)还没有对应的事件对象,程序读不到这类变动。 这一节要做的就是从 CEvent 抽象类再派生一个“修改事件”类,把已有订单和持仓的变更也纳入事件流,方便后续策略对改单行为做响应。外汇与贵金属杠杆高,改单逻辑若误触发可能造成滑点放大,验证时先用模拟盘跑。
「给事件系统接上修改跟踪」
做修改事件的第一步是把之前跳过的一个整数型订单属性重新纳入。原来在枚举末尾的 ORDER_PROP_DIRECTION 被跳过,现在搜索场内外单向挂单要用它,所以把跳过的属性数量归零,并在宏里补上排序用不到的属性计数。 事件 ID 仍由一组标志拼出来。要在交易事件标志里加“订单修改”和“持仓修改”两个标志,再给 CEvent 派生类加一个“修改”状态、以及修改原因枚举。事件的整数型属性要预存修改前价格,实数型属性数量从 10 扩到 15,多出来的槽位放修改过程中的数据和价格。 CEvent 父类里加一个私有成员存品种 Digits(),在构造函数初始化列表里取一次,免得每个子类重复要。返回整数/实数属性描述、交易事件名、原因描述的方法都要补上新项,公有段再暴露访问新属性的接口。SetTypeEvent() 里顺手设好 Digits() 和具体修改事件定义。 挂单测距时发现函数库没有现成快速方案,就用整数型属性“以点数为单位的利润”当距离:挂单下它就是离现价的点数。找最远挂单=同方向全捞出来按这个距离排降序,BuyLimit/BuyStop/BuyStopLimit 都算买入方向。抽象订单类里改了按方向取类型的方法,并加了返回挂单距现价点数的逻辑,非挂单才显示利润点数。 新类 CEventModify 放在 \MQL5\Include\DoEasy\Objects\Events\EventModify.mqh,继承 CEvent。EventChartCustom() 的 dparam 传哪个价有讲究:只改挂单价就传新挂单价;同时动止损/止盈也传新挂单价;只改 SL 传新 SL,只改 TP 传新 TP;改持仓 SL/TP 就传对应持仓价;同改两者传开仓价。这样自定义程序能按修改类型反推每个价变了没。 CEventsCollection 里包含新类头文件,私有段加一个报价数据结构,构造时初始化。原来第七部分那个“订单持仓数量变才建事件”的方法之外,现在重载出第二个:现有订单或持仓被改时建修改事件。改进后那段方法会按改的类型拼标志、把新事件送进 CEventModify 构造器,并在改之前保存旧属性。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Order, deal, position integer properties | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = class="num">0, class=class="str">"cmt">// Order ticket ORDER_PROP_MAGIC, class=class="str">"cmt">// Order magic number ORDER_PROP_TIME_OPEN, class=class="str">"cmt">// Open time(MQL5 Deal time) ORDER_PROP_TIME_CLOSE, class=class="str">"cmt">// Close time(MQL5 Execution or removal time - ORDER_TIME_DONE) ORDER_PROP_TIME_OPEN_MSC, class=class="str">"cmt">// Open time in milliseconds(MQL5 Deal time in msc) ORDER_PROP_TIME_CLOSE_MSC, class=class="str">"cmt">// Close time in milliseconds(MQL5 Execution or removal time - ORDER_TIME_DONE_MSC) ORDER_PROP_TIME_EXP, class=class="str">"cmt">// Order expiration date(for pending orders)
订单属性枚举里的隐藏字段
在 MT5 的底层订单结构里,整数类属性由一组枚举值定义,覆盖状态、类型、关联仓位与盈亏标记。上面这段枚举列出了从 ORDER_PROP_STATUS 到 ORDER_PROP_DIRECTION 共 24 个整型属性,最后用宏 ORDER_PROP_INTEGER_TOTAL 写死为 24。 其中 ORDER_PROP_CLOSE_BY_SL 与 ORDER_PROP_CLOSE_BY_TP 是两个容易被忽略的标记位,分别记录该笔平仓是否由止损或止盈触发;做复盘脚本时直接读这两个字段,比去翻成交注释更稳。 ORDER_PROP_POSITION_BY_ID 存的是反向仓位 ID,对锁仓策略的配对平仓逻辑很关键;而 ORDER_PROP_TICKET_FROM / TO 则串起了母单与衍生单的票据链路。外汇与贵金属杠杆高,这类底层字段读错可能引发批量平仓误判,验证前先在策略测试器跑小样本。 下面把枚举片段原样贴出,开 MT5 在 Include 里搜 ORDER_PROP_DIRECTION 就能定位同款定义,数一下行数应也是 24。
ORDER_PROP_STATUS, class=class="str">"cmt">// Order status(from the ENUM_ORDER_STATUS enumeration) ORDER_PROP_TYPE, class=class="str">"cmt">// Order/deal type ORDER_PROP_REASON, class=class="str">"cmt">// Deal/order/position reason or source ORDER_PROP_STATE, class=class="str">"cmt">// Order status(from the ENUM_ORDER_STATE enumeration) ORDER_PROP_POSITION_ID, class=class="str">"cmt">// Position ID ORDER_PROP_POSITION_BY_ID, class=class="str">"cmt">// Opposite position ID ORDER_PROP_DEAL_ORDER_TICKET, class=class="str">"cmt">// Ticket of the order that triggered a deal ORDER_PROP_DEAL_ENTRY, class=class="str">"cmt">// Deal direction – IN, OUT or IN/OUT ORDER_PROP_TIME_UPDATE, class=class="str">"cmt">// Position change time in seconds ORDER_PROP_TIME_UPDATE_MSC, class=class="str">"cmt">// Position change time in milliseconds ORDER_PROP_TICKET_FROM, class=class="str">"cmt">// Parent order ticket ORDER_PROP_TICKET_TO, class=class="str">"cmt">// Derived order ticket ORDER_PROP_PROFIT_PT, class=class="str">"cmt">// Profit in points ORDER_PROP_CLOSE_BY_SL, class=class="str">"cmt">// Flag of closing by StopLoss ORDER_PROP_CLOSE_BY_TP, class=class="str">"cmt">// Flag of closing by TakeProfit ORDER_PROP_GROUP_ID, class=class="str">"cmt">// Order/position group ID ORDER_PROP_DIRECTION, class=class="str">"cmt">// Direction type(Buy, Sell) }; class="macro">#define ORDER_PROP_INTEGER_TOTAL(class="num">24) class=class="str">"cmt">// Total number of integer properties
◍ 订单排序的枚举字段怎么排
在 MT5 里给挂单和成交记录做自定义排序时,先得弄清楚底层用了哪套枚举来映射属性。下面这段声明把整数型、双精度型、字符串型订单属性在排序索引里的偏移算了出来,跳过的整数属性数量由 ORDER_PROP_INTEGER_SKIP 定为 0,也就是全部整数属性都参与排序。 FIRST_ORD_DBL_PROP 等于 ORDER_PROP_INTEGER_TOTAL 减掉跳过项,标记双精度属性从哪个索引号开始;FIRST_ORD_STR_PROP 再叠加上 ORDER_PROP_DOUBLE_TOTAL,字符串属性顺延在后。理清这两个宏,写排序器时才不会把属性类别指错位置。 ENUM_SORT_ORDERS_MODE 里从 0 到 13 共 14 个枚举值,分别绑定 ticket、magic、开平时间(含毫秒)、过期时间、状态、类型、来源、持仓与对冲 ID 等。实际盯盘时若想按持仓 ID 聚合挂单,直接取 SORT_BY_ORDER_POSITION_ID(枚举值 11)就能让同仓订单紧挨着排。 外汇与贵金属杠杆高,历史订单排序仅用于复盘与风控统计,不代表任何方向判断。
class="macro">#define ORDER_PROP_INTEGER_SKIP(class="num">0) class=class="str">"cmt">// Number of order properties not used in sorting class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Possible criteria of orders and deals sorting | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_ORD_DBL_PROP(ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) class="macro">#define FIRST_ORD_STR_PROP(ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { class=class="str">"cmt">//--- Sort by integer properties SORT_BY_ORDER_TICKET = class="num">0, class=class="str">"cmt">// Sort by order ticket SORT_BY_ORDER_MAGIC = class="num">1, class=class="str">"cmt">// Sort by order magic number SORT_BY_ORDER_TIME_OPEN = class="num">2, class=class="str">"cmt">// Sort by order open time SORT_BY_ORDER_TIME_CLOSE = class="num">3, class=class="str">"cmt">// Sort by order close time SORT_BY_ORDER_TIME_OPEN_MSC = class="num">4, class=class="str">"cmt">// Sort by order open time in milliseconds SORT_BY_ORDER_TIME_CLOSE_MSC = class="num">5, class=class="str">"cmt">// Sort by order close time in milliseconds SORT_BY_ORDER_TIME_EXP = class="num">6, class=class="str">"cmt">// Sort by order expiration date SORT_BY_ORDER_STATUS = class="num">7, class=class="str">"cmt">// Sort by order status(market order/pending order/deal/balance, credit operation) SORT_BY_ORDER_TYPE = class="num">8, class=class="str">"cmt">// Sort by order type SORT_BY_ORDER_REASON = class="num">9, class=class="str">"cmt">// Sort by order/position reason/source SORT_BY_ORDER_STATE = class="num">10, class=class="str">"cmt">// Sort by order status SORT_BY_ORDER_POSITION_ID = class="num">11, class=class="str">"cmt">// Sort by position ID SORT_BY_ORDER_POSITION_BY_ID = class="num">12, class=class="str">"cmt">// Sort by opposite position ID SORT_BY_ORDER_DEAL_ORDER = class="num">13 class=class="str">"cmt">// Sort by order a deal is based on };
「订单排序枚举的尾部字段怎么用」
上面这组枚举值接在订单排序常量表的后半段,编号从 14 到 23 覆盖的是交易方向、成交来源、盈亏点数等维度,之后又切到双精度与字符串属性。实际写 EA 时,若想按 Buy/Sell 方向把挂单排开,直接传 SORT_BY_ORDER_DIRECTION(值 23)即可,比自己事后用循环分类省事。 双精度段从 FIRST_ORD_DBL_PROP 开始顺延,OPEN 是基准,SL、TP、PROFIT 依次 +2、+3、+4。如果你要按「未成交残余量」排序,用 FIRST_ORD_DBL_PROP+8(即 SORT_BY_ORDER_VOLUME_CURRENT),回测里排查部分成交的冰山单会比较直观。 字符串段只有两个:SYMBOL 和 COMMENT,分别对应 FIRST_ORD_STR_PROP 与 +1。多品种策略想把订单按品种聚拢再看注释,这两个常量够用,不用手动 QString 比较。外汇与贵金属杠杆高,排序逻辑只影响展示与批量处理顺序,不改变敞口风险,参数验证请在 MT5 策略测试器里跑一遍。
SORT_BY_ORDER_DEAL_ENTRY = class="num">14, class=class="str">"cmt">// Sort by deal direction – IN, OUT or IN/OUT SORT_BY_ORDER_TIME_UPDATE = class="num">15, class=class="str">"cmt">// Sort by position change time in seconds SORT_BY_ORDER_TIME_UPDATE_MSC = class="num">16, class=class="str">"cmt">// Sort by position change time in milliseconds SORT_BY_ORDER_TICKET_FROM = class="num">17, class=class="str">"cmt">// Sort by parent order ticket SORT_BY_ORDER_TICKET_TO = class="num">18, class=class="str">"cmt">// Sort by derived order ticket SORT_BY_ORDER_PROFIT_PT = class="num">19, class=class="str">"cmt">// Sort by order profit in points SORT_BY_ORDER_CLOSE_BY_SL = class="num">20, class=class="str">"cmt">// Sort by order closing by StopLoss flag SORT_BY_ORDER_CLOSE_BY_TP = class="num">21, class=class="str">"cmt">// Sort by order closing by TakeProfit flag SORT_BY_ORDER_GROUP_ID = class="num">22, class=class="str">"cmt">// Sort by order/position group ID SORT_BY_ORDER_DIRECTION = class="num">23, class=class="str">"cmt">// Sort by direction(Buy, Sell) class=class="str">"cmt">//--- Sort by real properties SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, class=class="str">"cmt">// Sort by open price SORT_BY_ORDER_PRICE_CLOSE = FIRST_ORD_DBL_PROP+class="num">1, class=class="str">"cmt">// Sort by close price SORT_BY_ORDER_SL = FIRST_ORD_DBL_PROP+class="num">2, class=class="str">"cmt">// Sort by StopLoss price SORT_BY_ORDER_TP = FIRST_ORD_DBL_PROP+class="num">3, class=class="str">"cmt">// Sort by TakeProfit price SORT_BY_ORDER_PROFIT = FIRST_ORD_DBL_PROP+class="num">4, class=class="str">"cmt">// Sort by profit SORT_BY_ORDER_COMMISSION = FIRST_ORD_DBL_PROP+class="num">5, class=class="str">"cmt">// Sort by commission SORT_BY_ORDER_SWAP = FIRST_ORD_DBL_PROP+class="num">6, class=class="str">"cmt">// Sort by swap SORT_BY_ORDER_VOLUME = FIRST_ORD_DBL_PROP+class="num">7, class=class="str">"cmt">// Sort by volume SORT_BY_ORDER_VOLUME_CURRENT = FIRST_ORD_DBL_PROP+class="num">8, class=class="str">"cmt">// Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL = FIRST_ORD_DBL_PROP+class="num">9, class=class="str">"cmt">// Sort by profit+commission+swap criterion SORT_BY_ORDER_PRICE_STOP_LIMIT = FIRST_ORD_DBL_PROP+class="num">10, class=class="str">"cmt">// Sort by Limit order when StopLimit order is activated class=class="str">"cmt">//--- Sort by class="type">class="kw">string properties SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, class=class="str">"cmt">// Sort by symbol SORT_BY_ORDER_COMMENT = FIRST_ORD_STR_PROP+class="num">1, class=class="str">"cmt">// Sort by comment