轻松快捷开发 MetaTrader 程序的函数库(第一部分)。 概念,数据管理和首期成果·进阶篇
(2/3)· 把订单、成交、时间序列统一成可排序对象,开发 MT5 程序能少写一半样板代码
接上篇的概念铺垫,我们继续深挖函数库的数据结构落地。多数开发者在每個 EA 里重写一遍订单遍历和报价缓存,却没意识到这些动作本质都是同一套对象列表操作。
◍ 订单状态枚举与空壳构造
在自建订单管理类里,先把 COrder 的构造与析构写成空实现是很常见的起步做法:不给成员做初始化,只是把类框架立住,后续再往里填持仓、挂单的检索逻辑。 下面这段枚举把 MT5 终端里的订单生命周期拆成了 8 个整型状态:从当前挂单、有效市价单,到历史市价单、已删除挂单,再到余额操作、赠金操作、成交与未知状态。实际写 EA 时,用 ENUM_ORDER_STATUS 做分支判断,比直接啃 MqlTradeTransaction 的原始字段更直观。 [CODE] 里贴的就是这部分骨架,注意版权声明那两行是源文件自带的 property,你抄代码时可以删掉,不影响编译。外汇与贵金属杠杆高,状态误判可能导致重复下单,先在策略测试器里跑通状态机再上实盘。
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() 按编号取数即可,不用硬背常量名。
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 这一项。
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 后编译这段,能验证订单类骨架是否跑通。 外汇和贵金属杠杆高,这类底层容器写错一个属性长度,实盘可能静默丢单,上真实账户前务必用策略测试器跑一遍。
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 构造。外汇与贵金属杠杆交易高风险,这类封装仅降低代码耦合,不预示任何胜率。
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 缺失可能导致属性填充不全。
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); };