轻松快捷开发 MetaTrader 程序的函数库(第一部分)。 概念,数据管理和首期成果·综合运用
🧩

轻松快捷开发 MetaTrader 程序的函数库(第一部分)。 概念,数据管理和首期成果·综合运用

(3/3)·从抽象类到实盘数据流转,打通前两篇铺垫,给出可直接套用的库落地路径

实战向 第 3/3 篇
写 EA 时反复手写订单遍历和报价缓存,代码越堆越乱还容易漏边界。把共性动作抽成库函数,调用层只问结果不看过程,开发速度能直接降一个量级。
本章目录
  1. 订单与成交对象的整型属性读取接口
  2. 订单对象的属性读取接口
  3. 订单抽象类的属性分区与索引偏移
  4. 订单与成交对象的属性读取接口
  5. 订单对象的属性读取与比较接口
  6. 订单对象的比较与魔幻数读取
  7. 订单号与父子关系的取数分支
  8. 从订单对象反查持仓与翻转仓 ID
  9. 用毫秒级时间戳区分订单生命周期
  10. 订单状态分流拿类型字段
  11. 挂单与历史单的类型映射细节
  12. 从订单状态反查成交与触发来源
  13. 从订单状态反查持仓与平仓诱因
  14. 从订单状态反推止盈与开平时间
  15. 订单时间与失效字段的跨版本取法
  16. 从订单状态分流拿开仓数据
  17. 平仓价与浮盈的状态分支取法
  18. 订单类里佣金隔夜利息与手数的取法
  19. 按订单状态分流取量价字段
  20. 按订单状态抓取止损止盈与触发价
  21. 订单状态分支里取价格与品名
  22. 订单备注与外部 ID 的跨版本取法
  23. 订单对象构造时的属性落盘
  24. 订单快照落库的字段映射
  25. 订单历史对象的属性读取接口
  26. 订单对象的时间与价格取值接口
  27. 订单对象的费用与属性读取接口
  28. 从订单对象里抠出外部ID和利润点数
  29. 订单描述类方法的接口声明
  30. 头文件与类库的引用骨架
  31. 按终端语言切换提示文本的实现
  32. EA 里的几个底层服务函数
  33. 订单来源追溯的 MQL5 实现
  34. 用 reason 枚举还原每笔单子的真实出处
  35. 成交描述与类型的中文映射逻辑
  36. 成交记录里的类型分支怎么写
  37. 成交与订单属性的多语种描述映射
  38. 订单属性本地化输出的条件分支
  39. 订单属性描述里的毫秒与方向分支
  40. 订单属性描述方法的字符串拼装逻辑
  41. 订单属性本地化输出的三元嵌套
  42. 订单属性描述方法的双语与空值处理
  43. 订单状态与类型的双语描述实现
  44. 订单类型与方向的中文映射实现
  45. 订单属性分三类打印到日志
  46. 在 MT5 里跑通 COrder 抽象类的第一个坑
  47. 把历史成交和挂单塞进一个有序容器
  48. 遍历挂单列表逐笔打印
  49. 从单个基础对象扩到完整订单体系
  50. 把工具请下神坛

◍ 订单与成交对象的整型属性读取接口

在 MT5 的EA封装类里,订单(Order)和成交(Deal)对象的属性大多通过无参 const 方法直接拉取,返回类型以 long 和 datetime 为主。这类接口不依赖外部传参,调用时锁定当前被 Select 的订单上下文,适合在持仓遍历或回测复盘里快速取字段。 下面这段声明集中列出了 22 个常用属性方法,其中 OrderMagicNumber、OrderTicket、OrderType、OrderOpenTime、OrderCloseTime、OrderExpiration 是实战里最高频的六个;其余如 OrderCloseByStopLoss 返回 bool,用来判断该笔订单是否由止损触发关闭。 注意 OrderOpenTimeMSC 与 OrderCloseTimeMSC 返回的是毫秒级时间戳(long),精度比 datetime 类型的 OrderOpenTime 高三个数量级,在做高频剥头皮策略的进出场对齐时更不容易撞帧。外汇与贵金属杠杆交易高风险,用这些属性做过滤时务必先在策略测试器跑至少 3 个月 tick 数据验证。

MQL5 / C++
  class=class="str">"cmt">//--- 从其参数中获取并返回所选订单的整数型属性
  class="type">long                OrderMagicNumber(class="type">void)        class="kw">const;
  class="type">long                OrderTicket(class="type">void)             class="kw">const;
  class="type">long                OrderTicketFrom(class="type">void)         class="kw">const;
  class="type">long                OrderTicketTo(class="type">void)           class="kw">const;
  class="type">long                OrderPositionID(class="type">void)         class="kw">const;
  class="type">long                OrderPositionByID(class="type">void)       class="kw">const;
  class="type">long                OrderOpenTimeMSC(class="type">void)        class="kw">const;
  class="type">long                OrderCloseTimeMSC(class="type">void)       class="kw">const;
  class="type">long                OrderType(class="type">void)               class="kw">const;
  class="type">long                OrderTypeByDirection(class="type">void)    class="kw">const;
  class="type">long                OrderTypeFilling(class="type">void)        class="kw">const;
  class="type">long                OrderTypeTime(class="type">void)           class="kw">const;
  class="type">long                OrderReason(class="type">void)             class="kw">const;
  class="type">long                DealOrder(class="type">void)               class="kw">const;
  class="type">long                DealEntry(class="type">void)               class="kw">const;
  class="type">bool                OrderCloseByStopLoss(class="type">void)    class="kw">const;
  class="type">bool                OrderCloseByTakeProfit(class="type">void)  class="kw">const;
  class="type">class="kw">datetime            OrderOpenTime(class="type">void)           class="kw">const;
  class="type">class="kw">datetime            OrderCloseTime(class="type">void)          class="kw">const;
  class="type">class="kw">datetime            OrderExpiration(class="type">void)         class="kw">const;
  class="type">class="kw">datetime            PositionTimeUpdate(class="type">void)      class="kw">const;

「订单对象的属性读取接口」

在 MT5 的自定义订单类里,持仓或挂单的关键属性被封装成一组 const 成员函数,调用时不需要传参,直接返回对应数值或字符串。 datetime PositionTimeUpdateMSC(void) const 返回订单时间更新的毫秒级时间戳;下面这组 double 函数覆盖开盘价、收盘价、利润、佣金、隔夜利息、交易量、未执行交易量、止损价、止盈价以及 StopLimit 挂单位价,共 10 个实数属性。 字符串类属性只有三个:OrderSymbol 取交易品种名,OrderComment 取订单注释,OrderExternalID 取外部系统对接 ID。把这些接口直接接到 EA 的持仓遍历循环里,就能在不需要 CTrade 二次查询的情况下拿到明细。 外汇与贵金属杠杆高、点差跳变频繁,用 OrderStopLoss 或 OrderTakeProfit 读数做风控逻辑时,要考虑到止损被点到却未成交的概率,别把返回价当已平仓依据。

MQL5 / C++
class="type">class="kw">datetime PositionTimeUpdateMSC(class="type">void) class="kw">const;

class=class="str">"cmt">//--- 从参数中获取并返回所选订单的实数属性:(class="num">1)开盘价,(class="num">2)收盘价,(class="num">3)利润,
class=class="str">"cmt">//---  (class="num">4) 佣金, (class="num">5) 隔夜利息, (class="num">6) 交易量, (class="num">7) 未执行的交易量 (class="num">8) 止损价, (class="num">9) 止盈价 (class="num">10) StopLimit 订单价位
class="type">class="kw">double OrderOpenPrice(class="type">void) class="kw">const;
class="type">class="kw">double OrderClosePrice(class="type">void) class="kw">const;
class="type">class="kw">double OrderProfit(class="type">void) class="kw">const;
class="type">class="kw">double OrderCommission(class="type">void) class="kw">const;
class="type">class="kw">double OrderSwap(class="type">void) class="kw">const;
class="type">class="kw">double OrderVolume(class="type">void) class="kw">const;
class="type">class="kw">double OrderVolumeCurrent(class="type">void) class="kw">const;
class="type">class="kw">double OrderStopLoss(class="type">void) class="kw">const;
class="type">class="kw">double OrderTakeProfit(class="type">void) class="kw">const;
class="type">class="kw">double OrderPriceStopLimit(class="type">void) class="kw">const;

class=class="str">"cmt">//--- 从参数中获取并返回所选订单的字符串属性:(class="num">1)品名,(class="num">2)注释,(class="num">3)兑换 ID,
class="type">class="kw">string OrderSymbol(class="type">void) class="kw">const;
class="type">class="kw">string OrderComment(class="type">void) class="kw">const;
class="type">class="kw">string OrderExternalID(class="type">void) class="kw">const;

class=class="str">"cmt">//---
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 闭合参数构造函数                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,class="kw">const class="type">class="kw">ulong ticket)
  {
  }

订单抽象类的属性分区与索引偏移

在 MT5 的 EA 架构里,把每一笔订单或成交单抽象成 COrder 类,可以避免在策略逻辑里反复调用 OrderGetInteger / OrderGetDouble 这类散落函数。该类继承 CObject,私有成员用三块定长数组分别装整数、实数、字符串属性:m_long_prop 长度为 ORDER_PROP_INTEGER_TOTAL,m_double_prop 为 ORDER_PROP_DOUBLE_TOTAL,m_string_prop 为 ORDER_PROP_STRING_TOTAL。 关键在索引换算。MQL5 的枚举 ENUM_ORDER_PROP_DOUBLE 和 ENUM_ORDER_PROP_STRING 并不是从 0 开始排的,而是接着整数属性的总数往后编。所以取实数属性时,用 (int)property - ORDER_PROP_INTEGER_TOTAL 把枚举值映射到 double 数组下标;取字符串属性再多减一个 ORDER_PROP_DOUBLE_TOTAL。这样一次映射就能用数组随机访问,不必每次走 API。 受保护构造函数 COrder(ENUM_ORDER_STATUS, ulong ticket) 只对内开放,外部靠默认构造加后续附着。MagicNumber、Ticket、PositionID 等只读方法被声明在 protected,意味着派生类(比如挂单类、成交类)才能直接用,调用方代码碰不到底层字段。 开 MT5 新建个 include 目录把 Defines.mqh 放好,把这段类头编译过去,若 ORDER_PROP_INTEGER_TOTAL 在你的终端版本是 15,那么 ENUM_ORDER_PROP_DOUBLE 里 ORDER_VOLUME 的实际数组位就是它枚举值减 15——自己打一行 Print 验证偏移有没有对上。

MQL5 / C++
class=class="str">"cmt">//|                                                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <Object.mqh>
class="macro">#include "..\Defines.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 抽象订单类                                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class COrder : class="kw">public CObject
  {
class="kw">private:
   class="type">class="kw">ulong             m_ticket;                                          class=class="str">"cmt">// 选定的订单/成交单号 (MQL5)
   class="type">long              m_long_prop[ORDER_PROP_INTEGER_TOTAL];              class=class="str">"cmt">// 整数型属性
   class="type">class="kw">double            m_double_prop[ORDER_PROP_DOUBLE_TOTAL];            class=class="str">"cmt">// 实数型属性
   class="type">class="kw">string            m_string_prop[ORDER_PROP_STRING_TOTAL];            class=class="str">"cmt">// 字符串型属性

   class=class="str">"cmt">//--- 返回实数属性在数组中的实际位置索引
   class="type">int               IndexProp(ENUM_ORDER_PROP_DOUBLE class="kw">property)   class="kw">const { class="kw">return (class="type">int)class="kw">property-ORDER_PROP_INTEGER_TOTAL; }
   class=class="str">"cmt">//--- 返回字符串属性在数组中的实际位置索引
   class="type">int               IndexProp(ENUM_ORDER_PROP_STRING class="kw">property)   class="kw">const { class="kw">return (class="type">int)class="kw">property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL;}
class="kw">public:
   class=class="str">"cmt">//--- 默认构造函数
                     COrder(class="type">void){;}
class="kw">protected:
   class=class="str">"cmt">//--- 受保护的参数构造函数
                     COrder(ENUM_ORDER_STATUS order_status,class="kw">const class="type">class="kw">ulong ticket);

   class=class="str">"cmt">//--- 从其参数中获取并返回所选订单的整数型属性
   class="type">long              OrderMagicNumber(class="type">void)        class="kw">const;
   class="type">long              OrderTicket(class="type">void)            class="kw">const;
   class="type">long              OrderTicketFrom(class="type">void)        class="kw">const;
   class="type">long              OrderTicketTo(class="type">void)          class="kw">const;
   class="type">long              OrderPositionID(class="type">void)        class="kw">const;

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

在 MT5 的 CTrade / COrderInfo 体系里,订单、成交、持仓三套对象共用一批属性读取方法,但返回类型按语义切得很细。时间类统一用 datetime 或带 MSC 后缀的 long(毫秒精度),类型与标识类用 long,布尔类只负责回答「这笔是不是被 SL/TP 拍掉的」这种二值问题。 实数属性那一组最容易被忽略的是注释里列的全集:开盘价、收盘价、利润、佣金、隔夜利息、交易量、未执行交易量、止损价、止盈价、StopLimit 挂单价,共 10 项。写回测或做单笔成本归因时,OrderCommission 和 OrderProfit 必须配对取,否则贵金属点差跳动的账面利润会误导你。 外汇与贵金属杠杆品种波动剧烈,用这些接口做自动统计时,务必先确认订单状态已离场再读 ClosePrice,否则可能读到 0 或上一笔残留值。开 MT5 随便下一笔 demo 单,用 OrderOpenPrice 和 OrderProfit 打印一次,就能验证这套 getter 的实时行为。

MQL5 / C++
class="type">long OrderPositionByID(class="type">void) class="kw">const;
class="type">long OrderOpenTimeMSC(class="type">void) class="kw">const;
class="type">long OrderCloseTimeMSC(class="type">void) class="kw">const;
class="type">long OrderType(class="type">void) class="kw">const;
class="type">long OrderTypeByDirection(class="type">void) class="kw">const;
class="type">long OrderTypeFilling(class="type">void) class="kw">const;
class="type">long OrderTypeTime(class="type">void) class="kw">const;
class="type">long OrderReason(class="type">void) class="kw">const;
class="type">long DealOrder(class="type">void) class="kw">const;
class="type">long DealEntry(class="type">void) class="kw">const;
class="type">bool OrderCloseByStopLoss(class="type">void) class="kw">const;
class="type">bool OrderCloseByTakeProfit(class="type">void) class="kw">const;
class="type">class="kw">datetime OrderOpenTime(class="type">void) class="kw">const;
class="type">class="kw">datetime OrderCloseTime(class="type">void) class="kw">const;
class="type">class="kw">datetime OrderExpiration(class="type">void) class="kw">const;
class="type">class="kw">datetime PositionTimeUpdate(class="type">void) class="kw">const;
class="type">class="kw">datetime PositionTimeUpdateMSC(class="type">void) class="kw">const;
class="type">class="kw">double OrderOpenPrice(class="type">void) class="kw">const;
class="type">class="kw">double OrderClosePrice(class="type">void) class="kw">const;
class="type">class="kw">double OrderProfit(class="type">void) class="kw">const;
class="type">class="kw">double OrderCommission(class="type">void) class="kw">const;

「订单对象的属性读取与比较接口」

在 MT5 的订单封装类里,掉期、成交量、止损止盈这些数值型字段都暴露成 const 方法,直接调 OrderSwap()、OrderStopLoss() 就能拿到当前选中订单的值,不用再翻 ENUM_ORDER_PROP_DOUBLE 枚举。 字符串类属性(品名、注释、外部 ID)同理,OrderSymbol() 返回交易品种,OrderComment() 常用来存 EA 的标记,方便后续按注释过滤持仓。 GetProperty 做了三个重载,按 INTEGER / DOUBLE / STRING 三类枚举分流到不同成员变量数组;其中 DOUBLE 和 STRING 版本借 IndexProp 做下标映射,改这个类时要保证映射表和枚举顺序一致,否则读出来是错值。 SupportProperty 目前三个虚函数都写死返回 true,意味着默认认为订单支持全部属性;真要接历史订单或跨品种回测,这里不重写就可能误判字段可用性。 Compare 留了 mode 参数做全属性比对,挂进 CObject 容器后可用于排序或去重,但注意 mode=0 的具体优先级得看实现,别默认当成按时间排序。

MQL5 / C++
class="type">class="kw">double OrderSwap(class="type">void) class="kw">const;
class="type">class="kw">double OrderVolume(class="type">void) class="kw">const;
class="type">class="kw">double OrderVolumeCurrent(class="type">void) class="kw">const;
class="type">class="kw">double OrderStopLoss(class="type">void) class="kw">const;
class="type">class="kw">double OrderTakeProfit(class="type">void) class="kw">const;
class="type">class="kw">double OrderPriceStopLimit(class="type">void) class="kw">const;

class=class="str">"cmt">//--- 从参数中获取并返回所选订单的字符串属性:(class="num">1)品名,(class="num">2)注释,(class="num">3)兑换 ID,
class="type">class="kw">string OrderSymbol(class="type">void) class="kw">const;
class="type">class="kw">string OrderComment(class="type">void) class="kw">const;
class="type">class="kw">string OrderExternalID(class="type">void) class="kw">const;

class="kw">public:
class=class="str">"cmt">//--- 从属性数组里返回 (class="num">1) 整数, (class="num">2) 实数,和 (class="num">3) 字符串型订单属性
class="type">long GetProperty(ENUM_ORDER_PROP_INTEGER class="kw">property) class="kw">const { class="kw">return m_long_prop[class="kw">property]; }
class="type">class="kw">double GetProperty(ENUM_ORDER_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return m_double_prop[this.IndexProp(class="kw">property)]; }
class="type">class="kw">string GetProperty(ENUM_ORDER_PROP_STRING class="kw">property) class="kw">const { class="kw">return m_string_prop[this.IndexProp(class="kw">property)]; }

class=class="str">"cmt">//--- 返回订单支持该属性的标志
class="kw">virtual class="type">bool SupportProperty(ENUM_ORDER_PROP_INTEGER class="kw">property) { class="kw">return true; }
class="kw">virtual class="type">bool SupportProperty(ENUM_ORDER_PROP_DOUBLE class="kw">property) { class="kw">return true; }
class="kw">virtual class="type">bool SupportProperty(ENUM_ORDER_PROP_STRING class="kw">property) { class="kw">return true; }

class=class="str">"cmt">//--- 按照所有可能的属性比较 COrder 对象
class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const;
class=class="str">"cmt">//---
};

订单对象的比较与魔幻数读取

COrder 类的 Compare 方法按属性类型分流比较:mode 小于 ORDER_PROP_INTEGER_TOTAL 时比整数属性,小于 ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL 时比实数属性,再往后是字符串属性。每段都返回 1 / -1 / 0 三态,正好对接 MQL5 标准容器的排序契约。 实际跑起来,整数属性总数 ORDER_PROP_INTEGER_TOTAL 在 MQL5 里是 18,双精度属性 ORDER_PROP_DOUBLE_TOTAL 是 8,字符串属性 ORDER_PROP_STRING_TOTAL 是 3;mode 值超过 29 必然落空返回 0,调试时若比较失效先查 mode 越界。 OrderMagicNumber 在 MQL5 分支里不看全局函数,而是按订单状态走不同 API:活跃持仓取 POSITION_MAGIC,挂单取 ORDER_MAGIC,已成交历史取 DEAL_MAGIC。EA 同时管多种状态订单时,直接调这个方法比自己写 switch 省事,也避免漏掉历史成交的魔幻数。 外汇与贵金属杠杆高、滑点随机,用这类封装做订单归类时,历史档回测和实盘 tick 下的状态切换可能不一致,建议开 MT5 用策略测试器跑一遍多状态混合样本。

MQL5 / C++
COrder::COrder(ENUM_ORDER_STATUS order_status,class="kw">const class="type">class="kw">ulong ticket)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 按照所有可能的属性比较 COrder 对象                               | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int COrder::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const
  {
   class="kw">const COrder *order_compared=node;
class=class="str">"cmt">//--- 比较两个订单的整数型属性
   if(mode<ORDER_PROP_INTEGER_TOTAL)
     {
      class="type">long value_compared=order_compared.GetProperty((ENUM_ORDER_PROP_INTEGER)mode);
      class="type">long value_current=this.GetProperty((ENUM_ORDER_PROP_INTEGER)mode);
      class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0);
     }
class=class="str">"cmt">//--- 比较两个订单的实数型属性
   else if(mode<ORDER_PROP_DOUBLE_TOTAL+ORDER_PROP_INTEGER_TOTAL)
     {
      class="type">class="kw">double value_compared=order_compared.GetProperty((ENUM_ORDER_PROP_DOUBLE)mode);
      class="type">class="kw">double value_current=this.GetProperty((ENUM_ORDER_PROP_DOUBLE)mode);
      class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0);
     }
class=class="str">"cmt">//--- 比较两个订单的字符串型属性
   else if(mode<ORDER_PROP_DOUBLE_TOTAL+ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_STRING_TOTAL)
     {
      class="type">class="kw">string value_compared=order_compared.GetProperty((ENUM_ORDER_PROP_STRING)mode);
      class="type">class="kw">string value_current=this.GetProperty((ENUM_ORDER_PROP_STRING)mode);
      class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0);
     }
   class="kw">return class="num">0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回魔幻数字                                                                              | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderMagicNumber() class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderMagicNumber();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE     : res=::PositionGetInteger(POSITION_MAGIC);            break;
      case ORDER_STATUS_MARKET_PENDING     : res=::OrderGetInteger(ORDER_MAGIC);                  break;
      case ORDER_STATUS_DEAL               : res=::HistoryDealGetInteger(m_ticket,DEAL_MAGIC);    break;
      case ORDER_STATUS_HISTORY_PENDING   :

◍ 订单号与父子关系的取数分支

在封装 COrder 类时,取订单号不能只靠一个 ::OrderTicket(),得先判状态。MQL5 下用 switch 吃 ORDER_PROP_STATUS,市场挂单、历史挂单、成交单都直接把 m_ticket 强转 long 返回,其余状态给 0,避免拿到野指针。 ORDER_STATUS_MARKET_ACTIVE、ORDER_STATUS_MARKET_PENDING、ORDER_STATUS_HISTORY_PENDING、ORDER_STATUS_HISTORY_ORDER、ORDER_STATUS_DEAL 这 5 个分支共享同一行 res=(long)m_ticket,说明 MT5 里活跃与历史订单的 ticket 字段同源,回测时不必区分内存区。 父子订单号在 MQL4 里只能从 OrderComment 扒字符串:注释含 "from #" 就截第 6 位起转整数。MQL5 下这段被 #ifdef 吃掉,意味着跨版本移植时父单追踪逻辑会静默失效,得自己补属性映射。 开 MT5 按 F4 把下面片段塞进类里,挂 EURUSD 迷你单后打印 OrderTicket() 与 OrderTicketFrom(),能直接看到活跃单返回非零、父单号在 MQL5 恒为 0。外汇与贵金属杠杆高,这类底层取数错一位可能造成平仓对象误判,验证后再上实盘。

MQL5 / C++
class="type">long COrder::OrderTicket(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderTicket();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE   :
      case ORDER_STATUS_MARKET_PENDING  :
      case ORDER_STATUS_HISTORY_PENDING :
      case ORDER_STATUS_HISTORY_ORDER   :
      case ORDER_STATUS_DEAL             : res=(class="type">long)m_ticket; break;
      class="kw">default                           : res=class="num">0; break;
     }
   class="kw">return res;
class="macro">#endif
  }

class="type">long COrder::OrderTicketFrom(class="type">void) class="kw">const
  {
   class="type">long ticket=class="num">0;
class="macro">#ifdef __MQL4__
   class="type">class="kw">string order_comment=::OrderComment();
   if(::StringFind(order_comment,"from #")>WRONG_VALUE) ticket=::StringToInteger(::StringSubstr(order_comment,class="num">6));
class="macro">#endif
   class="kw">return ticket;
  }

「从订单对象反查持仓与翻转仓 ID」

在 MT5 的订单封装类里,想从一笔订单回溯它所属的持仓,不能像 MQL4 那样只靠魔术码糊弄。COrder::OrderPositionID 按订单状态分流:活跃市价单直接取 POSITION_IDENTIFIER,挂单取 ORDER_POSITION_ID,历史类取 HistoryOrderGetInteger,成交记录则走 HistoryDealGetInteger 的 DEAL_POSITION_ID,默认分支给 0。 MQL4 分支下 OrderPositionID 直接返回 OrderMagicNumber,而 OrderPositionByID(翻转仓 ID)在 MQL4 恒为 0,因为老平台没有持仓反向关联概念;MT5 里仅挂单与历史挂单能从 ORDER_POSITION_BY_ID 拿到值。 OrderTicketTo 这个函数更偏兼容层写法:MQL4 时从订单注释里扒 'to #' 子串,用 StringSubstr(comment,4) 截掉前 4 字符再转整数,非 MQL4 环境直接返回 0。实盘跑 EA 前,建议你在策略测试器里对一笔反向开仓订单打印 OrderPositionID 与 OrderPositionByID,确认挂单阶段就能拿到关联仓号,否则平仓逻辑可能漏掉对冲对。 外汇与贵金属杠杆高,这类底层 ID 错乱会让仓位管理失控,验证代码行为前先用模拟账户跑通。

MQL5 / C++
class="type">long COrder::OrderTicketTo(class="type">void) class="kw">const
  {
   class="type">long ticket=class="num">0;
class="macro">#ifdef __MQL4__
   class="type">class="kw">string order_comment=::OrderComment();
   if(::StringFind(order_comment,"to #")>WRONG_VALUE) ticket=::StringToInteger(::StringSubstr(order_comment,class="num">4));
class="macro">#endif
   class="kw">return ticket;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回仓位的 ID                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderPositionID(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderMagicNumber();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE     : res=::PositionGetInteger(POSITION_IDENTIFIER);                break;
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                     break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      class="kw">default                             : res=class="num">0;                                                      break;
     }
   class="kw">return res;
class="macro">#endif
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回翻转仓位的 ID                                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderPositionByID(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return class="num">0;
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_BY_ID);                  break;
      case ORDER_STATUS_HISTORY_PENDING   :

用毫秒级时间戳区分订单生命周期

在 MT5 里,订单状态不同,取开平时间的函数完全不同。若只用秒级 Time(),遇到同一秒内挂单又成交的剥头皮单,持单时长会被算成 0,回测统计就会失真。 下面这段 COrder 成员函数按状态分流:活跃市价单走 POSITION_TIME_MSC,挂单走 ORDER_TIME_SETUP_MSC,历史类订单走 HistoryOrderGetInteger 的 ORDER_TIME_SETUP_MSC 或 ORDER_TIME_DONE_MSC,成交记录走 DEAL_TIME_MSC。MQL4 下则直接映射 OrderOpenTime / OrderCloseTime,无毫秒精度。 实际验证时,可在 EA 里打印 OrderOpenTimeMSC() 与 OrderCloseTimeMSC() 差值,对比 TimeCurrent()*1000 看偏移。外汇与贵金属杠杆高,毫秒级滑点可能直接吃掉短线利润,此类精度工具仅用于复盘,不代表实盘可复现。

MQL5 / C++
class="type">long COrder::OrderOpenTimeMSC(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return (class="type">long)::OrderOpenTime();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetInteger(POSITION_TIME_MSC);           break;
      case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetInteger(ORDER_TIME_SETUP_MSC);           break;
      case ORDER_STATUS_HISTORY_PENDING  :
      case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP_MSC); break;
      case ORDER_STATUS_DEAL             : res=::HistoryDealGetInteger(m_ticket,DEAL_TIME_MSC);   break;
      class="kw">default                            : res=class="num">0;                                                 break;
     }
   class="kw">return res;
class="macro">#endif
  }
class="type">long COrder::OrderCloseTimeMSC(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return (class="type">long)::OrderCloseTime();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING  :
      case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE_MSC);  break;

◍ 订单状态分流拿类型字段

在跨 MQL4/MQL5 的订单封装里,取成交时间或订单类型不能一把梭,得先按订单状态走分支。MQL5 环境下用 GetProperty(ORDER_PROP_STATUS) 拿到 ENUM_ORDER_STATUS,再 switch 分流到持仓、挂单、历史订单或成交记录各自的 GetInteger 接口。 以 OrderType() 为例:市价活跃单读 POSITION_TYPE,市价挂单读 ORDER_TYPE,历史挂单与历史订单读 HistoryOrderGetInteger 的 ORDER_TYPE,成交记录读 HistoryDealGetInteger 的 DEAL_TYPE,default 兜底返回 0。 OrderTypeByDirection() 则只在 MARKET_ACTIVE 时把 POSITION_TYPE_BUY / SELL 映射回 ORDER_TYPE_BUY / SELL;挂单与历史挂单分支在原文截断处未写完,实盘复制时注意补完 return 逻辑,否则可能返回脏值。外汇与贵金属杠杆高,这类状态判断错乱会直接导致平仓信号误触发。

MQL5 / C++
class="type">long COrder::OrderType(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return (class="type">long)::OrderType();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE     : res=::PositionGetInteger(POSITION_TYPE);                 break;
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE);                       break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE);  break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_TYPE);    break;
      class="kw">default                             : res=class="num">0;                                                      break;
     }
   class="kw">return res;
class="macro">#endif
  }

class="type">long COrder::OrderTypeByDirection(class="type">void) class="kw">const
  {
   ENUM_ORDER_STATUS status=(ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS);
   if(status==ORDER_STATUS_MARKET_ACTIVE)
     {
      class="kw">return(this.OrderType()==POSITION_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
     }
   if(status==ORDER_STATUS_MARKET_PENDING || status==ORDER_STATUS_HISTORY_PENDING)
     {

「挂单与历史单的类型映射细节」

在封装订单类时,挂单的「真实方向」不能只看表面类型。下面这段逻辑把 Buy Limit / Buy Stop(MQL5 下还含 Buy Stop Limit)统一归为 ORDER_TYPE_BUY,其余归为 ORDER_TYPE_SELL;若订单已进入历史,则直接返回其原始 OrderType(),查不到就给 WRONG_VALUE。 MQL4 与 MQL5 的成交属性差异很大。MQL4 没有细分填充策略,直接返回 ORDER_FILLING_RETURN;MQL5 则按订单状态走 OrderGetInteger 或 HistoryOrderGetInteger 取 ORDER_TYPE_FILLING,默认分支落 0。 订单生存期(OrderTypeTime)同理:MQL4 写死 ORDER_TIME_GTC,MQL5 按市场挂单、历史挂单、历史成交三种状态分别取 ORDER_TYPE_TIME 字段。写跨版本 EA 时,这类分支漏掉一个状态就可能拿到 0 值,回测和实盘表现会偏离。 外汇与贵金属杠杆高,这类底层封装错误不会立刻爆仓,但会在极端滑点时被放大,建议开 MT5 用脚本打印三态返回值验证。

MQL5 / C++
   class="kw">return
      (
       this.OrderType()==ORDER_TYPE_BUY_LIMIT ||
       this.OrderType()==ORDER_TYPE_BUY_STOP
       class="macro">#ifdef __MQL5__
       ||
       this.OrderType()==ORDER_TYPE_BUY_STOP_LIMIT
       class="macro">#endif ?
       ORDER_TYPE_BUY :
       ORDER_TYPE_SELL
      );
   }
   if(status==ORDER_STATUS_HISTORY_ORDER)
   {
      class="kw">return this.OrderType();
   }
   class="kw">return WRONG_VALUE;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 按差额返回执行类型                                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderTypeFilling(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return (class="type">long)ORDER_FILLING_RETURN;
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE_FILLING);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_FILLING);break;
      class="kw">default                             : res=class="num">0;                                                      break;
     }
   class="kw">return res;
class="macro">#endif
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 退货订单生存期                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderTypeTime(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return (class="type">long)ORDER_TIME_GTC;
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE_TIME);                break;
      case ORDER_STATUS_HISTORY_PENDING   :

从订单状态反查成交与触发来源

在 MT5 封装类里,订单的不同生命周期状态决定了该去哪个历史池取字段。上面这段把 ORDER_STATUS_MARKET_ACTIVE、PENDING、HISTORY_* 和 DEAL 分得很死:活跃持仓走 PositionGetInteger(POSITION_REASON),挂单走 OrderGetInteger(ORDER_REASON),而历史订单与成交分别走 HistoryOrderGetInteger 和 HistoryDealGetInteger。 MQL4 分支则是用魔法码和止盈止损标记硬猜原因:OrderCloseByStopLoss 命中就归 ORDER_REASON_SL,TakeProfit 命中归 TP,剩下带魔法号的归 EXPERT,否则 WRONG_VALUE。这种推断在外汇与贵金属高频刷单场景下可能漏掉客户端手动平仓的情况,概率不低。 DealOrder 在 MQL4 直接返回 OrderTicket,MT5 下则要根据状态切到对应历史函数取关联订单号。开 MT5 把这段贴进 EA 调试,打印各状态分支的返回值,能立刻看出你的历史订单到底被归到哪一类的。

MQL5 / C++
class="type">long COrder::OrderReason(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return
     (
       this.OrderCloseByStopLoss()   ?  ORDER_REASON_SL     :
       this.OrderCloseByTakeProfit() ?  ORDER_REASON_TP     :  
       this.OrderMagicNumber()!=class="num">0    ?  ORDER_REASON_EXPERT : WRONG_VALUE
     );
class="macro">#else
   class="type">long res=WRONG_VALUE;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
       case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetInteger(POSITION_REASON);          break;
       case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetInteger(ORDER_REASON);                break;
       case ORDER_STATUS_HISTORY_PENDING  :
       case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetInteger(m_ticket,ORDER_REASON);break;
       case ORDER_STATUS_DEAL             : res=::HistoryDealGetInteger(m_ticket,DEAL_REASON);  break;
       class="kw">default                            : res=WRONG_VALUE;                                    break;
     }
   class="kw">return res;
class="macro">#endif
  }
class="type">long COrder::DealOrder(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderTicket();
class="macro">#else
   class="type">long res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))

◍ 从订单状态反查持仓与平仓诱因

在 MT5 的订单封装类里,同一笔 ticket 可能因状态不同散落在持仓、挂单、历史订单、成交四个池子。上面这段 switch 按 ORDER_STATUS_MARKET_ACTIVE / MARKET_PENDING / HISTORY_PENDING / HISTORY_ORDER / DEAL 分别取 POSITION_IDENTIFIER、ORDER_POSITION_ID、DEAL_ORDER,把‘这笔指令最终对应哪张仓’串起来。 DealEntry 只处理已成交状态,直接读 DEAL_ENTRY 拿到 IN/OUT/INOUT 方向;其余状态返回 WRONG_VALUE,说明未成交单没有入场方向可言。 OrderCloseByStopLoss 在 MQL5 下不靠注释字符串,而是用 OrderReason()==ORDER_REASON_SL 或 DealReason()==DEAL_REASON_SL 判定。比 MQL4 里找 '[sl' 子串稳,不会因为券商改注释模板就漏判。外汇和贵金属波动剧烈,SL 触发频率高,统计胜率时这套判定直接影响样本纯度。 把下面代码贴进 COrder 类编译,能在回测里直接筛出被止损吃掉的历史单,验证你策略的止损命中分布。

MQL5 / C++
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
	   {
		case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetInteger(POSITION_IDENTIFIER);				break;
		case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetInteger(ORDER_POSITION_ID);						  break;
		case ORDER_STATUS_HISTORY_PENDING  :
		case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
		case ORDER_STATUS_DEAL             : res=::HistoryDealGetInteger(m_ticket,DEAL_ORDER);				  break;
		class="kw">default                            : res=class="num">0;																  break;
	   }
   class="kw">return res;
class="type">long COrder::DealEntry(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderType();
class="macro">#else
   class="type">long res=WRONG_VALUE;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
	   {
		case ORDER_STATUS_DEAL  : res=::HistoryDealGetInteger(m_ticket,DEAL_ENTRY);break;
		class="kw">default                 : res=WRONG_VALUE;															  break;
	   }
   class="kw">return res;
class="macro">#endif
  }
class="type">bool COrder::OrderCloseByStopLoss(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return(::StringFind(::OrderComment(),"[sl")>WRONG_VALUE);
class="macro">#else
   class="kw">return
	   (
		this.Status()==ORDER_STATUS_HISTORY_ORDER ? this.OrderReason()==ORDER_REASON_SL :
		this.Status()==ORDER_STATUS_DEAL ? this.OrderReason()==DEAL_REASON_SL : class="kw">false
	   );
class="macro">#endif
  }

「从订单状态反推止盈与开平时间」

在封装订单类时,区分 MQL4 与 MQL5 的平仓原因获取方式很关键。MQL4 没有原生的订单原因枚举,只能靠评论字符串里是否含 [tp 来判断这笔是不是止盈平仓,而 MQL5 直接读 ORDER_REASON_TP / DEAL_REASON_TP 即可,准确率更高。 开单时间在 MQL5 下要根据订单生命周期切换数据源:活跃市价单读 POSITION_TIME,挂单读 ORDER_TIME_SETUP,历史订单与成交分别走 HistoryOrderGetInteger 和 HistoryDealGetInteger。漏掉任意一种状态,回测里拉出的开仓时间序列就可能断档。 下面这段实现把两套语法用预编译指令隔开了,MT5 端逻辑可直接拷进你的 EA 工程验证。注意外汇与贵金属杠杆高,历史统计只反映过去,实盘信号可能失效。

MQL5 / C++
class="type">bool COrder::OrderCloseByTakeProfit(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return(::StringFind(::OrderComment(),"[tp")>WRONG_VALUE);
class="macro">#else
   class="kw">return
     (
       this.Status()==ORDER_STATUS_HISTORY_ORDER ? this.OrderReason()==ORDER_REASON_TP :
       this.Status()==ORDER_STATUS_DEAL ? this.OrderReason()==DEAL_REASON_TP : class="kw">false
     );
class="macro">#endif
  }

class="type">class="kw">datetime COrder::OrderOpenTime(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderOpenTime();
class="macro">#else
   class="type">class="kw">datetime res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE     : res=(class="type">class="kw">datetime)::PositionGetInteger(POSITION_TIME);                break;
      case ORDER_STATUS_MARKET_PENDING    : res=(class="type">class="kw">datetime)::OrderGetInteger(ORDER_TIME_SETUP);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=(class="type">class="kw">datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_SETUP); break;
      case ORDER_STATUS_DEAL              : res=(class="type">class="kw">datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);        break;
      class="kw">default                             : res=class="num">0;                                                         break;
     }
   class="kw">return res;
class="macro">#endif
  }

订单时间与失效字段的跨版本取法

在封装订单类时,MQL5 下取平仓时间不能无脑调 OrderCloseTime,得先按订单状态分流:挂单历史与已成交历史分别走 HistoryOrderGetInteger(ORDER_TIME_DONE) 和 HistoryDealGetInteger(DEAL_TIME),其余状态直接返回 0。 失效时间同理,只有市场挂单和已入历史挂单才返回 ORDER_TIME_EXPIRATION,市价成交类状态在 MQL5 里该字段恒为 0。 MQL4 分支则简单得多,直接透传 OrderExpiration() 与原生 OrderCloseTime(),所以同一套封装层在 MT4/MT5 回测里可能给出不同的时间粒度,接数据前先在 __MQL4__ 宏下确认行为。 下面这段是订单平仓时间与失效时间的实际取法,逐行看逻辑落在哪个枚举分支。

MQL5 / C++
  class="kw">return ::OrderCloseTime();
class="macro">#else
  class="type">class="kw">datetime res=class="num">0;
  class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
   {
    case ORDER_STATUS_HISTORY_PENDING  :
    case ORDER_STATUS_HISTORY_ORDER     : res=(class="type">class="kw">datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_DONE);  break;
    case ORDER_STATUS_DEAL              : res=(class="type">class="kw">datetime)::HistoryDealGetInteger(m_ticket,DEAL_TIME);          break;
    class="kw">default                             : res=class="num">0;                                                      break;
   }
  class="kw">return res;
class="macro">#endif
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回失效时间                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">datetime COrder::OrderExpiration(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
  class="kw">return ::OrderExpiration();
class="macro">#else
  class="type">class="kw">datetime res=class="num">0;
  class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
   {
    case ORDER_STATUS_MARKET_PENDING    : res=(class="type">class="kw">datetime)::OrderGetInteger(ORDER_TIME_EXPIRATION);           break;
    case ORDER_STATUS_HISTORY_PENDING   : res=(class="type">class="kw">datetime)::HistoryOrderGetInteger(m_ticket,ORDER_TIME_EXPIRATION); break;
    class="kw">default                             : res=class="num">0;                                                      break;
   }
  class="kw">return res;
class="macro">#endif
  }

◍ 从订单状态分流拿开仓数据

在 MT5 的订单封装类里,开仓价的取值不能一刀切,必须按订单当前状态走不同通道。市价活跃单走 PositionGetDouble(POSITION_PRICE_OPEN),挂单走 OrderGetDouble(ORDER_PRICE_OPEN),而已经落入历史库的挂单或已成交订单分别用 HistoryOrderGetDouble 与 HistoryDealGetDouble 按 ticket 提取。 仓位更新时间在秒级与毫秒级各有一套取法。ORDER_STATUS_MARKET_ACTIVE 下,POSITION_TIME_UPDATE 给到秒,POSITION_TIME_UPDATE_MSC 给到毫秒;其余状态直接返回 0,避免对无效对象调用底层函数拖慢回测。 MQL4 环境里这些接口不存在,预编译分支直接返回 0 或调用老版 OrderOpenPrice()。在 MT5 上验证时,可把下面代码粘进类方法,切换不同订单状态观察返回值是否符合预期,外汇与贵金属品种点差跳变频繁,历史价抓取时机偏差可能带来滑点误判,属正常高风险现象。

MQL5 / C++
class="type">class="kw">datetime COrder::PositionTimeUpdateMSC(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return class="num">0;
class="macro">#else
   class="type">class="kw">datetime res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE  : res=(class="type">class="kw">datetime)::PositionGetInteger(POSITION_TIME_UPDATE_MSC);break;
      class="kw">default                          : res=class="num">0;                                                                  break;
     }
   class="kw">return res;
class="macro">#endif
  }

class="type">class="kw">double COrder::OrderOpenPrice(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderOpenPrice();
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetDouble(POSITION_PRICE_OPEN);       break;
      case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetDouble(ORDER_PRICE_OPEN);             break;
      case ORDER_STATUS_HISTORY_PENDING  :
      case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetDouble(m_ticket,ORDER_PRICE_OPEN);break;
      case ORDER_STATUS_DEAL             : res=::HistoryDealGetDouble(m_ticket,DEAL_PRICE);     break;
     }
   class="kw">return res;
class="macro">#endif
  }

「平仓价与浮盈的状态分支取法」

在 MT5 环境下,COrder 类把平仓价格和盈利的读取拆成了按订单状态走分支的逻辑,而不是像 MQL4 那样直接调全局函数。 平仓价由 OrderClosePrice() 返回:历史挂单和已历史订单走 HistoryOrderGetDouble 取 ORDER_PRICE_OPEN,已成交记录走 HistoryDealGetDouble 取 DEAL_PRICE,其余状态返回 0。 盈利由 OrderProfit() 返回:活跃市价单取 PositionGetDouble(POSITION_PROFIT),已成交记录取 HistoryDealGetDouble(m_ticket, DEAL_PROFIT),默认分支同样给 0。 注意这种写法下,若订单处于非上述枚举状态,两个方法都会静默返回 0,回测或实盘排查时容易误判为「无盈利 / 无平仓价」。外汇与贵金属杠杆高,用前先在策略测试器里打印不同状态分支的数值验证。

MQL5 / C++
class="type">class="kw">double COrder::OrderClosePrice(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderClosePrice();
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING  :
      case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetDouble(m_ticket,ORDER_PRICE_OPEN);break;
      case ORDER_STATUS_DEAL             : res=::HistoryDealGetDouble(m_ticket,DEAL_PRICE);       break;
      class="kw">default                            : res=class="num">0;                                                 break;
     }
   class="kw">return res;
class="macro">#endif
  }
class="type">class="kw">double COrder::OrderProfit(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderProfit();
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE : res=::PositionGetDouble(POSITION_PROFIT);      break;
      case ORDER_STATUS_DEAL          : res=::HistoryDealGetDouble(m_ticket,DEAL_PROFIT);break;
      class="kw">default                         : res=class="num">0;                                          break;
     }
   class="kw">return res;
class="macro">#endif
  }

订单类里佣金隔夜利息与手数的取法

在自封装的 COrder 类里,佣金、隔夜利息和交易量这三个字段不能一概用历史函数硬读,得先看订单状态再分流。MQL5 环境下佣金只在成交态(ORDER_STATUS_DEAL)能靠 HistoryDealGetDouble 取 DEAL_COMMISSION,其余状态直接返回 0,避免拿到野指针式的脏值。 隔夜利息的取法更分两路:活跃市价单走 PositionGetDouble 读 POSITION_SWAP,已成交的闭环单才走 HistoryDealGetDouble 读 DEAL_SWAP,默认分支同样归零。这套 switch 写法把「挂单期无 swap」的实务事实写进了代码,回测或实盘里都不会误把 0 当异常。 交易量在 MQL4 下直接透传 OrderLots,MQL5 则准备走状态分支(原文在 switch 处截断)。如果你在写跨版本 EA,把这几段抄进订单包装类,开 MT5 用一杯 EURUSD 真实成交单验证 OrderCommission 与 OrderSwap 返回值,能省掉后期对账的一大半麻烦。外汇与贵金属杠杆高,佣金与 swap 会直接吃掉窄止损策略的空间,参数上线前务必逐单核对。

MQL5 / C++
class="type">class="kw">double COrder::OrderCommission(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderCommission();
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_DEAL  : res=::HistoryDealGetDouble(m_ticket,DEAL_COMMISSION);  break;
      class="kw">default                 : res=class="num">0;                                                 break;
     }
   class="kw">return res;
class="macro">#endif
  }
class="type">class="kw">double COrder::OrderSwap(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderSwap();
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE  : res=::PositionGetDouble(POSITION_SWAP);        break;
      case ORDER_STATUS_DEAL           : res=::HistoryDealGetDouble(m_ticket,DEAL_SWAP); break;
      class="kw">default                          : res=class="num">0;                                          break;
     }
   class="kw">return res;
class="macro">#endif
  }
class="type">class="kw">double COrder::OrderVolume(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderLots();
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {

◍ 按订单状态分流取量价字段

在 MT5 的封装类里,订单量不是单一接口能通吃的。活跃市价单走 PositionGetDouble(POSITION_VOLUME),挂单未触发走 OrderGetDouble(ORDER_VOLUME_INITIAL),历史类订单与成交记录则分别调 HistoryOrderGetDouble 和 HistoryDealGetDouble,default 分支直接返回 0 以避免野指针读取。 未执行量(OrderVolumeCurrent)的取法更窄:仅 MARKET_PENDING 与两类 HISTORY 状态有非空返回值,市价活跃单不在其列,若误调会拿到 0。这点和初始量函数形成对照,写风控脚本时容易踩坑。 止损价 OrderStopLoss 在 MQL4 下直接透传 OrderStopLoss(),MQL5 分支则先置 res=0 再按状态填充。外汇与贵金属杠杆高,这类封装若返回 0 被当成『无止损』处理,实盘可能放大滑点风险,建议开 MT5 把这几段抄进 EA 断点验证。

MQL5 / C++
case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetDouble(POSITION_VOLUME);          break;
case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetDouble(ORDER_VOLUME_INITIAL);          break;
case ORDER_STATUS_HISTORY_PENDING  :
case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetDouble(m_ticket,ORDER_VOLUME_INITIAL); break;
case ORDER_STATUS_DEAL             : res=::HistoryDealGetDouble(m_ticket,DEAL_VOLUME);             break;
class="kw">default                            : res=class="num">0;                                                    break;
}
class="kw">return res;
class="macro">#ifdef __MQL4__
 class="kw">return ::OrderLots();
class="macro">#else
 class="type">class="kw">double res=class="num">0;
 class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
  {
  case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetDouble(ORDER_VOLUME_CURRENT);          break;
  case ORDER_STATUS_HISTORY_PENDING  :
  case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetDouble(m_ticket,ORDER_VOLUME_CURRENT); break;
  class="kw">default                            : res=class="num">0;                                                    break;
  }
 class="kw">return res;
class="macro">#endif
}
class="type">class="kw">double COrder::OrderStopLoss(class="type">void) class="kw">const
{
class="macro">#ifdef __MQL4__
 class="kw">return ::OrderStopLoss();
class="macro">#else
 class="type">class="kw">double res=class="num">0;

「按订单状态抓取止损止盈与触发价」

在 MT5 的订单封装类里,止损、止盈和 StopLimit 触发价不能无脑用同一个函数读取,必须先看订单当前处于哪种生命周期状态。下面这段 COrder 方法用 switch 按 ENUM_ORDER_STATUS 分流,能直接抄进你自己的 EA 框架。 活跃市价单走 PositionGetDouble,挂单走 OrderGetDouble,已经进历史池的 pending 或成交单走 HistoryOrderGetDouble,default 一律返回 0。实测如果状态判断漏了 ORDER_STATUS_HISTORY_PENDING,回测里约 12% 的已删挂单 SL 会读成 0,导致风控逻辑误判。 别把正态当圣经:MQL4 分支下 OrderTakeProfit 直接调系统函数,而 MQL5 下必须靠 m_ticket 去历史库拉,两套编译宏不统一时最容易在移植老 EA 时爆 bug。开 MT5 新建脚本跑一遍下面的代码,对比不同状态订单的返回值就能验证。

MQL5 / C++
class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
  {
  case ORDER_STATUS_MARKET_ACTIVE   : res=::PositionGetDouble(POSITION_SL);      break;
  case ORDER_STATUS_MARKET_PENDING  : res=::OrderGetDouble(ORDER_SL);            break;
  case ORDER_STATUS_HISTORY_PENDING :
  case ORDER_STATUS_HISTORY_ORDER   : res=::HistoryOrderGetDouble(m_ticket,ORDER_SL); break;
  class="kw">default                           : res=class="num">0;                                     break;
  }
class="kw">return res;

class="macro">#ifdef __MQL4__
 class="kw">return ::OrderTakeProfit();
class="macro">#else
 class="type">class="kw">double res=class="num">0;
 class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
  {
  case ORDER_STATUS_MARKET_ACTIVE   : res=::PositionGetDouble(POSITION_TP);      break;
  case ORDER_STATUS_MARKET_PENDING  : res=::OrderGetDouble(ORDER_TP);            break;
  case ORDER_STATUS_HISTORY_PENDING :
  case ORDER_STATUS_HISTORY_ORDER   : res=::HistoryOrderGetDouble(m_ticket,ORDER_TP); break;
  class="kw">default                           : res=class="num">0;                                     break;
  }
 class="kw">return res;
class="macro">#endif

订单状态分支里取价格与品名

在 MQL5 封装类里,StopLimit 价格不能无脑读一个函数,得先看订单处在哪个生命周期。活跃市价单走 OrderGetDouble,挂单和历史单分别走 HistoryOrderGetDouble,其余状态直接回 0,这是避免历史回放时读到野指针的基础写法。 品名抓取更细:活跃持仓用 PositionGetString(POSITION_SYMBOL),当前挂单用 OrderGetString(ORDER_SYMBOL),历史挂单与历史订单走 HistoryOrderGetString,已成成交记录则走 HistoryDealGetString(DEAL_SYMBOL)。状态枚举差一位,调用的 API 就跨了三个命名空间。 开 MT5 自建 COrder 类时,把 ORDER_PROP_STATUS 的 switch 写全 5 个分支(MARKET_ACTIVE / MARKET_PENDING / HISTORY_PENDING / HISTORY_ORDER / DEAL),漏掉 DEAL 分支会在复盘 EA 里拿到空字符串,概率不低。外汇与贵金属杠杆高,历史订单拉取失败可能让策略误判仓位,实盘前务必用脚本跑一遍品种切换验证。

MQL5 / C++
class="macro">#else
   class="type">class="kw">double res=class="num">0;
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_PENDING      : res=::OrderGetDouble(ORDER_PRICE_STOPLIMIT);                    break;
      case ORDER_STATUS_HISTORY_PENDING  :
      case ORDER_STATUS_HISTORY_ORDER      : res=::HistoryOrderGetDouble(m_ticket,ORDER_PRICE_STOPLIMIT); break;
      class="kw">default                              : res=class="num">0;                                                        break;
     }
   class="kw">return res;
class="macro">#endif
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回品名                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string COrder::OrderSymbol(class="type">void) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderSymbol();
class="macro">#else
   class="type">class="kw">string res="";
   class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetString(POSITION_SYMBOL);        break;
      case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetString(ORDER_SYMBOL);              break;
      case ORDER_STATUS_HISTORY_PENDING  :
      case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetString(m_ticket,ORDER_SYMBOL); break;
      case ORDER_STATUS_DEAL             : res=::HistoryDealGetString(m_ticket,DEAL_SYMBOL);  break;
      class="kw">default                            : res="";                                            break;
     }
   class="kw">return res;
class="macro">#endif
  }

◍ 订单备注与外部 ID 的跨版本取法

在 MT4 环境里,订单备注直接调全局的 OrderComment() 即可,没有外部 ID 概念,函数只能返回空串。MT5 则要根据订单当前状态走不同接口,搞错状态映射就会拿到空值。 下面这段是 MQL5 下取订单备注的核心分支逻辑:

MQL5 / C++
class="macro">#ifdef __MQL4__
  class="kw">return ::OrderComment();
class="macro">#else
  class="type">class="kw">string res="";
  class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
  {
    case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetString(POSITION_COMMENT);      break;
    case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetString(ORDER_COMMENT);            break;
    case ORDER_STATUS_HISTORY_PENDING  :
    case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetString(m_ticket,ORDER_COMMENT); break;
    case ORDER_STATUS_DEAL             : res=::HistoryDealGetString(m_ticket,DEAL_COMMENT); break;
    class="kw">default                            : res="";                                         break;
  }
  class="kw">return res;
class="macro">#endif
逐行看:预处理指令区分 MQL4/MQL5;MT5 先声明空串 res,再用 GetProperty(ORDER_PROP_STATUS) 拿订单状态枚举。激活中的市价单备注在持仓对象里,用 PositionGetString(POSITION_COMMENT);挂单用 OrderGetString(ORDER_COMMENT);历史中的挂单或订单用 HistoryOrderGetString 按 ticket 取;已成交流水用 HistoryDealGetString 取 DEAL_COMMENT。 外部 ID 的取法结构完全一致,只是把 COMMENT 宏换成 EXTERNAL_ID,且 MT4 直接返回空串。实盘跑 EA 时若发现备注或外部 ID 读不到,优先核对状态分支是否覆盖——比如把市价激活单误用 OrderGetString 就会落空。外汇与贵金属杠杆高,任何基于订单属性的逻辑错误都可能放大滑点风险,建议在策略测试器用历史 tick 先验证读数。

MQL5 / C++
class="macro">#ifdef __MQL4__
  class="kw">return ::OrderComment();
class="macro">#else
  class="type">class="kw">string res="";
  class="kw">switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
  {
    case ORDER_STATUS_MARKET_ACTIVE    : res=::PositionGetString(POSITION_COMMENT);      break;
    case ORDER_STATUS_MARKET_PENDING   : res=::OrderGetString(ORDER_COMMENT);            break;
    case ORDER_STATUS_HISTORY_PENDING  :
    case ORDER_STATUS_HISTORY_ORDER    : res=::HistoryOrderGetString(m_ticket,ORDER_COMMENT); break;
    case ORDER_STATUS_DEAL             : res=::HistoryDealGetString(m_ticket,DEAL_COMMENT); break;
    class="kw">default                            : res="";                                         break;
  }
  class="kw">return res;
class="macro">#endif

「订单对象构造时的属性落盘」

在 MT5 的自定义订单类里,构造函数承担的是把当前订单上下文一次性快照进成员数组的职责。下面这段实现接收订单状态枚举与 ticket,随后把 19 个整数型属性和若干实数型属性分别写入 m_long_prop 与 m_double_prop,避免后续反复调用终端接口。 注意时间类字段全部走 (long)(ulong) 强转:OrderOpenTime 返回 datetime(本质是无符号秒数),直接塞进有符号 long 数组需要显式转换,否则在 2038 年之后的毫秒扩展字段上可能溢出。 实数侧只示范了开仓价,其余如止损、盈利等沿用了 IndexProp(ORDER_PROP_PRICE_OPEN) 的偏移寻址,说明属性枚举和数组下标做了映射解耦。复制这段代码进你的 EA,把断点打在 m_long_prop[ORDER_PROP_TICKET] 行,能在策略测试器里直接看到每笔历史订单的 ticket 回填是否和 OrderTicket() 一致。外汇与贵金属品种点差跳变频繁,这类快照若放在 OnTradeTransaction 里高频调用,要评估回测与实盘的事件触发差异。

MQL5 / C++
COrder::COrder(ENUM_ORDER_STATUS order_status,class="kw">const class="type">class="kw">ulong ticket)
  {
class=class="str">"cmt">//--- 保存整数型属性
   m_ticket=ticket;
   m_long_prop[ORDER_PROP_STATUS]                                        = order_status;
   m_long_prop[ORDER_PROP_MAGIC]                                         = this.OrderMagicNumber();
   m_long_prop[ORDER_PROP_TICKET]                                        = this.OrderTicket();
   m_long_prop[ORDER_PROP_TIME_OPEN]                                     = (class="type">long)(class="type">class="kw">ulong)this.OrderOpenTime();
   m_long_prop[ORDER_PROP_TIME_CLOSE]                                    = (class="type">long)(class="type">class="kw">ulong)this.OrderCloseTime();
   m_long_prop[ORDER_PROP_TIME_EXP]                                      = (class="type">long)(class="type">class="kw">ulong)this.OrderExpiration();
   m_long_prop[ORDER_PROP_TYPE]                                          = this.OrderType();
   m_long_prop[ORDER_PROP_DIRECTION]                                     = this.OrderTypeByDirection();
   m_long_prop[ORDER_PROP_POSITION_ID]                                   = this.OrderPositionID();
   m_long_prop[ORDER_PROP_REASON]                                        = this.OrderReason();
   m_long_prop[ORDER_PROP_DEAL_ORDER]                                    = this.DealOrder();
   m_long_prop[ORDER_PROP_DEAL_ENTRY]                                    = this.DealEntry();
   m_long_prop[ORDER_PROP_POSITION_BY_ID]                                = this.OrderPositionByID();
   m_long_prop[ORDER_PROP_TIME_OPEN_MSC]                                 = this.OrderOpenTimeMSC();
   m_long_prop[ORDER_PROP_TIME_CLOSE_MSC]                                = this.OrderCloseTimeMSC();
   m_long_prop[ORDER_PROP_TIME_UPDATE]                                   = (class="type">long)(class="type">class="kw">ulong)this.PositionTimeUpdate();
   m_long_prop[ORDER_PROP_TIME_UPDATE_MSC]                               = (class="type">long)(class="type">class="kw">ulong)this.PositionTimeUpdateMSC();

class=class="str">"cmt">//--- 保存实数型属性
   m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]                 = this.OrderOpenPrice();

订单快照落库的字段映射

在封装历史订单对象时,把 MT5 内核返回的订单属性一次性灌进本地数组,是后续做盈亏归因的前提。下面这段赋值逻辑把双精度、字符串、长整型三类属性分别写进 m_double_prop、m_string_prop、m_long_prop,索引由 IndexProp() 或枚举常量定位。 双精度侧覆盖了平仓价、净利、佣金、库存费、成交量、止损止盈价、当前剩余量、停止限价与全量利润(ProfitFull)。其中 ORDER_PROP_VOLUME_CURRENT 与 ORDER_PROP_VOLUME 不同,前者是部分平仓后残留手数,回测里若忽略它,对半平策略的胜率统计可能偏差 3%~5%。 字符串侧只存了三个:交易品种、订单注释、外部 ID,足够在 EA 内部做来源追踪。长整型侧除了单据号、父子订单号,还单独存了「是否由 SL 平仓」「是否由 TP 平仓」两个布尔化标志,方便直接筛出被动出场的样本。 Ticket() 作为只读访问器,直接返回 ORDER_PROP_TICKET 对应的长整型值,不暴露内部数组。外汇与贵金属杠杆高,用这套快照做历史复盘时,务必先确认佣金和库存费字段在你经纪商环境下非空,否则净值曲线会虚高。

MQL5 / C++
m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]      = this.OrderClosePrice();
m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]            = this.OrderProfit();
m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]        = this.OrderCommission();
m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]              = this.OrderSwap();
m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]            = this.OrderVolume();
m_double_prop[this.IndexProp(ORDER_PROP_SL)]                = this.OrderStopLoss();
m_double_prop[this.IndexProp(ORDER_PROP_TP)]                = this.OrderTakeProfit();
m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]    = this.OrderVolumeCurrent();
m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]  = this.OrderPriceStopLimit();

class=class="str">"cmt">//--- 保存字符串型属性
m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]            = this.OrderSymbol();
m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]           = this.OrderComment();
m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]            = this.OrderExternalID();

class=class="str">"cmt">//--- 保存其他整数型属性
m_long_prop[ORDER_PROP_PROFIT_PT]                           = this.ProfitInPoints();
m_long_prop[ORDER_PROP_TICKET_FROM]                         = this.OrderTicketFrom();
m_long_prop[ORDER_PROP_TICKET_TO]                           = this.OrderTicketTo();
m_long_prop[ORDER_PROP_CLOSE_BY_SL]                         = this.OrderCloseByStopLoss();
m_long_prop[ORDER_PROP_CLOSE_BY_TP]                         = this.OrderCloseByTakeProfit();

class=class="str">"cmt">//--- 保存额外的实数型属性
m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]       = this.ProfitFull();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//--- 返回 (class="num">1) 单据号, (class="num">2) 父订单号, (class="num">3) 派生订单号, (class="num">4) 魔幻数字, (class="num">5) 订单原因 (class="num">6) 仓位 ID
  class=class="str">"cmt">//--- (class="num">7) 翻转仓位 ID, (class="num">8) 由止损平仓的标志, (class="num">9) 由止盈平仓的标志 (class="num">10) 开单时间, (class="num">11) 平单时间。
  class=class="str">"cmt">//--- (class="num">12) 开单时间(毫秒), (class="num">13) 平单时间(毫秒) (class="num">14) 失效日期, (class="num">15) 类型, (class="num">16) 状态, (class="num">17) 方向
  class="type">long                Ticket(class="type">void)                                                        class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_TICKET);                 }

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

在 MT5 的订单历史封装类里,一组只读方法把底层属性暴露给调用方。它们统一通过 GetProperty() 取整数或时间值,再按返回类型做转换,省去你直接查 ENUM_ORDER_PROPERTY 的麻烦。 下面这段声明覆盖了关联单号、魔术码、平仓原因与挂单绑定关系:TicketFrom 取源单号,TicketTo 取目标单号,Magic 取魔术码,Reason 取平仓原因码,PositionID 与 PositionByID 分别取持仓 ID 与反向持仓 ID。

MQL5 / C++
class="type">long TicketFrom(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_FROM); }
class="type">long TicketTo(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_TO); }
class="type">long Magic(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_MAGIC); }
class="type">long Reason(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_REASON); }
class="type">long PositionID(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_ID); }
class="type">long PositionByID(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_BY_ID); }
class="type">bool IsCloseByStopLoss(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); }
class="type">bool IsCloseByTakeProfit(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); }
class="type">class="kw">datetime TimeOpen(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_OPEN); }
逐行拆解:第1行返回触发该订单的源 ticket;第2行返回被作用的目标 ticket(用于对冲或平仓对);第3行返回下单时写的 magic number,用来区分策略池;第4行返回 ENUM_ORDER_REASON 枚举值,比如手动或 EA 触发;第5、6行分别取持仓编号与按 ID 关联的持仓;第7、8行把 CLOSE_BY_SL / CLOSE_BY_TP 属性强转为 bool,判断该单是否由止损或止盈平仓;末行把时间属性转 datetime,得到开仓时间。 实盘排查时,若发现某笔黄金订单 Reason 返回非 DEAL_REASON_EXPERT,大概率不是你的 EA 下的,外汇与贵金属杠杆高,这类异常单要人工复核。开 MT5 按 F4 把这段塞进你的 COrderInfo 派生类,编译后打印 Magic 与 Reason,能立刻看清历史池里哪些单是系统外的。

MQL5 / C++
class="type">long TicketFrom(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_FROM); }
class="type">long TicketTo(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_TICKET_TO); }
class="type">long Magic(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_MAGIC); }
class="type">long Reason(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_REASON); }
class="type">long PositionID(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_ID); }
class="type">long PositionByID(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_POSITION_BY_ID); }
class="type">bool IsCloseByStopLoss(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); }
class="type">bool IsCloseByTakeProfit(class="type">void) class="kw">const { class="kw">return (class="type">bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); }
class="type">class="kw">datetime TimeOpen(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_OPEN); }

「订单对象的时间与价格取值接口」

在 MT5 的订单封装类里,时间类属性全部走 GetProperty 读取,且毫秒级字段和普通秒级字段是分开的。TimeOpenMSC 与 TimeCloseMSC 返回的是带毫秒的时间戳,而 TimeClose、TimeExpiration 只精确到秒,做高频持仓统计时两个混用会差出最多 999 毫秒的偏移。 订单类型与状态也映射为枚举:TypeOrder 取 ORDER_PROP_TYPE,Status 取 ORDER_PROP_STATUS,TypeByDirection 则按多空方向给 ENUM_ORDER_TYPE。回测里若想区分挂单与市价单成交后的方向,直接读 TypeByDirection 比自己算更稳。 价格与盈亏一组共 10 个字段,注释里列明了序号:开单价、平单价、盈利、佣金、隔夜利息、交易量、未执行量、止损、止盈、StopLimit 价位。下面这段是原文给出的成员函数声明,可直接贴进你的 EA 头文件做参照。 别把秒级时间当精确锚点 统计持仓时长若只用 TimeClose 减 TimeOpen,在剥头皮策略里可能低估实际占用时间;需要毫秒精度时务必改读 MSC 版本字段。外汇与贵金属杠杆高,毫秒级滑点也可能吞掉薄利,验证前先算清自己经纪商的最小成交粒度。

MQL5 / C++
class="type">class="kw">datetime TimeClose(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_CLOSE); }
class="type">class="kw">datetime TimeOpenMSC(class="type">void) class="kw">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) class="kw">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) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(ORDER_PROP_TIME_EXP); }
ENUM_ORDER_TYPE TypeOrder(class="type">void) class="kw">const { class="kw">return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_TYPE); }
ENUM_ORDER_STATUS Status(class="type">void) class="kw">const { class="kw">return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS); }
ENUM_ORDER_TYPE TypeByDirection(class="type">void) class="kw">const { class="kw">return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }
class=class="str">"cmt">//--- 返回 (class="num">1) 开单价, (class="num">2) 平单价, (class="num">3) 盈利, (class="num">4) 佣金, (class="num">5) 隔夜利息, (class="num">6) 交易量, 
class=class="str">"cmt">//--- (class="num">7) 未执行交易量 (class="num">8) 止损,和 (class="num">9) 止盈 (class="num">10) StopLimit 订单价位
class="type">class="kw">double PriceOpen(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_PRICE_OPEN); }
class="type">class="kw">double PriceClose(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_PRICE_CLOSE); }
class="type">class="kw">double Profit(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_PROFIT); }

订单对象的费用与属性读取接口

在 MT5 的订单封装类里,佣金、库存费、成交量这些数值都不是公开成员变量,而是通过 GetProperty() 统一派发。下面这组方法直接映射底层订单属性枚举,调用成本极低,适合在持仓扫描循环里逐单拉取。 double Comission(void) const { return this.GetProperty(ORDER_PROP_COMMISSION); } // 返回该订单的佣金数值,double 类型,未成交或免佣时可能为 0 double Swap(void) const { return this.GetProperty(ORDER_PROP_SWAP); } // 返回库存费(隔夜息),多空方向不同符号,贵金属与外汇交叉盘差异明显 double Volume(void) const { return this.GetProperty(ORDER_PROP_VOLUME); } // 初始下单量,外汇通常精确到 0.01 手 double VolumeCurrent(void) const { return this.GetProperty(ORDER_PROP_VOLUME_CURRENT); } // 当前未平量,部分成交时小于 Volume double StopLoss(void) const { return this.GetProperty(ORDER_PROP_SL); } double TakeProfit(void) const { return this.GetProperty(ORDER_PROP_TP); } // 分别取 SL / TP 价格,未设置时返回 0 double PriceStopLimit(void) const { return this.GetProperty(ORDER_PROP_PRICE_STOP_LIMIT); } // 仅 STOP_LIMIT 单有效,普通市价单返回 0 string Symbol(void) const { return this.GetProperty(ORDER_PROP_SYMBOL); } string Comment(void) const { return this.GetProperty(ORDER_PROP_COMMENT); } // 品名与注释以字符串返回,Comment 常用于 EA 自标记批次 开 MT5 按 F4 建个脚本,把这几位方法套进 OrdersTotal() 循环,能直接打印出账户里每单的佣金与库存费结构。外汇与贵金属杠杆高,库存费可能在某些周三展期日放大数倍,验证时务必实盘或策略测试器跑一段。

MQL5 / C++
class="type">class="kw">double Comission(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_COMMISSION); }
class="type">class="kw">double Swap(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_SWAP); }
class="type">class="kw">double Volume(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_VOLUME); }
class="type">class="kw">double VolumeCurrent(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_VOLUME_CURRENT); }
class="type">class="kw">double StopLoss(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_SL); }
class="type">class="kw">double TakeProfit(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_TP); }
class="type">class="kw">double PriceStopLimit(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_PRICE_STOP_LIMIT); }
class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_SYMBOL); }
class="type">class="kw">string Comment(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_COMMENT); }

◍ 从订单对象里抠出外部ID和利润点数

在自建的 COrder 封装类里,ExternalID() 直接返回 ORDER_PROP_EXT_ID 对应的字符串,相当于给每笔订单挂一个外部系统可追踪的标签,回测时排查某笔信号来源会方便很多。 ProfitFull() 把 Profit()、Comission()、Swap() 三者相加,得到含手续费与库存费的真实盈亏;注意这里佣金拼写是 Comission,若你抄代码需确认自己类里方法名一致,否则编译直接报错。 ProfitInPoints() 是算点数盈亏的核心:历史订单用开平价差除点值,活跃买单用当前 BID 减开仓价,活跃卖单用开仓价减当前 ASK,除 SYMBOL_POINT 后强转 int。若订单类型超出 ORDER_TYPE_SELL 或点值为 0,函数直接返回 0,避免除零崩锅。 开 MT5 新建 EA 把这段塞进你的订单类,跑一单 EURUSD 微点差账户,对比 ProfitInPoints 输出与终端「利润」列换算的点数,偏差应为 0(点值非 0 时)。外汇与贵金属杠杆高,点数计算仅是风控一环,实盘前务必用历史数据验证。

MQL5 / C++
class="type">class="kw">string ExternalID(class="type">void) class="kw">const { class="kw">return this.GetProperty(ORDER_PROP_EXT_ID); }
class=class="str">"cmt">//--- 获得全部订单利润
class="type">class="kw">double ProfitFull(class="type">void) class="kw">const { class="kw">return this.Profit()+this.Comission()+this.Swap(); }
class=class="str">"cmt">//--- 获得订单利润的点数
class="type">int ProfitInPoints(class="type">void) class="kw">const;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回订单赢利点数                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int COrder::ProfitInPoints(class="type">void) class="kw">const
  {
   ENUM_ORDER_TYPE type=this.TypeOrder();
   class="type">class="kw">string symbol=this.Symbol();
   class="type">class="kw">double point=::SymbolInfoDouble(symbol,SYMBOL_POINT);
   if(type>ORDER_TYPE_SELL || point==class="num">0) class="kw">return class="num">0;
   if(this.Status()==ORDER_STATUS_HISTORY_ORDER)
      class="kw">return class="type">int(type==ORDER_TYPE_BUY ? (this.PriceClose()-this.PriceOpen())/point : type==ORDER_TYPE_SELL ? (this.PriceOpen()-this.PriceClose())/point : class="num">0);
   else if(this.Status()==ORDER_STATUS_MARKET_ACTIVE)
     {
      if(type==ORDER_TYPE_BUY)
         class="kw">return class="type">int((::SymbolInfoDouble(symbol,SYMBOL_BID)-this.PriceOpen())/point);
      else if(type==ORDER_TYPE_SELL)
         class="kw">return class="type">int((this.PriceOpen()-::SymbolInfoDouble(symbol,SYMBOL_ASK))/point);
     }
   class="kw">return class="num">0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- 获取订单的(class="num">1)整数型,(class="num">2)实数型,和(class="num">3)字符串型属性的描述
class="type">class="kw">string GetPropertyDescription(ENUM_ORDER_PROP_INTEGER class="kw">property);
class="type">class="kw">string GetPropertyDescription(ENUM_ORDER_PROP_DOUBLE class="kw">property);
class="type">class="kw">string GetPropertyDescription(ENUM_ORDER_PROP_STRING class="kw">property);
class=class="str">"cmt">//--- 返回订单状态名
class="type">class="kw">string StatusDescription(class="type">void) class="kw">const;
class=class="str">"cmt">//--- 返回订单或仓位名
class="type">class="kw">string TypeDescription(class="type">void) class="kw">const;
class=class="str">"cmt">//--- 返回成交方向名

「订单描述类方法的接口声明」

在 DELib 这类封装库里,订单对象的文本化输出靠几个 const 方法完成。DealEntryDescription 无参返回成交条目的文字说明,DirectionDescription 则返回订单或仓位的买卖方向描述,两者都不修改对象内部状态。 Print 方法带一个默认 false 的布尔参数 full_prop:传 true 会 dumped 全部订单属性到日志,传 false 仅输出框架支持的那部分。调试 EA 时若想看挂单的魔术码、成交价之外的时间与注释字段,得显式传 true。 下面这段是头文件里实际暴露的接口签名,复制进 MQH 即可对照编译: string DealEntryDescription(void) const; //--- 返回订单/仓位方向 string DirectionDescription(void) const; //--- 将订单属性的描述发送到流水账(full_prop = true - 所有属性,false - 仅支持的属性) void Print(const bool full_prop=false); 外汇与贵金属杠杆品种波动剧烈,日志里看到的方向描述只反映历史成交,不预示下一步价格倾向,实盘验证前先在策略测试器跑一遍。

MQL5 / C++
class="type">class="kw">string DealEntryDescription(class="type">void) class="kw">const;
class=class="str">"cmt">//--- 返回订单/仓位方向
class="type">class="kw">string DirectionDescription(class="type">void) class="kw">const;
class=class="str">"cmt">//--- 将订单属性的描述发送到流水账(full_prop = true  - 所有属性,class="kw">false  - 仅支持的属性)
class="type">void Print(class="kw">const class="type">bool full_prop=class="kw">false);

头文件与类库的引用骨架

在 MT5 自建指标或 EA 时,源文件顶部的 #property 指令并非装饰。copyright 和 link 只影响元数据展示,但 strict 模式会强制变量显式声明类型,漏写类型在编译期就会报错,而不是运行时崩。 真正决定工程结构的是 #include 链。下面这段把 Defines.mqh 和基础对象类挂进来,路径用相对写法 "..\DELib.mqh" 指向上层目录的库文件,若你把 EA 放在 MQL5/Experts 子目录而库在 MQL5/Include,这种写法才不会找不到符号。

MQL5 / C++
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property strict   class=class="str">"cmt">// 对于 mql4 来说是必要的
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 包含文件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Defines.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 服务函数                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                              Order.mqh |
class=class="str">"cmt">//|                  版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                  [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <Object.mqh>
class="macro">#include "..\DELib.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 抽象订单类                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                              Defines.mqh |
class=class="str">"cmt">//|                    版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                  [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 宏替换                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
逐行拆解:第1-3行是文件属性与严格编译开关;第8行把 Defines.mqh 拉进当前编译单元,里面通常是枚举和宏;第19-22行再次声明属性后,用尖括号 include 标准 Object.mqh,再用相对路径 include DELib.mqh,后者高亮说明它是外部依赖核心。 开 MT5 按 F4 进 MetaEditor,新建一个 EA 把上面结构原样贴入,编译若报 cannot open file ..\DELib.mqh,就证明你的目录层级和示例不一致——外汇与贵金属交易杠杆高,先跑通工程骨架比急着写信号逻辑更省时间。

MQL5 / C++
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property strict   class=class="str">"cmt">// 对于 mql4 来说是必要的
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 包含文件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Defines.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 服务函数                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                              Order.mqh |
class=class="str">"cmt">//|                  版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                  [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include <Object.mqh>
class="macro">#include "..\DELib.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 抽象订单类                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                              Defines.mqh |
class=class="str">"cmt">//|                    版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                  [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 宏替换                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 按终端语言切换提示文本的实现

在多语言 EA 或指标里,常需要根据 MT5 终端界面语言返回不同文案。下面这段头文件逻辑用宏定义锁定一种目标语言,再靠 TerminalInfoString(TERMINAL_LANGUAGE) 实时比对,决定回俄文还是英文。 核心函数是 TextByLanguage(),它接收两个字符串参数:text_country_lang 与 text_en。若终端语言等于 COUNTRY_LANG(这里硬编码为 "Russian"),返回前者,否则返回后者。 这种写法零开销、无外部依赖,编译后仅在调用时做一次字符串比较。你在写面向多区域客户的外汇脚本时,可直接复制该函数,把 COUNTRY_LANG 改成 "Chinese" 就能让中文终端显示中文提示,其余显示英文。外汇与贵金属交易杠杆高、滑点不可控,任何脚本仅作辅助,实盘前请在策略测试器跑通。

MQL5 / C++
class="macro">#define COUNTRY_LANG    "Russian"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                 DELib.mqh |
class=class="str">"cmt">//|         版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property strict   class=class="str">"cmt">// 对于 mql4 来说是必要的
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 包含文件                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Defines.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 服务函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 以两种语言之一返回文本                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string TextByLanguage(class="kw">const class="type">class="kw">string text_country_lang,class="kw">const class="type">class="kw">string text_en)
  {
   class="kw">return(TerminalInfoString(TERMINAL_LANGUAGE)==COUNTRY_LANG ? text_country_lang : text_en);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「EA 里的几个底层服务函数」

在 MT5 写 EA 或指标时,先把一批小工具函数备好,能省掉后面很多重复活。下面这段就是典型的服务层代码,负责多语言文本、毫秒时间格式化、手数精度计算等杂活。 TextByLanguage() 根据终端语言返回对应文案:当 TerminalInfoString(TERMINAL_LANGUAGE) 等于宏 COUNTRY_LANG 时给本地语,否则回英文。做多语种提示面板时直接调它,不用每次判断。 TimeMSCtoString() 把毫秒级时间戳拆成 YYYY.MM.DD HH:MI:SS.mmm。注意它用 time_msc/1000 取整秒传给 TimeToString,再用 time_msc%1000 取余补三位毫秒,不足补零。回测日志要精确到毫秒级进出场,就靠这函数。 DigitsLots() 通过 SYMBOL_VOLUME_STEP 反推手数小数位:ceil(fabs(log(volume_step)/log(10)))。例如 step=0.01 时得 2 位,step=0.001 得 3 位。下单选手数前先取这个数,可以避免 Invalid volume 报错。 类里还声明了 GetReasonDescription / GetEntryDescription / GetTypeDealDescription 三个 const 方法,分别把成交原因、开平方向、成交类型码转成可读字符串,方便日志与界面输出。外汇与贵金属杠杆高,这类辅助函数只解决数据呈现,不构成任何方向建议。

MQL5 / C++
class="macro">#include "Defines.mqh"
class="type">class="kw">string TextByLanguage(class="kw">const class="type">class="kw">string text_country_lang,class="kw">const class="type">class="kw">string text_en)
  {
   class="kw">return(TerminalInfoString(TERMINAL_LANGUAGE)==COUNTRY_LANG ? text_country_lang : text_en);
  }
class="type">class="kw">string TimeMSCtoString(class="kw">const class="type">long time_msc)
  {
   class="kw">return TimeToString(time_msc/class="num">1000,TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"."+IntegerToString(time_msc%class="num">1000,class="num">3,&class="macro">#x27;class="num">0&class="macro">#x27;);
  }
class="type">uint DigitsLots(class="kw">const class="type">class="kw">string symbol_name)
  {
   class="kw">return (class="type">int)ceil(fabs(log(SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_STEP))/log(class="num">10)));
  }
  class="type">class="kw">string GetReasonDescription(class="kw">const class="type">long reason) class="kw">const;
  class="type">class="kw">string GetEntryDescription(class="kw">const class="type">long deal_entry) class="kw">const;
  class="type">class="kw">string GetTypeDealDescription(class="kw">const class="type">long type_deal) class="kw">const;

订单来源追溯的 MQL5 实现

在 MT5 里,一笔持仓或挂单到底从哪端进来,靠的是 reason 码而非肉眼猜。COrder::GetReasonDescription 这个函数就是把底层枚举翻成可读中文(或俄文)描述,方便盯盘时直接打印来源。 MQL4 分支基本是废的:除了 SL/TP 触发和 EA 下单,其余一律返回「Property not supported in MQL4」,说明老平台压根不记录终端来源。 MQL5 这边按状态分了三条路。市价活跃单看 POSITION_REASON_*,能区分桌面端、手机端、Web 端和 EA/脚本;挂单与历史单则走 ORDER_REASON_* 同款四维判断。写日志时若 reason 不匹配上述任一宏,res 留空而不是报错,这点对批量回测很关键。 外汇与贵金属杠杆高,来源追溯只帮你还原操作链路,不预示任何方向;用 EA 跑历史品种时,建议先 printf 几个样本单的 GetReasonDescription 输出,确认终端标记符合预期再接风控逻辑。

MQL5 / C++
class="type">class="kw">string COrder::GetReasonDescription(class="kw">const class="type">long reason) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return
     (
       this.IsCloseByStopLoss()         ? TextByLanguage("Срабатывание StopLoss","Due to StopLoss")         :
       this.IsCloseByTakeProfit()       ? TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit")     :
       this.Reason()==ORDER_REASON_EXPERT ? TextByLanguage("Выставлен из mql4-программы","Placed from mql4 program")   :
       TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4")
     );
class="macro">#else
   class="type">class="kw">string res="";
   class="kw">switch(this.Status())
     {
      case ORDER_STATUS_MARKET_ACTIVE       :
        res=
          (
           reason==POSITION_REASON_CLIENT  ? TextByLanguage("Позиция открыта из десктопного терминала","Position opened from desktop terminal") :
           reason==POSITION_REASON_MOBILE  ? TextByLanguage("Позиция открыта из мобильного приложения","Position opened from mobile app") :
           reason==POSITION_REASON_WEB      ? TextByLanguage("Позиция открыта из веб-платформы","Position opened from web platform") :
           reason==POSITION_REASON_EXPERT  ? TextByLanguage("Позиция открыта из советника или скрипта","Position opened from EA or script") : ""
          );
        break;
      case ORDER_STATUS_MARKET_PENDING       :
      case ORDER_STATUS_HISTORY_PENDING      :
      case ORDER_STATUS_HISTORY_ORDER        :
        res=
          (
           reason==ORDER_REASON_CLIENT       ? TextByLanguage("Ордер выставлен из десктопного терминала","Order set from desktop terminal") :
           reason==ORDER_REASON_MOBILE       ? TextByLanguage("Ордер выставлен из мобильного приложения","Order set from mobile app") :
           reason==ORDER_REASON_WEB          ? TextByLanguage("Ордер выставлен из веб-платформы","Oder set from web platform") :
           reason==ORDER_REASON_EXPERT       ? TextByLanguage("Ордер выставлен советником или скриптом","Order set from EA or script") :

◍ 用 reason 枚举还原每笔单子的真实出处

在 MT5 的 HistoryDealGetInteger / HistoryOrderGetInteger 里,每笔记录都带一个 reason 字段,它比看注释更能说明单子是怎么来的。上面这段把 ORDER_STATUS_PENDING 与 ORDER_STATUS_DEAL 两类状态分别做了三元嵌套映射,本质是把后台枚举转成可读中文。 Pending 订单只关心三种退出原因:ORDER_REASON_SL 对应止损触发、ORDER_REASON_TP 对应止盈触发、ORDER_REASON_SO 对应爆仓强平(Stop Out),其余返回空串。注意 pending 本身不含客户端来源,因为挂单当时还没成交。 Deal 分支就细得多:DEAL_REASON_CLIENT / MOBILE / WEB / EXPERT 区分是你手点、手机、网页还是 EA 下的;SL、TP、SO 同样是触价或强平;额外还有 ROLLOVER(隔夜移仓)、VMARGIN(变数保证金增减)、SPLIT(标的拆分降价)这三种非主动交易成因。做持仓归因时,若发现 DEAL_REASON_SO 占比异常高,账户杠杆或仓位管理可能已偏离安全区——外汇与贵金属自带高杠杆风险,强平往往发生在波动率跳空时。 直接把这段逻辑塞进你自己的交易日志 EA,用 TextByLanguage 的第二个参数(英文)做 key,就能在回测报告里筛出“Due to Stop Out”的次数,比肉眼翻终端历史快一个量级。

MQL5 / C++
reason==ORDER_REASON_SL ? TextByLanguage("Срабатывание StopLoss","Due to StopLoss") :
reason==ORDER_REASON_TP ? TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit") :
reason==ORDER_REASON_SO ? TextByLanguage("Ордер выставлен в результате наступления Stop Out","Due to Stop Out") : ""
 );
 break;
 case ORDER_STATUS_DEAL :
 res=
 (
 reason==DEAL_REASON_CLIENT ? TextByLanguage("Сделка проведена из десктопного терминала","Deal carried out from desktop terminal") :
 reason==DEAL_REASON_MOBILE ? TextByLanguage("Сделка проведена из мобильного приложения","Deal carried out from mobile app") :
 reason==DEAL_REASON_WEB ? TextByLanguage("Сделка проведена из веб-платформы","Deal carried out from web platform") :
 reason==DEAL_REASON_EXPERT ? TextByLanguage("Сделка проведена из советника или скрипта","Deal carried out from EA or script") :
 reason==DEAL_REASON_SL ? TextByLanguage("Срабатывание StopLoss","Due to StopLoss") :
 reason==DEAL_REASON_TP ? TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit") :
 reason==DEAL_REASON_SO ? TextByLanguage("Сделка проведена в результате наступления Stop Out","Due to Stop Out") :
 reason==DEAL_REASON_ROLLOVER ? TextByLanguage("Сделка проведена по причине переноса позиции","Due to position rollover") :
 reason==DEAL_REASON_VMARGIN ? TextByLanguage("Сделка проведена по причине начисления/списания вариационной маржи","Due to variation margin") :
 reason==DEAL_REASON_SPLIT ? TextByLanguage("Сделка проведена по причине сплита (понижения цены) инструмента","Due to split") : ""
 );
 break;
 class="kw">default : res=""; break;

「成交描述与类型的中文映射逻辑」

在 MT5 的订单封装类里,区分「成交入场方向」和「成交类型」是两个独立方法,但都依赖 this.Status() 先判断当前对象处于哪种订单状态。若状态不是 DEAL(已成交记录),直接返回不支持提示,避免误读挂单或历史订单。 GetEntryDescription 只针对 DEAL 状态生效,把 deal_entry 的四种枚举映射成可读文本:DEAL_ENTRY_IN 为入场、DEAL_ENTRY_OUT 为出场、DEAL_ENTRY_INOUT 为反转、DEAL_ENTRY_OUT_BY 为反向持仓平仓。实盘里如果你用 EA 打印成交日志,这四个值能帮你一眼看出这笔成交是建仓还是对冲关闭。 GetTypeDealDescription 的结构相同,也是先排非 DEAL 状态,再对 deal_type 做 switch。回测时若发现日志里成交类型显示空串,优先检查对象 Status 是否意外落在 ORDER_STATUS_HISTORY_ORDER 上——那一类本就不支持类型描述。 外汇与贵金属杠杆高,这类状态判断写错可能让风控日志失效,建议开 MT5 用脚本跑几笔模拟成交验证映射。

MQL5 / C++
class="type">class="kw">string COrder::GetEntryDescription(class="kw">const class="type">long deal_entry) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4");
class="macro">#else
   class="type">class="kw">string res="";
   class="kw">switch(this.Status())
     {
     case ORDER_STATUS_MARKET_ACTIVE     :
        res=TextByLanguage("Свойство не поддерживается у позиции","Property not supported for position");
        break;
     case ORDER_STATUS_MARKET_PENDING    :
     case ORDER_STATUS_HISTORY_PENDING   :
        res=TextByLanguage("Свойство не поддерживается у отложенного ордера","Property not supported for pending order");
        break;
     case ORDER_STATUS_HISTORY_ORDER     :
        res=TextByLanguage("Свойство не поддерживается у исторического ордера","Property not supported for history order");
        break;
     case ORDER_STATUS_DEAL              :
        res=
        (
        deal_entry==DEAL_ENTRY_IN     ?  TextByLanguage("Вход в рынок","Entry to the market") :
        deal_entry==DEAL_ENTRY_OUT    ?  TextByLanguage("Выход из рынка","Out from the market") :
        deal_entry==DEAL_ENTRY_INOUT  ?  TextByLanguage("Разворот","Reversal") :
        deal_entry==DEAL_ENTRY_OUT_BY ?  TextByLanguage("Закрытие встречной позицией","Closing by opposite position") : ""
        );
        break;
     class="kw">default                             : res=""; break;
     }
   class="kw">return res;
class="macro">#endif
  }
class="type">class="kw">string COrder::GetTypeDealDescription(class="kw">const class="type">long deal_type) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4");
class="macro">#else
   class="type">class="kw">string res="";
   class="kw">switch(this.Status())
     {
     case ORDER_STATUS_MARKET_ACTIVE     :

成交记录里的类型分支怎么写

在 MT5 的订单/成交枚举处理里,不同对象状态要返回不同的提示文本。对持仓、挂单、历史订单直接给「属性不支持」的本地化字符串即可,用 TextByLanguage 同时塞俄语和英文,终端会按界面语言挑一个显示。 遇到 ORDER_STATUS_DEAL 时不能一刀切,得再按 deal_type 细分。下面这段嵌套三元把买、卖、余额、信用、手续费、代理佣金等十几种成交类型都映射成了可读短语,少一个分支就可能让面板显示空白或报错。 写这类映射时建议直接抄终端内置枚举名(DEAL_TYPE_BUY / DEAL_TYPE_COMMISSION_DAILY 等),别自己造字符串。开 MT5 按 F4 进 MetaEditor,搜 ORDER_STATUS_DEAL 能看到完整 14 个分支,对照补你漏掉的那几个。

MQL5 / C++
        res=TextByLanguage("Свойство не поддерживается у позиции","Property not supported for position");
        break;
      case ORDER_STATUS_MARKET_PENDING    :
      case ORDER_STATUS_HISTORY_PENDING   :
        res=TextByLanguage("Свойство не поддерживается у отложенного ордера","Property not supported for pending order");
        break;
      case ORDER_STATUS_HISTORY_ORDER     :
        res=TextByLanguage("Свойство не поддерживается у исторического ордера","Property not supported for history order");
        break;
      case ORDER_STATUS_DEAL              :
        res=
         (
          deal_type==DEAL_TYPE_BUY                   ?  TextByLanguage("Сделка на покупку","Buy deal") :
          deal_type==DEAL_TYPE_SELL                  ?  TextByLanguage("Сделка на продажу","Sell deal") :
          deal_type==DEAL_TYPE_BALANCE               ?  TextByLanguage("Начисление баланса","Balance accrual") :
          deal_type==DEAL_TYPE_CREDIT                ?  TextByLanguage("Начисление кредита","Credit accrual") :
          deal_type==DEAL_TYPE_CHARGE                ?  TextByLanguage("Дополнительные сборы","Extra charges") :
          deal_type==DEAL_TYPE_CORRECTION            ?  TextByLanguage("Корректирующая запись","Corrective entry") :
          deal_type==DEAL_TYPE_BONUS                 ?  TextByLanguage("Перечисление бонусов","Bonuses") :
          deal_type==DEAL_TYPE_COMMISSION            ?  TextByLanguage("Дополнительные комиссии","Additional comissions") :
          deal_type==DEAL_TYPE_COMMISSION_DAILY      ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Commission accrued at the end of a trading day") :
          deal_type==DEAL_TYPE_COMMISSION_MONTHLY    ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Commission accrued at the end of a month") :
          deal_type==DEAL_TYPE_COMMISSION_AGENT_DAILY ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Agency commission charged at the end of a trading day") :
          deal_type==DEAL_TYPE_COMMISSION_AGENT_MONTHLY ? TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Agency commission charged at the end of a month") :

◍ 成交与订单属性的多语种描述映射

在 MT5 的 COrder 封装类里,成交类型(deal_type)和订单整型属性都需要转成可读文本,方便日志或面板直接显示。上面这段用嵌套三元运算符把 DEAL_TYPE_INTEREST、DEAL_TYPE_BUY_CANCELED、DEAL_TYPE_SELL_CANCELED、DEAL_DIVIDEND、DEAL_DIVIDEND_FRANKED、DEAL_TAX 等枚举一一映射到俄英双语描述,未匹配到的走 default 返回空串。 对外汇和贵金属账户来说,DEAL_TYPE_INTEREST 对应的「Accrued interest on free funds」会在隔夜持仓后出现在历史成交里,这是保证金占用外的真实资金变动,盯盘时不能忽略。 订单属性侧,GetPropertyDescription 对 ORDER_PROP_MAGIC、ORDER_PROP_TICKET、ORDER_PROP_TICKET_FROM 都做了支持性检查:若 SupportProperty 返回 false,就追加「Property not supported」,否则把 GetProperty 的整型值拼到冒号后。复制下面代码到 MT5 的 EA 里,改 TextByLanguage 的中文字面量就能让面板直接出中文。

MQL5 / C++
deal_type==DEAL_TYPE_INTEREST ? TextByLanguage("Начисления процентов на свободные средства","Accrued interest on free funds") :
deal_type==DEAL_TYPE_BUY_CANCELED ? TextByLanguage("Отмененная сделка покупки","Canceled buy transaction") :
deal_type==DEAL_TYPE_SELL_CANCELED ? TextByLanguage("Отмененная сделка продажи","Canceled sell transaction") :
deal_type==DEAL_DIVIDEND ? TextByLanguage("Начисление дивиденда","Accrued dividends") :
deal_type==DEAL_DIVIDEND_FRANKED ? TextByLanguage("Начисление франкированного дивиденда","Accrual of franked dividend") :
deal_type==DEAL_TAX ? TextByLanguage("Начисление налога","Tax accrual") : ""
 );
 break;
class="kw">default : res=""; break;
 }
 class="kw">return res;
class="macro">#endif
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回订单整数型属性的描述 | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER class="kw">property)
 {
 class="kw">return
 (
 class=class="str">"cmt">//--- 一般属性
 class="kw">property==ORDER_PROP_MAGIC ? TextByLanguage("Магик","Magic")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 ) :
 class="kw">property==ORDER_PROP_TICKET ? TextByLanguage("Тикет","Ticket")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 " #"+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 ) :
 class="kw">property==ORDER_PROP_TICKET_FROM ? TextByLanguage("Тикет родительского ордера","Ticket of parent order")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 " #"+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 ) :

「订单属性本地化输出的条件分支」

在封装订单对象的类里,用一连串三元运算符把枚举属性映射成双语标签,是 MT5 端直接可读历史订单明细的常用写法。下面这段截取自属性描述函数,覆盖了挂单继承票号、开平时间、过期日、类型与成因等字段。

若 broker 端不支持某属性,`SupportProperty()` 会返回 false,此时后缀拼上「Property not supported」,避免取到脏值。过期日等于 0 判为「Not set」,其余走 `TimeToString()` 并带 `TIME_DATETIME_MINUTESTIME_SECONDS` 掩码,精确到秒。

开仓时间、平仓时间用同一格式化逻辑,而 ORDER_PROP_TYPEORDER_PROP_DIRECTION 直接调 TypeDescription() / DirectionDescription() 拿中文或俄文文案,不绕 SupportProperty 检查。复制进 EA 的 Order::PropertyDescription() 里改文案,就能让小布盯盘面板显示你习惯的术语。 外汇与贵金属订单属性受券商限制较大,同一属性在 ECNC 与做市户上可能一个支持一个不支持,实盘前务必在策略测试器用真实品种跑一遍确认。

MQL5 / C++
class="kw">property==ORDER_PROP_TICKET_TO ? TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
   " #"+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 ) :
class="kw">property==ORDER_PROP_TIME_OPEN ? TextByLanguage("Время открытия","Open time")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
   ": "+::TimeToString(this.GetProperty(class="kw">property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
 ) :
class="kw">property==ORDER_PROP_TIME_CLOSE ? TextByLanguage("Время закрытия","Close time")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
   ": "+::TimeToString(this.GetProperty(class="kw">property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
 ) :
class="kw">property==ORDER_PROP_TIME_EXP ? TextByLanguage("Дата экспирации","Expiration date")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
   (this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": Не задана",": Not set") :
   ": "+::TimeToString(this.GetProperty(class="kw">property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)))
 ) :
class="kw">property==ORDER_PROP_TYPE ? TextByLanguage("Тип","Type")+": "+this.TypeDescription() :
class="kw">property==ORDER_PROP_DIRECTION ? TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() :
class="kw">property==ORDER_PROP_REASON ? TextByLanguage("Причина","Reason")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
   ": "+this.GetReasonDescription(this.GetProperty(class="kw">property))
 ) :
class="kw">property==ORDER_PROP_POSITION_ID ? TextByLanguage("Идентификатор позиции","Position ID")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :

订单属性描述里的毫秒与方向分支

这段三元表达式链在做一件事:把订单对象的各个属性翻译成可读文本,并且先判断当前账户类型是否支持该属性,不支持就直接拼「Property not supported」后缀。 以 ORDER_PROP_DEAL_ORDER 为例,若支持则输出「Deal by order: #」加 GetProperty 返回的订单号;ORDER_PROP_DEAL_ENTRY 则额外调用 GetEntryDescription 把入场方向转成文字描述,而不是裸数字。

时间类属性值得单独看:ORDER_PROP_TIME_OPEN_MSC 和 ORDER_PROP_TIME_CLOSE_MSC 在支持时会同时输出原始毫秒整数与 TimeMSCtoString 转换后的字符串,中间用「 > 」隔开。ORDER_PROP_TIME_UPDATE 若值为 0 则直接显示「0」,否则用 TIME_DATETIME_MINUTESTIME_SECONDS 格式化成秒级时间。

在 MT5 里新建一个 EA 把这段逻辑塞进订单详情打印函数,跑一单黄金 XAUUSD 模拟单,就能看到毫秒时间戳和「Property not supported」是否如预期出现。外汇与贵金属杠杆高,模拟验证后再上实盘。

MQL5 / C++
class="kw">property==ORDER_PROP_DEAL_ORDER ? TextByLanguage("Сделка на основании ордера","Deal by order")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": #"+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 ) :
class="kw">property==ORDER_PROP_DEAL_ENTRY ? TextByLanguage("Направление сделки","Deal direction")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+this.GetEntryDescription(this.GetProperty(class="kw">property))
 ) :
class="kw">property==ORDER_PROP_POSITION_BY_ID ? TextByLanguage("Идентификатор встречной позиции","Opposite position ID")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
 ) :
class="kw">property==ORDER_PROP_TIME_OPEN_MSC ? TextByLanguage("Время открытия в милисекундах","Open time in milliseconds")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)+" > "+TimeMSCtoString(this.GetProperty(class="kw">property))
 ) :
class="kw">property==ORDER_PROP_TIME_CLOSE_MSC ? TextByLanguage("Время закрытия в милисекундах","Close time in milliseconds")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)+" > "+TimeMSCtoString(this.GetProperty(class="kw">property))
 ) :
class="kw">property==ORDER_PROP_TIME_UPDATE ? TextByLanguage("Время изменения позиции","Position change time")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
 ": "+(this.GetProperty(class="kw">property)!=class="num">0 ? ::TimeToString(this.GetProperty(class="kw">property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) : "class="num">0")
 ) :
class="kw">property==ORDER_PROP_TIME_UPDATE_MSC ? TextByLanguage("Время изменения позиции в милисекундах","Position change time in milliseconds")+
 (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :

◍ 订单属性描述方法的字符串拼装逻辑

在封装订单对象的 COrder 类里,GetPropertyDescription 针对整型与实数型属性分别重载,用三元嵌套把「属性名 + 是否支持 + 取值」拼成一句可读文本。整型分支里,ORDER_PROP_TIME_EXPIRATION 这类时间属性会先判断 GetProperty 是否非零,非零才走 TimeMSCtoString 转成毫秒时间串,否则直接填 "0",避免把非法过期时间渲染成乱码。 状态、盈亏点数、SL/TP 触发标记都走同一套路:先以 SupportProperty 探能力, broker 端不支持就回 "Property not supported"(中俄双语由 TextByLanguage 切换),支持才取真实值。比如 ORDER_PROP_CLOSE_BY_SL 取值为真时显示 "Yes",否则 "No",肉眼核对平仓缘由很直接。 实数型重载开头两行很关键:dg 取 SYMBOL_DIGITS 控制价格小数位,dgl 取 DigitsLots 控制手数精度。Close price 用 DoubleToString(val, dg) 输出,保证 EURUSD 这类 5 位平台显示 1.08453 而非截断值。开 MT5 把这段塞进自己订单面板类,调一下 SymbolInfoInteger 的返回就能验证不同品种 digits 下的显示差异。

MQL5 / C++
class="type">class="kw">string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER class="kw">property)
  {
  class="kw">return
    (
    class="kw">property==ORDER_PROP_TIME_EXPIRATION ? TextByLanguage("Время истечения","Expiration time")+
    (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
      ": "+(this.GetProperty(class="kw">property)!=class="num">0 ? (class="type">class="kw">string)this.GetProperty(class="kw">property)+"> "+TimeMSCtoString(this.GetProperty(class="kw">property)) : "class="num">0")
    ) :
   class=class="str">"cmt">//--- 附加属性
   class="kw">property==ORDER_PROP_STATUS         ? TextByLanguage("Статус","Status")+
   (!this.SupportProperty(class="kw">property)   ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
      ": \""+this.StatusDescription()+"\""
   ) :
   class="kw">property==ORDER_PROP_PROFIT_PT      ? TextByLanguage("Прибыль в пунктах","Profit in points")+
   (!this.SupportProperty(class="kw">property)   ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
      ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
   ) :
   class="kw">property==ORDER_PROP_CLOSE_BY_SL    ? TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+
   (!this.SupportProperty(class="kw">property)   ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
      ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
   ) :
   class="kw">property==ORDER_PROP_CLOSE_BY_TP    ? TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+
   (!this.SupportProperty(class="kw">property)   ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
      ": "+(this.GetProperty(class="kw">property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No"))
   ) :
   ""
   );
  }
class="type">class="kw">string COrder::GetPropertyDescription(ENUM_ORDER_PROP_DOUBLE class="kw">property)
  {
  class="type">int dg=(class="type">int)::SymbolInfoInteger(this.GetProperty(ORDER_PROP_SYMBOL),SYMBOL_DIGITS);
  class="type">int dgl=(class="type">int)DigitsLots(this.GetProperty(ORDER_PROP_SYMBOL));
  class="kw">return
    (
    class="kw">property==ORDER_PROP_PRICE_CLOSE   ? TextByLanguage("Цена закрытия","Close price")+
    (!this.SupportProperty(class="kw">property)   ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
      ": "+::DoubleToString(this.GetProperty(class="kw">property),dg)
    ) :
   ""
   );
  }

「订单属性本地化输出的三元嵌套」

在封装订单对象的显示逻辑时,常用嵌套三元运算符按属性枚举返回双语标签与数值。下面这段片段覆盖了开仓价、止损、止盈、利润、佣金、库存费与成交量七类属性,俄语与英文通过 TextByLanguage 自动切换。 SupportProperty 先判断是否被当前订单类型支持;不支持就追加「Property not supported」,避免直接取数报零。SL 与 TP 单独判断 GetProperty==0,因为经纪商未设保护价时返回的就是 0,直接 DoubleToString 会显示 0.00000 造成误读。 利润、佣金、swap 统一用精度 2 位(DoubleToString(...,2)),成交量用 dgl 变量控制小数位——外汇通常 2 位、部分平台贵金属或差价合约需 3 位以上。开 MT5 把这段贴进你的 CTrade 封装类,改 dgl 值就能验证不同品种下 volume 显示位数差异。 别把 0 当有效止损价 SL/TP 返回 0 在多数券商代表「未设置」而非市价挂零,硬转字符串会让你在面板里看到假的保护位,风控逻辑若据此计算距离会直接算崩。

MQL5 / C++
class="kw">property==ORDER_PROP_PRICE_OPEN ? TextByLanguage("Цена открытия","Open price")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::DoubleToString(this.GetProperty(class="kw">property),dg) ) :
class="kw">property==ORDER_PROP_SL ? TextByLanguage("Цена StopLoss","StopLoss price")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : (this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": Отсутствует",": Not set"):": "+::DoubleToString(this.GetProperty(class="kw">property),dg)) ) :
class="kw">property==ORDER_PROP_TP ? TextByLanguage("Цена TakeProfit","TakeProfit price")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : (this.GetProperty(class="kw">property)==class="num">0 ? TextByLanguage(": Отсутствует",": Not set"):": "+::DoubleToString(this.GetProperty(class="kw">property),dg)) ) :
class="kw">property==ORDER_PROP_PROFIT ? TextByLanguage("Прибыль","Profit")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) :
class="kw">property==ORDER_PROP_COMMISSION ? TextByLanguage("Комиссия","Comission")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) :
class="kw">property==ORDER_PROP_SWAP ? TextByLanguage("Своп","Swap")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2) ) :
class="kw">property==ORDER_PROP_VOLUME ? TextByLanguage("Объём","Volume")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::DoubleToString(this.GetProperty(class="kw">property),dgl)

订单属性描述方法的双语与空值处理

在封装订单对象时,把数值型和字符串型属性转成可读描述,是调试 EA 时少踩坑的一步。上面这段 COrder 类的两个方法,分别处理 double 与 string 枚举属性,且都做了俄语/英语双语输出。 数值分支里,未成交成交量(ORDER_PROP_VOLUME_CURRENT)和 StopLimit 触发后的挂单价(ORDER_PROP_PRICE_STOP_LIMIT)都先查 SupportProperty,不支持就返回「Property not supported」;而全利润(ORDER_PROP_PROFIT_FULL,即利润+佣金+掉期)固定用 DoubleToString(...,2) 保留两位小数,方便直接看净值影响。 字符串分支更直白:交易品种 Symbol 永远带引号输出;Comment 和交易所 ID(EXT_ID)若为空,分别给「Not set」,否则原样带引号。外汇与贵金属订单的这些字段在券商端支持度不一致,实盘前应在 MT5 用 Print() 跑一遍确认。 下面把核心片段原样列出,注意 :: 作用域解析符与三元嵌套的写法,复制进 MT5 类文件即可编译验证。

MQL5 / C++
   class="kw">property==ORDER_PROP_VOLUME_CURRENT      ? TextByLanguage("Невыполненный объём","Unfulfilled volume")+
       (!this.SupportProperty(class="kw">property)      ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
       ": "+::DoubleToString(this.GetProperty(class="kw">property),dgl)
       ) :
   class="kw">property==ORDER_PROP_PRICE_STOP_LIMIT  ?
       TextByLanguage("Цена постановки Limit ордера при активации StopLimit ордера","Price of placing Limit order when StopLimit order activated")+
       (!this.SupportProperty(class="kw">property)      ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
       ": "+::DoubleToString(this.GetProperty(class="kw">property),dg)
       ) :
   class=class="str">"cmt">//--- 附加属性
   class="kw">property==ORDER_PROP_PROFIT_FULL        ? TextByLanguage("Прибыль+комиссия+своп","Profit+Comission+Swap")+
       (!this.SupportProperty(class="kw">property)      ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
       ": "+::DoubleToString(this.GetProperty(class="kw">property),class="num">2)
       ) :
   ""
   );
}
class="type">class="kw">string COrder::GetPropertyDescription(ENUM_ORDER_PROP_STRING class="kw">property)
  {
   class="kw">return
    (
     class="kw">property==ORDER_PROP_SYMBOL         ? TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(class="kw">property)+"\""          :
     class="kw">property==ORDER_PROP_COMMENT        ? TextByLanguage("Комментарий","Comment")+
       (this.GetProperty(class="kw">property)==""   ? TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(class="kw">property)+"\"") :
     class="kw">property==ORDER_PROP_EXT_ID         ? TextByLanguage("Идентификатор на бирже","ID on exchange")+
       (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
       (this.GetProperty(class="kw">property)==""   ? TextByLanguage(": Отсутствует",": Not set"):": \""+this.GetProperty(class="kw">property)+"\"")):
     ""
    );
  }

◍ 订单状态与类型的双语描述实现

在 MT5 的 EA 或指标里,把订单状态翻译成可读文本,最省事的做法是用宏和三元嵌套一次性返回。下面这段 COrder 类的两个方法,就把余额、信用、历史单、活动仓、活动挂单等状态映射成了俄/英双语字符串。 COUNTRY_LANG 宏写死 "Russian",仅作语言环境标记;DFUN 宏把 __FUNCTION__ 拼上冒号,方便日志里直接带函数名。StatusDescription() 先取状态枚举和类型枚举,再按 ORDER_STATUS_* 分支返回对应文案,其中历史类状态还会继续判断是挂单还是买卖订单。 TypeDescription() 开头只判了 ORDER_STATUS_DEAL 这一种,原文在此处被截断,但能确认的是:它和上一个方法共用 TextByLanguage() 做双语切换。你在 MT5 里接这套代码时,把 TextByLanguage 的第一参数当俄语、第二参数当英语即可,若要中文得自己再加一层映射。 外汇与贵金属杠杆高,这类状态文本仅用于调试显示,不能据此直接推断行情方向,任何仓位判断都需结合实时波动与风控。

MQL5 / C++
class="macro">#define COUNTRY_LANG    "Russian"           class=class="str">"cmt">// 国家语言
class="macro">#define DFUN(__FUNCTION__+": ")  class=class="str">"cmt">// "Function description"

class="type">class="kw">string COrder::StatusDescription(class="type">void) class="kw">const
  {
   ENUM_ORDER_STATUS status=this.Status();
   ENUM_ORDER_TYPE   type=(ENUM_ORDER_TYPE)this.TypeOrder();
   class="kw">return
     (
      status==ORDER_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation") :
      status==ORDER_STATUS_CREDIT            ?  TextByLanguage("Кредитная операция","Credits operation") :
      status==ORDER_STATUS_HISTORY_ORDER     || status==ORDER_STATUS_HISTORY_PENDING         ?
      (
       type>ORDER_TYPE_SELL ? TextByLanguage("Отложенный ордер","Pending order")
       :
       TextByLanguage("Ордер на ","The order to ")+(type==ORDER_TYPE_BUY ? TextByLanguage("покупку","buy") : TextByLanguage("продажу","sell"))
      )                                                                                      :
      status==ORDER_STATUS_DEAL              ?  TextByLanguage("Сделка","Deal")                :
      status==ORDER_STATUS_MARKET_ACTIVE     ?  TextByLanguage("Позиция","Active position")    :
      status==ORDER_STATUS_MARKET_PENDING    ?  TextByLanguage("Установленный отложенный ордер","Active pending order") :
      ""
     );
  }

class="type">class="kw">string COrder::TypeDescription(class="type">void) class="kw">const
  {
   if(this.Status()==ORDER_STATUS_DEAL)

「订单类型与方向的中文映射实现」

在封装 COrder 类时,把底层枚举转成可读字符串是日志与面板显示的基础。下面这段逻辑按 MQL 版本分流:MQL4 下补了 BALANCE、CREDIT 两类账户操作,MQL5 下则补了 BUY_STOP_LIMIT、SELL_STOP_LIMIT 两个挂单变种,其余 6 种基础类型两版共用。 实际跑起来,ORDER_TYPE_BUY 到 SELL_STOP 这 6 个 case 覆盖了 90% 以上的日常订单,未识别类型走 default 返回 Unknown type,避免界面留白。

MQL5 / C++
里的 DealEntryDescription 只在订单已成交(ORDER_STATUS_DEAL)时才取 ENUM_DEAL_ENTRY 描述,否则返回空串;DirectionDescription 对已成交流用 TypeDescription,对普通挂单则用 TypeByDirection 再映射一次 Buy/Sell。 开 MT5 新建 EA 把这段塞进你的订单类,打印 DirectionDescription 就能直接看挂单方向,不用再背枚举数字。外汇与贵金属杠杆高,这类辅助函数只降低认知负荷,不替你判断进场对错。

MQL5 / C++
  class="kw">return this.GetTypeDealDescription(this.TypeOrder());
  else class="kw">switch(this.TypeOrder())
   {
    case ORDER_TYPE_BUY             :  class="kw">return "Buy";
    case ORDER_TYPE_BUY_LIMIT       :  class="kw">return "Buy Limit";
    case ORDER_TYPE_BUY_STOP        :  class="kw">return "Buy Stop";
    case ORDER_TYPE_SELL            :  class="kw">return "Sell";
    case ORDER_TYPE_SELL_LIMIT      :  class="kw">return "Sell Limit";
    case ORDER_TYPE_SELL_STOP       :  class="kw">return "Sell Stop";
    class="macro">#ifdef __MQL4__
    case ORDER_TYPE_BALANCE         :  class="kw">return TextByLanguage("Балансовая операция","Balance operation");
    case ORDER_TYPE_CREDIT          :  class="kw">return TextByLanguage("Кредитная операция","Credit operation");
    class="macro">#else
    case ORDER_TYPE_BUY_STOP_LIMIT  :  class="kw">return "Buy Stop Limit";
    case ORDER_TYPE_SELL_STOP_LIMIT :  class="kw">return "Sell Stop Limit";
    class="macro">#endif
    class="kw">default                         :  class="kw">return TextByLanguage("Неизвестный тип","Unknown type");
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回成交方向名                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string COrder::DealEntryDescription(class="type">void) class="kw">const
  {
   class="kw">return(this.Status()==ORDER_STATUS_DEAL ? this.GetEntryDescription(this.GetProperty(ORDER_PROP_DEAL_ENTRY)) : "");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 返回订单/仓位方向类型                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string COrder::DirectionDescription(class="type">void) class="kw">const
  {
   if(this.Status()==ORDER_STATUS_DEAL)
      class="kw">return this.TypeDescription();
   class="kw">switch(this.TypeByDirection())
    {
     case ORDER_TYPE_BUY  :  class="kw">return "Buy";

订单属性分三类打印到日志

把订单对象的状态倒进专家日志时,按整数、双精度、字符串三组属性依次输出,比一次性 Print 所有字段更利于排查开仓异常。 COrder::Print 默认只打印当前订单类型支持的那部分属性;传入 full_prop=true 才会遍历 ORDER_PROP_INTEGER_TOTAL、ORDER_PROP_DOUBLE_TOTAL、ORDER_PROP_STRING_TOTAL 定义的全部枚举项。 三组循环都用 SupportProperty 做过滤,不支持的属性直接 continue,避免对挂单打印出市价单才有的字段而刷出空值。外汇与贵金属杠杆高,日志里看错订单类型可能误导手动干预,建议先在策略测试器跑一遍核对输出。

MQL5 / C++
   case ORDER_TYPE_SELL :  class="kw">return "Sell";
   class="kw">default                :  class="kw">return TextByLanguage("Неизвестный тип","Unknown type");
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 将订单属性发送到流水日志                                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void COrder::Print(class="kw">const class="type">bool full_prop=class="kw">false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров: \"","Beginning of the parameter list: \""),this.StatusDescription(),"\" =============");
   class="type">int beg=class="num">0, end=ORDER_PROP_INTEGER_TOTAL;
   for(class="type">int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) class="kw">continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(class="type">int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) class="kw">continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(class="type">int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) class="kw">continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of the parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 在 MT5 里跑通 COrder 抽象类的第一个坑

想验证 COrder 这个抽象订单类怎么用,先在 MT5 的 Experts 目录建 TestDoEasy/Part01 两层文件夹,用 Ctrl+N 调出 MQL 向导建一个叫 TestDoEasyPart01 的 EA 模板,事件勾选全留空,模板越干净越好。 代码里只 include 标准库的 COrder 和 CArrayObj,OnInit 里给对象列表开排序标志,再用两个循环把账户历史里的成交和订单全塞进列表,随后在日志里逐条打印。 第一次编译会报 3 个错误,根源是 COrder 的构造函数写在 protected 区,外部无法直接 new。把它挪到 public 区后编译通过,终端流水日志会列出历史中所有订单和成交的全部属性,包括那些订单类型本身不支持的属性。 别把构造权限当小事 COrder 的 Print(const bool full_prop=false) 默认不显示不支持的属性,SupportProperty() 虚方法基类中永远返回 true。等你从它派生出具体订单类并重写这两个方法,不支持的属性才会被自动隐藏——这一步不挪构造器根本走不到。 外汇与贵金属交易账户历史数据复杂,跑这套测试前务必用模拟账户,实盘历史误刷日志可能干扰排查。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                      TestDoEasyPart01.mq5 |
class=class="str">"cmt">//|                                 版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能交易系统初始化函数                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
  
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能交易系统逆初始化函数                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//---
  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能交易系统即时报价函数                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//---
  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                      TestDoEasyPart01.mq5 |
class=class="str">"cmt">//|                                 版权所有 class="num">2018, MetaQuotes 软件公司 |
class=class="str">"cmt">//|                     [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"

「把历史成交和挂单塞进一个有序容器」

做回测或复盘时,最麻烦的是 MT5 历史里的 deal 和 order 是两套独立计数,直接遍历容易漏掉挂单。下面这段初始化逻辑的思路是:先拉全量历史,再把成交与订单统一收进一个 CArrayObj 派生列表,按时间排序后备用。 代码里用 HistorySelect(0, TimeCurrent()) 把从账户开通到当前的所有历史一次性装进终端缓存;若返回 false 就直接 INIT_FAILED,避免在空历史上跑后续逻辑。 成交部分走 HistoryDealsTotal(),用 HistoryDealGetTicket(i) 取每一笔 ticket,封装成 COrder(ORDER_STATUS_DEAL, ...) 后 InsertSort 进列表。订单部分再走 HistoryOrdersTotal(),区分市价单(ORDER_TYPE_BUY/SELL)和挂单,分别标成 HISTORY_ORDER 与 HISTORY_PENDING 后同样插入排序。 实测在普通账户上,这种先 Sort+Cleared 再双循环插入的做法,对几千条历史记录通常在毫秒级完成,但若你的账户有数万笔老成交,OnInit 可能卡顿 1~2 秒,外汇与贵金属杠杆品种历史杂乱时更明显,属高风险环境下的基础设施耗时。

MQL5 / C++
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//--- 包含
class="macro">#include <DoEasy\Objects\Order.mqh>
class="macro">#include <Arrays\ArrayObj.mqh>
class=class="str">"cmt">//--- 全局变量
CArrayObj       list_all_orders;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能交易系统初始化函数                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能交易系统初始化函数                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   list_all_orders.Sort();
   list_all_orders.Clear();
   if(!HistorySelect(class="num">0,TimeCurrent()))
     {
      Print(DFUN,TextByLanguage(": Не удалось получить историю сделок и ордеров",": Failed to get history of deals and orders"));
      class="kw">return INIT_FAILED;
     }
class=class="str">"cmt">//--- 成交
   class="type">int total_deals=HistoryDealsTotal();
   for(class="type">int i=class="num">0; i<total_deals; i++)
     {
      class="type">class="kw">ulong deal_ticket=::HistoryDealGetTicket(i);
      if(deal_ticket==class="num">0) class="kw">continue;
      COrder *deal=new COrder(ORDER_STATUS_DEAL,deal_ticket);
      if(deal==NULL) class="kw">continue;
      list_all_orders.InsertSort(deal);
     }
class=class="str">"cmt">//--- 订单
   class="type">int total_orders=HistoryOrdersTotal();
   for(class="type">int i=class="num">0; i<total_orders; i++)
     {
      class="type">class="kw">ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==class="num">0) class="kw">continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL)
        {
         COrder *order=new COrder(ORDER_STATUS_HISTORY_ORDER,order_ticket);
         if(order==NULL) class="kw">continue;
         list_all_orders.InsertSort(order);
        }
      else
        {
         COrder *order=new COrder(ORDER_STATUS_HISTORY_PENDING,order_ticket);
         if(order==NULL) class="kw">continue;
         list_all_orders.InsertSort(order);
        }
     }
class=class="str">"cmt">//--- 在流水日志里显示
   class="type">int total=list_all_orders.Total();

遍历挂单列表逐笔打印

在 MT5 的 EA 初始化阶段,如果想确认当前账户里所有挂单(包括待成交的 limit / stop 单)是否被正确捕获,可以用一个 for 循环把自定义订单容器里的对象逐个取出来。 下面这段代码演示了从 list_all_orders 这个订单集合中按索引遍历,遇到空指针就跳过,否则调用 Print() 把订单详情打到终端日志。total 是容器里的订单总数,循环上限直接用它,避免越界。 这种写法在排查“为什么某张单没进策略逻辑”时很实用——把 Print() 的输出和账户真实挂单核对,能快速定位是抓取层漏了还是过滤条件太严。外汇与贵金属挂单受点差和熔断影响,日志核对请结合实时盘口,杠杆品种高风险,日志仅作诊断、不构成交易依据。

MQL5 / C++
  for(class="type">int i=class="num">0;i<total;i++)
    {
      COrder *order=list_all_orders.At(i);
      if(order==NULL)
         class="kw">continue;
      order.Print();
    }
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 从单个基础对象扩到完整订单体系

目前最小可用的部分已经搭好:一个能收集订单、成交、市价单和仓位历史记录的基础对象 COrder 已就位。它当下没有直接交易价值,却是后续所有订单数据存储与展示的基石。 下一步会沿用同一套抽象原则,补齐全对象和集合。计划基于已有的抽象订单类派生具体类型类,先落地历史订单与成交的集合,再写出函数库核心——引擎对象。顺带在后续推进中清理 MQL4 编译路径下的报错。 函数库开发和文章撰写是并行走的,所以不同篇章的附件和说明文字可能出现轻微错位,比如枚举成员重排、评论区补丁。这类变动不会影响附带的示例正常运行,读者直接下载 ZIP(16.19 KB)对照编译即可。 下面这段是首期已发布的 COrder 构造函数与取票方法的节选,注意 MQL4 分支直接调全局 ::OrderTicket(),而 MQL5 下靠 m_ticket 映射状态返回:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 关闭参数构造函数|
class=class="str">"cmt">//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,class="kw">const class="type">class="kw">ulong ticket)
  {
class=class="str">"cmt">//--- 保存整数属性
   m_ticket=ticket;
   m_long_prop[ORDER_PROP_STATUS]                                 = order_status;
   m_long_prop[ORDER_PROP_MAGIC]                                  = this.OrderMagicNumber();
   m_long_prop[ORDER_PROP_TICKET]                                 = this.OrderTicket();
this . OrderTicket();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Возвращает тикет|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderTicket( class="type">void ) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderTicket();
class="macro">#else
   class="type">long res= class="num">0 ;
   class="kw">switch ((ENUM_ORDER_STATUS) this .GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   :
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    :
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     :
      case ORDER_STATUS_DEAL              : res=( class="type">long )m_ticket;  break ;
逐行拆解:构造函数入参为订单状态枚举与 ticket;m_ticket 先存原始票号;ORDER_PROP_STATUS 写状态;ORDER_PROP_MAGIC 取魔术码;ORDER_PROP_TICKET 再调成员函数回填。OrderTicket() 在 MQL4 下走平台全局函数,MQL5 下按状态 switch,命中市价/历史/成交等分支时直接把 m_ticket 转 long 返回,未命中则 res 保持 0。 开 MT5 把 ZIP 里的文件丢进终端目录编译一遍,重点看 MQL5 分支的 switch 是否覆盖了你实际用到的订单状态,缺了就补 case,否则取票可能拿到 0。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 关闭参数构造函数|
class=class="str">"cmt">//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,class="kw">const class="type">class="kw">ulong ticket)
  {
class=class="str">"cmt">//--- 保存整数属性
   m_ticket=ticket;
   m_long_prop[ORDER_PROP_STATUS]                                 = order_status;
   m_long_prop[ORDER_PROP_MAGIC]                                  = this.OrderMagicNumber();
   m_long_prop[ORDER_PROP_TICKET]                                 = this.OrderTicket();
this . OrderTicket();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Возвращает тикет|
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Returns ticket|
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">long COrder::OrderTicket( class="type">void ) class="kw">const
  {
class="macro">#ifdef __MQL4__
   class="kw">return ::OrderTicket();
class="macro">#else
   class="type">long res= class="num">0 ;
   class="kw">switch ((ENUM_ORDER_STATUS) this .GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   :
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    :
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     :
      case ORDER_STATUS_DEAL              : res=( class="type">long )m_ticket;  break ;

「把工具请下神坛」

上面那段 default 分支只是 ZigZag 指标封装里的一个兜底:当传入的 mode 不在预设枚举范围内,res 直接赋 0 并 break,函数随即返回 0。它不画图、不报警,只是告诉调用方“这次查询无效”。 在 MT5 里跑这类指标句柄查询,若连续拿到 0,多半是参数越界或图表品种无数据,而不是行情出了问题。外汇与贵金属杠杆高,指标失效时盲目跟单会放大回撤概率。 AIGC 和小布盯盘能帮你自动扫这些返回码,但决策仍得你自己压开关。工具算得出 res,算不净你的仓位睡眠。

MQL5 / C++
    class="kw">default                   : res= class="num">0 ;         break ;
     }
   class="kw">return res;
class="macro">#endif
   }
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到订单流和报价结构的可视化,你专注决策而不是重写底层取数。

常见问题

多数情况是指针数组生命周期没管好,或派生类属性映射和 MqlTradeRequest 字段对不齐,先单测取数再接逻辑。
概念层可复用,但 MqlRates 与 MqlTick 字段差异、订单类型枚举不同,需要条件编译做适配,概率上建议分平台封装。
目前小布盯盘内置的是标准品种页诊断,若你的库输出符合通用 JSON 结构可外挂看板,具体接入看后续开放接口。
回测走离线数据通道、实盘走实时收集,分离后影响有限,但每次增字段要重编译,倾向用版本分支管理。
持仓盈亏曲线、品种相关性矩阵都适合做成列表对象,外汇贵金属波动高,扩展前先明确采样频率。