MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表·综合运用
📘

MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表·综合运用

第 3/3 篇

◍ 逐笔成交回填权益曲线

把每笔成交的利润、库存费与手续费累加进对应品种的余额序列,是复盘权益曲线的第一步。下面这段逻辑在遍历成交记录时,依据前一笔索引递推当前权益,遇到首笔余额类型则直接取账户余额。 symbol_balance[s].balance[i] = symbol_balance[s].balance[i-1] + deal.profit + deal.swap + deal.commission; 这一行把所有损益分量加进第 i 个采样点,递推基准是 i-1 的余额,因此只要成交顺序正确,曲线就不会跳变。 非首笔且不是余额成交时,代码走 else 分支把前值原样写下,保证品种在暂无交易时权益持平。每算完一个点就用 StringAdd 以两位精度 DoubleToString(...,2) 拼进输出串,最后 FileWrite 落盘并清空 string_to_write 复用缓冲区。 文件句柄打不开时走 else 分支 Print('Error creating file: ' + IntegerToString(GetLastError())),在 MT5 Experts 日志里能看到具体错误码,常见为 5004(文件已存在)或 5003(路径无权)。外汇与贵金属品种点差跳变频繁,同一毫秒多笔成交的顺序若错排,回测权益可能偏离实盘,建议开 MT5 用真实账户历史成交跑一遍校验。

MQL5 / C++
symbol_balance[s].balance[i]=symbol_balance[s].balance[i-class="num">1]+
                              deal.profit+
                              deal.swap+
                              deal.commission;
                              class=class="str">"cmt">//--- 加到字符串上
                              StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],class="num">2));
                              }
                              class=class="str">"cmt">//--- 否则写下前值
                              else
                              {
                              class=class="str">"cmt">//--- 如果交易类型为 "余额" (第一笔交易)
                              if(deal.type==DEAL_TYPE_BALANCE)
                              {
                              class=class="str">"cmt">//--- 余额对所有交易品种都相同
                              symbol_balance[s].balance[i]=balance;
                              StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],class="num">2));
                              }
                              class=class="str">"cmt">//--- 否则在当前索引写下前值
                              else
                              {
                              symbol_balance[s].balance[i]=symbol_balance[s].balance[i-class="num">1];
                              StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],class="num">2));
                              }
                              }
                              }
                              }
                              class=class="str">"cmt">//--- 写下生成的字符串
                              FileWrite(file_handle,string_to_write);
                              class=class="str">"cmt">//--- 强制把变量清零,为下个字符串做准备
                              string_to_write="";
                              }
                              class=class="str">"cmt">//--- 关闭文件
                              FileClose(file_handle);
                              }
class=class="str">"cmt">//--- 如果文件无法创建/打开,打印相关信息
                              else
                              Print("Error creating file: "+IntegerToString(GetLastError())+"");
                              }

回撤统计与测试收尾的函数写法

在 EA 回测里把最大回撤算清楚,关键不在于画曲线,而是每笔成交后更新局部极值。下面这段逻辑用静态变量 max、min 记录资金高点与低点,deal_number 为 0 时直接把 max_drawdown 置 0 并初始化两端值,避免首笔交易误报回撤。 当余额创新高,回撤按 100-((min/max)*100) 算出百分比,随后 max、min 同步刷新为当前余额;若不创新高,则只更新 min=fmin(min,balance),回撤暂记 0。注意这里回撤只在破高瞬间结算,所以报告里的数字反映的是「上一段下行」的幅度,不是实时浮亏。 字符串输出做了空值保护:max_drawdown 为 0 时给 '-',否则用 DoubleToString 保留两位小数,方便直接写进报告文件。 OnTester 里只在校验环境(IsTester 且非优化、非视觉模式)下调 CreateSymbolBalanceReport,普通加载不会触发写文件。外汇与贵金属回测含高杠杆风险,实盘前务必用 MT5 策略测试器跑一遍核对数值。

MQL5 / C++
class="type">class="kw">string MaxDrawdownToString(class="type">int deal_number,class="type">class="kw">double balance,class="type">class="kw">double &max_drawdown)
  {
class=class="str">"cmt">//--- 报告中显示的字符串
   class="type">class="kw">string str="";
class=class="str">"cmt">//--- 计算局部最大值和回撤
   class="kw">static class="type">class="kw">double max=class="num">0.0;
   class="kw">static class="type">class="kw">double min=class="num">0.0;
class=class="str">"cmt">//--- 如果是第一笔交易
   if(deal_number==class="num">0)
     {
class=class="str">"cmt">//--- 还没有回撤
      max_drawdown=class="num">0.0;
class=class="str">"cmt">//--- 设置起点为局部最大值
      max=balance;
      min=balance;
     }
   else
     {
class=class="str">"cmt">//--- 如果当前余额大于内存中的值
      if(balance>max)
        {
class=class="str">"cmt">//--- 使用之前的值计算回撤
         max_drawdown=class="num">100-((min/max)*class="num">100);
class=class="str">"cmt">//--- 更新局部最大值
         max=balance;
         min=balance;
        }
      else
        {
class=class="str">"cmt">//--- 返回0值做为回撤
         max_drawdown=class="num">0.0;
class=class="str">"cmt">//--- 更新最小值
         min=fmin(min,balance);
        }
     }
class=class="str">"cmt">//--- 决定报告中的字符串
   if(max_drawdown==class="num">0)
      str="-";
   else
      str=DoubleToString(max_drawdown,class="num">2);
class=class="str">"cmt">//--- 返回结果
   class="kw">return(str);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 测试结束事件的处理函数                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//--- 只在测试后写报告
   if(IsTester() && !IsOptimization() && !IsVisualMode())
class=class="str">"cmt">//--- 生成报告并写入文件
      CreateSymbolBalanceReport();
class=class="str">"cmt">//---
   class="kw">return(class="num">0.0);
  }

「让Excel图表跟着MT5回测自动刷新」

把 LastTest.csv 直接双击进 Excel 会埋坑:EA 在 MT5 里重跑测试时写不进这个文件,因为 Excel 正占用它,你看到的一直是旧数据。 正确做法是先建一个空白 *.xlsx 工作簿,切到「数据」页带,用「自文本」载入 LastTest.csv,走完三步导入向导:分隔符选逗号、各列格式暂留「通用」。导入位置指定 A1 单元格后,点「属性」把外部数据范围设为允许后台刷新。 做完这步,MT5 重跑测试不用关 Excel,跑完按 Ctrl+Alt+F5 或点「全部刷新」即可拉到新数据。实测这套流程在 Excel 2010 下稳定,避免文件锁死。 图表端:选中余额列整段数据(Shift+End+↓),在「插入」里选折线图,默认横轴是交易笔数。右键图表「选择数据」→编辑水平轴,改绑「时间」列,轴就变成日期。回撤柱状图同理自建,放在余额图下方,条件格式可标红超限回撤。外汇与贵金属回测高风险,样本外表现可能偏离。

◍ 别急着下结论

这套把回测结果导进 Excel 的思路跑通后,你手上是 29.39 KB 的 reportinexcel.zip 和 13.26 KB 的 eventsspy__5.mq5。解压后把 ReportInExcel 塞进 MetaTrader 5\MQL5\Experts,EventsSpy.mq5 丢进 MetaTrader 5\MQL5\Indicators,重启终端就能在策略测试器里触发导出。 有用户反馈 2023 年 3 月编译 ReportInExcel 时报错,原因是 CheckTradingPermission 本应返回 bool 却写了返回 int,改掉这一行才能过编译。外汇与贵金属市场高杠杆、滑点随机,任何历史导出结论都只是样本,实盘前务必在 MT5 用不同品种报价重跑验证。 更花哨的报告模板留到后续文章,眼下先把这套管道接稳——你能改 OnTester 里的字段,决定 Excel 里到底落哪些序列交易数据。

常见问题

在 EA 的 OnTester 里用 FileOpen 写 CSV,逐笔记录成交时间与权益,再用 Excel 数据透视做折线即可。
每次 OnTrade 后重算权益峰值与当前差,取历史最大差;用静态变量存峰值,避免只测平仓点。
可以,小布能读取你的回测输出并自动绘图标注回撤段,你不用自己导 CSV 进 Excel。
把 CSV 放固定路径,Excel 用 Power Query 连该文件,设打开时刷新或加宏定时刷。
未必是代码错,多为样本过拟合或点差未计;别急着下结论,先去掉极端参数重测一遍。