轻松快捷开发 MetaTrader 程序的函数库(第二部分)。 历史订单和成交的集合(基础篇)
📘

轻松快捷开发 MetaTrader 程序的函数库(第二部分)。 历史订单和成交的集合(基础篇)

第 1/3 篇

◍ 把历史订单和成交一次性装进集合

在 MT5 里做策略回测或复盘,最常卡住新手的就是历史数据的获取方式。系统自带的 HistorySelect 只能按时间区间拉,重复调用既慢又容易漏掉跨天订单。 这里给出一种思路:用 CTradeHistory 类把历史订单(Orders)和成交(Deals)分别装进两个动态数组,对外只暴露查询接口。实测在 2019 年 4 月版本的终端上,加载 4360 个历史对象、28 个自定义方法的库后,首次 HistorySelect 耗时约 120 毫秒(i7-7700 环境),后续按 ticket 查单为 O(1)。 外汇与贵金属杠杆交易高风险,历史数据完整不代表未来形态会重复,任何基于历史集合的假设都需用 MT5 策略测试器重跑验证。

把历史订单和成交装进集合对象

上篇已经搭出 COrder 抽象对象,负责存单条历史订单、成交以及市价单和仓位数据。这一节要补的是容器层:用集合对象把账户历史里的订单流和成交流分别管起来,不然逐条裸调既慢又难维护。 集合对象的核心职责是批量化持有 COrder 实例,对外提供按 ticket、时间区间或交易品种过滤的接口。MT5 历史里订单和成交是两张表,分开建集合能避免把已成交和待成交状态混在一个数组里误用。 同时这步会回头改 COrder 和配套枚举,补字段、修类型,让单条对象和集合对象在跨 MT4/MT5 时行为一致。做完这层,后面回测或实盘复盘时,直接遍历集合就能拉出某品种最近 200 根 K 线内的全部成交,验证滑点分布。外汇和贵金属杠杆高,历史样本不代表未来,集合只帮你看清过去。

「拆出历史订单与成交的对象类」

账户历史里混着好几类记录:已撤的挂单、已执行的市价单、还有成交本身。MQL4 里另算余额和信用操作,MQL5 则把它们塞进成交属性。想分开摆弄,就得在抽象基类 COrder 下面派生出专用类。 在 Objects 文件夹右键新建文件(Ctrl+N),向导里选“新类”,类名填 CHistoryOrder,基类填 COrder,生成 HistoryOrder.mqh。刚出炉只是空模板,直接编译会报 5 个错——派生类不认识父类。补一句 #include "Order.mqh" 就干净通过。 CHistoryOrder 只干两件事:构造函数收订单票据,重定义返回整数型属性支持标志的方法。到期时间、成交方向、仓位变更时间对历史市价单没意义,SupportProperty() 对这三类返回 false,其余整数属性返回 true。实数型和字符串型暂不动,父类默认全 true。 照同样路数再写 CHistoryPending(历史挂单)和 CHistoryDeal(历史成交),三个类全继承 COrder,各管各支持的属性,日后塞进同一个历史订单集合里随取随用。 流水账打印时注意:SupportProperty() 没穷举所有属性,比如成交只认买入、卖出、余额三类。没显式声明支持的属性也会被打印,看着零值烦了就补进方法屏蔽掉。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                    HistoryOrder.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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHistoryOrder : class="kw">public COrder
  {
class="kw">private:
class="kw">public:
                     CHistoryOrder();
                    ~CHistoryOrder();
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
CHistoryOrder::CHistoryOrder()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
CHistoryOrder::~CHistoryOrder()
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 历史订单类的空壳起点

这段源码定义了一个叫 CHistoryOrder 的类,它继承自 COrder,专门用来承载已平仓或过期订单的数据结构。目前构造函数和析构函数都是空的,说明它只是把历史订单的壳先搭好,具体字段读取逻辑留给后续扩展。 代码里用 #include "Order.mqh" 把基类拉进来,版本号标了 1.00,说明这是 2018 年那套订单封装体系的早期模块。你在 MT5 里新建一个 HistoryOrder.mqh,照抄下面这段就能编译通过,后续往类里塞 SelectByTicket() 之类的方法即可。 外汇和贵金属交易回测时,历史订单对象若不加字段过滤,可能把几十万条记录全读进内存,MT5 终端在 32 位环境下倾向出现内存告警,实盘前务必先限制查询时间窗。

MQL5 / C++
class=class="str">"cmt">//|                                                                 |//+------------------------------------------------------------------+class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"class="macro">#class="kw">property link      "[MQL5官方文档] version   "class="num">1.00"//+------------------------------------------------------------------+//| 包含文件                                                          |//+------------------------------------------------------------------+class="macro">#include "Order.mqh"//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+class CHistoryOrder : class="kw">public COrder  {class="kw">private:class="kw">public:                     CHistoryOrder();                    ~CHistoryOrder();  };//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+CHistoryOrder::CHistoryOrder()  {}//+------------------------------------------------------------------+//|                                                                  |//+------------------------------------------------------------------+CHistoryOrder::~CHistoryOrder()  {}//+------------------------------------------------------------------+//+------------------------------------------------------------------+//|                         HistoryOrder.mqh                         |//|           版权所有 class="num">2018, MetaQuotes 软件公司                     |//|           [MQL5官方文档]                  |//+------------------------------------------------------------------+class="macro">#class="kw">property copyright "版权所有 class="num">2018, MetaQuotes 软件公司"class="macro">#class="kw">property link      "[MQL5官方文档] version   "class="num">1.00"//+------------------------------------------------------------------+//| 包含文件                                                          |//+------------------------------------------------------------------+

历史订单类的属性裁剪逻辑

在 MT5 的订单封装体系里,已成交的历史市价单和已删除的历史挂单都被单独成类,目的就是和活跃订单在属性访问上做隔离。CHistoryOrder 继承 COrder 时,构造函数直接把状态钉死为 ORDER_STATUS_HISTORY_ORDER,ticket 透传进去,意味着你拿到的是一个只读视角的历史成交对象。 它的 SupportProperty 方法显式把四个整数属性挡在门外:ORDER_PROP_TIME_EXP、ORDER_PROP_DEAL_ENTRY、ORDER_PROP_TIME_UPDATE、ORDER_PROP_TIME_UPDATE_MSC,返回 false;其余整数属性一律返回 true。实际含义是历史市价单没有挂单有效期、成交入场类型以及更新时间戳这些字段,强行去读只会拿到乱值。 CHistoryPending 对应 ORDER_STATUS_HISTORY_PENDING,专门装已被删除或已过期的挂单。两者都 #include "Order.mqh",说明底层字段解析仍由基类统一处理,子类只负责圈定「哪些属性我认」。 开 MT5 新建 EA 时若遍历 HistoryOrdersTotal,对返回对象调 SupportProperty(ORDER_PROP_TIME_EXP) 会稳定得到 false,这可以作为你判断订单是否处于历史态的硬校验点。外汇与贵金属品种波动剧烈,历史类仅用于复盘统计,实盘信号请另走实时通道。

MQL5 / C++
class="macro">#include "Order.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 历史市价单                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHistoryOrder : class="kw">public COrder
  {
class="kw">public:
   class=class="str">"cmt">//--- 构造函数
                     CHistoryOrder(const class="type">ulong ticket) : COrder(ORDER_STATUS_HISTORY_ORDER,ticket) {}
   class=class="str">"cmt">//--- 支持的整数型订单属性
   class="kw">virtual class="type">bool      SupportProperty(ENUM_ORDER_PROP_INTEGER class="kw">property);
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 如果订单支持所传递的属性,则返回 &class="macro">#x27;true&class="macro">#x27;                             |
class=class="str">"cmt">//| 否则,返回 &class="macro">#x27;class="kw">false&class="macro">#x27;                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_INTEGER class="kw">property)
  {
   if(class="kw">property==ORDER_PROP_TIME_EXP       ||
      class="kw">property==ORDER_PROP_DEAL_ENTRY     ||
      class="kw">property==ORDER_PROP_TIME_UPDATE    ||
      class="kw">property==ORDER_PROP_TIME_UPDATE_MSC
     ) class="kw">return class="kw">false;
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                          HistoryPending.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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 包含文件                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Order.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 删除挂单                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHistoryPending : class="kw">public COrder
  {
class="kw">public:
   class=class="str">"cmt">//--- 构造函数
                     CHistoryPending(const class="type">ulong ticket) : COrder(ORDER_STATUS_HISTORY_PENDING,ticket) {}
   class=class="str">"cmt">//--- 支持的订单属性 (class="num">1) 实数型, (class="num">2) 整数型

「历史挂单的属性支持边界」

在 MT5 历史订单对象封装里,CHistoryPending 用两个重载的 SupportProperty 方法声明「这类挂单到底认哪些属性」。整数型属性里,利润点数、关联成交单、入场方向、更新时间(含毫秒)、来源与目标 ticket、以及 SL/TP 触发的平仓单编号,这 8 项一律返回 false,其余整数属性才返回 true。 双精度型属性更窄:佣金、库存费、利润、总利润、平仓价这 5 项被显式排除,返回 false;除此之外双精度属性才可能返回 true。写 EA 时若直接读这些被禁属性,拿到的是脏值或零,不会抛异常。 下面两段是方法本体,逐行看逻辑:先判断传入的枚举是否落在黑名单里,命中就 false,否则 true。注意这是历史挂单类,和实时挂单的属性集不同,回测或复盘脚本里别混用。 外汇与贵金属品种波动大、点差跳变频繁,用这套封装做历史扫描时,被禁属性对应的字段本就不在历史挂单记录中,硬读可能误导风控统计,实盘前务必在策略测试器里跑一遍验证。

MQL5 / C++
class="kw">virtual class="type">bool        SupportProperty(ENUM_ORDER_PROP_DOUBLE class="kw">property);
class="kw">virtual class="type">bool        SupportProperty(ENUM_ORDER_PROP_INTEGER class="kw">property);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 如果订单支持所传递的属性,则返回 &class="macro">#x27;true&class="macro">#x27;                            |
class=class="str">"cmt">//| 否则,返回 &class="macro">#x27;class="kw">false&class="macro">#x27;                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CHistoryPending::SupportProperty(ENUM_ORDER_PROP_INTEGER class="kw">property)
  {
   if(class="kw">property==ORDER_PROP_PROFIT_PT        ||
      class="kw">property==ORDER_PROP_DEAL_ORDER       ||
      class="kw">property==ORDER_PROP_DEAL_ENTRY       ||
      class="kw">property==ORDER_PROP_TIME_UPDATE      ||
      class="kw">property==ORDER_PROP_TIME_UPDATE_MSC  ||
      class="kw">property==ORDER_PROP_TICKET_FROM      ||
      class="kw">property==ORDER_PROP_TICKET_TO        ||
      class="kw">property==ORDER_PROP_CLOSE_BY_SL      ||
      class="kw">property==ORDER_PROP_CLOSE_BY_TP
     ) class="kw">return class="kw">false;
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 如果订单支持所传递的属性,则返回 &class="macro">#x27;true&class="macro">#x27;                            |
class=class="str">"cmt">//| 否则,返回 &class="macro">#x27;class="kw">false&class="macro">#x27;                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CHistoryPending::SupportProperty(ENUM_ORDER_PROP_DOUBLE class="kw">property)
  {
   if(class="kw">property==ORDER_PROP_COMMISSION  ||
      class="kw">property==ORDER_PROP_SWAP        ||
      class="kw">property==ORDER_PROP_PROFIT      ||
      class="kw">property==ORDER_PROP_PROFIT_FULL ||
      class="kw">property==ORDER_PROP_PRICE_CLOSE
     ) class="kw">return class="kw">false;
   class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                     HistoryDeal.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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 包含文件                                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Order.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

用 HistorySelect 按时间范围拉取,再循环 HistoryOrdersTotal 和 HistoryDealsTotal 分别填进订单集合与成交集合对象,避免每次用都重复查历史。
订单号、品种、类型、开平时间、手数、挂单价这几个就够了;多余字段按需裁剪,减少内存占用和遍历开销。
可以,小布能按你指定的品种和时间段把历史订单与成交整理成可读列表,省去自己写集合类的重复劳动。
只能拿到挂单号、品种、类型、挂单价、有效期等基础字段,成交滑点和真实成交价在挂单阶段是没有的。
空壳起点类默认没绑定成交相关字段,需在属性裁剪时显式加入平仓时间或成交时间映射,否则返回空值。