MQL5 交易工具包(第 5 部分):使用仓位函数扩展历史管理 EX5 库(基础篇)
「用仓位函数把历史管理库撑开」
在 MT5 里做历史回测或复盘时,系统自带的 HistorySelect 只能按时间拉成交,想要按品种、按魔法码、按开仓方向过滤,原生接口写起来很啰嗦。把仓位函数封装进 EX5 库,等于给历史管理加了一层语义化的查询壳。 下面这段 MQL5 演示了如何从已选历史中,按 Symbol 与魔术码取出对应平仓记录条数。它依赖先调用 HistorySelect 划定时间窗,再在库内做二次筛选。
class="type">int CountClosedByMagic(class="type">class="kw">string sym,class="type">ulong magic){ class="type">int total=HistoryDealsTotal(); class="type">int cnt=class="num">0; for(class="type">int i=class="num">0;i<total;i++){ class="type">ulong ticket=HistoryDealGetTicket(i); if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=sym) class="kw">continue; if(HistoryDealGetInteger(ticket,DEAL_MAGIC)!=magic) class="kw">continue; if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT) cnt++; } class="kw">return cnt; }
class="type">int CountClosedByMagic(class="type">class="kw">string sym,class="type">ulong magic){ class="type">int total=HistoryDealsTotal(); class="type">int cnt=class="num">0; for(class="type">int i=class="num">0;i<total;i++){ class="type">ulong ticket=HistoryDealGetTicket(i); if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=sym) class="kw">continue; if(HistoryDealGetInteger(ticket,DEAL_MAGIC)!=magic) class="kw">continue; if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT) cnt++; } class="kw">return cnt; }
给历史引擎开几个能直接用的查询口
上一篇文章里搭的 HistoryManager EX5 库,核心是一套后台跑的历史数据引擎,负责把订单、交易、挂单、仓位抓出来并排序分类。那版里唯一能对外调用的只有打印函数,只能把四类对象的简单描述丢进 MT5 日志,库使用者没法直接问「这笔仓位拿了多久、净赚多少」。 这一节要补的是用户层函数。基于已有的底层检索,我们开放一批直接查询接口:交易持续秒数、上一次平仓对应的开仓与平仓单号、仓位是由挂单触发还是市价直接进、按点值折算的盈利/止损/止盈,以及扣掉佣金和库存费后的净利润。导入这个 EX5,项目里用一行调用就能把历史仓位的关键字段拉出来。 落地动作很具体:打开上一篇文章末尾附的 HistoryManager.mq5(本文末也给了 HistoryManager_Part1.mq5),在 PrintPendingOrdersHistory() 函数下方接着写,先建 GetTotalDataInfoSize()。外汇与贵金属杠杆高、滑点和库存费波动大,点值折算结果仅作历史统计参考,不预示未来。
◍ 动态探明历史数据集规模的实现
在 MT5 里做历史数据回检,经常要先知道某类数组里到底装了多少条记录。GetTotalDataInfoSize() 就是干这个的:传入一个 uint 类型的常量,它返回对应数据集的元素总数,类型为 int。和 FetchHistoryByCriteria() 搭配,能在不硬编码长度的前提下动态分配后续处理流程。 函数签名很简单,输入 dataToGet 是无符号整数,输出是整型大小。内部先给 totalDataInfo 赋 0 作为兜底,再用 switch 按常量分流:成交用 dealInfo、订单用 orderInfo、仓位用 positionInfo、挂单用 pendingOrderInfo,各自走 ArraySize() 取长度。 若常量不匹配任何预设分支,default 里保持 0 返回,调用方便且不会越界。下面代码在 MT5 里建个脚本直接编译,就能用 Print(GetTotalDataInfoSize(GET_DEALS_HISTORY_DATA)) 验证当前账户历史成交条数。外汇与贵金属历史查询受经纪商数据保留策略影响,返回 0 可能是数据未加载,属正常现象,相关交易存在高风险。
class="type">int GetTotalDataInfoSize(class="type">uint dataToGet) { class="type">int totalDataInfo = class="num">0; class=class="str">"cmt">//- Saves the total elements of the specified history found class="kw">switch(dataToGet) { case GET_DEALS_HISTORY_DATA: class=class="str">"cmt">//- Check if we have any available deals history data totalDataInfo = ArraySize(dealInfo); class=class="str">"cmt">//- Save the total deals found break; case GET_ORDERS_HISTORY_DATA: class=class="str">"cmt">//- Check if we have any available orders history data totalDataInfo = ArraySize(orderInfo); class=class="str">"cmt">//- Save the total orders found break; case GET_POSITIONS_HISTORY_DATA: class=class="str">"cmt">//- Check if we have any available positions history data totalDataInfo = ArraySize(positionInfo); class=class="str">"cmt">//- Save the total positions found break; case GET_PENDING_ORDERS_HISTORY_DATA: class=class="str">"cmt">//- Check if we have any available pending orders history data totalDataInfo = ArraySize(pendingOrderInfo); class=class="str">"cmt">//- Save the total pending orders found break; class="kw">default: class=class="str">"cmt">//-- Unknown entry totalDataInfo = class="num">0; break; } class="kw">return(totalDataInfo); }
「分级回退的历史检索逻辑」
查最近平仓记录时,别一上来就拉整个账户历史。MT5 服务端拉全量历史会拖慢 EA,尤其多品种同时跑时资源占用明显。合理的做法是先框最近 24 小时,找不到再按周外扩,最多扫 53 周(约一年),仍空才退到全账户历史。 FetchHistoryByCriteria() 就是按这个思路写的内置过程:从 TimeCurrent() 减 1 个 D1 周期起步,用 GetHistoryData() 取数;GetTotalDataInfoSize() 返回 0 就进 while 把 interval 加 1,每周延展一次,直到 interval>53 跳出。一年都没命中,fromDateTime 置 0(1970 纪元)拉全量,最后还空就 return false。 实盘里若你要抓‘最近 5 个已平买入头寸’,把这个布尔函数包一层过滤即可。外汇和贵金属杠杆高、滑点跳空频繁,历史分笔可能在极端行情日缺失,回退到全历史会更慢,建议先在策略测试器用旧账户数据验证耗时。
class="type">bool FetchHistoryByCriteria(class="type">uint dataToGet) { class="type">int interval = class="num">1; class=class="str">"cmt">//- Modulates the history period class=class="str">"cmt">//- Save the history period for the last class="num">24 hours class="type">class="kw">datetime fromDateTime = TimeCurrent() - class="num">1 * (PeriodSeconds(PERIOD_D1) * interval); class="type">class="kw">datetime toDateTime = TimeCurrent(); class=class="str">"cmt">//- Get the specified history GetHistoryData(fromDateTime, toDateTime, dataToGet); class=class="str">"cmt">//- If we have no history in the last class="num">24 hours we need to keep increasing the retrieval class=class="str">"cmt">//- period by one week untill we scan a full year(class="num">53 weeks) while(GetTotalDataInfoSize(dataToGet) <= class="num">0) { interval++; fromDateTime = TimeCurrent() - class="num">1 * (PeriodSeconds(PERIOD_W1) * interval); toDateTime = TimeCurrent(); GetHistoryData(fromDateTime, toDateTime, dataToGet); class=class="str">"cmt">//- If no history is found after a one year scanning period, we exit the while loop if(interval > class="num">53) { break; } } class=class="str">"cmt">//- If we have not found any trade history in the last year, we scan and cache the intire account history fromDateTime = class="num">0; class=class="str">"cmt">//-- class="num">1970 (Epoch) toDateTime = TimeCurrent(); class=class="str">"cmt">//-- Time now GetHistoryData(fromDateTime, toDateTime, dataToGet); class=class="str">"cmt">//- If we still havent retrieved any history in the account, we log this info by class=class="str">"cmt">//- printing it and exit the function by returning false if(GetTotalDataInfoSize(dataToGet) <= class="num">0) { class="kw">return(false); class=class="str">"cmt">//- Specified history not found, exit and class="kw">return false } else {
历史数据校验的退出逻辑
在 MQL5 自定义指标或 EA 的初始化环节,常需要确认指定品种与周期的历史数据是否已就位。上面这段代码片段出现在遍历历史序列的收尾处,一旦匹配到所需数据便直接返回 true,中断后续查找。 对交易者而言,这意味着脚本不会在缺失行情时盲目计算信号;若你的 MT5 终端在周末或断网后重连,这类显式 return(true) 能避免指标在空数据上画出错误箭头。外汇与贵金属市场高波动,历史缺口可能导致回测与实盘偏差,建议手动在策略测试器里勾选「使用真实历史」验证一次。
class="kw">return(true); class=class="str">"cmt">//- Specified history found, exit and class="kw">return true } }
◍ 抓取最近平仓记录的引用函数
GetLastClosedPositionData() 干一件事:把账户里最近一笔平仓的明细塞进外部传进来的 lastClosedPositionInfo 引用里。它自己不翻历史,而是调用 FetchHistoryByCriteria(GET_POSITIONS_HISTORY_DATA) 去捞仓位历史,最近平仓永远排在 positionInfo 数组的第 0 位。 如果 FetchHistoryByCriteria() 返回 false,说明当前连一笔平仓历史都找不到,函数会用 Print() 打出「No trading history available」并直接 return false,调用方据此知道取数失败。 取数成功时,lastClosedPositionInfo = positionInfo[0] 这一行完成赋值,随后 return true。外汇与贵金属交易历史可能因经纪商只保留有限时段而缺失,这类情况下该函数会稳定返回 false,属正常边界表现。 下面这段是完整实现,注意 export 修饰符让它能被其他 MQL5 文件 import 复用: bool GetLastClosedPositionData(PositionData &lastClosedPositionInfo) export { if(!FetchHistoryByCriteria(GET_POSITIONS_HISTORY_DATA)) { Print(__FUNCTION__, ": No trading history available. Last closed position can't be retrieved."); return(false); } lastClosedPositionInfo = positionInfo[0]; return(true); } 逐行拆解:第 1 行定义导出函数,形参为 PositionData 类型的引用,修改会影响调用方变量;第 3 行尝试拉取平仓历史,感叹号表示失败则进分支;第 5 行打印含函数名的错误便于调试;第 6 行返回 false 终止;第 9 行把数组首元素(最近平仓)赋给引用;第 10 行返回 true 表示成功。开 MT5 把这段贴进 EA,配合有成交的账户历史即可验证 lastClosedPositionInfo 是否被填充。
class="type">bool GetLastClosedPositionData(PositionData &lastClosedPositionInfo) class="kw">export { if(!FetchHistoryByCriteria(GET_POSITIONS_HISTORY_DATA)) { Print(__FUNCTION__, ": No trading history available. Last closed position can&class="macro">#x27;t be retrieved."); class="kw">return(false); } lastClosedPositionInfo = positionInfo[class="num">0]; class="kw">return(true); }