样本外验证怎么做:为什么这里没有「留 30% 当测试集」

唯一的工具是 rolling walk-forward,而它验的不是一组参数。

最后更新 2026-09
要点总结
  • 工作页上没有「训练/测试比例」这个设定。唯一的样本外工具是第五分页的 rolling walk-forward,两个输入框填的是天数,而且只有 rolling、不提供 anchored。
  • 做法:每一轮在训练窗里重新挑一次参数,套到紧接着的测试窗;测试窗互不重叠,接成一整段样本外序列。
  • 判准是 WFE(样本外效率)=样本外 Sharpe ÷ 样本内平均 Sharpe。0.5 及格、0.7 算好是业界惯例,不是证明——这句是产品自己的说明原文。
  • 实例:教学范例策略 btc_sma_cross 在 2026-09-17 那次执行,样本内平均 Sharpe 1.3471、样本外 −0.45、WFE −0.334;而同一支策略同一天的 MCPT 是过的。两个工具问的不是同一件事。
  • 它不会给你一组可以拿去用的参数。那次执行 20 轮选出 4 组不同参数,成绩不属于其中任何一组。

你在教科书上、甚至在我们自己的〈回测是什么?回测报告怎么看〉里都读过同一句话:不要在全部数据上优化,至少留 20–30% 做样本外验证。然后你打开工作页,把每个分页都点过一遍,找不到那个比例设定。

没找到是正常的,产品没有这个功能。样本外这件事在 Blave 上只有一个实作:第五分页的 rolling walk-forward,而它填的是天数、验的东西也跟简单切分不太一样。这篇讲清楚差在哪、那个分页上的每个数字怎么读。

概念上的样本外,跟这里的样本外差在哪

如何避免策略过拟合〉里有一张表,把三种做法标了等级:全数据优化(无效)、前 70% 训练后 30% 测试(有效但有限)、滚动窗口重选(最真实)。那张表是概念层的判断;产品层的事实是——只有第三种有按钮

做法 概念上 在 Blave 上
用全部历史优化,回报全部历史样本内,必然乐观回测分页本来就是在整段历史上算成绩,所以才需要另外验
切一刀:前段训练、后段测试有效但只能用一次没有这个功能,找不到比例设定
Rolling walk-forward:每轮重选、测下一段最接近实盘的做法工作页第五分页「样本外验证」

差别不只是界面。简单切分验的是「这一组参数在后段数据上好不好」;rolling walk-forward 验的是「用历史挑参数」这个做法本身在这支策略上还站不站得住——它把「用一段历史挑参数」这个动作重复做了 n 次,每次都用没看过的下一段来记分。

它实际上怎么跑

一句话:在每一个训练窗里重新挑一次参数,套到紧接着的测试窗;把所有测试窗接成一条。

样本外序列 = 20 段测试窗接起来的那一条。
训练窗 测试窗
12022-01-05 → 2024-12-302024-12-31 → 2025-01-29
22022-02-04 → 2025-01-292025-01-30 → 2025-02-28
32022-03-06 → 2025-02-282025-03-01 → 2025-03-30
(中间各轮同样每次往前挪一个测试窗,共 20 轮)
202023-07-29 → 2026-07-232026-07-24 → 2026-08-22

测试窗首尾相接到日:轮 1 停在 2025-01-29、轮 2 从 2025-01-30 接上去,中间没有空隙也没有重叠——20 段接起来就是那条样本外序列。

训练窗则是大幅重叠:三年的窗每一轮只往前挪 30 天,所以任两轮的训练期几乎是同一段数据。重叠的东西拼不成一条连续的线——这就是为什么图上只有样本外那一条。

实作上它跑两趟:第一趟把每一组候选参数的信号在全历史上只算一次,切出各轮训练片段定价;第二趟只重算各轮的胜出组合,把测试片段串成一条、用一次定价算完整段。所以它比「跑 n 次完整扫描」快得多——下面那个 20 轮、4.7 年小时线的例子,2026-09-17 那次执行 3.5 秒跑完(界面确认框给的是通则:「通常两分钟左右跑完,结果会出现在这个分页。」)。

只有 rolling,没有 anchored

Rolling 是训练窗每一轮整个往前滑;anchored 是起点固定、训练窗越滚越长。产品只提供 rolling,anchored 刻意不提供。 你跟 agent 要 anchored,它会回一句固定的话就照跑 rolling:

这里只提供 rolling(训练窗跟着往前滑);anchored 不提供。

也不用想着绕过去:跑 walk-forward 的那支 lib 是唯读的,常驻程序会直接拒绝编辑;而且结果档里没有字段可以标记「这是 anchored 跑的」,真的改了,画面还是会照 rolling 显示。

训练窗与测试窗怎么填

分页上只有两个输入框:训练窗测试窗,单位都是天,旁边即时显示会切出「共 n 轮」。两个都不填,就走这张默认表:

数据长度(天) 训练窗(天) 测试窗(多久重选一次,天)
≥ 11851095(三年)30
455–1184365(一年)30
< 455测试窗的 4 倍总天数 ÷ 12,最少 30

三年那一阶是有理由的:它比较接近「用户从整段回测里挑一组固定参数」的真实做法。中等长度的历史若直接套比例公式,训练窗会短于一年,所以中间补了 365/30 这一阶。

两个硬上下限会直接挡下来,不给半调子的结果:少于 3 轮不跑、也不写结果档(「数据只够 {n} 轮,至少要 3 轮才看得出样本外表现」);超过 1000 轮一样挡(「把测试窗调大」)。末尾不足一轮的数据不会硬凑,图下方会注明「末尾 {d} 天不足一轮,未纳入」。

样本外效率(WFE)怎么读

结论卡只看一个数字:

WFE = 样本外 Sharpe ÷ 样本内平均 Sharpe

分子是拼接后那一整段样本外序列的 Sharpe;分母是各轮训练窗里选中那一格自己的 Sharpe 的平均。四档标签照这个分界(都是「小于」,所以 0.5 落在「刚过门槛」、0.7 落在「高于门槛」):

WFE结论卡显示
算不出来样本外效率:无法判断
< 0.2样本外效率:远低于门槛
< 0.5样本外效率:低于门槛
< 0.7样本外效率:刚过门槛
≥ 0.7样本外效率:高于门槛

而那个数字旁边的说明,请照字面读:

Walk-Forward Efficiency(WFE):样本外 Sharpe ÷ 样本内 Sharpe(即上表两行)。业界常用 0.5 当及格线、0.7 以上算好;样本内 Sharpe 低于 0.25 就不做这个除法。这是惯例,不是证明。

分母有两道防线,而且顺序有意义

一个很小或负的分母会把整个诊断倒过来:一支样本内从来就不强的策略,反而会因为「没吐回什么」而拿到很高的「效率」。所以有两道防线:

  1. 训练窗 Sharpe ≤ 0 的那一轮算失败,不加入样本内平均。它仍然留在轮表、留在参数漂移图、留在获利窗比例里——只是不进分母。每轮明细会注明「第 {k} 轮训练窗 Sharpe ≤ 0,不计入样本外效率的平均。」
  2. 扣完之后的样本内平均低于 0.25,就不做这个除法,直接显示「无法判断」。

一次真实的 walk-forward:2026-09-17 那次执行

教学范例策略 btc_sma_cross(BTCUSDT 1h,2022-01-01 起、共 1,720 天,SMA 45/100,参数一字未改)。数据落在第一阶,默认窗 1095/30,切出 20 轮(测试窗合计 600 天),末尾 25 天不足一轮、未纳入。

项目 样本外(测试窗接起来,600 天) 样本内(20 轮训练窗各自算完取平均)
Sharpe−0.451.3471
年化报酬−16.55%+50.16%
最大回撤−46.57%
交易笔数183
样本外效率 WFE = −0.45 ÷ 1.3471 = −0.334   →  远低于门槛

三件事值得指出。第一,没有任何一轮被排除——20 轮的训练窗 Sharpe 全都大于 0,分母是完整的 20 轮平均、不是挑剩的。

第二,分母 1.3471 远高于 0.25,第二道防线没启动,除法有效:「WFE 是负的」和「WFE 无法判断」是两回事,前者是算出来的结果,后者是拒绝作答。

第三,那两栏涵盖的期间本来就不同——样本内是 20 个互相重叠的训练窗的平均,样本外是其后那 600 天,所以不是干净的同期对照;WFE 要量的就是这个落差本身。

提示: 这是 END = None 的回测,跑到执行当天为止,本身也是一张快照。引用任何回测或验证数字都要标执行日期。

同一支策略,MCPT 是过的

同一天、同一份码,这支策略的 MCPT p 值是 0.0160(n = 2000),stdout 自己印 significant edge at 95%——照上架关卡的判准是过的。

检验 它问的问题 结果
MCPT在这组固定的部位序列上,这个成绩分不分得出实力与运气?p = 0.0160 → 过
Walk-forward照历史挑参数这件事本身,到下一段还管不管用?WFE = −0.334 → 远低于门槛

两个都没算错,因为它们回答的不是同一个问题。这也是为什么 p 值再漂亮都不能当成样本外验过了(见〈MCPT p 值怎么看〉)。顺带说清楚:这不是在说这支策略坏掉了——它是 workspace 附的教学范例,这里用它示范的是两个检验的语意差异。

挑参数用邻域平均,回报的却是那一格自己的 Sharpe

每一轮选参数用的是 3×3 邻域平均(跟参数扫描同一条规则,也就是〈如何避免策略过拟合〉讲的挑高原不挑峰值),但轮表上回报的训练 Sharpe 是选中那一格自己的数字。这是刻意的:

分母用什么后果
该格自己的 Sharpe(现行)分母偏高,WFE 偏保守
3×3 邻域平均邻域平均在结构上必然低于被它包住的那一格 → 分母被压小 → WFE 灌水,整个分页读起来过度乐观

分母灌水的诊断不如不做。

换参数的接缝不会先平仓

两轮交界的地方,序列带的是新那一轮自己的部位——是被剪开,不是归零。强制每个接缝平仓等于凭空捏造 n−1 笔没人交易过的出场。

换参数的成本不会被漏掉:接缝处权重改变,定价时照收手续费。也因为每一轮的数字都是从同一条拼好的序列切出来的、不重新定价,轮表加总就等于整段总数。

只有训练窗扣预热期,测试窗不扣

信号在全历史上算(rolling 指标才准),但每个训练片段各自跳过自己开头的预热根数——不是只有第一轮。没有这一步,第一轮会在指标还没热的 K 线上训练、挑到噪声,各轮的有效长度也不一样、不可比。上面那张轮表就是实证:策略的 START 写的是 2022-01-01,但轮 1 的训练窗从 2022-01-05 才开始——差的那几天正是 WARMUP = 100 根小时线(约 4.2 天)被扣掉。

测试窗不裁切,因为它已经热了,而且必须连续才能拼接。顺带回答一个进阶读者常问的问题:这里不需要 embargo 隔离期——测试窗就紧贴在挑它参数的训练窗后面,它之后的数据完全没被用到。

提示: 图上为什么只有一条线?界面自己写得很清楚:「样本内没有曲线:各轮训练窗互相重叠,拼不出一条连续的线」。

它不会给你参数,这是硬合约

跑完之后没有「套用建议参数」这个按钮,agent 也不准因为这个结果去改你的策略码。空状态就把话讲在前面了:

不会产生可采用的参数 —— 验的是「用历史挑参数」这个做法本身。

为什么不能抄?看那次执行的参数漂移。20 轮只选出 4 组不同参数:

(45,100) ×14 → (55,80) → (45,100) → (45,80) ×3 → (35,80)

前 14 轮都停在同一格。就算「大部分时候都选 45/100」看起来像个结论,那条 −0.45 的样本外曲线也不是 45/100 的成绩——它是 4 组参数接力跑出来的一整段,不属于其中任何一组,所以没有一组可以被「采用」。你要求套用时,agent 会回固定的一句:

样本外验证不产生可采用的参数——它的成绩来自每轮重选,不属于任何一组;要换参数请走参数扫描的稳健点。

参数漂移网格会画出每一轮选中哪一格、被选中几轮,虚线框标出你目前的参数,图例也明写「最后一轮选中的参数只是参考,不是建议值」——那次执行最后一轮选的是 35/80,只代表「最后那一轮的训练窗刚好挑到这一格」。另外,这个分页不显示 p 值、也永不跑 MCPT;跑一次 walk-forward 在 agent 的工作守则里算一次迭代

打开这个分页,先看这五件事

1

轮数够不够? 少于 3 轮根本不会跑。轮数太少时,WFE 只是一两个测试窗的运气。

2

有没有轮被排除? 每轮明细会注明哪几轮训练窗 Sharpe ≤ 0。上面那次一轮都没被排除;被排除的轮越多,代表这支策略连在自己的训练窗里都常常挑不到东西。

3

WFE 是「无法判断」还是一个负数? 不一样。「无法判断」代表样本内平均低于 0.25、这个比值没有意义;负数代表除法做了、样本外那段是亏的。

4

参数漂移得多凶? 看「{n} 轮选出 {u} 组不同参数」那一行。上面那次是 20 轮 4 组;每轮都跳到完全不同的格子,等于每次重选都在追不同的东西。

5

你是不是在找建议参数? 没有。要换参数走参数扫描的稳健点,不是抄最后一轮。

诚实提醒: 两件事要讲白。第一,这不是上架关卡。策略库那三道品质关卡是诚实回测的手续费、统计显著(MCPT)、参数稳健,walk-forward 不在里面——它是你的自助工具。第二,0.5 和 0.7 是惯例,不是证明。产品自己的说明就是这样写的,本文不会讲得比界面更武断:WFE 高只代表「用历史挑参数这件事,在这段数据上没有明显吐回去」,不代表未来会复制同一个结果;反过来,一次 WFE 为负,也只是那一组窗、那一段数据下的一个结果。

接下来

跑过一次之后,如果 WFE 掉得很难看,问题通常不在样本外这一步,而在挑参数那一步——那是〈如何避免策略过拟合〉的范围。如果你手上还没有值得验的策略,策略库里的官方策略每一支都附真实回测与品质关卡结果,可以先看数字再决定:策略库