MQL5 Cookbook: 减少过度配合的影响以及处理报价缺失·综合运用
(3/3)·从NeuroShell实例到MQL5 EA落地,解决历史数据不足时的参数可信度难题
从成交笔数算局部最大回撤
在 MT5 回测里统计回撤,不能只盯净值曲线终点,得按成交序号逐笔算局部峰值。下面这段逻辑用静态变量 max / min 存住历史局部最高与最低余额,第一笔成交(deal_number==0)直接把两者都初始化成起始 balance,回撤置 0。 当后续 balance 大于已存 max 时,先用旧值算回撤:钱数回撤 = max - min,百分比回撤 = 100 - (min/max)*100,随后把 max、min 都刷新成当前 balance。若 balance 没超过 max,则本次回撤输出 0,只用 fmin 把 min 往下探,捕捉更深的浮亏谷底。 若某笔仓位已能取到 ticket(HistoryDealGetTicket 返回值 >0),就顺手抓交易注释和末笔标志,方便在报告里定位哪笔单子对应哪段回撤。外汇与贵金属杠杆高,回测最大回撤只是历史概率表征,实盘可能突破该值。 把文件写盘的部分也别漏:每拼好一行 string_to_write 就 FileWrite 落盘,然后立刻置空变量等下一行;全部循环结束 FileClose 收尾。创建或打开文件失败则用 Print 把 GetLastError 码打出来,省得无声崩。
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("创建文件出错!错误: "+IntegerToString(GetLastError())+""); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回局部最大值的最大回撤 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void TesterDrawdownMaximum(class="type">int deal_number, class="type">class="kw">double balance, class="type">class="kw">double &percent_drawdown, class="type">class="kw">double &money_drawdown) { class="type">ulong ticket =class="num">0; class=class="str">"cmt">// 交易订单编号 class="type">class="kw">string str =""; class=class="str">"cmt">// 在报告中显示的字符串 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">//--- 还没有回撤 percent_drawdown =class="num">0.0; money_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">//--- 使用前值计算回撤: class=class="str">"cmt">// 钱数 money_drawdown=max-min; class=class="str">"cmt">// 表示为百分率 percent_drawdown=class="num">100-((min/max)*class="num">100); class=class="str">"cmt">//--- 更新局部最大值 max=balance; min=balance; } class=class="str">"cmt">//--- 否则 else { class=class="str">"cmt">//--- 返回0值做为回撤 money_drawdown=class="num">0.0; percent_drawdown=class="num">0.0; class=class="str">"cmt">//--- 更新最小值 min=fmin(min,balance); class=class="str">"cmt">//--- 如果仓位的交易订单编号已经获得,那么... if((ticket=HistoryDealGetTicket(deal_number))>class="num">0) { class=class="str">"cmt">//--- ...取得交易注释 GetHistoryDealProperties(ticket,D_COMMENT); class=class="str">"cmt">//--- 最后一笔交易的标志
「用注释锚点抓回测最后一笔」
在 MT5 策略测试器里跑完一轮,想单独统计期末回撤,最省事的办法是给最后一笔成交打一个特殊注释。上面这段代码用静态变量 last_deal 做开关,只认一次注释为「测试结束」的成交,避免重复计算。 money_drawdown=max-min 拿到的是资金回撤的绝对值;percent_drawdown+=100-((min/max)*100) 把峰值到谷底的回撤折成百分比,两次浮点除法在 max 接近 0 时会溢出,实盘前记得加保护。外汇与贵金属杠杆高,回测回撤不代表实盘风险下限,参数需自行压力测试。 DrawdownToString 是个薄封装:传入小于等于 0 的回撤直接返回空串,否则用 DoubleToString 保留两位小数。复制进 EA 的 Tools 区就能在日志里直接打印可读回撤。
class="kw">static class="type">bool last_deal=false; class=class="str">"cmt">//--- 测试中的最后一笔交易可以根据"测试结束"注释来找到 if(deal.comment=="测试结束" && !last_deal) { class=class="str">"cmt">//--- 设置标志 last_deal=true; class=class="str">"cmt">//--- 更新回撤值: class=class="str">"cmt">// 钱数 money_drawdown=max-min; class=class="str">"cmt">// 表示为百分率 percent_drawdown+=class="num">100-((min/max)*class="num">100); } } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 把回撤转换为字符串 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string DrawdownToString(class="type">class="kw">double drawdown) { class="kw">return((drawdown<=class="num">0) ? "" : DoubleToString(drawdown,class="num">2)); }
◍ 三品种同跑下的手数阶梯与样本量
MT5 策略测试器做多品种优化时,先把开仓基础手数压到最小 0.01,再把 VolumeIncrease 设成递增步长,避免三个品种同时加仓把浮亏放大过快。外汇与贵金属杠杆高,这种阶梯加仓一旦回撤,本金侵蚀速度可能远快于线性预期,跑前先估自己机器能扛多少并发。 优化完挑「最大采收率」那组结果,把 VolumeIncrease 定在 0.1 手。实测把三个品种的结果导进 Excel 2010,图表里回撤金额用绿色标作副轴,肉眼能直接比对谁拖后腿。 Excel 2010 不是无限画布:官方规格里单图表系列数有硬上限,完整限制得查 Microsoft Office 的 Excel 规格页。但表格本身能塞 255 个交易品种的数据行,瓶颈只在你的 CPU 和内存。 拿定好的参数再跑第 3 集合的 7 个品种,总成交 6901 笔,Excel 刷新图表几乎无感。想复现,直接开 MT5 测试器选三品种、基础手 0.01、递增 0.1,导出 CSV 丢进 Excel 看副轴回撤。
把工具请下神坛
前文那套跨品种降过拟合的思路,实测只在 3 个品种上跑过优化,不是 7 个全样本;若把剩余 4 个品种也纳入参数寻优,回测数值大概率还能往上走,但边际效用取决于系统本身是否已经够硬。更稳的路子是手握几套逻辑互不相关的系统,而不是死磕单框架调参。 落地动作很直接:解压后把 ReduceOverfittingEA 塞进 MetaTrader 5\MQL5\Experts,EventsSpy.mq5 丢进 Indicators,SymbolsList.txt 放 Files 目录,MT5 重启就能挂。外汇与贵金属杠杆高,多币种同跑会放大回撤,实盘前先用策略测试器跑一遍样本外数据。 工具只是把过拟合风险摊薄的手段,别神化压缩包里的 EA;它替你管了品种清单和事件监听,但进出场规则仍由你定。