6.6 回测与参数优化

第 43 节 / 共 43 节 练习题 2 组 课程:MQL4 教程与 MT4 EA 编程
回测不是“点一下开始,看净利润”。它是一套实验设计:你要知道数据区间、成交假设、成本模型,还要知道优化出来的参数在未来是否仍然成立。参数优化最容易制造“样本内冠军”,也最容易在实盘打回原形。

先选建模方式

  • Every tick:每个 Tick 都模拟,最接近实盘,也最慢。适合最终验证和短周期策略。
  • Control points:用控制点生成价格路径,速度快,适合中期参数扫描。
  • Open prices only:只按开盘价成交,适合日线、H4 等长周期,不适合日内。

建模质量(Modeling quality)低于 90% 时,回测结果只能当参考。点差、滑点、佣金、隔夜利息都要设置;默认点差往往偏乐观。先把成本调成真实水平,再看收益曲线。

看哪些指标

  • 净利润:绝对收益,受手数和复利影响,不能单独比较策略优劣。
  • 盈亏比(Profit Factor):总盈利 / 总亏损。1.0 是打平,1.3 以上才有讨论价值。
  • 最大回撤:从峰值到谷底的最大跌幅。决定你能不能拿得住。
  • 交易次数:太少(<30)时统计意义弱;太多可能被成本吃掉。
  • 期望收益:平均每笔盈亏,乘以交易次数才是长期结果。

不要只看净利润。一个策略净利润很高但最大回撤 60%,实盘很难执行;另一个净利润一般但回撤 8%、交易 200 次,反而更可信。

参数优化:网格与过拟合

策略测试器的“优化”会对输入参数做网格扫描,输出每组参数的收益。常见的错误是直接选样本内最高分。参数越细、组合越多,越容易找到“刚好拟合历史噪声”的一组。表现就是:样本内盈亏比 2.1,样本外只剩 1.1。

更稳的做法是看“参数邻域”:最优参数附近一圈是否也表现良好?如果 8/40 是 2.1,但 8/30 只有 1.0、8/50 只有 1.1,说明这是孤峰,换一段数据就崩。如果 8/30、8/40、8/50 都在 1.6 以上,说明这个区域比较稳健。

样本外与前推滚动

把历史数据切成两段:前 70% 做样本内优化,后 30% 做样本外验证。样本外不参与选参,只用来检验。更严格的是前推滚动(walk-forward):每次用过去 N 年优化,预测下一年,然后窗口向前滚动,最后看多次样本外的综合表现。

前推滚动不能保证未来盈利,但能有效暴露“参数对时间段过度敏感”的问题。至少做一次样本外,再决定要不要上线。

完整示例:可优化的均线 EA

下面的 EA 把快线、慢线、手数、止损、止盈、最大点差都做成 input,方便测试器扫描。信号使用 shift=1 和 shift=2 的已收盘均线,避免当前 Bar 重绘。CanTrade 在测试器里跳过点差检查,在实盘里限制最大点差。

#property strict
input int FastMA = 8;
input int SlowMA = 40;
input double Lots = 0.10;
input int StopLoss = 50;
input int TakeProfit = 100;
input int MagicNumber = 20261102;
input int MaxSpread = 30;
 
datetime lastBar = 0;
 
bool CanTrade() {
    if(!IsTradeAllowed()) return false;
    if(IsTesting()) return true;
    if(MarketInfo(Symbol(), MODE_SPREAD) > MaxSpread) return false;
    return true;
}
 
void OnTick() {
    if(!CanTrade()) return;
    if(Time[0] == lastBar) return;
    lastBar = Time[0];
 
    double fast1 = iMA(NULL, 0, FastMA, 0, MODE_SMA, PRICE_CLOSE, 1);
    double slow1 = iMA(NULL, 0, SlowMA, 0, MODE_SMA, PRICE_CLOSE, 1);
    double fast2 = iMA(NULL, 0, FastMA, 0, MODE_SMA, PRICE_CLOSE, 2);
    double slow2 = iMA(NULL, 0, SlowMA, 0, MODE_SMA, PRICE_CLOSE, 2);
 
    bool golden = (fast1 > slow1 && fast2 <= slow2);
    bool dead = (fast1 < slow1 && fast2 >= slow2);
 
    if(golden)
        OrderSend(Symbol(), OP_BUY, Lots, Ask, 3,
        NormalizeDouble(Ask - StopLoss * Point, Digits),
        NormalizeDouble(Ask + TakeProfit * Point, Digits),
        "adv6", MagicNumber, 0, clrBlue);
 
    if(dead)
        OrderSend(Symbol(), OP_SELL, Lots, Bid, 3,
        NormalizeDouble(Bid + StopLoss * Point, Digits),
        NormalizeDouble(Bid - TakeProfit * Point, Digits),
        "adv6", MagicNumber, 0, clrRed);
}

注意:信号用 `fast1/slow1` 与 `fast2/slow2` 比较,而不是 `fast0/slow0`。shift=0 的均线会随当前价格跳动,用它做交叉判断,回测里会出现“看到未来”的假信号。

成本压力测试

把点差调大 50%、滑点调大 1 点、佣金翻倍,再跑一遍。如果策略立刻从盈利变成亏损,说明它的边际很薄,实盘很难复制。好的策略应该在最差成本假设下仍然可控,而不是只在理想成本下漂亮。

常见错误

  • 用 shift=0 的指标做信号:当前 Bar 未收盘,回测会重绘,结果虚高。
  • 只做样本内优化:16 组参数选最高分,过拟合概率极高。
  • 忽略点差和滑点:默认成本偏低,实盘收益被吃掉。
  • 交易次数太少就下结论:10 笔交易的高盈亏比没有统计意义。
  • 参数孤峰:最优参数附近表现很差,说明是拟合噪声,不是稳健区域。

小结

回测的可信度来自实验设计,不是收益曲线的好看程度。先固定成本假设,再看回撤与交易次数,再做样本外和前推滚动。优化时选“邻域稳定”的参数,不选“单点最高”的参数。这样得到的结论,才有资格拿到实盘去验证。

测试器设置清单

  • 品种与周期:与实盘一致。用 H1 优化的参数拿到 M5 上跑,结果没有可比性。
  • 数据区间:至少覆盖一轮完整趋势和一轮震荡,避开只有单边行情的样本。
  • 建模方式:最终验证用 Every tick;参数扫描可用 Control points,但要接受误差。
  • 初始资金与杠杆:与实盘账户一致,否则手数和回撤比例会失真。
  • 点差/滑点/佣金:按经纪商的真实水平设置,不要用 0 点差。
  • 执行延迟:部分版本支持随机延迟,能模拟网络抖动。

优化结果怎么读

测试器的优化报告是一张表:每行一组参数,每列一个指标。不要只按净利润排序。先看交易次数,少于 30 的结果统计意义弱;再看最大回撤,超过可承受范围直接淘汰;然后看盈亏比和期望收益;最后才看净利润。把“参数—指标”画成散点或热力图,比单看最高分更容易发现稳定区域。

如果最优参数是 8/40,但 8/39、8/41 的表现断崖式下跌,说明这是孤峰。稳健区域通常是一整片颜色相近的格子,而不是一个亮点。实盘更愿意要“2.0 的平原”,而不是“2.5 的针尖”。

前推滚动的步骤

  1. 把数据按时间切成多个窗口,例如 2018–2020 优化、2021 验证,然后窗口向前滚动。
  2. 每次只用窗口内的数据选参数,绝不能用验证段的数据。
  3. 记录每次验证段的收益、回撤、交易次数,最后看平均值和最差值。
  4. 如果多次验证中有一半以上亏损,说明参数不稳定,不要上线。

前推滚动不能消除过拟合,但能让你看到“参数在不同时间段的表现分布”。分布比单点更有说服力。

上线后的监控

实盘和回测的差异主要来自成本、滑点、执行延迟和行情结构变化。上线后至少记录:实际成交价与请求价的偏差、每日交易次数、实际最大回撤、错误码分布。连续两周偏离回测区间,就应该暂停策略复查,而不是加仓摊平。

把回测的“预期值”写成一张小卡片:盈亏比、最大回撤、月交易次数。实盘每周对照一次。偏离在 20% 以内可以观察,超过 50% 要停手。这个简单的对照表,比任何复杂监控都更能防止“策略已经失效但还在跑”。

数据质量与缺口

回测结果首先取决于数据。选择“历史数据”时优先用经纪商提供的 M1 数据,不要用来源不明的 CSV。数据缺口会表现为某段 K 线突然跳空、点差为 0、成交量缺失;这种数据上跑出的高收益没有意义。测试前用“图表—历史数据”检查缺口,或写一个脚本统计连续缺失的分钟数。

不同经纪商的数据不完全一致,同一策略在 A 经纪商回测盈利、B 经纪商回测亏损,可能不是策略问题,而是点差、隔夜利息、数据源不同。上线前用目标经纪商的模拟账户再跑一遍,不要直接套用别家的回测结论。

参数敏感性与稳健性

稳健的参数区域有三个特征:邻域表现接近、跨时间段表现接近、跨品种表现接近。只满足第一个还不够:8/40 在 EURUSD 上很好,换到 GBPUSD 就亏损,说明它拟合的是品种特性,不是市场规律。至少用两个品种、两段时间做交叉验证,再决定是否上线。

参数越少越好。每增加一个参数,就多一个过拟合的维度。能用固定逻辑解决的问题,不要做成参数;必须做成参数的,给出合理范围和步长,不要用 0.01 这种细粒度扫描,除非你有足够的数据支撑。

交互演示:参数优化为什么会过拟合

4×4 参数网格逐个回测,样本内冠军是 8/40,盈亏比 2.10;换到样本外只剩 1.15。单步走完,理解“最高分”为什么不能直接上线。

提示:动画默认暂停。点「播放」连续演示,点「单步」自己控制节奏;桌面端也可以用空格播放、方向键单步、R 重置。

练习题

  • 6.6 回测与参数优化 选择题
    1.参数优化时,样本内最高分直接上线的主要风险是? (2分) [选择]
    A. 回测速度太慢
    B. 过拟合,样本外表现可能大幅下降
    C. 手续费太低
    D. 图表颜色不对
    2.下列哪个指标最能反映策略的“回撤风险”? (2分) [选择]
    A. 净利润
    B. 最大回撤
    C. 交易次数
    D. 点差
    3.使用 shift=0 的均线做交叉信号,回测结果通常比 shift=1 更可信。 (2分) [判断]
  • 6.6 回测与参数优化 编程实战(AI 批改)
    1.【实战】写一个可优化的均线 EA 骨架 (10分) [MQL4自动批改]
    写一个 EA:用 input 定义 FastMA、SlowMA、Lots、StopLoss、TakeProfit、MagicNumber、MaxSpread;CanTrade 中判断 IsTradeAllowed,实盘时限制 MODE_SPREAD;OnTick 中用 Time[0] 与 lastBar 保证每根 K 线只执行一次;用 iMA 取 shift=1 和 shift=2 的快慢线,金叉 OrderSend 买入、死叉卖出,SL/TP 用 NormalizeDouble。必须出现 input、iMA、OrderSend、NormalizeDouble。