多重检验偏差与 Deflated Sharpe
直觉
你回测了 200 个策略,挑出夏普最高的那个——它看起来棒极了。但这几乎必然是假象:就算所有策略都没真本事,200 次里也总能撞到一个「运气最好」的。这就是多重检验偏差(multiple testing):试的次数越多,最佳成绩越虚高。Bailey 与 López de Prado 提出的 Deflated Sharpe Ratio(DSR,缩水夏普) 就是把「试了多少次」和「样本多短」一起算进去,回答「这个夏普是真本事,还是 N 选 1 的运气」。
Deflated Sharpe 公式
先算「在零假设(无任何技能)下, 次试验能期望到的最高夏普」:
其中 为欧拉常数, 是夏普估计的标准误(与样本长度 、偏度、峰度有关)。再把观测到的夏普与 比较:
DSR 可理解为「这个夏普不是运气的概率」——通常要 才敢相信。
「人话」解释:为什么「试得越多,最优越虚」?
抛 200 枚硬币,总有一枚连续 10 次正面——你不会说那枚硬币「有本事」。回测同理:200 个策略里的「冠军」,很可能只是噪声里最幸运的一个。 就是「纯靠运气,200 选 1 能选到的最高夏普」;如果冠军夏普只比 高一点点,DSR 就很低,意思是「这点优势,运气也解释得通」。 对策:少试、先验地限定策略族、预留独立的、从不参与挑选的最终测试集。
可运行案例:200 个随机策略的「冠军」有多假?
模拟 200 个纯随机的多空信号回测,挑出夏普冠军,再用 DSR 判定它是不是运气。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm
df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
ret = df['adj_close'].pct_change().dropna()
T = len(ret); N = 200 # 试了 200 个策略
rng = np.random.default_rng(7)
sharpes = []; best = -9; winner = None
for _ in range(N):
sig = pd.Series(rng.choice([-1, 0, 1], size=T), index=ret.index
).replace(0, np.nan).ffill().fillna(0).shift(1).fillna(0) # 随机信号 + 防前视
pr = sig * ret - np.abs(sig.diff().fillna(0)) * 2e-4 # 轻成本
sr = pr.mean() / pr.std() * np.sqrt(252)
sharpes.append(sr)
if sr > best: best = sr; winner = pr
sharpes = np.array(sharpes); sr = sharpes.max()
print(f"{N} 个随机策略: 冠军夏普={sr:.2f} (全体均值={sharpes.mean():.2f}, std={sharpes.std():.2f})")
sk, ku = winner.skew(), winner.kurtosis() # pandas kurtosis 即超额峰度
gamma = 0.5772156649
z1 = norm.ppf(1 - 1/N); z2 = norm.ppf(1 - 1/(N*np.e))
sr_std = np.sqrt((1 - sk*sr + (ku-1)/4*sr**2) / (T-1))
SR0 = sr_std * ((1-gamma)*z1 + gamma*z2)
DSR = norm.cdf((sr - SR0) / sr_std)
print(f"零假设下 {N} 选 1 的期望最高夏普 SR0={SR0:.2f}")
print(f"冠军 Deflated Sharpe = {DSR:.2f} (>0.95 才算'非运气')")
print("→ 名义夏普漂亮, DSR 却很低: 只是 200 选 1 的运气, 不可投产。")
plt.figure(figsize=(9, 3.4))
plt.hist(sharpes, bins=30, color='lightgray', edgecolor='k')
plt.axvline(sr, color='crimson', lw=2, label=f'冠军 SR={sr:.2f}')
plt.axvline(SR0, color='navy', ls='--', lw=1.6, label=f'SR0={SR0:.2f}(运气上限)')
plt.title(f'{N} 个随机策略的夏普分布'); plt.xlabel('年化夏普'); plt.legend(fontsize=9)
plt.tight_layout(); plt.show()
小结
- 试的策略/参数越多,最佳夏普越虚高——多重检验偏差;
- Deflated Sharpe 把试验次数 、样本长度 、偏度峰度一起算进去,给出「非运气」的概率;
- 实务对策:少试、预限定策略族、留独立测试集——把「冠军」放到一个从未参与挑选的样本上再验一次。
小测验
1. 试的策略/参数越多,最佳夏普通常会?
2. Deflated Sharpe 额外考虑了哪些因素?
3. 某策略名义夏普很高、但 DSR 很低,说明?