自动优化 MetaTrader 5 专用 EA·综合运用
⚙️

自动优化 MetaTrader 5 专用 EA·综合运用

(3/3)· 手工每周重跑优化在 MT5 下既不现实也易漏,本文给出可托管的全自动化收尾方案

实战向进阶 第 3/3 篇
很多交易者以为 MT5 自带调度就能周级重优化 EA,结果卡在沙箱权限和单实例限制上。靠人工周六点开测试器,一次断网就整周参数失效。MT5 的专用 EA 若没有双实例与文件复制机制,自动优化只是空谈。

从 XML 回测表里捞最优止损

这段逻辑干的事很直接:把策略回测导出的 XML 表格递归遍历,按行提取利润、止损和回撤,挑出利润更高、同等利润下止损更小的那一组参数。外汇与贵金属品种波动大,这类参数筛选只代表历史样本表现,实盘存在滑点与时序断裂风险,结论仅作概率参考。 核心在 DisplayNodesSL 函数里:遇到无子节点的叶节点就把值拼进 str,遇到 Table 下的 Row 且首列不是 'Pass' 时,用 StringSplit 按逗号拆出数组,取索引 2 为利润、10 为止损、8 为回撤。下面这段是实际落地的截取,可直接拷进 MT5 脚本验证。 别把首行当数据 XML 导出的表常带列标题行,代码里用 StringCompare(res[0],"Pass",true)!=0 跳过,若你换了的表头关键字不同,这行必须改,否则会把标题当成交易记录算进最优解。

MQL5 / C++
class="type">void DisplayNodesSL( CEasyXmlNode *Node )
  {
   for(class="type">int i=class="num">0; i<Node.Children().Total(); i++)
    {
     CEasyXmlNode *ChildNode=Node.Children().At(i);
     if(ChildNode.Children().Total()==class="num">0)
      {
       str+=ChildNode.getValue()+",";
      }
     else
      {
       DisplayNodesSL(ChildNode);
       if(Node.getName()=="Table" && ChildNode.getName()=="Row")
        {
         class="type">class="kw">string res[];
         StringSplit(str,&class="macro">#x27;,&class="macro">#x27;,res);
         class=class="str">"cmt">// 略过列标题
         if(StringCompare(res[class="num">0],"Pass",true)!=class="num">0)
          {
           class="type">class="kw">double profit=StringToDouble(res[class="num">2]);
           class="type">int sl=(class="type">int) StringToInteger(res[class="num">10]);
           PrintFormat("[%s]  Profit=%.2lf StopLoss=%d DD=%s",str,profit,sl,res[class="num">8]);
           if(profit>BetterProfit || (profit==BetterProfit && sl<BetterSL))
            {
             BetterProfit=profit;
             BetterSL=sl;
            }
          }
        }
       if(Node.getName()=="Table")
         str="";
      }
    }
  }

◍ 让定时器替 EA 跑完四段优化

前面几步已经把参数模块和全局变量名在 OnInit() 里拼好,真正干活的是 OnTimer()。它只在周六本地时间早 6 点之后触发,平时直接 return,避免交易时段占用终端资源。 代码里先判断 dt.day_of_week!=6 就清掉 bOptimisationDone 标志并退出,确保每周只跑一轮;hour<6 也退出,等于把优化窗口锁在周六 06:00 之后。这个时间点选得务实——周末流动性停了,回测不会受实时报价干扰。 整段逻辑是串行四段优化:先删旧 Optimise.ini,生成配置并复制进 \MQL5\Profiles\Test,随后依次跑 SL-1、STO(两个参数同跑)、SL-2(用新 STO 重算)、TP。每段都靠 CreateAndCopyParametersFile() 改参数文件,StartOptimizer() 拉起测试器,CopyReport() 回拷报告,LoadResults() 解析写回全局变量。 外汇与贵金属杠杆高、滑点跳空频繁,这套自动优化出的参数仅代表历史样本上的较优解,下周实盘仍可能失效,上 MT5 跑前先把 BetterSL / BetterSTOFilter 等变量打印出来核对。

MQL5 / C++
class="type">void OnTimer()
  {
   class="type">MqlDateTime dt;
   class="type">class="kw">datetime now=TimeLocal(dt);
class=class="str">"cmt">// 在周六
   if(dt.day_of_week!=class="num">6)
     {
       bOptimisationDone=false;
       class="kw">return;
     }
class=class="str">"cmt">// 早上 class="num">6:class="num">00
   if(dt.hour<class="num">6)
       class="kw">return;
class=class="str">"cmt">// 已完成?
   if(bOptimisationDone==true)
       class="kw">return;
class=class="str">"cmt">// 删除以前的 "optimise.ini"
   FileDelete("Optimiser\\Optimise.ini");
class=class="str">"cmt">// 创建 EA 配置文件并将其复制到 \MQL5\Profiles\Test(测试器实例)
   if(CreateAndCopyParametersFile(true,false,false,false,class="num">0,class="num">0,true,false)==false)
       class="kw">return;
class=class="str">"cmt">// 复制 common.ini -> optimise.ini
   if(CopyAndMoveCommonIni()==false)
       class="kw">return;
class=class="str">"cmt">// 在 optimise.ini 里添加 [Tester] 部分
   if(AddTesterStanza()==false)
       class="kw">return;
   Print("=======================\nOptimization SL-class="num">1");
class=class="str">"cmt">// 开始第一次优化止损
   StartOptimizer();
class=class="str">"cmt">// 将报告文件复制到工作目录
   if(CopyReport()==false)
       class="kw">return;
class=class="str">"cmt">// 分析报告   
   if(LoadResults(_SL)==false)
       class="kw">return;
   Print("=======================\nOptimization STO");
class=class="str">"cmt">// 为优化 STO 创建参数文件(class="num">2 个参数将同时优化) 
   if(CreateAndCopyParametersFile(false,false,true,true,BetterSL,class="num">0,true,false)==false)
       class="kw">return;
class=class="str">"cmt">// 开始优化 STO
   StartOptimizer();
class=class="str">"cmt">// 将报告文件复制到工作目录
   if(CopyReport()==false)
       class="kw">return;
   if(LoadResults(_STO)==false)
       class="kw">return;
   Print("=======================\nOptimization SL-class="num">2");
class=class="str">"cmt">// 为优化第二个 SL创建参数文件(使用新的 STO 参数值重新计算)
   if(CreateAndCopyParametersFile(true,false,false,false,class="num">0,class="num">0,BetterSTOFilter,BetterSTOTimeFrameFilter)==false)
       class="kw">return;
class=class="str">"cmt">// 开始优化  
   StartOptimizer();
   if(CopyReport()==false)
       class="kw">return;
   if(LoadResults(_SL)==false)
       class="kw">return;
   Print("=======================\nOptimization TP");
class=class="str">"cmt">// 为优化 TP 创建参数文件
   if(CreateAndCopyParametersFile(false,true,false,false,BetterSL,class="num">0,BetterSTOFilter,BetterSTOTimeFrameFilter)==false)
       class="kw">return;
class=class="str">"cmt">// 开始优化 
   StartOptimizer();
   if(CopyReport()==false)
       class="kw">return;
   if(LoadResults(_TP)==false)
       class="kw">return;
class=class="str">"cmt">// 结论

「用全局变量把优化结果喂给实盘EA」

这段逻辑干的事很直接:优化器跑完一轮,把胜出的 SL、TP、随机过滤开关写进终端全局变量,另一个名叫 BuddyIlan 的 EA 实时读取并套用,不需要手动改参数。 回撤拦截面是硬规矩:一旦 BetterDD 超过 50.0,立刻 GlobalVariableSet 写 Stop=1,让 EA 停手。外汇和贵金属波动突变频繁,这种 50% 回撤熔断在实盘里可能救你一命,但也可能错过后续反弹,属于概率取舍。 OnInit 里先把变量名拼好,格式是 "BuddyIlan."+_Symbol+".SL" 这类,按品种隔离,EURGBP 的优化结果不会串到 XAUUSD 上。下面日志是 2018.07.07 13:20 起在 EURGBP M15 跑的真实片段,能看到终端路径、优化 SL-1 启动、以及两次 Waiting report 轮询,说明报告是异步拿的。 开 MT5 验证时,重点看全局变量窗口里 BuddyIlan.EURGBP.* 这组值有没有随优化跳变;若 BetterDD>50 却没 Stop=1,八成是 GlobalVariableSet 返回 false 被忽略了。

MQL5 / C++
  PrintFormat("=======================\nSL=%d TP=%d STOFilter=%s STOTimeFrameFilter=%s(Profit=%.2lf DD=%lf)\n=======================",
                    BetterSL,BetterTP,(BetterSTOFilter==true)?"true":"false",(BetterSTOTimeFrameFilter==true)?"true":"false",BetterProfit,BetterDD);
class=class="str">"cmt">// 设置全局变量 - 正在运行的 BuddyIlan EA 将读取并使用这些新值
class=class="str">"cmt">// 如果发现回撤超过 class="num">50%,EA 停止交易
  if(BetterDD>class="num">50.0 && GlobalVariableSet(gVarStop,class="num">1.0)==false)
    {
      PrintFormat("Error setting Global Variable [%s]",gVarStop);
    }
  if(GlobalVariableSet(gVarSL,BetterSL)==false)
    {
      PrintFormat("Error setting Global Variable [%s]=%d",gVarSL,BetterSL);
    }
  if(GlobalVariableSet(gVarTP,BetterTP)==false)
    {
      PrintFormat("Error setting Global Variable [%s]=%d",gVarTP,BetterTP);
    }
  if(GlobalVariableSet(gVarSTOFilter,(BetterSTOFilter==true)?class="num">1.0:class="num">0.0)==false)
    {
      PrintFormat("Error setting Global Variable [%s]=%.1lf",gVarSTOFilter,(BetterSTOFilter==true)?class="num">1.0:class="num">0.0);
    }
  if(GlobalVariableSet(gVarSTOTimeFrameFilter,(BetterSTOTimeFrameFilter==true)?class="num">1.0:class="num">0.0)==false)
    {
      PrintFormat("Error setting Global Variable [%s]=%.1lf",gVarSTOTimeFrameFilter,(BetterSTOTimeFrameFilter==true)?class="num">1.0:class="num">0.0);
    }
  bOptimisationDone=true;
}
class="type">int OnInit()
  {
class=class="str">"cmt">// 全局变量
  gVarStop="BuddyIlan."+_Symbol+".Stop";
  gVarSL = "BuddyIlan." + _Symbol + ".SL";
  gVarTP = "BuddyIlan." + _Symbol + ".TP";
  gVarSTOFilter="BuddyIlan."+_Symbol+".STOFilter";
  gVarSTOTimeFrameFilter="BuddyIlan."+_Symbol+".STOTimeFrameFilter";

从日志里挑止损档位

EA 在 EURGBP 的 M15 周期跑完一轮参数寻优后,会把候选结果写进 Optimiser\BuddyIlanReport.xml,再逐行吐到日志里。每一行中括号里的数组就是一组参数快照,末尾的 Profit / StopLoss / DD 是给肉眼筛选用的人话版。 看 2018.07.07 13:21:38 那批输出:SL=500 时净利润 1032.26、最大回撤 6.2173;SL=1250 时净利润 1514.08、回撤 9.3136;SL=1500 时净利润 1444.10、回撤 15.4493。EA 自己用 -> SL=1250 (Profit=1514.08) 标了它认为的甜点,但回撤只有 9.31 而非最低。 外汇和贵金属杠杆高,这种回测净利润不代表实盘能复现,样本只是单币种单周期的历史片段。开 MT5 把同一 EA 的日志翻到 Optimization STO 段,对照 XML 里的数组第 11 项(止损值)和第 2 项(净利润),你能快速验证它挑 SL 的逻辑是不是只认利润最高。

◍ EURGBP M15 上优化器的逐轮回报读数

在 EURGBP 的 M15 周期上跑 BuddyIlanOptimizer,日志会先吐出一组带序号的候选参数与对应净值。2018.07.07 13:23:05 这批里,序号 3 的那行 Profit=1470.71、DD=17.3805,同时 STOFilter 与 STOTimeFrameFilter 都是 true,被箭头标成了当前优选组合。 对比同批前几行:序号 0 利润 1463.50 但两个过滤都为 false,序号 1 利润 1444.10 仅开 STOFilter,序号 2 利润 1430.53 仅开时段过滤。可见双过滤全开时,样本内利润反而略高于单开或全关,但回撤也从 14.4 抬到了 17.38,属于用回撤换夏普的取舍。 优化器不会一次停手,紧接就打 Optimization SL-2 进入下一轮。日志里出现 Waiting report (1) 到 (4) 的轮询,间隔约 15 秒一次,直到 13:24:30 才 Report found (ret=42) 并重新 Loading XML File。这种等待节奏说明报表是异步回传的,实盘前你最好在 MT5 终端把「允许 DLL」和日志级别调开,才能完整看到每轮 XML 落地。 外汇与贵金属品种这类跨周期优化天然高波动、高滑点风险,样本内 1470 点利润不代表样本外能复现,拿去跑之前先用自己的点差预设回测一遍。

「从日志看止损档位的盈亏分水岭」

在 EURGBP 的 M15 周期上跑网格类策略优化时,同一批参数里止损档位直接拉开了盈亏差距。日志里 SL=500 的那组净亏 730.10,而 SL=1250 的一组净利润冲到 1475.95,中间只差了 750 点止损空间,结果从负收益翻成正收益。 具体看这几组:SL=750 利润 662.55、SL=1000 利润 1400.75、SL=1500 利润 1470.71,最大回撤集中在 16.7~19.4 之间。说明在该样本窗口内,过窄的止损更容易被 EURGBP 的毛刺扫掉,偏宽的止损反而让仓位有喘息余地,但 SL 从 1250 加到 1500 时利润几乎不增,边界效应已钝化。 日志末尾切到 Optimization TP 并等待报告,说明止损扫完一轮后立刻转去止盈维度寻优。外汇与贵金属属高风险品种,这类网格 martingale 变体在实盘可能放大连亏,上述数字仅来自历史回测片段,不能直接外推。

优化器吐出的参数组合怎么挑

MT5 策略测试器的优化日志会一次性甩出多组结果,上面这段是 EURGBP M15 上 BuddyIlanOptimizer 在某次跑批里的真实输出。同一时间戳下并列了 5 组候选:TakeProfit 从 30 到 70 不等,净利润在 1243.46 到 2343.52 之间,最大回撤(DD)从 14.12% 到 20.44% 拉开。 光看 Profit 最容易踩坑。TP=70 那组利润 2343.52 最高,DD 15.04% 却不是最低;TP=30 那组 DD 冲到 17.38%,利润反而最薄。外汇和贵金属这种高杠杆品种,回撤失控比少赚几百点要命得多,参数优选得把 DD 和 Profit 放一起权衡。 日志末尾那两行等号分隔的是最终落地配置:SL=1250、TP=70、STOFilter=true、STOTimeFrameFilter=true,对应的就是利润最高那组。你在 MT5 里复现时,直接把这两个过滤器开关打开,再锁死 SL/TP 数值,能少绕很多弯。

MQL5 / C++
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) [class="num">1,class="num">11768.5700,class="num">1768.5700,class="num">8.2259,class="num">2.4484,class="num">1.1024,class="num">0.2233,class="num">0,class="num">14.1173,class="num">215,class="num">40,]  Profit=class="num">1768.57 TakeProfit=class="num">40 DD=class="num">14.117300
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) [class="num">4,class="num">12343.5200,class="num">2343.5200,class="num">13.5464,class="num">2.5709,class="num">1.3349,class="num">0.2519,class="num">0,class="num">15.0389,class="num">173,class="num">70,]  Profit=class="num">2343.52 TakeProfit=class="num">70 DD=class="num">15.038900
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) [class="num">0,class="num">11243.4600,class="num">1243.4600,class="num">5.2913,class="num">1.6399,class="num">0.6887,class="num">0.1039,class="num">0,class="num">17.3805,class="num">235,class="num">30,]  Profit=class="num">1243.46 TakeProfit=class="num">30 DD=class="num">17.380500
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) [class="num">3,class="num">12292.3500,class="num">2292.3500,class="num">11.8162,class="num">2.5837,class="num">0.9257,class="num">0.2538,class="num">0,class="num">20.4354,class="num">194,class="num">60,]  Profit=class="num">2292.35 TakeProfit=class="num">60 DD=class="num">20.435400
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) [class="num">2,class="num">12146.3900,class="num">2146.3900,class="num">11.0639,class="num">2.4416,class="num">1.2226,class="num">0.2292,class="num">0,class="num">15.0772,class="num">194,class="num">50,]  Profit=class="num">2146.39 TakeProfit=class="num">50 DD=class="num">15.077200
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) -> TP=class="num">70 (Profit=class="num">2343.52 DD=class="num">15.038900)
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) =======================
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) SL=class="num">1250 TP=class="num">70 STOFilter=true STOTimeFrameFilter=true (Profit=class="num">2343.52 DD=class="num">15.038900)
class="num">2018.07.class="num">07 class="num">13:class="num">26:class="num">00.219 BuddyIlanOptimizer(EURGBP,M15) =======================

◍ 把外部脚本塞进 MT5 编译闭环

这套自动优化思路真正落地的门槛很低:你只需要懂 MT5 自带的优化框架,加上一点 MQL5 文件读写,就能让 EA 在第二个终端环境里自我迭代参数。附带的 Files.zip(14.39 KB)里除了主 EA 源码,还有 Paul van Hemmen 的 XML 解析器和改过的 EasyXml.mqh,直接丢进 MetaEditor 编译即可跑。 有读者在讨论区问过:能不能用 Python 的 metatrader5 模块干同样的事?答案是否定的——Python 走的是外部 API,没法编译成 MT5 内部可执行文件;即便转成 DLL,也不如直接用 MQL5 写来得干净。 外汇和贵金属市场高杠杆、滑点随机,任何自动优化出来的参数集都只在历史样本里「倾向」有效。打开你的 MT5,把 ZIP 里的 mqh 挂上,先在第二环境放好被优化的 BuddyIlan,跑一轮看看递次分布再决定要不要上实盘。

把双实例巡检交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到优化器 EA 的全局变量写入状态;把重复劳动交给小布,你专注决策。

常见问题

MT5 对 EA 运行环境做了沙箱隔离,普通 MQL5 代码不能访问沙箱外路径,必须借 Windows DLL 与 xcopy 在双实例间搬文件。
可以,小布盯盘的品种页能呈现优化器 EA 写入的全局变量与报告生成情况,异常时你不用自己翻日志。
ShellExecuteW 不等待 DOS 命令返回,复制 common.ini 与启动优化需要时间,延迟是为避免文件未就绪就读取导致失败。
多数品种在周六凌晨结算后波动极低,此时跑优化得到的概率参数更贴近下周开盘前的静态环境,但是外汇贵金属仍属高风险,结果仅作参考。
只要命名带 EA 标识且由优化器写、实盘 EA 读,就不会冲突;关于跨实例变量完整设计见《自动优化 MetaTrader 5 专用 EA·基础篇》。