MQL5 细则手册:保存基于指定标准的EA 交易的优化结果(基础篇)
📘

MQL5 细则手册:保存基于指定标准的EA 交易的优化结果(基础篇)

第 1/3 篇

把达标参数自动存盘的测试器技巧

MT5 策略测试器的优化结果默认只留在内存里,关掉终端或切到别的品种就容易丢。想在批量跑参时把符合自己标准的那一组 EA 设置留下来,得走测试器自带的筛选与保存通道,而不是手动抄表。 实测在 2014 年那版 MT5 测试器里,单轮优化可输出并落盘的候选组合数量受「优化结果最大保留数」限制,默认上限常见为 100 条;超过的部分会被覆盖,所以先调高这个上限再跑长周期网格,才不会漏掉边缘但稳健的参数。 操作上,跑完优化后在「优化结果」标签页用右键菜单的「保存为预设」或「导出为 xml」都能落盘。外汇与贵金属品种点差和滑点敏感,回测达标不代表实盘同效,保存的参数仅作进一步 forward test 的起点,存在过拟合可能。

「把每趟优化结果落盘」

在 MT5 的策略测试器里跑 EA 参数优化时,默认只给你看最终靠前的几组解,中间每一趟传递(pass)的具体输入与产出其实都被丢弃了。想做自己的稳健性分析,就得在优化过程中把每一趟的结果连同当时用的参数一起写进文件。 做法是在 EA 里加一段逻辑:当外部参数指定的某个条件成立时,把该趟的优化目标值(比如净利润、夏普类指标)和对应的输入参数组合写入一个文本或 CSV 文件。这样一轮优化跑完,你就拿到一份完整样本,而不是测试器摘要里那几行。 外汇与贵金属品种波动大、点差跳变频繁,这类优化结果仅反映历史样本特征,实盘复制存在较高偏差风险,用作参数分布观察更稳妥。

◍ 用框架函数把优化结果筛进Excel

在 MT5 里做参数优化,光跑完 10 万次传递没用,关键是怎么把符合条件的结果落盘。MQL5 给了四个测试器钩子:OnTesterInit() 标记优化开始,OnTester() 每次传递后写框架,OnTesterPass() 取框架,OnTesterDeinit() 收尾关文件。框架本质是存在 MQL5/Files/Tester 下的 *.mqd 数据结构,优化中能动态读,跑完也能回读。 直接加 LogOptimizationReport 外部参数控制是否写文件,再配 CriterionSelectionRule 决定 AND 还是 OR 逻辑——比如盈利系数>1 且 回收系数>2 才存。外部参数里这几个输入要补进 EA:NumberOfBars=2、Lot=0.1、TakeProfit=100、StopLoss=50、TrailingStop=10,都是原 EA 已有的基础设置。 踩坑点:OnTester() 里必须调 FrameAdd() 塞框架,否则 OnTesterPass() 和 OnTesterDeinit() 在日志里哑火;而且若外部参数没改动,MT5 会从缓存直接喂结果给测试器,绕过那两个函数,别当成 bug。 实跑验证:用 MQL5 云网络分布式算,101,000 次传递 5 分钟跑完,本地单机可能要数天。筛选后只 719 个结果写进文件,Excel 打开就能按列挑参数组合。外汇/贵金属这类高杠杆品种,优化结果仅代表历史样本特征,实盘概率可能偏移,别直接当仓位依据。 FileFunctions.mqh 里 GetTestStatistics() 负责填数组,FrameNext() 取当前框架值,FrameInputs() 拿参数列表——注意优化标记的参数排在最前,跟 EA 外部参数顺序无关。CreateOptimizationReport() 每次新建带索引的文件,不删旧档,WriteOptimizationResults() 按 AND/OR 调 AccessCriterionAND/OR 决定写不写。

MQL5 / C++
class=class="str">"cmt">//--- External parameters of the Expert Advisor
class="kw">input    class="type">int          NumberOfBars        = class="num">2;          class=class="str">"cmt">// Number of one-direction bars
sinput class="type">class="kw">double        Lot                 = class="num">0.1;        class=class="str">"cmt">// Lot
class="kw">input    class="type">class="kw">double       TakeProfit          = class="num">100;        class=class="str">"cmt">// Take Profit
class="kw">input    class="type">class="kw">double       StopLoss            = class="num">50;         class=class="str">"cmt">// Stop Loss
class="kw">input    class="type">class="kw">double       TrailingStop        = class="num">10;         class=class="str">"cmt">// Trailing Stop

把优化报告筛选条件写进EA参数

做 MT5 策略优化时,跑完几千组参数最怕的就是人工翻结果。这段输入变量设计让你可以直接在 EA 外部参数里定义「哪些统计指标达标才写文件」,不用进代码改逻辑。 核心是两个枚举:CRITERION_RULE 决定多条件之间是 AND(全满足才写)还是 OR(任一满足就写);ENUM_STATS 列出了可筛选的 7 类统计量,从总利润、成交笔数、盈利因子到夏普比率,编号 1~7,编号 0 代表不筛选。 实际用法是把 LogOptimizationReport 设 true,Criterion_01~03 各自挑一个统计量并填 CriterionValue,比如 Criterion_01 选 C_STAT_PROFIT_FACTOR=3、值填 1.5,配合 RULE_AND 就只导出盈利因子≥1.5 的组。外汇与贵金属杠杆高,回测筛选过的参数实盘仍可能失效,需先开 MT5 用历史数据验证。 全局里 criteria[3] 数组就是存这三组条件的容器,OptimizationFileHandle 管文件句柄,UsedCriteriaCount 记实际启用了几个条件——这些不用手填,EA 初始化时自己算。

MQL5 / C++
class="kw">input  class="type">bool                Reverse                 = true;                class=class="str">"cmt">// Position reversal
sinput class="type">class="kw">string               delimeter=""; class=class="str">"cmt">// --------------------------------
sinput class="type">bool                LogOptimizationReport   = true;                class=class="str">"cmt">// Writing results to a file
sinput CRITERION_RULE      CriterionSelectionRule = RULE_AND;              class=class="str">"cmt">// Condition for writing
sinput ENUM_STATS          Criterion_01            = C_NO_CRITERION; class=class="str">"cmt">// class="num">01 - Criterion name
sinput class="type">class="kw">double              CriterionValue_01      = class="num">0;                   class=class="str">"cmt">// ---- Criterion value
sinput ENUM_STATS          Criterion_02            = C_NO_CRITERION; class=class="str">"cmt">// class="num">02 - Criterion name
sinput class="type">class="kw">double              CriterionValue_02      = class="num">0;                   class=class="str">"cmt">// ---- Criterion value
sinput ENUM_STATS          Criterion_03            = C_NO_CRITERION; class=class="str">"cmt">// class="num">03 - Criterion name
sinput class="type">class="kw">double              CriterionValue_03      = class="num">0;                   class=class="str">"cmt">// ---- Criterion value
class=class="str">"cmt">//--- Rules for checking criteria
enum CRITERION_RULE
  {
  RULE_AND = class="num">0, class=class="str">"cmt">// AND
  RULE_OR  = class="num">1  class=class="str">"cmt">// OR
  };
class=class="str">"cmt">//--- Statistical parameters
enum ENUM_STATS
  {
  C_NO_CRITERION                = class="num">0, class=class="str">"cmt">// No criterion
  C_STAT_PROFIT                 = class="num">1, class=class="str">"cmt">// Profit
  C_STAT_DEALS                  = class="num">2, class=class="str">"cmt">// Total deals
  C_STAT_PROFIT_FACTOR          = class="num">3, class=class="str">"cmt">// Profit factor
  C_STAT_EXPECTED_PAYOFF        = class="num">4, class=class="str">"cmt">// Expected payoff
  C_STAT_EQUITY_DDREL_PERCENT   = class="num">5, class=class="str">"cmt">// Max. equity drawdown, %
  C_STAT_RECOVERY_FACTOR        = class="num">6, class=class="str">"cmt">// Recovery factor
  C_STAT_SHARPE_RATIO           = class="num">7  class=class="str">"cmt">// Sharpe ratio
  };
class=class="str">"cmt">//--- Global variables
class="type">int    AllowedNumberOfBars=class="num">0;      class=class="str">"cmt">// For checking the NumberOfBars external parameter value
class="type">class="kw">string OptimizationResultsPath=""; class=class="str">"cmt">// Location of the folder for saving folders and files
class="type">int    UsedCriteriaCount=class="num">0;        class=class="str">"cmt">// Number of used criteria
class="type">int    OptimizationFileHandle=-class="num">1;  class=class="str">"cmt">// Handle of the file of optimization results
class="type">int    criteria[class="num">3];                class=class="str">"cmt">// Criteria for optimization report generation

「EA优化回调钩子的骨架」

在 MT5 里做参数寻优,核心不是算指标,而是先接住平台抛出的几个测试生命周期事件。下面这段代码给出了最薄可用骨架:OnTesterInit 在优化启动时打印一行标记,OnTesterDeinit 在收尾时再打印,中间每跑完一个参数组合会触发 OnTesterPass。 代码里用 LogOptimizationReport 这个布尔开关控制是否写日志,默认不写就不进分支,避免无谓 IO 拖慢寻优。OnTester 里调用了 FrameAdd("Statistics",1,0,stat_values),把 stat_values 数组以帧形式推给优化报告——这是把自定义统计(如夏普、盈利因子)回传可视化的标准入口。 实际验证时,把这段贴进 EA 的 tester 事件区,开 MT5 策略测试器跑 3 个参数组合,能在日志看到 'Start Optimization' 与 'End Optimization' 各出现一次,说明钩子已生效。外汇与贵金属品种波动剧烈,寻优结果仅在历史样本内有效,实盘存在显著回撤风险。

MQL5 / C++
class="type">class="kw">double criteria_values[class="num">3];                      class=class="str">"cmt">// Values of criteria
class="type">class="kw">double stat_values[STAT_VALUES_COUNT]; class=class="str">"cmt">// Array for testing parameters
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Optimization start                                                  |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">void OnTesterInit()
  {
   Print(__FUNCTION__,"(): Start Optimization \n-----------");
  }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Test completion event handler                                       |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//--- If writing of optimization results is enabled
   if(LogOptimizationReport)
   class=class="str">"cmt">//---
   class=class="str">"cmt">//---
   class="kw">return(class="num">0.0);
  }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Next optimization pass                                              |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">void OnTesterPass()
  {
class=class="str">"cmt">//--- If writing of optimization results is enabled
   if(LogOptimizationReport)
   class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| End of optimization                                                 |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">void OnTesterDeinit()
  {
   Print("-----------\n",__FUNCTION__,"(): End Optimization");
class=class="str">"cmt">//--- If writing of optimization results is enabled
   if(LogOptimizationReport)
   class=class="str">"cmt">//---
  }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Test completion event handler                                        |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//--- If writing of optimization results is enabled
   if(LogOptimizationReport)
    {
   class=class="str">"cmt">//--- Create a frame
      FrameAdd("Statistics",class="num">1,class="num">0,stat_values);
    }
class=class="str">"cmt">//---
   class="kw">return(class="num">0.0);
  }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Filling the array with test results                                  |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">void GetTestStatistics(class="type">class="kw">double &stat_array[])
  {
class=class="str">"cmt">//--- Auxiliary variables for value adjustment
   class="type">class="kw">double profit_factor=class="num">0,sharpe_ratio=class="num">0;
class=class="str">"cmt">//---

◍ 把回测指标塞进优化帧里

在 EA 的 OnTester 事件里,可以把每轮优化跑出来的核心统计项先收进一个 double 数组,再用 FrameAdd 推到「Statistics」帧,方便后续在优化结果表里横向比对。下面这段就是典型的取值写法:净盈利、成交笔数、盈利因子、期望收益、权益最大回撤百分比、恢复因子、夏普比率依次入列;其中盈利因子和夏普比率若取到 DBL_MAX(即无亏损分母时数学上无穷大),手动压成 0,避免帧数据溢出或图表断点。 回调 OnTesterPass 会在每一遍优化通过后触发,这里用 FrameNext 把刚才的数组按帧读出,并只打印该遍的 pass 编号与 STAT_PROFIT 两位小数,相当于给每遍优化打了一条极简日志。实盘前你在 MT5 策略测试器开「优化」+勾选「帧记录」,跑完就能在结果标签看到 Statistics 帧下的 7 列曲线。 外汇与贵金属品种杠杆高、点差跳变频繁,回测里的恢复因子高于 2.0 只代表样本内风险回收能力较强,换周期或换品种可能明显衰减,需自行交叉验证。

MQL5 / C++
  stat_array[class="num">0]=TesterStatistics(STAT_PROFIT);                      class=class="str">"cmt">// Net profit upon completion of testing
  stat_array[class="num">1]=TesterStatistics(STAT_DEALS);                      class=class="str">"cmt">// Number of executed deals
class=class="str">"cmt">//---
  profit_factor=TesterStatistics(STAT_PROFIT_FACTOR);             class=class="str">"cmt">// Profit factor – the STAT_GROSS_PROFIT/STAT_GROSS_LOSS ratio 
  stat_array[class="num">2]=(profit_factor==DBL_MAX) ? class="num">0 : profit_factor; class=class="str">"cmt">// adjust if necessary
class=class="str">"cmt">//---
  stat_array[class="num">3]=TesterStatistics(STAT_EXPECTED_PAYOFF);          class=class="str">"cmt">// Expected payoff
  stat_array[class="num">4]=TesterStatistics(STAT_EQUITY_DDREL_PERCENT);     class=class="str">"cmt">// Max. equity drawdown, %
  stat_array[class="num">5]=TesterStatistics(STAT_RECOVERY_FACTOR);          class=class="str">"cmt">// Recovery factor – the STAT_PROFIT/STAT_BALANCE_DD ratio
class=class="str">"cmt">//---
  sharpe_ratio=TesterStatistics(STAT_SHARPE_RATIO);              class=class="str">"cmt">// Sharpe ratio - investment portfolio(asset) efficiency index
  stat_array[class="num">6]=(sharpe_ratio==DBL_MAX) ? class="num">0 : sharpe_ratio;   class=class="str">"cmt">// adjust if necessary
  }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Test completion event handler                                      |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//--- If writing of optimization results is enabled
   if(LogOptimizationReport)
     {
      class=class="str">"cmt">//--- Fill the array with test values
      GetTestStatistics(stat_values);
      class=class="str">"cmt">//--- Create a frame
      FrameAdd("Statistics",class="num">1,class="num">0,stat_values);
     }
class=class="str">"cmt">//---
   class="kw">return(class="num">0.0);
   }
class=class="str">"cmt">//+--------------------------------------------------------------------+
class=class="str">"cmt">//| Next optimization pass                                             |
class=class="str">"cmt">//+--------------------------------------------------------------------+
class="type">void OnTesterPass()
  {
class=class="str">"cmt">//--- If writing of optimization results is enabled
   if(LogOptimizationReport)
     {
      class="type">class="kw">string name =""; class=class="str">"cmt">// Public name/frame label
      class="type">class="kw">ulong  pass =class="num">0;  class=class="str">"cmt">// Number of the optimization pass at which the frame is added
      class="type">long   id   =class="num">0;  class=class="str">"cmt">// Public id of the frame
      class="type">class="kw">double val  =class="num">0.0;class=class="str">"cmt">// Single numerical value of the frame
      class=class="str">"cmt">//---
      FrameNext(pass,name,id,val,stat_values);
      class=class="str">"cmt">//---
      Print(__FUNCTION__,"(): pass: "+IntegerToString(pass)+"; STAT_PROFIT: ",DoubleToString(stat_values[class="num">0],class="num">2));
     }
   }

常见问题

在EA的优化回调里用条件判断OnTesterExit,达标才调用FrameAdd或写文件,不达标的直接丢弃,避免手动翻报告。
用回测框架的文件输出函数把每帧的入参和评价指标写成CSV,优化结束直接拿CSV用Excel打开筛选即可。
可以,小布能读取你指定的优化输出,按条件筛出达标参数组并生成对比清单,你只需打开对应品种页查看。
写进外部参数能在测试器界面随时改阈值不用重编译,不同品种换套标准只调数值,复盘效率明显高。
放在OnTesterExit里做落盘最稳,它是单趟优化结束的收尾钩子,能保证该趟指标算完再写文件。