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

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

(2/3)· 把订单、成交、时间序列统一成可排序对象,开发 MT5 程序能少写一半样板代码

实战向 第 2/3 篇

接上篇的概念铺垫,我们继续深挖函数库的数据结构落地。多数开发者在每個 EA 里重写一遍订单遍历和报价缓存,却没意识到这些动作本质都是同一套对象列表操作。

◍ 订单状态枚举与空壳构造

在自建订单管理类里,先把 COrder 的构造与析构写成空实现是很常见的起步做法:不给成员做初始化,只是把类框架立住,后续再往里填持仓、挂单的检索逻辑。 下面这段枚举把 MT5 终端里的订单生命周期拆成了 8 个整型状态:从当前挂单、有效市价单,到历史市价单、已删除挂单,再到余额操作、赠金操作、成交与未知状态。实际写 EA 时,用 ENUM_ORDER_STATUS 做分支判断,比直接啃 MqlTradeTransaction 的原始字段更直观。 [CODE] 里贴的就是这部分骨架,注意版权声明那两行是源文件自带的 property,你抄代码时可以删掉,不影响编译。外汇与贵金属杠杆高,状态误判可能导致重复下单,先在策略测试器里跑通状态机再上实盘。

MQL5 / C++
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
COrder::COrder()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
COrder::~COrder()
  {
  }
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">//+------------------------------------------------------------------+
enum ENUM_ORDER_STATUS
  {
   ORDER_STATUS_MARKET_PENDING,                                                    class=class="str">"cmt">// 当前挂单
   ORDER_STATUS_MARKET_ACTIVE,                                                     class=class="str">"cmt">// 有效市价单
   ORDER_STATUS_HISTORY_ORDER,                                                     class=class="str">"cmt">// 历史市价单
   ORDER_STATUS_HISTORY_PENDING,                                                   class=class="str">"cmt">// 已删除的挂单
   ORDER_STATUS_BALANCE,                                                           class=class="str">"cmt">// 余额操作
   ORDER_STATUS_CREDIT,                                                            class=class="str">"cmt">// 赠金操作
   ORDER_STATUS_DEAL,                                                              class=class="str">"cmt">// 成交
   ORDER_STATUS_UNKNOWN                                                            class=class="str">"cmt">// 未知状态
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 订单、成交、仓位整数型属性                                                                       |

「订单整型属性的枚举骨架」

在 MT5 的订单历史与持仓查询里,整型类属性全部收口于 ENUM_ORDER_PROP_INTEGER 这个枚举。它从 0 开始编号,ORDER_PROP_TICKET 固定为 0,后续成员按声明顺序自增,例如 ORDER_PROP_MAGIC 实际等于 1,ORDER_PROP_TIME_OPEN 等于 2。 下面这段枚举定义了 22 个整型字段,覆盖了订单号、魔幻数、开平时间(秒与毫秒两套)、挂单失效时间、订单类型、状态、来源原因、关联仓位与成交方向等。其中 ORDER_PROP_TIME_OPEN_MSC 与 ORDER_PROP_TIME_CLOSE_MSC 用毫秒精度,比秒级字段更适合做高频策略的时延统计。 ORDER_PROP_PROFIT_PT 以点数记录盈利,ORDER_PROP_CLOSE_BY_SL 则标记是否由止损触发平仓——这两个在写复盘脚本时比浮点金额更抗滑点干扰。直接把枚举贴进 EA,用 OrderGetInteger() 按编号取数即可,不用硬背常量名。

MQL5 / C++
enum ENUM_ORDER_PROP_INTEGER
  {
   ORDER_PROP_TICKET = class="num">0,                                           class=class="str">"cmt">// 订单号
   ORDER_PROP_MAGIC,                                                class=class="str">"cmt">// 订单魔幻数字
   ORDER_PROP_TIME_OPEN,                                            class=class="str">"cmt">// 开单时间
   ORDER_PROP_TIME_CLOSE,                                           class=class="str">"cmt">// 平单时间
   ORDER_PROP_TIME_EXP,                                             class=class="str">"cmt">// 订单失效日期(针对挂单)
   ORDER_PROP_TYPE,                                                 class=class="str">"cmt">// 订单和成交类型
   ORDER_PROP_STATUS,                                               class=class="str">"cmt">// 订单状态 (来自 ENUM_ORDER_STATUS 枚举)
   ORDER_PROP_REASON,                                               class=class="str">"cmt">// 成交/订单/仓位原因或来源
   ORDER_PROP_POSITION_ID,                                          class=class="str">"cmt">// 仓位 ID
   ORDER_PROP_POSITION_BY_ID,                                       class=class="str">"cmt">// 逆向仓位 ID
   ORDER_PROP_DEAL_ORDER,                                           class=class="str">"cmt">// 成交执行所依据的订单
   ORDER_PROP_DEAL_ENTRY,                                           class=class="str">"cmt">// 成交方向 – IN(入), OUT(出) 或 IN/OUT(翻转)
   ORDER_PROP_TIME_OPEN_MSC,                                        class=class="str">"cmt">// 开单时间,单位毫秒
   ORDER_PROP_TIME_CLOSE_MSC,                                       class=class="str">"cmt">// 平单时间,单位毫秒 (执行或删除时间 - ORDER_TIME_DONE_MSC)
   ORDER_PROP_TIME_UPDATE,                                          class=class="str">"cmt">// 仓位变化时间,单位秒
   ORDER_PROP_TIME_UPDATE_MSC,                                      class=class="str">"cmt">// 仓位变化时间,单位毫秒
   ORDER_PROP_TICKET_FROM,                                          class=class="str">"cmt">// 父订单号
   ORDER_PROP_TICKET_TO,                                            class=class="str">"cmt">// 派生订单号
   ORDER_PROP_PROFIT_PT,                                            class=class="str">"cmt">// 盈利,单位点数
   ORDER_PROP_CLOSE_BY_SL,                                          class=class="str">"cmt">// 由止损平仓的标志
  };

订单属性枚举的分组与偏移约定

MT5 里订单、成交、仓位的三类属性被拆成整数、实数、字符串三组枚举,各自从 0 开始编号,但字符串组的起始值要靠前两组总数相加得出。 整数型属性总数被宏定义为 22(ORDER_PROP_INTEGER_TOTAL),实数型为 11(ORDER_PROP_DOUBLE_TOTAL),因此字符串组的 ORDER_PROP_SYMBOL 直接从下标 33 起算,避免和前面两组撞编号。 实数组里除了开平单价、盈利、佣金、swap,还单列了 PROFIT_FULL = 盈利+佣金+隔夜利息,以及 StopLimit 激活时的限价字段 PRICE_STOP_LIMIT;写 EA 读持仓浮盈时直接用 PROFIT_FULL 比自己加三项更不容易漏算。 开 MT5 的 MetaEditor 搜 ENUM_ORDER_PROP_DOUBLE,核对下你本地头文件是不是也是 11 个成员,老版本可能缺 PRICE_STOP_LIMIT 这一项。

MQL5 / C++
  ORDER_PROP_CLOSE_BY_TP,                                                                     class=class="str">"cmt">// 由止盈平仓的标志
  ORDER_PROP_DIRECTION,                                                                         class=class="str">"cmt">// 方向 (买入, 卖出)
};
class="macro">#define ORDER_PROP_INTEGER_TOTAL(class="num">22)                                                          class=class="str">"cmt">// 整数型属性的总数
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 订单、成交、仓位 实数型参数                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_DOUBLE
  {
  ORDER_PROP_PRICE_OPEN = ORDER_PROP_INTEGER_TOTAL,                                             class=class="str">"cmt">// 开单价 (MQL5 成交价)
  ORDER_PROP_PRICE_CLOSE,                                                                       class=class="str">"cmt">// 平单价
  ORDER_PROP_PROFIT,                                                                             class=class="str">"cmt">// 盈利
  ORDER_PROP_COMMISSION,                                                                         class=class="str">"cmt">// 佣金
  ORDER_PROP_SWAP,                                                                               class=class="str">"cmt">// 隔夜利息
  ORDER_PROP_VOLUME,                                                                             class=class="str">"cmt">// 交易量
  ORDER_PROP_VOLUME_CURRENT,                                                                     class=class="str">"cmt">// 未执行的交易量
  ORDER_PROP_SL,                                                                                 class=class="str">"cmt">// 止损价
  ORDER_PROP_TP,                                                                                 class=class="str">"cmt">// 止盈价
  ORDER_PROP_PROFIT_FULL,                                                                        class=class="str">"cmt">// 盈利+ 佣金 + 隔夜利息
  ORDER_PROP_PRICE_STOP_LIMIT,                                                                   class=class="str">"cmt">// 激活 StopLimit 订单时的限价订单价格
  };
class="macro">#define ORDER_PROP_DOUBLE_TOTAL(class="num">11)                                                            class=class="str">"cmt">// 实数型属性的总数
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 订单、成交、仓位 字符串型参数                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum ENUM_ORDER_PROP_STRING
  {
  ORDER_PROP_SYMBOL = (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL),                        class=class="str">"cmt">// 订单品种

◍ 订单对象的属性容器怎么搭

在 MT5 里做订单包装类,核心是把不同数据类型的订单属性分桶存。上面这段把整数、实数、字符串三类属性各自塞进定长数组,数组长度直接由宏决定,不写死数字。 ORDER_PROP_STRING_TOTAL 被定义为 3,对应 ORDER_PROP_SYMBOL、ORDER_PROP_COMMENT、ORDER_PROP_EXT_ID 三个字符串字段。改了枚举就得同步改这个宏,否则数组越界会在回测时报数组越界错误。 COrder 类私有成员里,m_ticket 用 ulong 存订单号,m_long_prop 用 ORDER_PROP_INTEGER_TOTAL 做长度,m_double_prop 和 m_string_prop 同理。开 MT5 在包含 Defines.mqh 后编译这段,能验证订单类骨架是否跑通。 外汇和贵金属杠杆高,这类底层容器写错一个属性长度,实盘可能静默丢单,上真实账户前务必用策略测试器跑一遍。

MQL5 / C++
  ORDER_PROP_COMMENT,                                                                     class=class="str">"cmt">// 订单注释
  ORDER_PROP_EXT_ID                                                                        class=class="str">"cmt">// 外部交易系统中的订单 ID
  };
class="macro">#define ORDER_PROP_STRING_TOTAL(class="num">3)                                                        class=class="str">"cmt">// 实字符串型属性的总数

class="macro">#include <Object.mqh>
class="macro">#include "..\Defines.mqh"

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="kw">public:
                                                                                             COrder();
                                                                            ~COrder();
  };

COrder::COrder()
  {
  }

「订单类的属性存储与索引偏移」

在 MT5 的自定义订单封装里,COrder 把订单的整数、实数、字符串三类属性分别塞进三个定长数组:m_long_prop、m_double_prop、m_string_prop,长度由 ORDER_PROP_INTEGER_TOTAL、ORDER_PROP_DOUBLE_TOTAL、ORDER_PROP_STRING_TOTAL 决定。这种扁平化存储比逐个成员变量更利于批量序列化。 实数属性的数组下标并不是枚举值本身。IndexProp(ENUM_ORDER_PROP_DOUBLE) 用 (int)property - ORDER_PROP_INTEGER_TOTAL 把 double 类枚举偏移掉前面整数属性的总量,字符串属性再减去 double 总量。你在扩展属性时若改了前置枚举总数,不重算偏移就会读错槽位。 析构函数目前是空实现,说明该类不持有需要手动释放的资源;但受保护构造函数 COrder(ENUM_ORDER_STATUS, ulong ticket) 意味着不能直接实例化,只能由持仓或历史订单子类继承后传 ticket 构造。外汇与贵金属杠杆交易高风险,这类封装仅降低代码耦合,不预示任何胜率。

MQL5 / C++
COrder::~COrder()
  {
  }
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 "..\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);
  };

订单类的属性数组与索引偏移

在 MT5 的自定义订单封装里,把订单的整数、实数、字符串三类属性分别塞进三个定长数组,是避开反复调用 OrderGetDouble 这类接口开销的常见做法。上面这段类定义里,m_long_prop 长度为 ORDER_PROP_INTEGER_TOTAL,m_double_prop 为 ORDER_PROP_DOUBLE_TOTAL,m_string_prop 为 ORDER_PROP_STRING_TOTAL,编译期就确定了内存布局。 实数属性的取索引函数直接拿枚举值减掉整数总量:return (int)property-ORDER_PROP_INTEGER_TOTAL。字符串属性再叠减一次实数总量。这样 ENUM_ORDER_PROP_DOUBLE 和 ENUM_ORDER_PROP_STRING 的原始枚举值虽是全局连续编号,落进各自数组时从 0 开始,不会越界。 受保护构造函数 COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) 只声明不实现函数体,等于把实例化卡在派生类手里。想验证这套索引逻辑,开 MT5 新建 EA,把 Order.mqh 的枚举总量打印出来,例如 ORDER_PROP_INTEGER_TOTAL 在较新头文件里是 15,实数总量 12,字符串总量 3,偏移减法就能对上。外汇与贵金属杠杆品种波动剧烈,回测订单结构时须留意历史 tick 缺失可能导致属性填充不全。

MQL5 / C++
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);
  };
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到对象列表式的订单与报价快照,你专注决策而非搬数据。

常见问题

适合存订单、成交、挂单和时间序列等具有相同属性的对象列表,标准库的集合类可直接管理生命周期与排序。
可以,小布盯盘品种页内置了订单与报价的对象化快照,相当于把文中函数库的首期成果做成了现成视图。
对象列表让每个 Bar 拥有统一属性结构,便于按自定义标准筛选和排序,后续扩展指标字段不需改动遍历逻辑。
概率上若仅追加类与方法、不改既有接口则影响较低,但每篇文章的编辑仍建议隔离测试后再并入实盘代码。