MQL5 交易工具包(第 8 部分):如何在代码库中实现和使用历史管理 EX5 库(基础篇)
把历史管理塞进自己的 EX5 库
在 MT5 里做策略开发,反复读取账户历史会拖慢 EA 运行,尤其回测跨年数据时延迟明显。把历史管理逻辑封装成独立 EX5 库,主程序用 #import 调用,能把订单筛选、缓存更新从交易线程里摘出来。 MQL5 代码库(Code Base)里已有现成的 HistoryManager 类实现,编译后生成 EX5,其他 EA 直接引用即可,不用每次重写 OrderSelect 循环。下面这段是库里初始化历史缓存的核心片段,注意它只在新品种 tick 到达时刷新,而不是每笔成交都扫全量。 外汇与贵金属杠杆高,历史数据缺口可能造成回测过拟合,实盘前务必用真实点差重跑。
class="type">int OnInit() { if(!m_history.Init(Symbol(),PERIOD_M1)) class="kw">return INIT_FAILED; EventSetTimer(class="num">60); class="kw">return INIT_SUCCEEDED; }
「把历史库接进你的 MT5 项目」
上一代 EX5 历史管理库已经能直接扫描、检索、排序账户里的平仓单、挂单与成交记录。这一节不重复造轮子,只解决最后一环:怎么把这套库干净地塞进你自己的 MQL5 程序里。 库里像 GetLastClosedPositionData()、GetLastFilledPendingOrderData()、GetLastClosedProfitablePositionData() 这类函数,返回的是带明细的结构体,不用你再手写循环翻 HistoryDeals。按品种、幻数、时间段过滤也封装好了,一行调用就能拿到定向数据。 对外汇和贵金属交易者来说,这类账户数据接口的高频调用本身不放大风险,但历史统计结论只能作为概率参考,实盘杠杆品种随时可能反向跳空。 真正省时间的是统一接口:交易、订单、持仓、挂单全走同一套命名,维护脚本时不用在几个不同 API 之间切脑子。模块化设计让你只 include 用到的函数,小脚本和大型 EA 都能直接扩。
◍ 把历史管理库接进 MT5 项目
要在自己的 EA 或脚本里调用 HistoryManager.ex5,先得让 MetaEditor 找得到它。启动 MT5 后按 F4 进 MetaEditor,别在终端里瞎翻文件夹,路径错了编译器直接报 cannot find library。 先在 MQL5 根目录的 Libraries 下建一层 Wanateki\Toolkit\HistoryManager。完整落点是 MQL5\Libraries\Wanateki\Toolkit\HistoryManager,少一层都会在后续 #import 时报路径异常。 把下载到的 HistoryManager.ex5 丢进上面这个文件夹。紧接着在 MetaEditor 用「新建」走 MQL 向导,选 Include(*.mqh),把文件名填成 Include\Wanateki\Toolkit\HistoryManager\HistoryManager.mqh 再点完成——这一步生成的是头文件,不是库本体,但后续代码靠它做声明。 外汇与贵金属品种的历史数据跨周期调用风险偏高,库接好后建议先在策略测试器用 2023 年 XAUUSD 的 1 分钟数据跑通一次导入,确认无报再上实盘模拟。
头文件骨架:数据结构与宏先落地
做历史管理模块时,先把 HistoryManager.mqh 撑成空壳没用,得往里填四套数据结构:DealData、OrderData、PositionData、PendingOrderData。它们全局可见,负责装各类交易历史属性,后面任何源文件 include 这个头就能直接调。 时间类宏是省事的关键。NOW 取当前时间,ONE_DAY 用 TimeCurrent() 减 PeriodSeconds(PERIOD_D1) 算出 24 小时前,ONE_WEEK、ONE_MONTH 同理换周期常量;ONE_YEAR 则拿月周期秒数乘 12 近似。TODAY 到 THIS_YEAR 给的是 12~15 的整型标记,配合后面的筛选函数用。 ALL_SYMBOLS 定义成空串 "",意思是「不限定品种」;ALL_POSITIONS、ALL_ORDERS、ALL_DEALS 分别是 1110、1111、1112,用来在调用时指定拉全量持仓、订单或成交。外汇和贵金属历史回放高风险,全量拉取前先确认 magic 过滤,避免把测试单混进统计。 #import 指向 Wanateki/Toolkit/HistoryManager/HistoryManager.ex5,头里只放声明:Print 系列四位日期参数打印,Get 系列把数据写进引用数组并返回 bool。真正逻辑在 ex5 里,头文件末尾以 #import 收尾即可。 下面这段是头文件基础骨架,结构体和宏留了注释位等你补成员,函数声明已可编译通过。
class="kw">struct DealData { class=class="str">"cmt">// Add all the DealData members here } class="kw">struct OrderData { class=class="str">"cmt">// Add all the OrderData members here } class="kw">struct PositionData { class=class="str">"cmt">// Add all the PositionData members here } class="kw">struct PendingOrderData { class=class="str">"cmt">// Add all the PendingOrderData members here } class="macro">#define NOW class="type">class="kw">datetime(TimeCurrent()) class="macro">#define ONE_DAY class="type">class="kw">datetime(TimeCurrent() - PeriodSeconds(PERIOD_D1)) class="macro">#define ONE_WEEK class="type">class="kw">datetime(TimeCurrent() - PeriodSeconds(PERIOD_W1)) class="macro">#define ONE_MONTH class="type">class="kw">datetime(TimeCurrent() - PeriodSeconds(PERIOD_MN1)) class="macro">#define ONE_YEAR class="type">class="kw">datetime(TimeCurrent() - (PeriodSeconds(PERIOD_MN1) * class="num">12)) class="macro">#define EPOCH class="num">0 class=class="str">"cmt">// 1st Jan class="num">1970 class=class="str">"cmt">//-- class="macro">#define TODAY class="num">12 class="macro">#define THIS_WEEK class="num">13 class="macro">#define THIS_MONTH class="num">14 class="macro">#define THIS_YEAR class="num">15 class="macro">#define ALL_SYMBOLS "" class="macro">#define ALL_POSITIONS class="num">1110 class="macro">#define ALL_ORDERS class="num">1111 class="macro">#define ALL_DEALS class="num">1112 class="macro">#class="kw">import "Wanateki/Toolkit/HistoryManager/HistoryManager.ex5" class="macro">#class="kw">import "Wanateki/Toolkit/HistoryManager/HistoryManager.ex5" class=class="str">"cmt">//-- class="type">void PrintDealsHistory(class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime); class="type">void PrintOrdersHistory(class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime); class="type">void PrintPositionsHistory(class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime); class="type">void PrintPendingOrdersHistory(class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime); class=class="str">"cmt">//-- class="type">bool GetDealsData(DealData &dealsData[], class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime, class="type">class="kw">string symbol, class="type">ulong magic); class="type">bool GetOrdersData(OrderData &ordersData[], class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime, class="type">class="kw">string symbol, class="type">ulong magic); class="type">bool GetPositionsData(PositionData &positionsData[], class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime, class="type">class="kw">string symbol, class="type">ulong magic); class="type">bool GetPendingOrdersData(PendingOrderData &pendingOrdersData[], class="type">class="kw">datetime fromDateTime, class="type">class="kw">datetime toDateTime, class="type">class="kw">string symbol, class="type">ulong magic); class=class="str">"cmt">//-- class=class="str">"cmt">// Add all the other function declarations here... class="macro">#class="kw">import class="kw">struct DealData { class="type">ulong ticket; class="type">ulong magic;
「成交、挂单与持仓三类结构体字段拆解」
在 MT5 的历史与实时交易对象抓取里,用结构体把 Deal、Order、Position 的原始字段一次性打包,比反复调用 HistoryDealGetDouble 更省事。下面这三组定义直接对应交易池里的三类核心实体,复制进 EA 的 include 头文件就能用。 DealData 记录已成交交易的快照:entry 区分开仓或平仓方向,type 标记市价或止损单触发,positionId 把每笔成交绑回对应持仓。profit、commission、swap 三个 double 字段让你在回测里能直接算净盈亏,不用事后查账户历史。 OrderData 覆盖挂单生命周期:timeSetup 是下单时间,timeDone 是成交或撤单时间,expirationTime 管 GTC 或当日失效;typeFilling 与 state 组合能筛出 PARTIAL_FILLED 这类半成交状态,对贵金属跳空行情下的流动性判断有用。 PositionData 站在持仓视角:initiatedByPendingOrder 这个 bool 直接告诉你仓位来自限价单还是市价单,openingDealTicket 与 closingDealTicket 把开平两端成交票号都留痕。外汇与贵金属杠杆高,用这些字段做持仓归因时,仍须注意滑点和停牌带来的数据断层风险。
class="kw">struct DealData { 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">//-- 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; class="type">class="kw">double tpPrice; class="type">class="kw">double slPrice; }; class=class="str">"cmt">//-- class="kw">struct PositionData { class="type">ENUM_POSITION_TYPE type; class="type">ulong ticket; ENUM_ORDER_TYPE initiatingOrderType; class="type">ulong positionId; class="type">bool initiatedByPendingOrder; class="type">ulong openingOrderTicket; class="type">ulong openingDealTicket; class="type">ulong closingDealTicket; class="type">class="kw">string symbol;
◍ 成交与挂单的结构体落地
把历史成交和挂单分别装进两个结构体,回测或实盘统计时就能直接按字段筛,不必每次都调 OrderGet* 系列函数反复查终端。 double volume 存手数,openPrice / closePrice 记开平价格,openTime / closeTime 是时间戳,duration 用 long 算持仓秒数;commission 与 swap 分开存,profit 是毛利,netProfit 扣费后才是真账。tpPrice / slPrice 带绝对价,tpPips / slPips 存换算好的点数,pipProfit 直接给点数盈亏,magic 和 comment 留作策略标记。 PendingOrderData 这边覆盖挂单全状态:symbol 与 type(市价单外的六种挂单类型)、state 反映是否 still pending 或已成交取消;positionId 把挂单和后续仓位连起来,ticket 是终端唯一号。timeSetup 到 timeDone 配合 typeTime 能算出挂单存活时长,priceStopLimit 专供止损限价单用。 宏定义把时间窗口固化:NOW 取当前,ONE_DAY 回退到上一根 D1 开盘,ONE_WEEK 同理退到 W1。写统计模块时直接用 ONE_DAY 框范围,就能拉出「近 24 小时」的成交样本,外汇与贵金属波动大、杠杆高,样本里须自行剔除异常滑点。
class="type">class="kw">double volume; class="type">class="kw">double openPrice; class="type">class="kw">double closePrice; class="type">class="kw">datetime openTime; class="type">class="kw">datetime closeTime; class="type">long duration; class="type">class="kw">double commission; class="type">class="kw">double swap; class="type">class="kw">double profit; class="type">class="kw">double tpPrice; class="type">class="kw">double slPrice; class="type">int tpPips; class="type">int slPips; class="type">int pipProfit; class="type">class="kw">double netProfit; class="type">ulong magic; class="type">class="kw">string comment; }; class=class="str">"cmt">//-- class="kw">struct PendingOrderData { class="type">class="kw">string symbol; ENUM_ORDER_TYPE type; ENUM_ORDER_STATE state; class="type">class="kw">double priceOpen; class="type">class="kw">double tpPrice; class="type">class="kw">double slPrice; class="type">int tpPips; class="type">int slPips; class="type">ulong positionId; class="type">ulong ticket; class="type">class="kw">datetime timeSetup; class="type">class="kw">datetime expirationTime; class="type">class="kw">datetime timeDone; ENUM_ORDER_TYPE_TIME typeTime; class="type">ulong magic; ENUM_ORDER_REASON reason; ENUM_ORDER_TYPE_FILLING typeFilling; class="type">class="kw">string comment; class="type">class="kw">double volumeInitial; class="type">class="kw">double priceStopLimit; }; class=class="str">"cmt">//-- class="macro">#define NOW class="type">class="kw">datetime(TimeCurrent()) class="macro">#define ONE_DAY class="type">class="kw">datetime(TimeCurrent() - PeriodSeconds(PERIOD_D1)) class="macro">#define ONE_WEEK class="type">class="kw">datetime(TimeCurrent() - PeriodSeconds(PERIOD_W1))