优化结果的可视化评估(基础篇)
📘

优化结果的可视化评估(基础篇)

第 1/3 篇

「用 MT5 测试报告看优化是否靠谱」

MT5 的策略测试器跑完一轮优化,别只盯着最终利润栏。报告里每一行参数组合都带着净值曲线、回撤和交易次数,这些才是判断过拟合的第一手材料。 以一篇 2022-03-23 发布的公开测试笔记为例,单次优化任务在社区里收获了 1841 次查看,说明这类可视化评估方法本身有稳定受众。把不同参数组的权益曲线叠在同一张图上,弯曲度差异大的组合,实盘失效概率往往更高。 外汇与贵金属品种波动受杠杆和消息面驱动,回测漂亮不等于实盘能复制,任何优化结论都只是「历史样本下倾向有效」,须以小额实盘验证。

◍ 一次跑完多准则优化的必要性

自定义优化准则让 EA 调参更顺手,但真要同时校验多个条件,传统做法是反复跑多次优化,耗时随准则数量线性放大,实盘前光回测就磨掉大半耐心。 更直接的做法是把多个自定义准则塞进同一次优化里并行评估,顺带把余额、净值曲线实时画出来。人脑接收的信息超 80% 来自视觉,能边跑边看图形,比盯着终值表格更容易抓到过拟合的尾巴。 本文落点是搭一套优化图形 + 最优准则挑选的流程,而且即便你 MQL5 只懂皮毛,也能借社区文章和论坛碎片拼出可用方案,不用从零啃文档。

把优化通测结果排成可比较的序列

做参数优化不能只盯 MT5 自带的 profit 和 recovery factor。每次优化通测跑完,先把这一轮的所有 pass 数据落盘,包括输入参数组合、成交次数、最大回撤,避免关掉优化器就丢现场。 给每次通测单独生成余额曲线和净值曲线图。MT5 优化结果页只给末态,自己导出 OnTester 里的 equity 数组画全周期曲线,才能看出某组参数是不是靠尾段一波拉伸撑起来的。 自定义准则建议至少算三个:sharpe 近似(均值/标准差)、回撤恢复比(总盈利/最大回撤时长)、参数敏感度(邻域 5% 扰动下收益衰减)。外汇与贵金属杠杆高,曲线断裂和跳空常导致这些准则失真,需人工复核。 按你定的加权准则对图形升序排,最优解不一定取第一,看前 5 名曲线的形态稳定性再定。小布知识库可接管这套排序,直接把最优结果和对应参数打印到日记,省去手动翻几百行。

「把改 EA 的代价压到最低」

给 EA 加回测截图与自定义筛选逻辑时,最忌讳直接动主策略文件。我们实际做法是把数据收集全部塞进独立的 SkrShotOpt.mqh 包含文件,自定义准则单独放 CustomCriterion.mqh 计算,ScreenShotOptimization.mq5 只负责画图与存盘。 这样主 EA 源码只需要补几行 include 和调用,后期升级 MT5 版本或换品种时,改动面控制在 10 行以内,回测工程不会越改越乱。 外汇与贵金属回测本身受点差和滑点影响大,属于高风险验证环境,任何截图优化结论都只能当作概率参考,不能直接当成实盘信号。

◍ 用成交事件驱动净值采样而不是每跳都写

做持仓快照最忌在 OnTick() 里无脑刷写。每来一个即时报价就检查一遍持仓和余额,CPU 和文件 IO 都扛不住,尤其黄金和美系直盘在数据行情里每秒十几跳,数组会爆得快。 实际做法是把净值极值跟踪放进 OnTick(),只维护 tempEquityMax / tempEquityMin 两个临时变量;真正的数组写入放到 OnTradeTransaction(),且只在成交数量变化(DEAL_ENTRY_IN / OUT / INOUT)时才动笔。这样一笔持仓从开到平,通常只落四个值:开仓时余额+净值、平仓时余额+期间最大或最小净值。 文件体积必须掐死。多笔交易连续跑,若每次都写全量帧数据,文件会膨胀到难以回放绘制。上面这套逻辑下,即便同根 K 线内先平后开,也只记一笔持仓——图形可视化不受损,数组长度却能砍掉一大截。 亏损了结写 tempEquityMin、盈利了结写 tempEquityMax(或按视图模式切),这个映射直接决定你后面看到的资金曲线是「回撤包络」还是「最低水位线」。外汇和贵金属杠杆高,净值摆动可能被放大数倍,采样漏掉极值会误判真实风险。

MQL5 / C++
  class="type">class="kw">double _Equity = AccountInfoDouble(ACCOUNT_EQUITY);
  if(tempEquityMax < _Equity)
      tempEquityMax = _Equity;
  if(tempEquityMin > _Equity)
      tempEquityMin = _Equity;
class="type">void IsOnTradeTransaction(class="kw">const MqlTradeTransaction & trans,
                          class="kw">const class="type">MqlTradeRequest & request,
                          class="kw">const class="type">MqlTradeResult & result)
  {
  if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
    if(HistoryDealSelect(trans.deal))
      {
      if(_deal_entry != DEAL_ENTRY_OUT && _deal_entry != DEAL_ENTRY_OUT_BY)
        _deal_entry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
      if(trans.deal_type == DEAL_TYPE_BUY || trans.deal_type == DEAL_TYPE_SELL)
        if(_deal_entry == DEAL_ENTRY_IN || _deal_entry == DEAL_ENTRY_OUT || _deal_entry == DEAL_ENTRY_INOUT || _deal_entry == DEAL_ENTRY_OUT_BY)
          allowed = true;
      }
  }
  if(allowed) class=class="str">"cmt">// if there was a trade
   {
    class="type">class="kw">double accBalance = AccountInfoDouble(ACCOUNT_BALANCE);
    class="type">class="kw">double accEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    ArrayResize(balance, _size + class="num">1);
    ArrayResize(equity, _size + class="num">1);
    balance[_size] = accBalance;
    if(_deal_entry != DEAL_ENTRY_OUT && _deal_entry != DEAL_ENTRY_OUT_BY) class=class="str">"cmt">// if a new position appeared
      equity[_size] = accEquity;
    else class=class="str">"cmt">// if position closed
      {
      if(changesB < accBalance)
        equity[_size] = tempEquityMin;
      else
        class="kw">switch(s_view)
          {
          case  min_max_E:
            equity[_size] = tempEquityMax;
            class="kw">break;
          class="kw">default:
            equity[_size] = tempEquityMin;
            class="kw">break;
          }
      tempEquityMax = accEquity;
      tempEquityMin = accEquity;
      }
    _size = _size + class="num">1;
    changesPos = PositionsTotal();

余额快照后的状态复位

在持仓回放逻辑里,把当前账户净值先存进 changesB,这一步是为了在后续比对中知道资金基线在哪。 紧接着将 _deal_entry 置为 -1,代表本次循环没有有效的开仓方向记录;同时把 allowed 设为 false,直接掐断这一轮继续撮合的可能。 这段复位代码通常出现在异常分支或单笔处理收尾处,若你不手动清状态,下一轮循环可能沿用上一次的 entry 或权限标记,回测结果会出现幽灵成交。

MQL5 / C++
    changesB = accBalance;
    _deal_entry = -class="num">1;
    allowed = class="kw">false;

常见问题

重点看样本外区间和前向测试的权益曲线是否平滑,若仅样本内暴赚、样本外直线回落,大概率过拟合,应丢弃该组参数。
单次跑完可同时按收益、回撤、夏普等多列排序比较,避免反复重跑浪费时间,也能直观看出参数间的权衡区。
小布可自动读取品种页的测试摘要,标出样本外衰减与极端回撤,并给出是否建议实盘跟随的倾向性提示。
把变动限定在独立函数内,复用已有优化缓存,只重算受影响分支,能把重编译和重测代价压到最低。
只在开平仓时记一笔余额快照并复位状态,文件更小、回测更快,且不影响事后看资金曲线形态。