回测赚的是实力还是运气:MCPT p 值怎么看

把报酬打乱几千次之后,你的成绩排在哪里——以及这个数字不回答什么。

最后更新 2026-09
要点总结
  • MCPT 是全样本置换检验:把回测期间的报酬序列随机打乱(默认 2000 次),看有多少次的 Sharpe 比你的实际成绩还好。没有任何样本内/样本外切分。
  • 打乱的是报酬序列,你的进出场位置固定不动——所以每一次置换的手续费与部位缩放都跟本尊完全一样,比的只有「你挑中的时间点好不好」。
  • 每一支 Type A 策略回测都会自动跑,种子固定 42,同一份回测永远得到同一个 p 值,不用下任何指令。
  • 上架关卡要两个条件同时成立:p < 0.05 且置换次数 ≥ 1000。数据太长时置换次数会被自动砍到 1000 以下,p 值再漂亮也过不了,而且画面上没有任何提示。
  • 过了也不代表什么。同一支教学范例策略在 2026-09-17 那次执行里,MCPT p = 0.016 过关,但它的样本外效率是 −0.334。p 值回答的是「分不分得出实力与运气」,不是「这支策略会赚」。

回测跑完,Sharpe 看起来不错。但你心里那个问题没被回答:这是策略真的抓到了什么,还是你刚好在一段上涨行情里随便切了几刀?一个能回答这题的方法是把数据打乱几千次,看你的成绩在那堆随机数里排第几——这就是工作页回测分页那一列「MCPT p 值」在做的事。这篇讲它在检验什么、怎么读、以及它过关之后仍然没回答的那个问题。

什么是 MCPT?

MCPT(Monte Carlo Permutation Test,蒙特卡洛排列检验)是一个统计检验:它把回测期间的报酬序列随机重排很多次,每次都用同一组进出场位置重新算一次 Sharpe,得到一整条「纯靠运气会长什么样」的分布,再看你的实际成绩落在这条分布的哪个位置。

项目内容
虚无假设这支策略选中的那些报酬期,不比随机选的好
p 值定义打乱后的 Sharpe 大于或等于实际 Sharpe 的比例
判读p < 0.05 → 95% 信心水准下有统计显著的优势
检验范围整段回测数据,没有训练/测试切分

p 值越小,代表「随便打乱也能打赢你」的情况越少见。以默认的 2000 次为例,p = 0.05 表示这 2000 次里有 100 次的随机数成绩不输你;p = 0.005 表示只有 10 次。

它打乱的是报酬,不是你的进出场

这是整个设计里最关键、也最容易误解的一点:被打乱的是前向报酬序列,你的部位序列全程固定

每一次置换做的事:

position(你的部位) ← 固定不动,循环外只算一次
× vol_scalar(部位缩放) ← 固定不动,循环外只算一次
− fee_cost(手续费) ← 固定不动,循环外只算一次
× 打乱后的报酬序列 ← 只有这一项每次重抽
= 这一次置换的 Sharpe

因为手续费与部位缩放都在循环外算完,每一次置换承受的交易成本与杠杆都一样。剩下唯一的变数就是「报酬出现的顺序」——也就是你挑中的那些时间点,比随机挑的时间点好吗

为什么不干脆打乱部位?

因为那是一道送分题。打乱一个二元的部位阵列,会造出远多于策略本身的进出场切换;在 fee = 0.0005 之下,每一次置换都要承受大约 30 到 40 倍的手续费拖累,所有置换后的 Sharpe 都会被逼成深度负值。不管你的策略有没有真优势,p 值都会漂亮得没有意义。

提示: 打乱部位=在测「一个疯狂进出场的策略赢不赢得了你」;打乱报酬=在测「你挑中的那些时间点,比随机挑的好吗」。后者才是问题本身。

这个 p 值是怎么跑出来的

它是自动的,不是选配。每一支 Type A 策略跑回测时都会跑一次,结果直接写进回测统计、显示在工作页回测分页。

设定默认说明
置换次数2000可在策略里用 MCPT_N 复写,但仍受下面那条预算公式限制
随机数种子42私有随机数生成器,不动全局种子——同一份回测永远得到同一个 p 值
手续费策略自己的 FEE回测用什么就传什么
关掉MCPT = False整个跳过,不写任何 MCPT 字段

两个值得记住的行为:扫参数时不跑 MCPT(一次扫描是几十到几百次回测,加上它会慢到不能用),所以你看到的 p 值永远来自「采用参数之后的那一次回测」;而策略上线之后,每一次 live 或排程触发都会把同一组 MCPT 字段原样带着走——同一份码、同一组参数,p 值仍然成立,所以上线中的策略会一直看得到它。

p 值多少算过关?判定不在你的机器上

工作页的回测分页不判过不过,它只显示数字,加一句说明。p < 0.05 时写的是:

随机打乱 {n} 次,只有 {pct} 的结果比它好;低于 0.05 是惯用门槛。

没过的时候写的是:

随机打乱 {n} 次,有 {pct} 的结果比它好——没过 0.05 的惯用门槛,这组结果分不出是实力还是运气。

真正的「过不过」出现在策略库的三道品质关卡。关卡 2 叫「统计显著」,说明写「MCPT 随机置换检验 p < 0.05」,而实际判定要两个条件同时成立

条件门槛
p 值< 0.05
置换次数≥ 1000
组合策略(Type C)显示「MCPT 不适用(组合策略)」,不算失败

三道关卡全部通过或不适用,策略才会挂上「已验证」徽章。分工讲白一点:你的机器只负责算出 p 值与置换次数,「这样算不算过」是上架端的判断。

一支真实策略的 MCPT:2026-09-17 那次执行

拿 workspace 附的教学范例 btc_sma_cross 跑一次(BTCUSDT 1h,2022-01-01 起,SMA_FAST = 45SMA_SLOW = 100,参数一字未改,41,287 根 K 线)。以下所有数字都来自 2026-09-17 那一次执行。

回测
总报酬+123.95%
Benchmark(买入持有)+64.36%
最大回撤−44.93%
Sharpe Ratio0.6751
交易笔数478
已付手续费(占本金)23.90%
MCPT
p 值0.0160
置换次数2000
直方图红线「实际 Sharpe」0.8985
分布范围−0.8565 ~ 1.4218

回测 stdout 自己印的那一行是:

MCPT p-value: 0.0160  (n=2000, significant edge at 95%)

怎么读:p = 0.0160 代表 2000 次随机打乱里,有 32 次的 Sharpe 不输给它——换句话说,这组进出场时点在这段历史上不太像是随便挑的。置换次数 2000 也高于 1000 的关卡要求,所以这一项是过的。

提示: 这是 END = None 的回测,跑到执行当天为止,所以它本身也是一张快照。同一支策略在〈回测是什么?回测报告怎么看〉里用的是另一次执行,总报酬、Sharpe、benchmark 三个数字都跟这里不一样。引用任何回测数字都要标执行日期,包括你自己拿去跟朋友讲的那组。

同一支策略,MCPT 过了、样本外没过

这是本文最重要的一段。上面那支策略在同一天、同一份码之下,另外跑了一次 walk-forward 样本外验证:

检验 它问的问题 2026-09-17 那次执行 结果
MCPT在这组固定的部位序列上,这个成绩分不分得出实力与运气?p = 0.0160(n = 2000)
Walk-forward照历史挑参数这件事本身,到下一段还管不管用?样本外效率 −0.334(样本外 Sharpe −0.45远低于门槛

一个过、一个没过,而且两个都没算错——因为它们回答的根本不是同一个问题。MCPT 检验的是「这一组已经决定好的进出场,放在这段历史上不像乱猜」;walk-forward 检验的是「用历史挑参数这个动作,换到没看过的下一段还成不成立」。前者过关不蕴含后者。

这不是在说这支策略坏掉了——它是 workspace 附的教学范例,重点在两个检验的语意差异。完整的 walk-forward 数字与怎么读,见〈样本外验证怎么做〉。

图上那行「Actual OOS Sharpe」是标错的

如果你让 agent 把 MCPT 的直方图画出来传进对话,那张图的图例上会印着一行字:

Actual OOS Sharpe = ⟨你的数字⟩

OOS 是 out-of-sample(样本外)的缩写。那行字是旧注解的残留标签,不要照着理解。 MCPT 从头到尾跑在整段回测数据上,没有任何训练窗、测试窗、或比例切分——它不可能是样本外的东西,上面那张对照表就是实例。

工作页回测分页里的那张图没有这个问题,红线标的是「实际 Sharpe」。另外,对话里那张图是手动重跑产生的,手动重跑用的参数跟自动那次不一定一样,所以图上的数字也不必然等于分页上那一列。

坑一:置换次数本身也是门槛,而且会被自动砍

自动 MCPT 实际跑几次,不完全由你决定。它有一条执行预算公式:

实跑次数 = min( MCPT_N, 20000, max( 200, 4e8 ÷ K 线数 ) )

K 线越多,4e8 ÷ K 线数 越小。上面那次执行是 41,287 根 K 线,离天花板还很远,所以 2000 次一次都没被砍。但官方文件记载的对照是:4 万根 → 2000 次;20 万根(5 分线两年)→ 2000 次;100 万根(1 分线两年)→ 400 次

问题在于上架关卡卡的是置换次数 ≥ 1000。照公式推算,K 线数超过约四十万根时实跑次数就会低于 1000——就算 p = 0.001,关卡照样判不通过。而画面上完全没有提示:被砍的消息只写进主机的 log,工作页的说明文字照样带入被砍过的次数,没有任何警示样式。

提示: 四十万根这个临界点是从公式(4e8 ÷ K 线数 < 1000)推出来的算术,本文那次执行没有触发、未经实证;「1 分线两年 → 400 次」则是官方文件记载的对照。另外,把 MCPT_N 调大救不了这件事——MCPT_N 只是把上限抬高,真正压住次数的是 max(200, 4e8 ÷ K 线数) 那一项。K 线太多时只有两条路:缩短回测区间,或换更长的 K 线周期。

坑二:红线那个 Sharpe 跟卡片上那个不是同一个数

看回上面那两张表:同一次回测,Sharpe Ratio 卡写 0.6751,直方图红线的「实际 Sharpe」写 0.8985,差了 0.22,肉眼就看得出来。这不是 bug,是两条不同的算法:

项目 MCPT 内部的 Sharpe(0.8985 回测分页的 Sharpe Ratio(0.6751
报酬怎么算简单报酬,不分开盘/收盘段隔夜段与盘中段分开计价
部位缩放一定会套:30% 目标波动、2 倍杠杆上限策略自己写了才有

关键在第二列:自动 MCPT 不会把策略自己的目标波动设定传进去,它一律用函式的默认值。btc_sma_cross 没有做波动率目标化,但它的 MCPT Sharpe 仍然是「套了 30% 目标波动、2 倍上限之后」的数字(目标波动在做什么,见〈波动率目标化怎么运作〉)。两个数字不一样是正常的,界面目前没有说明这件事。

回测分页没有 MCPT 那一列?

失败时它是安静的:没有任何错误消息,回测照常成功,前端就是少那一列。原因至少有四种,而画面不区分:

原因发生了什么
策略写了 MCPT = False直接跳过
这次回测 0 笔交易没有部位可以检验,跳过
主机上的 lib 是旧版加载失败,跳过
数据太短、Sharpe 算出 NaN、或任何例外跳过,回测统计照写

宁可不写也不写错的理由很实际:任何 NaN 或 inf 都会让整台机器的策略上传被打回,所以只要数字不干净,这几个字段就整组不写。

组合策略没有 MCPT,也没有替代品

Type C(组合策略,N 档标的配一组权重)的回测不跑 MCPT。原因是这个检验的结构是「一条价格序列对一条部位序列」,组合策略没有这个结构。值得诚实讲的是:没有替代检验

想做的事实际情况
把整个组合压成一条报酬序列再重抽那是对已实现报酬做 bootstrap,答的是另一个问题,而且产生不出 p 值——p 值正是 MCPT 存在的全部意义。这条路是明文禁止的,agent 也不准自己手刻一个东西顶替
期待关卡卡片显示什么「MCPT 不适用(组合策略)」,不是「未通过」——它不算失败,不影响「已验证」徽章

拿到 p 值之后,先检查这五件事

1

置换次数是多少? 说明文字里的那个次数低于 1000,这个 p 值就过不了上架关卡,不管它多小。先看次数,再看 p 值。

2

这是哪一次回测的 p 值? 扫参数的过程不跑 MCPT。你手上的 p 值只属于「采用参数之后跑的那一次回测」;中间改过码就要重跑才算数。

3

红线跟卡片对不起来? 正常。上面那次执行是 0.8985 对 0.6751,算法不同而已,不用去 debug。

4

你是不是把「显著」读成「会赚」了? 同一支策略可以 p = 0.016 过关、样本外效率 −0.334 惨败。p 值对未来没有任何陈述。

5

那一列根本不见? 先确认是四种原因里的哪一种(策略关掉了/0 笔交易/lib 旧版/数据不够),不要当成「检验没过」。

诚实提醒: MCPT 检验的是这一组固定的进出场位置放在这段历史上好不好。它不知道这组参数是从几百组候选里挑出来的——挑选本身带来的乐观偏误,它量不到。这就是为什么挑参数要挑高原而不是峰值(见〈如何避免策略过拟合〉),也是为什么本文那支策略 p 值过了、样本外还是掉下去。工作页自己的说明也只写「0.05 是惯用门槛」——它是惯例,不是保证。

接下来

p 值看懂了,下一个问题就是上面那张对照表的右半边:换一段没看过的数据还成不成立。那条路是样本外验证,工作页有独立的分页在做。策略库里挂「已验证」徽章的官方策略,关卡 2 过的就是本文讲的这个检验——每一支都附真实回测,数字可以直接看:策略库