MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表·综合运用
◍ 逐笔成交回填权益曲线
把每笔成交的利润、库存费与手续费累加进对应品种的余额序列,是复盘权益曲线的第一步。下面这段逻辑在遍历成交记录时,依据前一笔索引递推当前权益,遇到首笔余额类型则直接取账户余额。 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 用真实账户历史成交跑一遍校验。
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 策略测试器跑一遍核对数值。
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 里到底落哪些序列交易数据。