先说结论:任何回测收益都需要回答一个问题——“如果我的信号和市场收益之间毫无关系,纯靠运气能做出多好的成绩?“蒙特卡洛置换检验(Monte Carlo Permutation Test, MCPT)用最直接的方式回答它:把持仓序列随机打乱几千次,每次都重算一遍收益,看真实成绩在这堆”运气成绩”里排第几名。在本文的模拟实验里,一个带真实信息优势的策略年化收益 35.5%,在 2000 次置换中只有不到 1 次被运气超过(p ≈ 0.0005);而一个纯噪声策略的成绩落在置换分布的第 85 百分位之外——什么都证明不了。这个检验不需要假设收益服从正态分布,不需要推导渐近方差,只需要一台能跑循环的电脑。
为什么 t 检验在这里不够用#
标准做法是对策略日收益算 t 统计量:均值除以标准误,查正态表得 p 值。这条路在金融数据上有三个坑:
- 肥尾。日收益的峰度普遍在 5 以上,极端值让样本均值的分布远离正态,t 检验的名义显著性会失真——名义 5% 的检验实际可能在 8%~10% 拒绝。
- 序列相关。波动聚集让”有效样本量”远小于名义样本量,标准误被低估,t 值被高估。
- 小样本。三五年的日频回测只有一千出头个观测,渐近理论的”大样本”前提本来就勉强。
置换检验绕开全部三个问题:它不对收益分布做任何参数假设,零假设分布是从你自己的数据里直接构造出来的。这也是它和自助法 White Reality Check 的核心区别——bootstrap 从数据里有放回抽样来近似抽样分布,置换检验则是无放回地重排标签来构造严格意义上的零假设分布。
检验的机械结构#
零假设 H0:持仓信号与未来收益独立——策略没有任何择时能力,收益纯粹来自运气搭上了市场的顺风车。
在 H0 下,持仓序列 和市场收益序列 的配对方式是任意的:把 打乱重排后配给 ,得到的”策略”与原策略在统计上完全等价。于是流程是:
- 计算真实策略的检验统计量 (本文用年化收益,也可以用夏普率);
- 随机打乱持仓序列,保持市场收益不动,重算统计量,重复 N 次得到 ;
- 经验 p 值 = 。
分子分母各加 1 是 Davison-Hinkley 修正,保证 p 值永远不会等于 0——你最多只能说”2000 次置换里没有一次超过”,不能说”概率为零”。
import numpy as np
def perm_test(positions, returns, n_perm=2000, seed=7):
"""positions: 每日持仓方向(+1/-1/0);returns: 当日市场收益"""
r = np.asarray(returns)
obs = (positions * r).mean() * 252 # 实际年化收益
g = np.random.default_rng(seed)
perm_stats = np.empty(n_perm)
for i in range(n_perm):
shuffled = g.permutation(positions) # 打乱持仓,市场不动
perm_stats[i] = (shuffled * r).mean() * 252
p = (np.sum(perm_stats >= obs) + 1) / (n_perm + 1)
return obs, perm_stats, ppython注意被打乱的是持仓而不是收益。打乱收益会破坏市场本身的自相关和波动聚集结构,让零假设分布失真;打乱持仓则保留了市场收益的全部时序性质,只切断”信号→收益”的对应关系。这是实现里最容易搞反的一步。
实验:真信号 vs 纯噪声#
模拟一个 10 年(2520 个交易日)的市场,日收益取自由度 5 的 t 分布(贴近真实肥尾),然后构造两个策略:
- 策略 A:信号里掺入了真实信息(信号与当日收益相关系数约 0.12),按信号方向持仓;
- 策略 B:信号是纯高斯噪声,同样按方向持仓。
各跑 2000 次置换:

策略 A 的年化收益 35.5%,而 2000 个”打乱后的它自己”的收益分布集中在 ±15% 以内。实际值几乎落在分布之外,p = 0.0005——运气做不出这个成绩。

策略 B 年化 -7.3%,p = 0.845。它的成绩和随机重排后的自己毫无差别——这正是”没有任何信息含量”该有的样子。
样本长度决定检验的牙齿#
同一个有信息的策略,只用前 1 年数据检验时 p = 0.098——过不了 5% 的显著性线。逐步拉长样本:

| 样本长度 | 置换 p 值 |
|---|---|
| 1 年 | 0.098 |
| 2 年 | 0.054 |
| 3 年 | 0.008 |
| 5 年 | 0.002 |
| 10 年 | 0.001 |
一个真实存在的、年化 35% 量级的信号,用一年数据都无法在统计上和运气区分开。这解释了为什么短回测毫无说服力:不是策略不行,是检验没有功效。反过来,这也是最小回测长度 MinTRL 想解决的同一个问题的置换版视角。
多重检验:挑最优再检验是作弊#
置换检验有一个致命的使用禁区:先在一堆策略里挑出最好的,再对这一个做检验。模拟 300 轮实验,每轮生成 50 个纯噪声策略并挑出年化收益最高的:

50 个纯噪声策略里的”冠军”,中位数年化收益 15.8%,95 分位达到 22.1%。如果你对这个冠军单独跑置换检验,它多半”显著”——但这显著性是选择偏差制造的。检验统计量必须把选择过程包含进去:正确做法是在每次置换中也重演”从 50 个里挑最大”这一步,比较”真实冠军”与”置换冠军”的分布,这正是 White Reality Check 与 Hansen SPA 的构造逻辑。单策略置换检验只适用于事前就只有这一个策略的场景。
实现细节与变体#
打乱方式的选择。 完全随机重排隐含假设持仓序列本身无自相关的信息价值。如果策略持仓有很强的持续性(比如趋势跟踪一拿几个月),完全打乱会破坏持仓的块结构,让零分布的方差偏小、p 值偏乐观。解法是块置换(block permutation):把持仓切成若干天的块整块打乱,块长取持仓平均持续期的量级。
统计量的选择。 年化收益对肥尾敏感,夏普率对波动率变化敏感。稳妥做法是主检验用夏普率,敏感性分析换成总收益和最大回撤各跑一遍——三个统计量给出一致结论才算稳。
置换次数。 N=1000 时 p 值的分辨率是 0.001,蒙特卡洛标准误约 。要可靠地区分 p=0.04 和 p=0.06,N=2000 起步;只是粗筛的话 500 次够用。
入场型策略的处理。 如果策略不是每天持仓而是”信号触发才进场”,被打乱的对象应该是入场时点标记,持有期结构保持不变——否则置换后的假策略持仓天数和真策略不一致,比较失去意义。
它不能告诉你什么#
置换检验回答的是”信号与收益的配对是否好于随机”,仅此而已。它不能回答:
- 未来是否继续有效——p 值再小也只是对历史样本的陈述,样本外验证(如 Walk-Forward)是另一道独立的关卡;
- 收益能否覆盖成本——检验用的是无成本毛收益,一个 p=0.001 但单边换手极高的信号,扣完成本可能归零;
- 是 alpha 还是风险暴露——策略可能显著地承载了某种风险溢价(比如做空波动率),置换检验分不清”有信息”和”有暴露”,那是因子归因的活。
把它放在正确的位置:它是回测流水线里的第一道统计过滤器——通不过它的策略直接扔掉,通过它的策略再进入样本外验证、成本压力测试和归因分析。它把”这个回测值不值得继续花时间”变成了一个十行代码就能回答的问题,这就是它全部的价值,也已经足够大。