轻松快捷开发 MetaTrader 程序的函数库(第二部分)。 历史订单和成交的集合(基础篇)
◍ 把历史订单和成交一次性装进集合
在 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() 没穷举所有属性,比如成交只认买入、卖出、余额三类。没显式声明支持的属性也会被打印,看着零值烦了就补进方法屏蔽掉。
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 位环境下倾向出现内存告警,实盘前务必先限制查询时间窗。
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,这可以作为你判断订单是否处于历史态的硬校验点。外汇与贵金属品种波动剧烈,历史类仅用于复盘统计,实盘信号请另走实时通道。
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。注意这是历史挂单类,和实时挂单的属性集不同,回测或复盘脚本里别混用。 外汇与贵金属品种波动大、点差跳变频繁,用这套封装做历史扫描时,被禁属性对应的字段本就不在历史挂单记录中,硬读可能误导风控统计,实盘前务必在策略测试器里跑一遍验证。
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">//+------------------------------------------------------------------+