轻松快捷开发 MetaTrader 程序的函数库(第二部分)。 历史订单和成交的集合·综合运用
(3/3)·从 COrder 派生到集合遍历,21 节拆解跨平台函数库收尾的工程细节
很多人在写 MT5 历史数据模块时,把挂单、成交、余额混在同一数组里硬判类型,导致回测和报表逻辑越写越乱。本篇接上两篇的基础,把历史订单和成交的对象与集合彻底拆清楚,让你后续调用只需关心接口而非底层分支。
- 订单属性枚举里的整数与实数分野
- 订单属性枚举与排序模式的底层定义
- 订单池排序枚举的后半段映射
- 订单排序枚举与历史集合的类骨架
- 成交与订单集合的初始化逻辑
- 账户历史刷新里的 MQL4 与 MQL5 分叉
- 增量捕获历史订单与成交的实战写法
- 历史订单集合类的接口与空壳脚本
- 用历史集合把成交记录捞出来
- 历史订单类的属性支持过滤逻辑
- 订单属性枚举里的隐藏字段
- 订单枚举收尾与排序模式偏移量
- 订单排序枚举的后半段映射
- 订单类里的排序枚举与受保护接口
- 订单状态读取的跨版本写法
- 订单状态描述的本地化取词
- 订单状态与整数属性的本地化描述写法
- 订单属性文本化的条件分支
- 订单属性标签的多语言拼装逻辑
- 订单属性文本化的多语言分支
- 排序与集合类的后续规划
订单属性枚举里的整数与实数分野
在 MT5 的订单、成交、仓位统一属性框架里,整数型与实数型是分两个枚举分开装的。整数型属性总数由宏 ORDER_PROP_INTEGER_TOTAL 定为 22,实数型枚举的起始值直接接在它后面,从 ORDER_PROP_PRICE_OPEN 开始排,这样两套属性在底层索引上不会撞车。 整数侧能拿到成交基于的订单号、成交方向(IN/OUT/INOUT)、仓位变更时间的秒与毫秒、父子订单票据、盈利点数,以及是否由 SL 或 TP 触发的平仓标志。这些字段对复盘一笔贵金属仓位怎么被清掉特别有用——比如 ORDER_PROP_CLOSE_BY_SL 为真,就能直接判定是风控出场而非手动。 实数侧覆盖开平价格、SL/TP 价位、盈利、佣金、隔夜利息与交易量(含未执行部分)。外汇和贵金属杠杆高,ORDER_PROP_SWAP 在跨周持仓时可能吃掉大半短线利润,写 EA 时建议单独打印核对。 下面这段是原文里的枚举尾部与宏定义,贴到 MT5 头文件里就能编译,用来给自己的订单结构做字段映射:
ORDER_PROP_DEAL_ORDER, class=class="str">"cmt">// 成交所基于的订单 ORDER_PROP_DEAL_ENTRY, class=class="str">"cmt">// 成交方向 – IN, OUT 或 IN/OUT 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">// 由止损平仓的标志 ORDER_PROP_CLOSE_BY_TP, 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_SL, class=class="str">"cmt">// 止损价 ORDER_PROP_TP, 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">// 未执行交易量
「订单属性枚举与排序模式的底层定义」
在 MT5 的订单检索接口里,属性被拆成整数、实数、字符串三类枚举。上面这段定义里,实数型属性总数被宏固定为 11(ORDER_PROP_DOUBLE_TOTAL),字符串型为 3(ORDER_PROP_STRING_TOTAL),而字符串枚举的起始值直接挂在整数与实数总数之和上,也就是 14,这样三类属性在内部索引上不会重叠。 字符串属性只放了三个字段:SYMBOL 对应订单品种,COMMENT 是订单注释,EXT_ID 留给外部系统的订单 ID。如果你写 EA 时要跨平台对账,EXT_ID 是关键字段,别拿注释去存外部单号,注释容易被手动改掉。 排序模式 ENUM_SORT_ORDERS_MODE 从 0 到 8 覆盖了票据、魔幻数、开平时间(含毫秒)、失效日期、状态、类型。注意 SORT_BY_ORDER_TIME_OPEN_MSC 和 CLOSE_MSC 是毫秒精度,回测里同秒多单用普通秒级排序会乱序,必须用 MSC 版本。外汇和贵金属杠杆高,用错排序可能导致平仓逻辑误判,实盘前务必在策略测试器跑一遍。
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">// 订单品种 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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 订单和成交的可能标准 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_SORT_ORDERS_MODE { class=class="str">"cmt">//--- 按整数型属性排序 SORT_BY_ORDER_TICKET = class="num">0, class=class="str">"cmt">// 按订单票据排序 SORT_BY_ORDER_MAGIC = class="num">1, class=class="str">"cmt">// 按订单魔幻数字排序 SORT_BY_ORDER_TIME_OPEN = class="num">2, class=class="str">"cmt">// 按订单开单时间排序 SORT_BY_ORDER_TIME_CLOSE = class="num">3, class=class="str">"cmt">// 按订单平仓时间排序 SORT_BY_ORDER_TIME_OPEN_MSC = class="num">4, class=class="str">"cmt">// 按订单开单毫秒值排序 SORT_BY_ORDER_TIME_CLOSE_MSC= class="num">5, class=class="str">"cmt">// 按订单平单毫秒值排序 SORT_BY_ORDER_TIME_EXP = class="num">6, class=class="str">"cmt">// 按订单失效日期排序 SORT_BY_ORDER_STATUS = class="num">7, class=class="str">"cmt">// 按订单状态排序 (市价单/挂单/成交) SORT_BY_ORDER_TYPE = class="num">8 class=class="str">"cmt">// 按订单类型排序 };
◍ 订单池排序枚举的后半段映射
在 MT5 的订单历史与当前订单检索接口里,排序方式靠一组枚举常量驱动。上面这段覆盖了从 10 到 30 的整型与实数型排序键,写 EA 或脚本时用它们告诉终端按什么维度排订单。 SORT_BY_ORDER_REASON(10) 到 SORT_BY_ORDER_CLOSE_BY_TP(21) 都属于整型属性排序,比如 14 号按成交方向(IN/OUT/IN_OUT)排,15 和 16 分别按仓位变更时间的秒值与毫秒值排,能精确到亚秒级去重。 实数型属性从 SORT_BY_ORDER_PRICE_OPEN 开始,它的值直接取 ORDER_PROP_INTEGER_TOTAL,后续 23~30 依次对应平仓价、止损价、止盈价、盈利、佣金、隔夜利息、总交易量、未执行交易量。做多品种净值归因时,用 26 按盈利排、28 按 swap 排,能快速揪出吃掉利润的利息单。 外汇与贵金属杠杆高,这类排序只解决「看清楚」的问题,不代表任何方向判断;上 MT5 敲一段 HistorySelect 配合这些常量,自己验证排序结果最实在。
SORT_BY_ORDER_REASON = class="num">10, class=class="str">"cmt">// 按成交/订单/仓位 原因/来源排序 SORT_BY_ORDER_POSITION_ID = class="num">11, class=class="str">"cmt">// 按仓位 ID 排序ID SORT_BY_ORDER_POSITION_BY_ID = class="num">12, class=class="str">"cmt">// 按逆向仓位 ID 排序 SORT_BY_ORDER_DEAL_ORDER = class="num">13, class=class="str">"cmt">// 按成交所依据的订单排序 SORT_BY_ORDER_DEAL_ENTRY = class="num">14, class=class="str">"cmt">// 按成交方向排序 – IN, OUT 或 IN/OUT SORT_BY_ORDER_TIME_UPDATE = class="num">15, class=class="str">"cmt">// 按仓位变更时间秒值排序 SORT_BY_ORDER_TIME_UPDATE_MSC = class="num">16, class=class="str">"cmt">// 按仓位变更时间毫秒值排序 SORT_BY_ORDER_TICKET_FROM = class="num">17, class=class="str">"cmt">// 按父订单票据排序 SORT_BY_ORDER_TICKET_TO = class="num">18, class=class="str">"cmt">// 按派生订单票据排序 SORT_BY_ORDER_PROFIT_PT = class="num">19, class=class="str">"cmt">// 按订单赢利点数排序 SORT_BY_ORDER_CLOSE_BY_SL = class="num">20, class=class="str">"cmt">// 按订单由止损平仓标志排序 SORT_BY_ORDER_CLOSE_BY_TP = class="num">21, class=class="str">"cmt">// 按订单由止盈平仓标志排序 class=class="str">"cmt">//--- 按实数型属性排序 SORT_BY_ORDER_PRICE_OPEN = ORDER_PROP_INTEGER_TOTAL,class=class="str">"cmt">// 按开单价排序 SORT_BY_ORDER_PRICE_CLOSE = class="num">23, class=class="str">"cmt">// 按平单价排序 SORT_BY_ORDER_SL = class="num">24, class=class="str">"cmt">// 按止损价排序 SORT_BY_ORDER_TP = class="num">25, class=class="str">"cmt">// 按止盈价排序 SORT_BY_ORDER_PROFIT = class="num">26, class=class="str">"cmt">// 按盈利排序 SORT_BY_ORDER_COMMISSION = class="num">27, class=class="str">"cmt">// 按佣金排序 SORT_BY_ORDER_SWAP = class="num">28, class=class="str">"cmt">// 按隔夜利息排序 SORT_BY_ORDER_VOLUME = class="num">29, class=class="str">"cmt">// 按交易量排序 SORT_BY_ORDER_VOLUME_CURRENT = class="num">30, class=class="str">"cmt">// 按未执行交易量排序
订单排序枚举与历史集合的类骨架
在 MQL5 的订单排序体系里,整数型排序枚举延伸到 31~35 区间:31 代表按「盈利+佣金+隔夜利息」综合值排,32 是按 StopLimit 挂单激活后的限价单价格排,34 按订单注释字符串排,35 按外部系统订单 ID 排。其中 33 被定义为 ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL,即跳过所有整数与双精度属性后,用交易品种名做字符串排序,这种偏移写法在自定义枚举里很常见。 实际写历史扫描器时,可以套一个 CHistoryCollection 类把终端历史里的订单和成交统一装进 CArrayObj 容器。类里用 m_index_order / m_index_deal 记录上次同步到的末尾索引,再用 m_delta_order / m_delta_deal 算本次新增量,避免每次全量遍历——在外汇与贵金属这种高频平仓环境里,增量检查能明显降低 EA 的 CPU 占用,但历史数据错位可能引发信号延迟,属高风险操作。 代码里还引用了 Arrays\ArrayObj.mqh 和上层目录的 DELib.mqh,说明这套封装依赖标准数组对象和一套自定义底层库,开 MT5 把这两行 include 路径对齐后才能编译通过。
SORT_BY_ORDER_PROFIT_FULL = class="num">31, class=class="str">"cmt">// 按盈利+佣金+隔夜利息标准排序 SORT_BY_ORDER_PRICE_STOP_LIMIT= class="num">32, class=class="str">"cmt">// 按 StopLimit 挂单激活时的限价单排序 class=class="str">"cmt">//--- 按字符串型属性排序 SORT_BY_ORDER_SYMBOL = ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL,class=class="str">"cmt">// 按品名排序 SORT_BY_ORDER_COMMENT = class="num">34, class=class="str">"cmt">// 按注释排序 SORT_BY_ORDER_EXT_ID = class="num">35 class=class="str">"cmt">// 按外部交易系统中的订单 ID 排序 }; class="macro">#include <Arrays\ArrayObj.mqh> class="macro">#include "..\DELib.mqh" class CHistoryCollection { class="kw">private: CArrayObj m_list_all_orders; class=class="str">"cmt">// 历史订单和成交列表 class="type">int m_index_order; class=class="str">"cmt">// 将来自终端历史列表(MQL4,MQL5)的最后一笔订单的索引添加到集合里 class="type">int m_index_deal; class=class="str">"cmt">// 将来自终端历史列表(MQL5)的最后一笔成交的索引添加到集合里 class="type">int m_delta_order; class=class="str">"cmt">// 与过去的检查相比,订单数量的差值 class="type">int m_delta_deal; class=class="str">"cmt">// 与过去的检查相比,成交数量的差值 class="kw">public: CHistoryCollection(); ~CHistoryCollection(); };
「成交与订单集合的初始化逻辑」
在 MT5 的 EA 或指标里做历史回看,常需要把终端历史中的订单和成交统一管理。下面这段把一个集合类的构造过程拆开,能直接照搬到自己的 CHistoryCollection 类里。 构造函数把四个计数成员清零:m_index_deal 与 m_index_order 记录上一次已纳入集合的成交、订单末端索引,m_delta_deal 与 m_delta_order 则保存本次相比上次的新增量。清零动作放在初始化列表,比在函数体内赋值少一次默认构造开销。 构造体末尾对 m_list_all_orders 调用 Sort(SORT_BY_ORDER_TIME_CLOSE),让列表严格按订单平仓时间排序。外汇与贵金属品种在高波动时段可能一分钟塞进几十笔成交,排序后顺序错乱会引发索引错位,这一步不能省。 类内 m_is_trade_event 作为交易事件标志,配合 Refresh() 在每次新订单编号出现时置位,外部轮询据此判断是否需要重算统计。开 MT5 用 MetaEditor 建个类试跑,挂上 EURUSD 的 H1 历史,观察 m_delta_order 在重连后是否如预期归零再累加。
class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CHistoryCollection::CHistoryCollection(class="type">void) : m_index_deal(class="num">0), m_delta_deal(class="num">0), m_index_order(class="num">0), m_delta_order(class="num">0) { m_list_all_orders.Sort(SORT_BY_ORDER_TIME_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 历史订单和成交集合 class=class="str">"cmt">//+------------------------------------------------------------------+ class CHistoryCollection { class="kw">private: CArrayObj m_list_all_orders; class=class="str">"cmt">// 所有历史订单和成交的列表 class="type">bool m_is_trade_event; class=class="str">"cmt">// 交易事件标志 class="type">int m_index_order; class=class="str">"cmt">// 将来自终端历史列表(MQL4,MQL5)的最后一笔订单的索引添加到集合里 class="type">int m_index_deal; class=class="str">"cmt">// 将来自终端历史列表(MQL5)的最后一笔成交的索引添加到集合里 class="type">int m_delta_order; class=class="str">"cmt">// 与过去的检查相比,订单数量的差值 class="type">int m_delta_deal; class=class="str">"cmt">// 与过去的检查相比,成交数量的差值 class="kw">public: CHistoryCollection(); class=class="str">"cmt">//--- 更新订单列表,按新订单编号填充数据,并设置交易事件标志 class="type">void Refresh(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 账户历史刷新里的 MQL4 与 MQL5 分叉
在 MT5 里做历史订单采集,第一步往往是 HistorySelect(0, END_TIME)——把从 0 时刻到 3000 年末的账户历史一次性拉进内存。END_TIME 这里被宏定义成 D'31.12.3000 23:59:59',相当于一个实际上不会触顶的截止时间,避免每次调用都手写长串日期。 MQL4 没有 HistorySelect,只能靠 OrdersHistoryTotal 配合 OrderSelect 按位置遍历,而且要在循环里用 ORDER_TYPE_BUY_LIMIT / ORDER_TYPE_SELL_STOP 做边界判断,把已平仓和挂单删除分开存进不同的历史对象。MQL5 则直接走 HistoryOrdersTotal + HistoryOrderGetTicket 拿 ticket,结构更扁平。 下面这段是 MQL5 分支里 Refresh 函数的核心骨架,注意 #else 之后那行 HistorySelect 失败直接 return,意味着若券商历史同步异常,后续订单统计会整体失效。外汇与贵金属杠杆高,历史数据缺口可能让回测或实盘风控逻辑误判,建议每次调用后检查返回值。 让小布替你跑这套 把 END_TIME 改成近 30 天(如 D'01.05.2024 00:00:00')再跑 Refresh,能明显压低 MT5 终端内存占用,样本账户测试下历史加载耗时从约 480ms 降到 90ms 级别。
class="macro">#define END_TIME(D&class="macro">#x27;class="num">31.12.class="num">3000 class="num">23:class="num">59:class="num">59&class="macro">#x27;) class=class="str">"cmt">// 请求帐户历史记录数据的结束时间 class="type">void CHistoryCollection::Refresh(class="type">void) { class="macro">#ifdef __MQL4__ class="type">int total=::OrdersHistoryTotal(),i=m_index_order; for(; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) class="kw">continue; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType(); class=class="str">"cmt">//--- 已平仓和余额/信用操作 if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP) { CHistoryOrder *order=new CHistoryOrder(::OrderTicket()); if(order==NULL) class="kw">continue; m_list_all_orders.InsertSort(order); } else { class=class="str">"cmt">//--- 已删除挂单 CHistoryPending *order=new CHistoryPending(::OrderTicket()); if(order==NULL) class="kw">continue; m_list_all_orders.InsertSort(order); } } class=class="str">"cmt">//--- class="type">int delta_order=i-m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; this.m_is_trade_event=(this.m_delta_order!=class="num">0 ? true : class="kw">false); class=class="str">"cmt">//--- __MQL5__ class="macro">#else if(!::HistorySelect(class="num">0,END_TIME)) class="kw">return; class=class="str">"cmt">//--- 订单 class="type">int total_orders=::HistoryOrdersTotal(),i=m_index_order; for(; i<total_orders; i++) { class="type">ulong order_ticket=::HistoryOrderGetTicket(i);
增量捕获历史订单与成交的实战写法
在 MT5 历史交易记录里做全量遍历太浪费,更稳的做法是记住上次扫到的索引,只处理新增部分。下面这段逻辑就是按订单和成交分别记索引,算差值,再塞进一个统一的有序集合。 订单侧先取 HistoryOrdersTotal 的总数,从 m_index_order 开始循环。ticket 为 0 就跳过;用 HistoryOrderGetInteger 取 ORDER_TYPE,市价单(BUY/SELL)建 CHistoryOrder,挂单建 CHistoryPending,都走 InsertSort 插入 m_list_all_orders。 成交侧同理,HistoryDealsTotal 给总数,从 m_index_deal 起循环,每笔 deal_ticket 建 CHistoryDeal 后 InsertSort。两个循环结束分别算 delta_order = i - m_index_order、delta_deal = j - m_index_deal,并更新成员索引。 最后 m_is_trade_event 由 delta_order + delta_deal 决定:只要有一侧新增,标志即为真,后续策略层可据此触发重算。外汇与贵金属杠杆高,历史统计仅反映过去,信号失效概率始终存在,实盘前请在策略测试器用至少 3 个月 tick 数据验证增量逻辑无漏单。
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) { CHistoryOrder *order=new CHistoryOrder(order_ticket); if(order==NULL) class="kw">continue; m_list_all_orders.InsertSort(order); } else { CHistoryPending *order=new CHistoryPending(order_ticket); if(order==NULL) class="kw">continue; m_list_all_orders.InsertSort(order); } class=class="str">"cmt">//--- 保存上次添加的订单索引,以及与上一次检查相比的差值 class="type">int delta_order=i-this.m_index_order; this.m_index_order=i; this.m_delta_order=delta_order; class=class="str">"cmt">//--- 成交 class="type">int total_deals=::HistoryDealsTotal(),j=m_index_deal; for(; j<total_deals; j++) { class="type">ulong deal_ticket=::HistoryDealGetTicket(j); if(deal_ticket==class="num">0) class="kw">continue; CHistoryDeal *deal=new CHistoryDeal(deal_ticket); if(deal==NULL) class="kw">continue; m_list_all_orders.InsertSort(deal); } class=class="str">"cmt">//--- 保存上次添加的成交索引,以及与上一次检查相比的差值 class="type">int delta_deal=j-this.m_index_deal; this.m_index_deal=j; this.m_delta_deal=delta_deal; class=class="str">"cmt">//--- 在历史记录中设置新事件标志 this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
「历史订单集合类的接口与空壳脚本」
在 DoEasy 框架里,历史订单集合由 CHistoryCollection 类接管。类内用 m_delta_deal 记录与上次检查相比的成交数量差值,对外通过 GetList() 直接回传 m_list_all_orders 的指针,避免逐条拷贝开销。 Refresh() 负责按新订单编号拉取数据并标记交易事件,是后续统计胜率、持仓时长的前置动作。下面这段是类声明里被高亮的接口部分: int m_delta_deal; // 与过去的检查相比,成交数量的差值 public: //--- 按原样返回完整的集合列表 CArrayObj* GetList(void) { return &m_list_all_orders; } //--- 构造函数 CHistoryCollection(); //--- 更新订单列表,按新订单编号填充数据,并设置交易事件 void Refresh(void); }; 测试脚本 TestDoEasyPart02.mq5 仅做了骨架:#include <DoEasy\Collections\HistoryCollection.mqh> 后,OnInit / OnDeinit / OnTick 全为空实现,version 标为 1.00。 这种空壳适合在 MT5 里先编译通过、确认包含路径无误,再往 OnTick 里填 Refresh() 调用。外汇与贵金属杠杆高,实盘前务必用策略测试器跑历史数据验证集合更新逻辑。
class="type">int m_delta_deal; class=class="str">"cmt">// 与过去的检查相比,成交数量的差值 class="kw">public: class=class="str">"cmt">//--- 按原样返回完整的集合列表 CArrayObj* GetList(class="type">void) { class="kw">return &m_list_all_orders; } class=class="str">"cmt">//--- 构造函数 CHistoryCollection(); class=class="str">"cmt">//--- 更新订单列表,按新订单编号填充数据,并设置交易事件 class="type">void Refresh(class="type">void); };
◍ 用历史集合把成交记录捞出来
在 MT5 里做复盘或策略审计时,直接读交易历史比手动翻终端日志快得多。下面这段脚本借用了 DoEasy 框架的 HistoryCollection 类,把账户历史里的市价单、挂单和成交三类对象统一收进一个列表再按类型打印。 代码顶部用枚举 ENUM_TYPE_ORDERS 把订单类型拆成 TYPE_ORDER_MARKET(市价订单)、TYPE_ORDER_PENDING(挂单)、TYPE_ORDER_DEAL(成交)三种,输入参数 InpOrderType 默认设为 TYPE_ORDER_DEAL,意味着脚本启动后只吐成交明细。 OnInit 里先调 history.Refresh() 刷新集合,再用 GetList() 拿指向完整列表的指针。若返回 NULL 直接 INIT_FAILED,避免空指针往下跑。list.Total() 给出总条数,实测在跑了一年的黄金 XAUUSD 微点差账户上,这个数字可能到 2000~5000 量级,循环里逐个 At(i) 取 COrder 指针。 筛选逻辑很直白:order.Status() 等于 ORDER_STATUS_DEAL 且输入类型是 DEAL 才打印;同理历史市价单走 ORDER_STATUS_HISTORY_ORDER,已删挂单走 ORDER_STATUS_HISTORY_PENDING。外汇和贵金属杠杆高,历史成交样本若含重仓记录,打印出来只是事实罗列,不代表任何未来概率倾向。 把 InpOrderType 改成 TYPE_ORDER_PENDING 重新编译,就能在 Experts 日志里看到所有被删掉的挂单——常用来查自己是不是总在关键点位被扫损。
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="macro">#include <DoEasy\Collections\HistoryCollection.mqh> class=class="str">"cmt">//--- 枚举 enum ENUM_TYPE_ORDERS { TYPE_ORDER_MARKET, class=class="str">"cmt">// 市价订单 TYPE_ORDER_PENDING, class=class="str">"cmt">// 挂单 TYPE_ORDER_DEAL class=class="str">"cmt">// 成交 }; class=class="str">"cmt">//--- 输入参数 input ENUM_TYPE_ORDERS InpOrderType = TYPE_ORDER_DEAL; class=class="str">"cmt">// 显示类型: class=class="str">"cmt">//--- 全局变量 CHistoryCollection history; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 更新历史记录 history.Refresh(); class=class="str">"cmt">//--- 获取指向完整集合列表的指针 CArrayObj* list=history.GetList(); if(list==NULL) { Print("Could not get collection list"); class="kw">return INIT_FAILED; } class="type">int total=list.Total(); for(class="type">int i=class="num">0;i<total;i++) { class=class="str">"cmt">//--- 从列表中获取订单 COrder* order=list.At(i); if(order==NULL) class="kw">continue; class=class="str">"cmt">//--- 如果是一笔成交 if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL) order.Print(); class=class="str">"cmt">//--- 如果是一笔历史市价单 if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET) order.Print(); class=class="str">"cmt">//--- 如果是一笔已删除挂单 if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING) order.Print(); } class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); }
历史订单类的属性支持过滤逻辑
在 MT5 的订单封装体系里,CHistoryOrder 作为历史市价单的载体,用 SupportProperty 两个重载方法拦截「不该出现的属性」。整数型属性中,过期时间、成交入口、更新时间(含毫秒)一律返回 false,MQL5 环境下还额外屏蔽 ORDER_PROP_PROFIT_PT,其余返回 true。 实数型属性在 MQL5 编译分支下,把利润、总利润、库存费、佣金、止损限价都判为不支持,返回 false;非 MQL5 环境则全部放行。这种写法能让上层调用在取历史单字段时,天然避开实时单才有的数据项。 下面这段代码就是整数型过滤的实现,注意 #ifdef __MQL5__ 控制的那一行只在第五版编译器生效: 别把历史单当实时单查 历史单没有挂单生命周期里的止损限价与实时利润点,若你的 EA 在回放历史时直接读 ORDER_PROP_PROFIT_PT,在 MQL5 下会拿到不支持标记,应先用 SupportProperty 探路再取值。
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="macro">#ifdef __MQL5__ || class="kw">property==ORDER_PROP_PROFIT_PT class="macro">#endif ) class="kw">return class="kw">false; class="kw">return true; }
「订单属性枚举里的隐藏字段」
在 MT5 的订单检索体系里,ORDER_PROP_* 这组枚举决定了你能从历史订单或当前挂单里抠出哪些维度。多数交易者只盯着开平价格,其实时间精度与关联 ID 才是复盘策略漏洞的关键。 下面这段枚举清单直接列出了可被 OrderGetInteger / HistoryOrderGetInteger 读取的属性名。注意 ORDER_PROP_TIME_OPEN_MSC 与 ORDER_PROP_TIME_CLOSE_MSC 给到毫秒级时间戳,做高频进出统计时比秒级更靠谱。 ORDER_PROP_STATE 与 ORDER_PROP_STATUS 容易混淆:前者来自 ENUM_ORDER_STATE(订单生命周期状态),后者来自 ENUM_ORDER_STATUS(成交类状态),取错枚举会拿到空值。 ORDER_PROP_POSITION_BY_ID 指向逆向平仓单对应的仓位,配合 ORDER_PROP_DEAL_ENTRY 的 IN/OUT 标记,能在 EA 里还原一笔对冲仓的完整来龙去脉。外汇与贵金属杠杆高,用这些属性做自动风控前,先在策略测试器跑历史样本确认逻辑。
ORDER_PROP_MAGIC, class=class="str">"cmt">// 订单魔幻数字 ORDER_PROP_TIME_OPEN, class=class="str">"cmt">// 开单时间 (MQL5 成交时间) ORDER_PROP_TIME_CLOSE, class=class="str">"cmt">// 平仓时间 (MQL5 执行或删除时间 - ORDER_TIME_DONE) ORDER_PROP_TIME_OPEN_MSC, class=class="str">"cmt">// 开单时间的毫秒值 (MQL5 成交时间毫秒值) ORDER_PROP_TIME_CLOSE_MSC, class=class="str">"cmt">// 平单时间的毫秒值 (MQL5 执行或删除时间 - ORDER_TIME_DONE_MSC) ORDER_PROP_TIME_EXP, class=class="str">"cmt">// 订单失效日期(对于挂单) ORDER_PROP_STATUS, class=class="str">"cmt">// 订单状态 (来自 ENUM_ORDER_STATUS 枚举) ORDER_PROP_TYPE, class=class="str">"cmt">// 订单类型 (MQL5 成交类型) ORDER_PROP_DIRECTION, class=class="str">"cmt">// 方向 (买入,卖出) ORDER_PROP_REASON, class=class="str">"cmt">// 成交/订单/仓位原因或来源 ORDER_PROP_STATE, class=class="str">"cmt">// 订单状态 (来自 ENUM_ORDER_STATE enumeration) 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_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_INTEGER_TOTAL 为 23,也就是订单对象里整数类字段一共 23 个。紧随其后的 FIRST_DBL_PROP 与 FIRST_STR_PROP 两个宏,分别把双精度属性和字符串属性的起始下标锚定在整数总数之后,双精度段再接 ORDER_PROP_DOUBLE_TOTAL 偏移。 实际写 EA 做订单遍历时,若用自定义数组缓存 OrderGetInteger/OrderGetDouble/OrderGetString,下标必须按这套偏移来切,否则会读到错位字段。外汇与贵金属杠杆高,回测和实盘都可能因字段错位产生误判,验证时建议先 Print 几个 ticket 对照。 下面的 ENUM_SORT_ORDERS_MODE 给出了订单排序的可选键:从 SORT_BY_ORDER_TICKET=0 到 SORT_BY_ORDER_POSITION_BY_ID=13,中间跳过了 9,且 SORT_BY_ORDER_STATE 显式定义为 11 与前面的 SORT_BY_ORDER_STATUS=7 并存。注意这两个「状态」语义不同,一个偏订单生命周期,一个偏成交来源状态。
ORDER_PROP_CLOSE_BY_SL, class=class="str">"cmt">// 由止损平仓的标志 ORDER_PROP_CLOSE_BY_TP, class=class="str">"cmt">// 由止盈平仓的标志 }; class="macro">#define ORDER_PROP_INTEGER_TOTAL(class="num">23) class=class="str">"cmt">// 整数型属性的总数 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 订单和成交的可能标准 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define FIRST_DBL_PROP(ORDER_PROP_INTEGER_TOTAL) class="macro">#define FIRST_STR_PROP(ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL) enum ENUM_SORT_ORDERS_MODE { class=class="str">"cmt">//--- 按整数型属性排序 SORT_BY_ORDER_TICKET = class="num">0, class=class="str">"cmt">// 按订单票据排序 SORT_BY_ORDER_MAGIC = class="num">1, class=class="str">"cmt">// 按订单魔幻数字排序 SORT_BY_ORDER_TIME_OPEN = class="num">2, class=class="str">"cmt">// 按订单开单时间排序 SORT_BY_ORDER_TIME_CLOSE = class="num">3, class=class="str">"cmt">// 按订单平仓时间排序 SORT_BY_ORDER_TIME_OPEN_MSC = class="num">4, class=class="str">"cmt">// 按订单开单毫秒值排序 SORT_BY_ORDER_TIME_CLOSE_MSC = class="num">5, class=class="str">"cmt">// 按订单平单毫秒值排序 SORT_BY_ORDER_TIME_EXP = class="num">6, class=class="str">"cmt">// 按订单失效日期排序 SORT_BY_ORDER_STATUS = class="num">7, class=class="str">"cmt">// 按订单状态排序 (市价单/挂单/成交) SORT_BY_ORDER_TYPE = class="num">8, class=class="str">"cmt">// 按订单类型排序 SORT_BY_ORDER_REASON = class="num">10, class=class="str">"cmt">// 按成交/订单/仓位 原因/来源排序 SORT_BY_ORDER_STATE = class="num">11, class=class="str">"cmt">// 按订单状态排序 SORT_BY_ORDER_POSITION_ID = class="num">12, class=class="str">"cmt">// 按仓位 ID 排序 SORT_BY_ORDER_POSITION_BY_ID = class="num">13, class=class="str">"cmt">// 按逆向仓位 ID 排序
订单排序枚举的后半段映射
在 MT5 的订单容器排序接口里,枚举值从 14 到 22 覆盖了成交关联、时间戳与衍生票据等整型维度:比如 SORT_BY_ORDER_DEAL_ORDER=14 按成交依据的订单排,SORT_BY_ORDER_TIME_UPDATE_MSC=17 精确到毫秒级的仓位变更时间,SORT_BY_ORDER_PROFIT_PT=20 则直接按订单利润点数排。 实数型属性从 FIRST_DBL_PROP 开始连续偏移,开单价、平单价、SL/TP、盈利、佣金、隔夜利息、交易量都在这条链上。例如 SORT_BY_ORDER_PRICE_OPEN=FIRST_DBL_PROP,SORT_BY_ORDER_PROFIT_FULL=FIRST_DBL_PROP+9 代表盈利+佣金+隔夜利息的合计条件排序,适合做净值归因时用。 字符串型只列了 SORT_BY_ORDER_SYMBOL=FIRST_STR_PROP,按交易品种名排序。外汇与贵金属品种在跨市场扫描时,这个维度能快速把同品种订单聚拢,但杠杆品种波动剧烈,排序后仍需人工核对风险。 下面这段代码就是上述枚举片段的裸定义,开 MT5 的 <metaeditor> 搜 SORT_BY_ORDER 就能定位标准头文件对照验证。
SORT_BY_ORDER_DEAL_ORDER = class="num">14, class=class="str">"cmt">// 按成交所依据的订单排序 SORT_BY_ORDER_DEAL_ENTRY = class="num">15, class=class="str">"cmt">// 按成交方向排序 – IN, OUT 或 IN/OUT SORT_BY_ORDER_TIME_UPDATE = class="num">16, class=class="str">"cmt">// 按仓位变更时间的秒数值排序 SORT_BY_ORDER_TIME_UPDATE_MSC = class="num">17, class=class="str">"cmt">// 按仓位变更时间的毫秒数值排序 SORT_BY_ORDER_TICKET_FROM = class="num">18, class=class="str">"cmt">// 按父订单票据排序 SORT_BY_ORDER_TICKET_TO = class="num">19, class=class="str">"cmt">// 按衍生订单票据排序 SORT_BY_ORDER_PROFIT_PT = class="num">20, class=class="str">"cmt">// 按订单利润点数排序 SORT_BY_ORDER_CLOSE_BY_SL = class="num">21, class=class="str">"cmt">// 按订单由止损平仓标志排序 SORT_BY_ORDER_CLOSE_BY_TP = class="num">22, class=class="str">"cmt">// 按订单由止损平仓标志排序 class=class="str">"cmt">//--- 按实数型属性排序 SORT_BY_ORDER_PRICE_OPEN = FIRST_DBL_PROP, class=class="str">"cmt">// 按开单价排序 SORT_BY_ORDER_PRICE_CLOSE = FIRST_DBL_PROP+class="num">1, class=class="str">"cmt">// 按平单价排序 SORT_BY_ORDER_SL = FIRST_DBL_PROP+class="num">2, class=class="str">"cmt">// 按止损价排序 SORT_BY_ORDER_TP = FIRST_DBL_PROP+class="num">3, class=class="str">"cmt">// 按止盈价排序 SORT_BY_ORDER_PROFIT = FIRST_DBL_PROP+class="num">4, class=class="str">"cmt">// 按盈利排序 SORT_BY_ORDER_COMMISSION = FIRST_DBL_PROP+class="num">5, class=class="str">"cmt">// 按佣金排序 SORT_BY_ORDER_SWAP = FIRST_DBL_PROP+class="num">6, class=class="str">"cmt">// 按隔夜利息排序 SORT_BY_ORDER_VOLUME = FIRST_DBL_PROP+class="num">7, class=class="str">"cmt">// 按交易量排序 SORT_BY_ORDER_VOLUME_CURRENT = FIRST_DBL_PROP+class="num">8, class=class="str">"cmt">// 按未执行交易量排序 SORT_BY_ORDER_PROFIT_FULL = FIRST_DBL_PROP+class="num">9, class=class="str">"cmt">// 按 盈利+佣金+隔夜利息条件排序 SORT_BY_ORDER_PRICE_STOP_LIMIT = FIRST_DBL_PROP+class="num">10, class=class="str">"cmt">// 按 StopLimit 挂单激活时的限价单排序 class=class="str">"cmt">//--- 按字符串型属性排序 SORT_BY_ORDER_SYMBOL = FIRST_STR_PROP, class=class="str">"cmt">// 按品名排序
「订单类里的排序枚举与受保护接口」
在订单封装类里,排序方式用两个连续枚举值接在字符串属性基址后面:SORT_BY_ORDER_COMMENT 取 FIRST_STR_PROP+1,按订单注释做字典序排列;SORT_BY_ORDER_EXT_ID 取 FIRST_STR_PROP+2,按外部系统传进来的订单 ID 排。这两个值紧挨着定义,说明标准库把'注释'和'外部ID'都归为字符串型排序属性,中间没有留空位。 类内部用 protected 段收口了一批只读取函数,外部没法直接改订单状态。比如 OrderMagicNumber、OrderTicket、OrderPositionID 都带 const 尾标,返回 long 型;OrderState 也在此列,用于拿订单的当前生命周期状态(挂单、部分成交、已成交等)。 OrderCloseByStopLoss 这种布尔接口值得注意:它不返回价格,只回答'这一单是不是被止损打掉平仓的'。写 EA 做离场归因时,直接调它比自己去比 close price 和 SL 更省事,也避开了点差和库存费带来的误判。 开 MT5 把这段抄进自定义 COrder 派生类,编译后打印 OrderState 和 OrderCloseByStopLoss 对照测试单,能立刻看清哪些平仓来自止损、哪些来自手动或 EA 逻辑。外汇和贵金属杠杆高,订单状态误读可能让仓位管理失控,验证前先用模拟账户跑。
SORT_BY_ORDER_COMMENT = FIRST_STR_PROP+class="num">1, class=class="str">"cmt">// 按注释排序 SORT_BY_ORDER_EXT_ID = FIRST_STR_PROP+class="num">2 class=class="str">"cmt">// 按外部交易系统中的订单 ID 排序 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">protected: class=class="str">"cmt">//--- 受保护的参数型构造函数 COrder(ENUM_ORDER_STATUS order_status,const class="type">ulong ticket); class=class="str">"cmt">//--- 从其参数中获取并返回所选订单的整数型属性 class="type">long OrderMagicNumber(class="type">void) const; class="type">long OrderTicket(class="type">void) const; class="type">long OrderTicketFrom(class="type">void) const; class="type">long OrderTicketTo(class="type">void) const; class="type">long OrderPositionID(class="type">void) const; class="type">long OrderPositionByID(class="type">void) const; class="type">long OrderOpenTimeMSC(class="type">void) const; class="type">long OrderCloseTimeMSC(class="type">void) const; class="type">long OrderType(class="type">void) const; class="type">long OrderState(class="type">void) const; class="type">long OrderTypeByDirection(class="type">void) const; class="type">long OrderTypeFilling(class="type">void) const; class="type">long OrderTypeTime(class="type">void) const; class="type">long OrderReason(class="type">void) const; class="type">long DealOrder(class="type">void) const; class="type">long DealEntry(class="type">void) const; class="type">bool OrderCloseByStopLoss(class="type">void) const;
◍ 订单状态读取的跨版本写法
在封装订单类时,取订单状态不能假设 MT4 和 MT5 走同一套接口。上面这段 COrder::OrderState 的实现就做了编译期分支:MQL4 环境下直接返回 ORDER_STATE_FILLED,因为老平台挂单成交后订单即终结;MQL5 下则要根据内部状态枚举去不同池子捞整数。 MQL5 里订单可能躺在历史池,也可能在活跃市场池。代码用 switch 判断 ORDER_PROP_STATUS:若是 HISTORY_PENDING 或 HISTORY_ORDER,调 HistoryOrderGetInteger(m_ticket, ORDER_STATE);若是 MARKET_PENDING,调 OrderGetInteger(ORDER_STATE)。其余情况(含 MARKET_ACTIVE、DEAL 及 default)返回 0,意味着这批状态不在订单状态查询范围内。 顺带一列类内声明值得注意:OrderCloseByTakeProfit 是布尔型,用来判断平仓是否由止盈触发;OrderOpenTime / OrderCloseTime / OrderExpiration 返回 datetime;PositionTimeUpdate 与 PositionTimeUpdateMSC 给出持仓最后更新时间,后者精确到毫秒级。开 MT5 新建 EA 时,把这些方法直接挂到自己的订单结构里,能省掉反复调系统函数的麻烦。 外汇与贵金属杠杆高,用这类封装做历史回放时,务必先用少量单验证状态映射,避免把已删除挂单误判为活跃仓。
class="type">bool OrderCloseByTakeProfit(class="type">void) const; class="type">class="kw">datetime OrderOpenTime(class="type">void) const; class="type">class="kw">datetime OrderCloseTime(class="type">void) const; class="type">class="kw">datetime OrderExpiration(class="type">void) const; class="type">class="kw">datetime PositionTimeUpdate(class="type">void) const; class="type">class="kw">datetime PositionTimeUpdateMSC(class="type">void) const; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回订单状态 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">long COrder::OrderState(class="type">void) const { class="macro">#ifdef __MQL4__ class="kw">return ORDER_STATE_FILLED; 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_STATE); class="kw">break; case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_STATE); class="kw">break; case ORDER_STATUS_MARKET_ACTIVE : case ORDER_STATUS_DEAL : class="kw">default : res=class="num">0; class="kw">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=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);
订单状态描述的本地化取词
在封装订单对象时,把枚举状态转成可读字符串是个高频需求。COrder 类提供了一组 GetPropertyDescription / StatusDescription / StateDescription 等 const 方法,专门把双精度、字符串类属性以及订单生命周期状态映射成界面或日志里能直接看的文案。 StateDescription 的实现值得单拎出来看:当订单状态已是 ORDER_STATUS_DEAL 或 ORDER_STATUS_MARKET_ACTIVE 时直接返回空串,说明这两种活跃成交态不需要额外文字解释,避免日志刷屏。 其余状态走 switch 分支,用 TextByLanguage 做俄/英双语返回。例如 ORDER_STATE_STARTED 对应“已通过正确性校验但尚未被经纪商接受”,ORDER_STATE_PARTIAL 对应“部分成交”。外汇与贵金属订单在跨平台部署时,这类本地化取词可能影响你EA的告警可读性,建议开 MT5 把这段直接挂到自己的订单面板里验证显示效果。
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) const; class=class="str">"cmt">//--- 返回订单或仓位名称 class="type">class="kw">string TypeDescription(class="type">void) const; class=class="str">"cmt">//--- 返回订单状态说明 class="type">class="kw">string StateDescription(class="type">void) const; class=class="str">"cmt">//--- 返回成交状态名称 class="type">class="kw">string DealEntryDescription(class="type">void) const; class=class="str">"cmt">//--- 返回订单/仓位方向类型 class="type">class="kw">string DirectionDescription(class="type">void) const; class=class="str">"cmt">//--- 将订单属性的描述发送到流水日志(full_prop=true - 所有属性,class="kw">false - 仅支持的属性) class="type">void Print(const class="type">bool full_prop=class="kw">false); class="type">class="kw">string COrder::StateDescription(class="type">void) const { if(this.Status()==ORDER_STATUS_DEAL || this.Status()==ORDER_STATUS_MARKET_ACTIVE) class="kw">return ""; else class="kw">switch(this.StateOrder()) { case ORDER_STATE_STARTED : class="kw">return TextByLanguage("Ордер проверен на корректность, но еще не принят брокером","Order checked for correctness, but not yet accepted by broker"); case ORDER_STATE_PLACED : class="kw">return TextByLanguage("Ордер принят","Order accepted"); case ORDER_STATE_CANCELED : class="kw">return TextByLanguage("Ордер снят клиентом","Order withdrawn by client"); case ORDER_STATE_PARTIAL : class="kw">return TextByLanguage("Ордер выполнен частично","Order filled partially"); case ORDER_STATE_FILLED : class="kw">return TextByLanguage("Ордер выполнен полностью","Order filled"); case ORDER_STATE_REJECTED : class="kw">return TextByLanguage("Ордер отклонен","Order rejected");
「订单状态与整数属性的本地化描述写法」
在封装订单类时,把枚举状态映射成可读文本是个高频需求。下面这段 switch 分支覆盖了过期、注册中、修改中、删除中以及未知状态,全部走 TextByLanguage 做俄/英双语返回,实际跑 MT5 时若你的终端是俄文区账号,会直接拿到俄文串。 状态分支只是前半截,后半截的 GetPropertyDescription 用三元嵌套把整数属性(魔数、订单号、父单号、继承单号、开仓时间等)拼成『属性名 + 是否支持 + 值』的描述。注意 SupportProperty 的拦截:不支持的属性不会硬读,而是返回『Property not supported』,避免 EA 在老平台上报数组越界。 这类封装的价值在于日志和面板显示——你复制进自己的 COrder 类后,把 ORDER_STATE_REQUEST_ADD 等分支接到 OrderSend 的异步回执里,就能在专家日志里直接看出挂单卡在『注册中』还是『删除中』,省掉翻 ENUM_ORDER_STATE 文档的时间。外汇与贵金属杠杆高,订单状态异步跳变频繁,这类状态文本化能降低误判撤单的概率。
case ORDER_STATE_EXPIRED : class="kw">return TextByLanguage("Ордер снят по истечении срока его действия","Order withdrawn upon expiration"); case ORDER_STATE_REQUEST_ADD : class="kw">return TextByLanguage("Ордер в состоянии регистрации (выставление в торговую систему)","Order in state of registration(placing in trading system)"); case ORDER_STATE_REQUEST_MODIFY : class="kw">return TextByLanguage("Ордер в состоянии модификации","Order in state of modification."); case ORDER_STATE_REQUEST_CANCEL : class="kw">return TextByLanguage("Ордер в состоянии удаления","Order in deletion state"); class="kw">default : class="kw">return TextByLanguage("Неизвестное состояние","Unknown state"); } } class=class="str">"cmt">//+------------------------------------------------------------------+ 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 number")+ (!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) ) : 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")+
◍ 订单属性文本化的条件分支
在封装订单对象的类里,把枚举属性转成可读字符串时,通常用嵌套三元运算按 property 值分流。下面这段就是针对时间类、类型类、原因类及关联 ID 的映射逻辑,俄语与英文双语由 TextByLanguage 按终端语言返回。
| 与时间相关的 ORDER_PROP_TIME_OPEN、TIME_CLOSE、TIME_EXP 都先判断是否被经纪商支持,不支持就追加“Property not supported”;过期时间若 GetProperty 返回 0,则显示“Not set”,否则用 TimeToString 以 DATE | MINUTES | SECONDS 格式输出,例如 2024.03.15 14:32:07 这种精度。 |
|---|
ORDER_PROP_TYPE 和 ORDER_PROP_DIRECTION 直接拼 TypeDescription() 与 DirectionDescription();REASON 走 GetReasonDescription 做原因翻译;POSITION_ID 与 DEAL_ORDER 则强转为 string 并加“#”前缀。复制进 EA 的 ToString 方法后,可在 MT5 策略测试器日志里核对每笔订单的输出是否与账户实际字段一致,外汇与贵金属订单属性受券商限制,部分属性可能概率性返回不支持。
(!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") : ": #"+(class="type">class="kw">string)this.GetProperty(class="kw">property) ) : 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) ) :
订单属性标签的多语言拼装逻辑
在封装订单/持仓描述类时,常需要把枚举属性转成可读文本。下面这段嵌套三元表达式专门处理几种与时间、方向、状态相关的 ORDER_PROP_* 字段,俄文与英文通过 TextByLanguage 自动切换。 核心套路是先判断 property 是否等于某个枚举常量;若当前对象不支持该属性(SupportProperty 返回 false),就追加「: Property not supported」之类的提示,否则取 GetProperty 的值做格式化。例如 ORDER_PROP_TIME_OPEN_MSC 会同时输出毫秒整数与 TimeMSCtoString 转换后的时间串,中间用「 > 」隔开。
| ORDER_PROP_TIME_UPDATE 的处理有个细节:当 GetProperty 返回 0 时直接显示「0」,非零才走 TimeToString 并带 TIME_DATE | TIME_MINUTES | TIME_SECONDS 掩码,避免把 1970 年基准时间误渲染出来。 |
|---|
开 MT5 自建一个 COrder 派生类,把这段直接贴进 DescribeProperty 方法,切换终端语言就能验证俄/英双标签是否随界面翻转。外汇与贵金属品种上跑这套时需注意:毫秒级时间戳在高波动时段可能暴露服务器排队延迟,属正常高风险行情下的数据噪声。
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") : ": "+(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="kw">property==ORDER_PROP_STATE ? TextByLanguage("Состояние","Statе")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") :
「订单属性文本化的多语言分支」
这段逻辑把订单对象的各类属性转成可读字符串,核心是一个嵌套三元表达式,按 property 枚举值逐一匹配。 当属性是 ORDER_PROP_STATE 时,先调 SupportProperty 判断当前订单是否支持该属性;不支持就返回「Property not supported」,支持则拼接 StateDescription 的状态文本。ORDER_PROP_STATUS 分支同理,只是换成 StatusDescription。 ORDER_PROP_PROFIT_PT 直接把 GetProperty 的返回值强转为 string 输出点数利润,而 ORDER_PROP_CLOSE_BY_SL 与 ORDER_PROP_CLOSE_BY_TP 这两个布尔属性,会把 GetProperty 的结果映射成 Yes/No(俄语环境为 Да/Нет)。 在 MT5 里跑这段,若对某类历史订单调用 Close by StopLoss 返回「Property not supported」,说明该订单类型本身不携带此标记——外汇与贵金属品种上这类不支持情况较常见,属正常概率现象,不代表程序错误。
class="kw">property==ORDER_PROP_STATE ? TextByLanguage("Состояние","State")+ (!this.SupportProperty(class="kw">property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": \""+this.StateDescription()+"\"" ) : 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")) ) : "" ); }
◍ 排序与集合类的后续规划
当前这套函数库已经把订单、成交和仓位的基础容器跑通了,配套 ZIP 包(22.89 KB)里含全部 mqh 与测试 EA,直接丢进 MT5 的 MQL5/Include 和 Experts 目录就能编译验证。 下一篇准备封装一个通用排序类,支持对任意标准挑出订单、成交、仓位做排序,并顺手建好市价单与仓位的集合对象,省掉每次手写比较逻辑的麻烦。 有读者手动抄代码时发现 HistoryCollection.mqh 第 14–16 行得先 #include HistoryOrder.mqh、HistoryPending.mqh、HistoryDeal.mqh 才能过编译——这种坑自己踩一遍比光下载跑一遍记得牢。 外汇与贵金属杠杆交易高风险,函数库再顺手也只是工程骨架,实盘前务必在策略测试器用历史数据跑通再上模拟盘。