MQL5 交易工具包(第 4 部分):开发历史管理 EX5 库(基础篇)
把历史订单塞进一个可复用 EX5 库
在 MT5 里做策略复盘或风控模块时,反复调 HistorySelect 拉历史订单是高频动作。把这套逻辑封成一个 EX5 库,比每次在 EA 里重写一遍更省事,也方便多项目复用。 库的核心入口就是 HistorySelect,指定起止时间后,用 HistoryDealsTotal 和 HistoryOrdersTotal 分别遍历成交与挂单。注意 MT5 的历史是分成交(deal)和订单(order)两套对象的,混用会拿错字段。 外汇与贵金属杠杆高、滑点跳空频繁,历史数据里可能出现异常成交记录,回测和实盘拉出来的笔数不一定一致,验证库之前先在小周期手动核对几十条。
◍ 为什么交易历史库值得单独造轮子
前面两篇已经把 PositionsManager.ex5 和 PendingOrdersManager.ex5 两个运行时库跑通了,一个管持仓、一个管挂单,还配了带 GUI 的演示样例。这一篇要补的短板是历史侧:把已平仓订单、成交流水和持仓历史捞出来再做加工。 具体落点是再写一个 EX5 库,专门负责检索和处理已完成订单、deal 和 position 的历史记录,并外接分析模块按灵活条件出交易报告——用来评估某个 EA、某套系统或单个品种的真实表现。 很多刚碰 MQL5 的人卡就卡在 HistorySelect / HistoryDealsTotal 那套时序与类型区分上,尤其分不清 order、deal、position 在历史表里谁对应谁。这个库就是给这类开发者避坑用的,也适合任何不想每次都手搓历史查询逻辑的程序员直接复用。 开头先掰几个新手高频误解点,这些点不理顺,后面写回测统计和报表都会埋雷。
「订单落地前:市价单与挂单的两条路径」
MQL5 里的交易事务,起点永远是订单执行。订单只分两类:直接吃当前价的市价单,以及等价格触发的挂单。 直接市价单用卖价或买价实时成交,执行后立刻变成未平仓头寸,系统给一个唯一单号和 POSITION_ID,靠这个 ID 才能在整个生命周期里稳定跟住头寸状态。 挂单有 BUY STOP、BUY LIMIT、SELL STOP、SELL LIMIT、BUY STOP LIMIT、SELL STOP LIMIT 六种。触发价没到之前它一直处于无效挂起态;价格碰线后转成市价单执行,同样拿到单号和 POSITION_ID。 开 MT5 用 OrdersTotal() 和 PositionsTotal() 分别数一下挂单与持仓,能直观看到挂单未触发时只进前者、触发后从前者消失并出现在后者。外汇与贵金属杠杆高,市价滑点和挂单跳空都可能让实际成交偏离触发价,需以账户真实回报为准评估。
头寸从开仓到平仓的状态流转
一个头寸在 MT5 里不是静态对象。部分平仓会在交易历史留下一笔退出交易;若做反手,原仓位被平掉同样记为退出交易;直到整仓被手动、止损、止盈或强平事件关闭,最终退出交易落库。这三步构成头寸生命周期的主干。 MQL5 里一切始于发往交易服务器的订单:挂单请求、市价买卖、部分关闭都先登记为订单。订单成交后才转入历史库成为交易(deal)。借助 ORDER_* 与 DEAL_* 属性,你能把每笔 deal 回溯到原始 order 并关联回 position,形成一条可审计的轨迹。 活跃未平头寸和挂单在工具箱「交易」标签页;已平头寸、订单、成交进「历史」标签页。标准历史函数对新手容易混淆 position 与 deal——position 是当前持续敞口,deal 是已完结的平仓记录。理清这点,后续写策略回测或绩效统计才不会把二者混算。 这种面包屑式跟踪让起源、修改时间、最终结果全透明。基于它你能写算法筛出某策略的改进点,比如统计反转单占比或强平触发频次,再回头调参数。
◍ 建好 HistoryManager 库文件的落地步骤
在 MetaEditor 里点「新建」走 MQL 向导,选「库」源文件类型,命名 HistoryManager.mq5。把它存进 Libraries\Toolkit 目录,和已有的 Positions Manager、Pending Orders Manager 的 EX5 放一起,后期组件多了不会乱找。 文件建出来后,先把属性指令下方那行 “My function” 注释删掉。版权和链接属性你随便改,但 #property library 这行必须原样保留,否则 MT5 不认它是库。 下面这段是初始文件骨架,注意 version 默认标了 1.00,后续迭代自己改号: 代码里被注释掉的 MyCalculator 只是向导给的示例,实际不会编进库,留着也不影响编译。真要验证,建完直接 F7 编译,左侧导航里出现 HistoryManager 且无报错就行。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| HistoryManager.mq5 | class=class="str">"cmt">//| Copyright class="num">2024, Wanateki Solutions Ltd. | class=class="str">"cmt">//| https://www.wanateki.com | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property library class="macro">#class="kw">property copyright "Copyright class="num">2024, Wanateki Solutions Ltd." class="macro">#class="kw">property link "https:class=class="str">"cmt">//www.wanateki.com" class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| My function | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// class="type">int MyCalculator(class="type">int value,class="type">int value2) class="kw">export class=class="str">"cmt">// { class=class="str">"cmt">// class="kw">return(value+value2); class=class="str">"cmt">// } class=class="str">"cmt">//+------------------------------------------------------------------+
「库顶层的常量、结构与全局数组怎么摆」
写 HistoryManager.mq5 这种历史管理库,第一件事不是在函数里绕逻辑,而是把三类东西钉在全局作用域:预处理器常量、数据结构、动态结构数组。全局声明的直接好处是库内任何模块随调随取,不用层层传参,也避免重复查询 MT5 历史接口带来的开销。 预处理器常量用来给历史数据分类。这里拆成五类并配了整数标识:订单历史 1001、成交历史 1002、头寸历史 1003、挂单历史 1004、全部历史 1005。主获取函数拿到标识后只拉对应子集,时间和算力都省了。 数据结构负责落地存储。DealData 装成交属性,从 ticket、magic 到 commission、swap、profit 共 17 个字段;OrderData 装订单属性,含三种时间字段(timeSetup/timeDone/expirationTime)和订单状态、填充类型等。下面代码是这两段结构加常量的原文,可直接粘进文件 #property 指令下方。 全局动态数组用前面 struct 做元素类型,作为库托管核心数据的唯一容器。查询时往里写,分析时从里读,整个库的状态中心就这一处。外汇与贵金属历史回放涉及高杠杆风险,回测结论仅代表概率倾向,实盘前请在 MT5 策略测试器跑通再上。
class="macro">#define GET_ORDERS_HISTORY_DATA class="num">1001 class="macro">#define GET_DEALS_HISTORY_DATA class="num">1002 class="macro">#define GET_POSITIONS_HISTORY_DATA class="num">1003 class="macro">#define GET_PENDING_ORDERS_HISTORY_DATA class="num">1004 class="macro">#define GET_ALL_HISTORY_DATA class="num">1005 class=class="str">"cmt">//- Data structure to store deal properties class="kw">struct DealData { class="type">ulong ticket; class="type">ulong magic; ENUM_DEAL_ENTRY entry; ENUM_DEAL_TYPE type; ENUM_DEAL_REASON reason; class="type">ulong positionId; class="type">ulong order; class="type">class="kw">string symbol; class="type">class="kw">string comment; class="type">class="kw">double volume; class="type">class="kw">double price; class="type">class="kw">datetime time; class="type">class="kw">double tpPrice; class="type">class="kw">double slPrice; class="type">class="kw">double commission; class="type">class="kw">double swap; class="type">class="kw">double profit; }; class=class="str">"cmt">//- Data structure to store order properties class="kw">struct OrderData { class="type">class="kw">datetime timeSetup; class="type">class="kw">datetime timeDone; class="type">class="kw">datetime expirationTime; class="type">ulong ticket; class="type">ulong magic; ENUM_ORDER_REASON reason; ENUM_ORDER_TYPE type; ENUM_ORDER_TYPE_FILLING typeFilling; ENUM_ORDER_STATE state; ENUM_ORDER_TYPE_TIME typeTime; class="type">ulong positionId; class="type">ulong positionById; class="type">class="kw">string symbol; class="type">class="kw">string comment; class="type">class="kw">double volumeInitial; class="type">class="kw">double priceOpen; class="type">class="kw">double priceStopLimit;