首页 / 交易学院 / 小布知识库 / 创建 EA 交易优化的自定义标准:OnTester 与 TCustomCriterion
创建 EA 交易优化的自定义标准:OnTester 与 TCustomCriterion
🎯

创建 EA 交易优化的自定义标准:OnTester 与 TCustomCriterion

回测与测试 难度 · 进阶 2013 11 分钟阅读
优化标准OnTester遗传算法回测MQL5

一、优化标准是什么,为什么内置的不够用

在 MT5 策略测试器里,「优化标准」是一个数值,数值越高代表这组参数越好。内置标准(最大余额、余额+最大夏普、余额+最小回撤等)覆盖了最常见目标,但真实需求往往更刁钻:你可能想同时看收益、回撤、交易次数、平滑度,甚至想惩罚「曲线某一段偏离直线太远」。内置列表塞不下这些组合,于是 MT5 留了后门——Custom max(自定义最大值),它的取值就是你 EA 里 OnTester() 函数的返回值。只要启用遗传算法优化,测试器每跑完一组参数就自动调用 OnTester,用你返回的那个 double 去排序、淘汰弱代。

理解这一点很关键:遗传优化是「降序排序、抛弃最差」的机制,所以你的自定义标准必须设计成「越大越好」。任何你关心的质量维度,都要转化成单一数值且方向正确,否则优化方向会完全反掉。

二、策略测试程序内置的优化标准清单

文档里内置的标准包括:Balance max(最大余额)、Balance+max Profit Factor(余额×获利系数)、Balance+max Expected Payoff(余额×预计获利)、Balance+min Drawdown(余额×低回撤)、Balance+max Recovery Factor(余额×回收系数)、Balance+max Sharpe Ratio(余额×夏普),以及本文重点的 Custom max。注意前几种是「余额乘某个风险指标」,本质是单目标加权;而 Custom max 把目标函数完全交给你,灵活度最高,也最容易写错。

自定义标准只在「遗传算法(快速遗传基础算法)」模式下生效。若选了完整搜索(遍历所有组合),OnTester 不会被用来挑参,测试器只是跑完保存结果,此时你需要自己离线处理。

三、OnTester() 函数:自定义标准的入口

OnTester 是预定义函数,每次单组参数测试快结束时、OnDeinit 之前自动调用,返回值 double 即该组参数的优化得分。要取测试结果,用 TesterStatistics(STAT_XXX)——它只能从 OnTester 和 OnDeinit 里调,否则结果未定义。最朴素的版本直接 return 0.0;真正有用的版本要从 STAT_PROFIT、STAT_BALANCE_DD、STAT_TRADES 等统计量里算出你想最大化的那个数。

举个例子:用回收系数(Recovery Factor,净利÷最大回撤)做标准。OnTester 里取 profit=TesterStatistics(STAT_PROFIT)、max_dd=TesterStatistics(STAT_BALANCE_DD),返回 profit/max_dd。注意文中代码省略了除零检查——实盘必须补上:当 max_dd==0 时应返回极小值或跳过,否则会触发除零错误导致该组参数评分异常。

double OnTester() {
  double profit = TesterStatistics(STAT_PROFIT);
  double max_dd = TesterStatistics(STAT_BALANCE_DD);
  if(max_dd <= 0.0) return 0.0;          // 补上除零保护
  double rec_factor = profit / max_dd;
  return rec_factor;
}

四、组合标准:余额 + 最小回撤 + 交易次数

单指标容易被钻空子(比如只优化夏普可能得到一个交易极少、运气成分高的参数)。更稳的组合是「余额 × 回撤倒数 × 交易次数」:balance 用 STAT_PROFIT,min_dd 用 STAT_BALANCE_DD 取倒数(回撤越小分数越高),trades_number 用 STAT_TRADES(样本越多结果越可靠)。三者相乘,逼着遗传算法去找「赚得多、回撤小、且不是靠一两次运气单」的参数区。

文中把 MA 周期的优化范围设成 100–2000,在 2009.06–2011.06 的 H1 上跑 FanExpert(三均线扇形 EA)。优化图显示标准最大值落在 MA1Period 约 980–1200 区间,最好与最差参数表对比可见:亏损、获利、交易次数被同时纳入,标准确实在起作用。要记住这是遗传搜索不是全遍历,图里某些好参数可能被自然选择淘汰了,别把最优值当成全局最优。

double OnTester() {
  double balance = TesterStatistics(STAT_PROFIT);
  double min_dd  = TesterStatistics(STAT_BALANCE_DD);
  if(min_dd > 0.0) min_dd = 1.0 / min_dd;   // 回撤取倒数
  double trades_number = TesterStatistics(STAT_TRADES);
  double param = balance * min_dd * trades_number;
  return param;
}

五、封装成类:TCustomCriterion 体系

每次手写 OnTester 既重复又慢。文中建了一套类体系:基类 TCustomCriterion 定义虚方法 GetCriterion();TSimpleCriterion 直接返回某个 STAT_XXX 的值(正比型);TSimpleDivCriterion 返回其倒数(反比型,用于回撤类);TSimpleMinCriterion / TSimpleMaxCriterion 加上下限,用于故意放弃不达标参数(比如最少交易次数、最大回撤红线)。这样在 EA 里 new 几个对象、往容器里一塞,OnTester 只需遍历调用 GetCriterion 再合成总分,扩展性和可读性都上来了。

性能是这类封装的硬要求:MT5 支持用云网络做大规模优化,标准算得太慢会拖垮整个搜索。所以文中特意选最简单直接的实现(甚至不惜牺牲一点面向对象优雅),并用标准数据组织类减少开销。实盘封装时,OnTester 里只做轻量算术,别在里面再开文件、再训练模型。

六、实战建议与常见陷阱

第一,方向永远「越大越好」,写错符号优化就反向。第二,必须处理边界(除零、负值、空仓),否则某组参数评分 NaN/异常会污染整代排序。第三,组合权重要克制——乘太多因子会把标准压成极端值,反而让遗传算法过早收敛到局部。第四,自定义标准本质是单目标,多目标冲突(收益 vs 回撤)靠你手动调权重,不是自动 Pareto 前沿。第五,也是最重要的:用自定义标准挑出的「最优参数」依然可能过拟合,它只是把你定义的目标函数最大化,不代表未来样本外也同样好,必须配合样本外验证、WFO 滚动窗口。

OnTester 自定义标准优化出来的参数,过拟合风险比内置标准更高——因为你把目标函数做得越贴合历史,越容易拟合噪声。务必做样本外测试,而不要只看遗传算法的「最优结果」列就上线。

七、小结

MT5 的 Custom max 优化标准把目标函数完全交给你,入口就是 OnTester():用 TesterStatistics 取统计量、算出一个「越大越好」的 double 返回。可以简单到回收系数,也可以组合成「余额×回撤倒数×交易次数」的多因子标准;规模大了就封装成 TCustomCriterion 类体系复用。记住四件事——只在遗传算法下生效、方向必须越大越好、务必处理边界值、挑出的参数仍要样本外验证防过拟合。

常见问题

在 EA 里定义 double OnTester(),函数内用 TesterStatistics(STAT_XXX) 取到净利、回撤、交易次数等统计量,算出一个 double 返回即可。测试器在遗传算法模式下每跑完一组参数就调用它,用返回值排序淘汰弱代。
是的,Custom max 只在「快速遗传基础算法」优化模式下生效。若选完整搜索(遍历所有组合),OnTester 不参与挑参,测试器只是跑完保存结果,需要你离线自己处理。
在 OnTester 里取 STAT_PROFIT 与 STAT_BALANCE_DD,把回撤取倒数(回撤越小分数越高)再与收益相乘,例如 return balance * (1.0/max_dd) * trades。这样既奖励赚钱又惩罚大回撤,还能用交易次数过滤掉样本过少的侥幸参数。
常用有 STAT_PROFIT(净利)、STAT_BALANCE_DD(最大余额回撤)、STAT_TRADES(交易次数)、STAT_WITHDRAWAL(提取)等,完整枚举见 ENUM_STATISTICS。注意它只能在 OnTester 与 OnDeinit 中调用,否则结果未定义。
会,而且风险比内置标准更高——你把目标函数做得越贴合历史,越容易拟合噪声。遗传算法的「最优结果」只是把你的标准最大化,不代表样本外同样好,必须配合样本外测试或 WFO 滚动窗口验证后再上线。