MQL5 交易工具包(第 5 部分):使用仓位函数扩展历史管理 EX5 库(基础篇)
📘

MQL5 交易工具包(第 5 部分):使用仓位函数扩展历史管理 EX5 库(基础篇)

第 1/3 篇

「用仓位函数把历史管理库撑开」

在 MT5 里做历史回测或复盘时,系统自带的 HistorySelect 只能按时间拉成交,想要按品种、按魔法码、按开仓方向过滤,原生接口写起来很啰嗦。把仓位函数封装进 EX5 库,等于给历史管理加了一层语义化的查询壳。 下面这段 MQL5 演示了如何从已选历史中,按 Symbol 与魔术码取出对应平仓记录条数。它依赖先调用 HistorySelect 划定时间窗,再在库内做二次筛选。

MQL5 / C++
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;
}
逐行拆解:函数入口收 symbol 与 magic 两个参数;HistoryDealsTotal 取当前历史池总单数;循环里先拿每单 ticket,再用 DealGetString 比品种,不等就跳过;DealGetInteger 比魔术码,再比 entry 类型是否为平仓,三层过滤后计数返回。 实盘或复盘接这套,你能用一行 CountClosedByMagic("XAUUSD",888) 直接知道某策略在历史窗内平了多少次。外汇与贵金属杠杆高、滑点跳空频繁,历史计数仅作策略行为分析,不预示后续盈亏。

MQL5 / C++
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 可能是数据未加载,属正常现象,相关交易存在高风险。

MQL5 / C++
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 个已平买入头寸’,把这个布尔函数包一层过滤即可。外汇和贵金属杠杆高、滑点跳空频繁,历史分笔可能在极端行情日缺失,回退到全历史会更慢,建议先在策略测试器用旧账户数据验证耗时。

MQL5 / C++
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) 能避免指标在空数据上画出错误箭头。外汇与贵金属市场高波动,历史缺口可能导致回测与实盘偏差,建议手动在策略测试器里勾选「使用真实历史」验证一次。

MQL5 / C++
    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 是否被填充。

MQL5 / C++
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);
  }

常见问题

可以用抓取最近平仓记录的引用函数,传入品种名和数量就能拿到最近 N 笔平仓单的引用,复制代码改个参数就能跑。
用动态探明历史数据集规模的函数先取实际条数,小于预期就跳过或告警,避免空查导致逻辑错乱。
可以,小布能把这类仓位查询逻辑接进对应品种页,自动跑出最近平仓和规模校验结果,你只看异常就行。
用分级回退的检索逻辑,主源空就切备用源,再空才走退出分支,代码里每层都带判空就行。
在校验函数里置退出标志并提前 return,外部调用处检查标志决定是否中止,别等后续函数踩空。