轻松快捷开发 MetaTrader 程序的函数库(第十一部分)。 与 MQL4 的兼容性 - 平仓事件·进阶篇
🔧

轻松快捷开发 MetaTrader 程序的函数库(第十一部分)。 与 MQL4 的兼容性 - 平仓事件·进阶篇

(2/3)· 还在用秒级时间硬套 MQL5 订单属性?本篇拆解平仓事件兼容里被忽略的毫秒坑与注释扩展

实战向 第 2/3 篇
直接把 MQL4 的秒级时间属性搬进 MQL5 订单结构,会在平仓事件回调里悄悄丢掉毫秒精度,导致排序和显示错位。很多人没意识到,残留的 _MSC 属性不删干净,后续继承类编译虽过但逻辑已脏。

订单排序枚举的尾段分布

在 MT5 的订单历史与持仓查询接口里,排序方式靠一组枚举值驱动。从 12 到 20 覆盖的是整数类属性:成交方向(IN/OUT/IN_OUT)、仓位变更时间、父子订单 ticket、点值利润、SL/TP 触发标记、分组 ID 与买卖方向。 实数属性的起点由 FIRST_ORD_DBL_PROP 接管,其后顺延:开平价格占 +0/+1,止损止盈占 +2/+3,利润与费用从 +4 排到 +6,成交量与未成交量分别是 +7 与 +8,全成本利润(含佣息)在 +9,StopLimit 激活后的挂单价在 +10。 字符串类从 FIRST_ORD_STR_PROP 起算,品种名占基准位,注释占 +1,自定义注释 SORT_BY_ORDER_COMMENT_EXT 落在 +2。开 MT5 头文件 OrderInfo.mqh 搜这些宏,能直接核对你的 EA 排序逻辑有没有越界。

MQL5 / C++
SORT_BY_ORDER_DEAL_ENTRY      = class="num">12,              class=class="str">"cmt">// Sort by deal direction – IN, OUT or IN/OUT
SORT_BY_ORDER_TIME_UPDATE   = class="num">13,              class=class="str">"cmt">// Sort by position change time in seconds
SORT_BY_ORDER_TICKET_FROM   = class="num">14,              class=class="str">"cmt">// Sort by parent order ticket
SORT_BY_ORDER_TICKET_TO     = class="num">15,              class=class="str">"cmt">// Sort by derived order ticket
SORT_BY_ORDER_PROFIT_PT     = class="num">16,              class=class="str">"cmt">// Sort by order profit in points
SORT_BY_ORDER_CLOSE_BY_SL   = class="num">17,              class=class="str">"cmt">// Sort by order closing by StopLoss flag
SORT_BY_ORDER_CLOSE_BY_TP   = class="num">18,              class=class="str">"cmt">// Sort by order closing by TakeProfit flag
SORT_BY_ORDER_GROUP_ID      = class="num">19,              class=class="str">"cmt">// Sort by order/position group ID
SORT_BY_ORDER_DIRECTION     = class="num">20,              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
SORT_BY_ORDER_COMMENT_EXT   = FIRST_ORD_STR_PROP+class="num">2,  class=class="str">"cmt">// Sort by custom comment

「订单对象的毫秒级时间与属性取数接口」

在 MT5 的订单封装类里,时间维度被拆成了两套:普通 datetime(秒精度)与 MSC 后缀的毫秒精度。高亮的那几行 SORT_BY_ORDER_TIME_OPEN_MSC、ORDER_PROP_TIME_CLOSE_MSC 等,就是给外部排序和属性读取用的毫秒字段,做高频撤单分析或跨系统对账时,秒级时间可能漏掉同一秒内多笔成交的顺序。 OrderOpenTime() 与 OrderCloseTime() 返回的是秒级 datetime,而 PositionTimeUpdateMSC() 拿的是持仓最后更新时间的毫秒值。若你的 EA 要比较两笔订单谁先被外部系统处理,直接用 Ticket() 配合 SORT_BY_ORDER_EXT_ID(枚举值 = FIRST_ORD_STR_PROP+3)更稳,避免本地秒级时间撞车。 下面这段是类里简化取属性的内联实现,全部走 GetProperty 宏,调用成本极低: Ticket() 取订单唯一号,TicketFrom() / TicketTo() 分别取父单与衍生单的 ticket,Magic() 取策略魔数。十九个基础属性(ticket、magic、reason、state 等)都按此模式暴露,回测里批量扫历史订单时可直接读,不用再调 OrderGetInteger。 外汇与贵金属杠杆高,用毫秒时间做微观结构统计时,样本偏差可能被放大,结论仅作概率参考。

MQL5 / C++
SORT_BY_ORDER_EXT_ID = FIRST_ORD_STR_PROP+class="num">3 class=class="str">"cmt">// Sort by order ID in an external trading system
};
class=class="str">"cmt">//+------------------------------------------------------------------+
SORT_BY_ORDER_TIME_OPEN_MSC
SORT_BY_ORDER_TIME_CLOSE_MSC
SORT_BY_ORDER_TIME_OPEN
SORT_BY_ORDER_TIME_CLOSE
ORDER_PROP_TIME_CLOSE_MSC
ORDER_PROP_TIME_UPDATE_MSC
  class="type">class="kw">datetime       OrderOpenTime(class="type">void)        const;
  class="type">class="kw">datetime       OrderCloseTime(class="type">void)       const;
  class="type">class="kw">datetime       OrderExpiration(class="type">void)      const;
  class="type">class="kw">datetime       PositionTimeUpdate(class="type">void)   const;
  class="type">class="kw">datetime       PositionTimeUpdateMSC(class="type">void) const;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Methods of a simplified access to the order object properties    |
class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//--- Return(class="num">1) ticket, (class="num">2) parent order ticket, (class="num">3) derived order ticket, (class="num">4) magic number, (class="num">5) order reason,
  class=class="str">"cmt">//--- (class="num">6) position ID, (class="num">7) opposite position ID, (class="num">8) group ID, (class="num">9) type, (class="num">10) flag of closing by StopLoss,
  class=class="str">"cmt">//--- (class="num">11) flag of closing by TakeProfit(class="num">12) open time, (class="num">13) close time, (class="num">14) open time in milliseconds,
  class=class="str">"cmt">//--- (class="num">15) close time in milliseconds, (class="num">16) expiration date, (class="num">17) state, (class="num">18) status, (class="num">19) order type by direction
  class="type">long           Ticket(class="type">void)                                                  const { class="kw">return this.GetProperty(ORDER_PROP_TICKET);            }
  class="type">long           TicketFrom(class="type">void)                                              const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_FROM);       }
  class="type">long           TicketTo(class="type">void)                                                const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_TO);         }
  class="type">long           Magic(class="type">void)                                                   const { class="kw">return this.GetProperty(ORDER_PROP_MAGIC);             }

◍ 订单对象的属性读取接口

在 MT5 的订单封装类里,一组 const 成员函数负责把底层属性暴露给策略逻辑。它们全部走 this.GetProperty(),传入不同的 ORDER_PROP_* 枚举来定位字段,返回类型依属性而定。 比如 Reason() 返回 long 类型的成交原因码,PositionID() 取关联持仓 ID,TypeOrder() 取订单类型枚举值。外汇与贵金属交易里,这些字段常用于复盘某笔 XAUUSD 订单是被 SL 还是 TP 关闭的。 IsCloseByStopLoss() 与 IsCloseByTakeProfit() 把 ORDER_PROP_CLOSE_BY_SL / TP 强转为 bool,直接回答「这单怎么死的」。注意贵金属杠杆高,滑点可能让实际关闭原因与预期有偏差。 下方代码里 TimeOpen / TimeClose 标灰的两行给出开平时间,返回 datetime。复制进 MT5 的 EA 头文件,就能在 OnTradeTransaction 里打印每笔订单的生命周期。

MQL5 / C++
class="type">long Reason(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_REASON); }
class="type">long PositionID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_ID); }
class="type">long PositionByID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_BY_ID); }
class="type">long GroupID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_GROUP_ID); }
class="type">long TypeOrder(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_TYPE); }
class="type">bool IsCloseByStopLoss(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); }
class="type">bool IsCloseByTakeProfit(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); }
class="type">class="kw">datetime TimeOpen(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_OPEN); }
class="type">class="kw">datetime TimeClose(class="type">void) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE); }

订单对象的毫秒级时间与属性读取

在 MT5 的订单封装类里,TimeOpenMSC() 和 TimeCloseMSC() 返回的是带毫秒的 datetime,精度比普通 TimeOpen() 高三个数量级,对剥头皮或 Tick 级回测有意义。 State() 返回 ENUM_ORDER_STATE,区分订单是挂单、部分成交还是已关闭;Status() 返回 ENUM_ORDER_STATUS,更偏经纪商侧的生命周期标记,两者不要混用。 CommentExt() 读取 ORDER_PROP_COMMENT_EXT,这是扩展备注字段,普通 Comment() 读不到,EA 间传递自定义标签时可借它避开冲突。 下面这段是类方法原样,可直接贴进 MT5 编辑器核对:

MQL5 / C++
class="type">class="kw">datetime          TimeOpenMSC(class="type">void)                 const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_OPEN_MSC);  }
class="type">class="kw">datetime          TimeCloseMSC(class="type">void)                const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE_MSC);  }
class="type">class="kw">datetime          TimeExpiration(class="type">void)              const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_EXP);        }
ENUM_ORDER_STATE  State(class="type">void)                       const { class="kw">return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE);    }
ENUM_ORDER_STATUS Status(class="type">void)                      const { class="kw">return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);  }
ENUM_ORDER_TYPE   TypeByDirection(class="type">void)             const { class="kw">return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }

class=class="str">"cmt">//--- Return(class="num">1) open price, (class="num">2) close price, (class="num">3) profit, (class="num">4) commission, (class="num">5) swap, (class="num">6) volume,
class=class="str">"cmt">//--- Return(class="num">1) symbol, (class="num">2) comment, (class="num">3) ID at an exchange
class="type">class="kw">string            Symbol(class="type">void)                      const { class="kw">return this.GetProperty(ORDER_PROP_SYMBOL);                      }
class="type">class="kw">string            Comment(class="type">void)                     const { class="kw">return this.GetProperty(ORDER_PROP_COMMENT);                     }
class="type">class="kw">string            CommentExt(class="type">void)                  const { class="kw">return this.GetProperty(ORDER_PROP_COMMENT_EXT);                 }

「订单对象的属性读取与构造落地」

在自建订单类里,把 MT5 原生订单字段映射成类内属性,能省掉反复调用 CTrade 接口的开销。下面这段把外部 ID、总利润、点值利润都做成了只读方法,调用时直接取,不走二次查询。 ExternalID() 返回 ORDER_PROP_EXT_ID 里存的外部标识;ProfitFull() 把 Profit()、Comission()、Swap() 三者相加,得到含手续费与库存费的真实盈亏;ProfitInPoints() 单独给出点数利润,方便做按点统计的回测。 SetGroupID() 和 SetCommentExt() 是写入口:前者把自定义分组 ID 写进 ORDER_PROP_GROUP_ID,后者把扩展注释写进 ORDER_PROP_COMMENT_EXT,用于多策略同账号隔离。 构造函数 COrder::COrder 用 ticket 和状态枚举初始化,把 magic、ticket、过期时间、订单类型、状态、方向、关联持仓 ID 一次性灌进 m_long_prop 数组。实盘外汇与贵金属波动剧烈、杠杆风险高,这种预存属性在高频扫描中可能降低延迟,但是否稳定仍取决于你的品种流动性和点差。

MQL5 / C++
class="type">class="kw">string ExternalID(class="type">void) const { class="kw">return this.GetProperty(ORDER_PROP_EXT_ID); }
class=class="str">"cmt">//--- Get the full order profit
class="type">class="kw">double ProfitFull(class="type">void) const { class="kw">return this.Profit()+this.Comission()+this.Swap(); }
class=class="str">"cmt">//--- Get order profit in points
class="type">int ProfitInPoints(class="type">void) const;
class=class="str">"cmt">//--- Set(class="num">1) group ID and(class="num">2) custom comment
class="type">void SetGroupID(const class="type">long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID,group_id); }
class="type">void SetCommentExt(const class="type">class="kw">string comment_ext) { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext); }

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Closed parametric constructor                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const class="type">ulong ticket)
  {
class=class="str">"cmt">//--- Save integer properties
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                           = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                            = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                           = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                         = this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                             = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                            = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                        = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                      = this.OrderPositionID();

◍ 把订单历史塞进属性数组的写法

在封装历史订单对象时,把 MT5 返回的各类字段分别写进 long / double / string 三类属性容器,是后续做统计和回放的前提。下面这段赋值直接对应订单的成交原因、关联单据、开平时间等元数据。 注意高亮的三行:ORDER_PROP_TIME_OPEN / CLOSE / UPDATE 取的是带毫秒的 MSC 版本(OrderOpenTimeMSC 等),精度到毫秒级,比你用 TimeCurrent() 秒级比对更能还原真实滑点区间。外汇与贵金属点差跳动快,毫秒时间戳对复盘成交质量有实际意义,但这类品种杠杆高、跳空频繁,仍属高风险。 double 组覆盖了开平仓价、利润、佣金、库存费、成交量与挂单的 SL/TP,其中 ORDER_PROP_VOLUME_CURRENT 抓的是当前未平部分,和初始 ORDER_PROP_VOLUME 不同,用来算部分平仓很关键。string 组只存品种名、注释、外部 ID,方便和小布知识库的标签体系做映射。 跑通后你可以在 MT5 策略测试器里用 Print(m_long_prop[ORDER_PROP_TIME_OPEN]) 验证毫秒值是否落进数组,再决定要不要接自己的分析脚本。

MQL5 / C++
this.m_long_prop[ORDER_PROP_REASON]                     = this.OrderReason();
this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]             = this.DealOrderTicket();
this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                    = this.DealEntry();
this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                = this.OrderPositionByID();
this.m_long_prop[ORDER_PROP_TIME_OPEN]                     = this.OrderOpenTimeMSC();
this.m_long_prop[ORDER_PROP_TIME_CLOSE]                    = this.OrderCloseTimeMSC();
this.m_long_prop[ORDER_PROP_TIME_UPDATE]                   = this.PositionTimeUpdateMSC();
class=class="str">"cmt">//--- Save real properties
this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]      = this.OrderOpenPrice();
this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]     = this.OrderClosePrice();
this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]           = this.OrderProfit();
this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]      = this.OrderCommission();
this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]            = this.OrderSwap();
this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]          = this.OrderVolume();
this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]              = this.OrderStopLoss();
this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]              = this.OrderTakeProfit();
this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]  = this.OrderVolumeCurrent();
this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]= this.OrderPriceStopLimit();
class=class="str">"cmt">//--- Save class="type">class="kw">string properties
this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]         = this.OrderSymbol();
this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]         = this.OrderComment();
this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]          = this.OrderExternalID();
class=class="str">"cmt">//--- Save additional integer properties
把兼容层诊断交给小布
这些订单属性清理与毫秒映射的检查,小布盯盘的 AIGC 已内置,打开对应品种页即可看到结构差异提示,你只需专注事件逻辑本身。

常见问题

不会。删除的是未被使用的秒/毫秒重复项,实际时序以毫秒默认存储,回测读取走统一接口,字段映射由函数库内部处理。
可在开平仓时写入策略标记,供后续图形外壳按文本过滤,或在平仓事件里做条件分组,不局限于显示。
可以。小布内置的兼容层扫描会标出未剔除的 _MSC 引用和排序枚举脏项,省去手动 Ctrl+F 全文件搜。
只要同步清掉 Order.mqh 私有与公有段的对应返回方法,并在抽象类移除引用,编译期即可消错,运行时走默认毫秒。
不影响。新增常量是独立分支,默认时间排序仍用毫秒,仅在显式指定注释排序时才走新路径。