回测与验证 intermediate 13 分钟阅读

MT5 回测报告怎么看:23 份报告,以及每个头条数字漏掉了什么

MT5 回测报告说的是一件事:某一批交易在某一段价格历史上跑出来的结果,而这句话的两半都会改变数字。我们把本站已发布的 23 份报告全部读了一遍:在同时公布了两段切片的 20 次运行里,有 14 次的头条盈利因子比 EA 真正被挑选出来的那段时期更好看;有一份报告只覆盖了它自己 315 笔交易里的 303 笔;而唯一一个换到第二份价格历史上重测的 EA,从 1.35 通过变成了 1.13 不通过。

发布于 · 审阅于

一份 MT5 回测报告,说的是某一批具体的交易在某一段具体的价格历史上跑出来的结果。几乎所有的误读,都来自把这句话丢掉一半。头条上的盈利因子被当成了智能交易系统的属性。它其实是另外两样东西的属性:这份报告覆盖的那批交易,以及测试器被喂进去的那些 tick。

这篇文章讲的是那套能产出决定的阅读顺序:报告在打印任何东西之前假定了什么,哪个指标回答哪个问题,每个数字会在哪里骗你,以及每一种读法在告诉你去改什么。产出一份报告是另一件活——怎么做一次回测把那一半从头到尾讲了一遍。

测试条件
实验编号EXP-REPORT-READING-001
上级实验EXP-BACKTEST-RUNBOOK-001
来源data/run-manifests/*.jsonresults, ledger_recomputed, shadow_runs
样本总体all 23 runs published on mt5depot at the time of derivation
MT5 版本号6090 and 6140
模型Every tick generated from M1 bars (Model 0) and Every tick based on real ticks (Model 4)
账户10,000 USD deposit on all 23 runs
最近验证2026-08-26

下面每一个数字都出自回测,而这 23 次运行全部带着 PASS 判定——它们是越过了上架关卡的那些运行。所以这是一组幸存者,这里没有任何内容能说明一次回测通过的概率有多大。它说的是:一份通过了的报告,从内部看是什么样子。

设置:报告在打印第一个数字之前假定了什么

MT5 报告顶端的 Settings 区块不是开场白。它是底下每一个数字都继承下来的假设清单,而其中有六项决定了剩下的部分值不值得读。经纪商名称和终端版本号写在报告自己的标题行里——我们这份读作 Exness-MT5Trial5 (Build 6090)

Settings 项它真正钉死了什么我们这组记录的是
Symbol: / Period:这个结果是哪些行情格局的一个样本22 次运行从 2019 年 1 月开始;cairn 从 2021 年 6 月开始
模型(tick 生成方式)每根K线内部的 tick 是怎么造出来的12 次运行用 Model 0(由 M1 K线生成),11 次用 Model 4(真实 tick)
Initial Deposit:页面上每一个百分比的分母23 次全部是 10,000 USD
点差每一条腿上被扣掉的成本23 次里只有 1 次真正测过——其余的都继承了价格历史自带的那个
手续费只有你自己设了,测试器才会施加的一项成本23 次全部为 0
Inputs:产生这份结果的那一整套设置,一个不漏每一次运行都对着报告核实过

Inputs: 这一行是人们会跳过的一行,而它恰恰是让一份报告真正可复现的那一行:它是你要把同一个页面拿回来,就必须重新录入的那一整套完整取值。

其中两行值得停一下。手续费那一行写着零,不等于手续费被忽略了。它的意思是被测品种上没有收取手续费。这是两句不同的话,而只有运行记录才能告诉你适用的是哪一句。另外,一个没测过的点差不是零点差:我们 23 次运行里有 22 次这一栏是空的,因为测试期间没有任何记录器在旁边运行。每一条腿就是拿了 M1 历史数据自带的那个点差。只有 tidewell-slack 能说出自己的均值,19.14 点。

这里还有一个命名陷阱。老一些的指南叫作建模质量的那一栏,MT5 打印出来的名字是 History Quality:,位置在 Results 区块里,紧挨着 Bars:Ticks:Symbols:。在一份 Build 6090 的报告里搜 “modelling quality”,你什么也搜不到。在我们自己的记录里,23 份清单中只有 2 份带着这个数字,因为它只在源报告写明了的时候才会被保留下来——这是这批记录的一个属性,而不是对 MetaTrader 打印它的频率的测量。实际的后果是:对于不是你自己生成的报告,你没法拿它当筛选条件。它底下真正的那个问题是 tick 数据的来源,而那个问题永远有答案:去问这次运行用的是哪一份历史数据。

逐个指标读这份报告

按这个顺序读这一页。它刻意不是 MetaTrader 打印的顺序,因为两个最省事的否决项都待在报告的底部。

  1. 先读 Settings。模型、Initial Deposit:、点差和手续费。底下每一个百分比都继承它们,而一次点差没被测过的运行,不是一次没有点差的运行。
  2. 在任何比率之前,先读 Total Trades:。一个比率和它的样本量是一个事实,不是两个。把笔数写在比率旁边。
  3. 问一句:头条覆盖的到底是哪些交易。如果旁边一并发布了成交清单,就检查它的总数是否和 Total Trades: 对得上。我们自己那 23 份报告里有一份对不上。
  4. Equity Drawdown Maximal:,而不是余额那一行,并且记下你取的是 Maximal 还是 Relative 那个数字——它们是分开的两行。
  5. 找到价格历史。哪家经纪商、哪个来源、哪一段时期。在我们的记录里,这才是那个改变了判定的字段;tick 模型不是。

下面这张表把整套读法放在一起,用的是报告真正打印出来的那些标签。“在我们这组里”这一列,是每个指标在 23 次已发布运行上的实际表现。它比教科书上的区间更管用,因为它测的就是你手上拿着的这一类报告。

报告标签它回答的问题在我们这组里危险信号
Total Trades:证据有多少每次运行 108 到 4,725 笔低于约 100;20 笔交易上的比率是传闻
Profit Factor:毛利润除以毛亏损(盈利因子23 次全部落在 1.22 到 1.59 之间在一段很短的窗口上高于约 2.0;先看 Total Trades:
Profit Trades (% of total):胜率——分布的形状,不是它的正负号30.80% 到 82.27%,全部盈利单独引用,旁边不放盈利因子
Total Net Profit:赢到的钱,还没有放进语境里同样的 10,000 USD 入金上,386.05 到 28,687.43 USD引用时不带 Initial Deposit: 和年数
Balance Drawdown Maximal:已平仓交易上最糟的一段下探(最大回撤0.67% 到 18.24%同一页上明明有净值那一行,却引用了这一行
Equity Drawdown Maximal:把未平仓持仓算在内最糟的一段下探23 次里 23 次都比余额那条更深被忽略——你的保证金活在这个数字上
Expected Payoff:每笔交易的平均结果,以货币计我们的清单里没有携带被当成预测来读,而不是平均值
Recovery Factor: / Sharpe Ratio:每单位痛苦换来的回报,两种算法我们的清单里没有携带拿去和交易笔数不同的报告比较
History Quality:tick 序列能不能分辨出你的止损23 次里有 2 次写明既缺席而且数据来源也不明

那个区块里有两个惯例,会在任何解读开始之前就把人绊倒。

Total Trades:Total Deals: 是两个不同的计数。在 MetaTrader 5 里,一个来回是两笔 deal——一进一出——所以一份写着 Total Trades: 50 的报告,旁边会写着 Total Deals: 100,而部分平仓会增加 deal 却不增加 trade。比率要对着 trade 读,绝不能对着 deal 读。

回撤一共有六行,不是一行。报告会为余额和净值各打印 AbsoluteMaximalRelative。更糟的是,其中两行里金额和百分比互换了位置:Balance Drawdown Maximal: 36.84 (0.37%) 把货币放在前面,而 Balance Drawdown Relative: 0.37% (36.84) 把百分比放在前面。两份报告可以分别从相邻的两行里引用 “0.37%” 和 “36.84”,说的是同一件事——也可能是两件不同的事。

Results 区块剩下的部分——Z-Score:AHPR:GHPR:LR Correlation:LR Standard Error:Margin Level: 以及 MFE/MAE 相关系数——描述的是资金曲线的形状,而不是它的大小。它们没有一个能救回一份交易笔数太少的报告,也没有一个在价格历史更换之后活得比盈利因子更久。请在上面那五步之后再读它们,或者干脆不读。

指标里有两行在实践中造成的伤害最大,所以请把它们放在一起读,而不是先后读。

净利润需要它的分母和它的日历。我们这 23 次运行全部从同样的 10,000 USD 起步,这让它们出奇地可比。它们的年化成绩依然跨越了 0.51% 到 57.38%。低的那一端很有教育意义。orrery 的盈利因子读作 1.46,最大回撤 0.67%——整个目录里最好看的一对数字。它在 7.58 年里挣了 386.05 USD。这份报告没有任何不诚实的地方。它回答的问题,和大多数读者以为自己在问的那个问题,不是同一个。

胜率和盈利因子在这里几乎是彼此独立的。在这 23 次已发布运行上,两者的相关系数是 0.37。因此,决定一个 EA 到底赚不赚钱的绝大部分因素,在胜率那一行里是看不见的。nautical 赢下 30.80% 的交易,盈利因子 1.22;gyre 赢下 82.27%,盈利因子 1.59。两个都盈利,而胜率相差 51 个百分点。

你的报告没给你看的那个盈利因子

这一部分是没有任何报告会给你看的,而这也正是我们能用自己的数据写出这篇文章的原因。每一次已发布的运行都保留着完整的平仓交易清单,所以每一份报告都能一分为二。一边是 EA 被挑选出来的那段切片——上架关卡据以判定的那段开发窗口。另一边是它上线之后发生的那些交易,没有任何东西是在它们身上被挑选出来的。我们 23 次运行里有 20 次同时公布了两段切片;另外三次完全没有上架后的切片。

EA开发切片上架后切片头条
orrery1.28(195 笔)6.43(15 笔)1.46(210 笔)
windrose1.31(101 笔)3.58(7 笔)1.38(108 笔)
cairn1.48(4,583 笔)1.64(142 笔)1.48(4,725 笔)
gyre1.55(136 笔)——(5 笔,零亏损单)1.59(141 笔)
tessera1.28(624 笔)0.88(21 笔)1.27(645 笔)
zerqon1.53(232 笔)0.72(21 笔)1.43(253 笔)

把 orrery 那一行的第一列和最后一列放在一起读。头条说 1.46;EA 真正被挑中的那段时期说 1.28;差别就是那 15 笔交易——占全部记录的 7%——跑出了 6.43 的盈利因子。用同样的方式读 zerqon 那一行,方向正好相反:开发切片 1.53,上架之后那 21 笔是 0.72,而 1.43 的头条把两者都藏了起来。

在公布了两段切片的那 20 次运行里,头条比开发切片更好看的有 14 次,更差的有 6 次。另外三次是有意被排除在外的:它们只有一段切片,所以它们的头条和它们的开发数字覆盖的是完全相同的那批交易,两者之间的差别根本不是样本总体效应——那只是报告自己保留两位小数,与重算出来的成交清单之间的差。almanac 打印的是 1.52,而清单重算出来是 1.5153。把同样这条四舍五入的下限套到那 20 次上,其中 13 次的差距依然大到四舍五入解释不了,其中 8 次是往好看的方向。

这种不对称不是丑闻。一个横跨两段时期的头条,会被其中运气更好的那一段拖过去。它的意思只是:当你要拿一个数字去对照关于未来表现的说法时,头条永远不是那个数字。

陷阱:这些数字是怎么误导人的

这五条底下的失败模式是同一个:一份报告是对一个样本总体的测量,而页面上的每一个指标,都在不声不响地继承测试器当时被指向的那个样本总体。

改进:每一种读法在告诉你去改什么

每次迭代只改一样东西,并且每次都重读同样这三个数字:交易笔数、盈利因子和净值回撤。下面的顺序是按成本排的——便宜的检查放在前面,因为其中两项经常直接把调查结束掉。

  1. 如果交易笔数很少,就把时期拉长——别去动参数。一个建立在大约 100 笔以下交易上的盈利因子,从一段更长的窗口里得到的变化,比你能调的任何设置都大。我们已发布的运行里最小的一次有 108 笔交易,而它也正是我们引用比率时最小心的那一次。
  2. 如果头条和开发切片对不上,就只在开发切片上重读一遍。那才是设置据以挑选出来的那个数字。它才是拿去和任何其他候选比较的那一个。
  3. 如果净值回撤明显高于余额回撤,就按净值那个数字来定仓位。这个差距告诉你 EA 会在同一段行情里持有多少个仓位。chrysalis 那次运行直说了:最多同时七个。
  4. 如果这份报告来自别人的数据,那就先在第二个来源上重跑一遍,其他都往后放。在我们整组记录里,这项检查的实测影响最大,而它是人们最后才做的那一项。

真正改变了数字的是什么:数据来源,不是 tick 模型

我们有一个 EA 被刻意重跑过,就是为了把这两个变量分开,而运行记录给每一次影子运行都标明了它隔离的是什么。

tidewell-slack 运行改了什么交易笔数盈利因子最大回撤历史质量判定
已发布Exness M1 历史数据,Model 03031.359.92%99%PASS
影子——隔离数据来源Dukascopy 历史数据,Model 03511.1318.18%99%FAIL
影子——隔离真实 tickDukascopy 历史数据,Model 43451.1118.20%100%FAIL

把它当成两个独立的实验来读。只改价格历史——第一行到第二行,tick 模型相同——把盈利因子从 1.35 拉到了 1.13,回撤几乎翻倍,判定也从通过翻成了不通过。在那份新历史数据上只改 tick 模型——第二行到第三行——盈利因子只动了 0.02,回撤只动了 0.02 个百分点。而且在那两次没通过的运行上,历史质量读作 99% 和 100%,也就是说大多数人拿来筛选的那个字段,全程都很优秀。

另一个 EA 从另一个角度指向同一件事。kestrel-hover 是在 Dukascopy 真实 tick 上发布的,盈利因子 1.33,回撤 7.11%,而它在 Exness M1 历史数据、Model 0 上的影子运行读作 1.30 和 6.76%——来源和模型同时都变了,结果却几乎没动。跨数据来源的稳定性是可能的。它只是没法靠单独一份报告来证明。

实用的规则是:一份报告作为可移植证据的程度,取决于它的价格历史有多可移植。在第二个来源上重跑一遍,成本是一个晚上。在我们的记录里,这是这篇文章里唯一一项真的改变过决定的检查。

下一步:从一份报告到一个决定

把报告读好是四步里的第二步。顺序是回测 → 阅读 → 前进分析 → 模拟盘,每一步都拿掉一种不同的犯错方式。一份四步都活下来的报告,赢得了一个小仓位;只活过前两步的那份,赢得的是再做一次测试。

  • 拿记录完整的东西来练。已发布的 EA每一项都带着完整的平仓交易清单和运行清单,所以你可以自己把头条重算一遍,看清楚到底是哪些交易产生了它。这是唯一一个能让这套读法真正留在手上的练习。
  • 如果某个数字把你送回策略本身而不是测试,那么 Builder 才是规则本身发生改变的地方。在设置上做搜索又是另一件活,写在参数优化里。
  • 在你根据刚读到的回撤数字去定任何仓位之前,EA 交易者的风险管理会把它换算成一个入金和一个手数。
  • 我们如何产出、打分并记录这一切,写在我们的测试方法里。

测试器里的过往结果不是预测,上面任何一次运行都不是对未来 12 个月的承诺。把一份报告读对,目的比预测更小,也更有用。它告诉你的是:什么已经被证明了,什么只是被打印出来了。

常见问题

MT5 回测报告里的盈利因子多少算好?
先问它覆盖的是哪些交易,再去评判这个数字。本站已发布的 23 份报告,每一份都落在 1.22 到 1.59 之间,而单独一份报告内部的跨度,比整个目录之间的跨度还要大:orrery 在它被挑选出来的那 195 笔交易上读作 1.28,把后来的 15 笔也算进去之后变成 1.46。一个盈利因子只有在你能说出它由多少笔交易产生、以及这些交易有没有被用来挑选设置的时候,才开始有意义。低于大约 25 笔时它几乎不含信息——我们那 20 段上架后切片里有 16 段就是这么小,而它们的盈利因子从 0.72 一直跑到 6.43。
为什么我的 MT5 报告里有两个不同的回撤数字?
因为 MetaTrader 是分开衡量余额曲线和净值曲线的,而净值把未平仓的持仓也算了进去。在我们全部 23 份报告里,净值回撤都是更深的那一条;差距最大的是 cairn,余额 14.52%,净值 25.62%,也就是有 11 个百分点的痛苦,被只报余额的说法藏了起来。这个差距是 EA 持仓方式的一个属性——chrysalis 那次运行把原因直白地记了下来:这个 EA 会在同一段行情里最多持有七个仓位。这里还有第二个维度:MetaTrader 会按 Maximal 和 Relative 两种定义各报一次,我们的运行里有 16 次记录了 Maximal 那一对,5 次记录了 Relative 那一对。请引用净值那一行,并且说明它来自哪一种定义。
胜率高是不是就说明这个 EA 好?
不是,而我们自己这组数据就是反例。这 23 份已发布报告里,胜率和盈利因子的相关系数是 0.37,绝大部分变化都没有被它解释掉。nautical 只赢下 30.80% 的交易,盈利因子依然有 1.22;zerqon 赢下 78.26%,读作 1.43。最清楚的案例是 zerqon 后来发生的事:在它上架之后的那 21 笔交易里,它依然赢了 76.19% 的时候,盈利因子却只有 0.72,亏掉 61.47 USD。胜率告诉你的是交易分布的形状,不是它的正负号。
别人用自己的数据跑出来的回测报告能信吗?
把它当成一个关于他们那份价格历史的说法,而不是关于这个 EA 的说法。我们手上有一个 EA 用两种方式都测过,而且 tick 模型保持不变:tidewell-slack 在 Exness M1 历史数据上公布了 303 笔交易,盈利因子 1.35,回撤 9.92%;同一个 EA 在同一段时期、在 Dukascopy 历史数据上跑出了 351 笔交易,1.13,回撤 18.18%——按同一道关卡就是不通过。再把那次 Dukascopy 运行从生成 tick 换成真实 tick,只让它动了 0.02,变成 1.11。改变判定的是数据来源,不是 tick 模型。在你问任何别的问题之前,先问这份报告来自哪一份价格历史。