你花了一个周末,把一个想法写成回测。年化收益 18%,波动 12%,夏普比率 1.5。你差点就要把房子押上去了。
先深呼吸。在签字之前,请记住一件事:夏普比率不是一个常数,它是一个被噪声严重污染的随机变量。你看到的 1.5,可能只是运气——一个均值为 0 的策略,在 60 个月样本里随机抽取,有可观的概率”看起来”夏普 0.4 以上。
这篇文要回答三个递进的问题:
- 夏普比率的估计误差到底有多大?
- 怎么在”有误差”的前提下,科学地问”我的真实夏普是否超过某个基准”?(概率夏普比率 PSR)
- 当你声称”我从 100 个策略里挑了最好的”,这个挑选动作本身又是多大的偏差?(Deflated Sharpe Ratio, DSR)
所有数字与图表均由文末 Python 真实计算生成,可复现。
一、夏普比率的抽样误差:它不是一个点,而是一个分布#
假设真实夏普比率 (也就是说,这个策略本质上和掷硬币没区别)。我们抽 60 个月的月度收益(约 5 年),算一遍 ,重复 4 万次,看看 长什么样。

结果很扎心:
- 4 万次实验的平均 ,确实围绕真值 0;
- 但标准差高达 0.1315,和闭式公式 几乎完全一致;
- 也就是说,真 SR=0 的策略,有大约 0.16% 的概率在 60 个月里”看起来”夏普 > 0.4,有 0.01% 的概率”看起来” > 0.5。
这是个反直觉但极其重要的结论:评价一个策略需要多少样本,取决于你想确认多小的夏普。5 年月度数据把误差控制到了 ±0.13 左右——这还只是 SR 接近 0 时。如果你的策略夏普只有 0.3,这个误差足以让它”看起来”是 0 或 0.6,纯看运气。
二、概率夏普比率 PSR:在误差之上问”是否超过基准”#
既然 有误差,正确的问法不是”我的夏普是多少”,而是”我的真实夏普超过某个基准 的概率有多大”。Marcos López de Prado 给出的概率夏普比率(Probabilistic Sharpe Ratio)正是这个:
其中 是收益偏度、 是收益峰度(注意不是超额峰度), 是标准正态 CDF。分子把估计值标准化,分母则修正了收益分布的非正态——这正是 PSR 比”朴素 t 检验”高明的地方。
为什么分母要修正偏度和峰度?因为金融收益有两个让正态假设失灵的特征:崩盘带来的负偏(左尾长) 和 肥尾(峰度高)。一个表面夏普不错、但全靠”平时小赚、偶尔巨亏”的策略,它的真实不确定性比正态假设下大得多。
下面这张图对比同一组估计(,60 个月)在三种处理下的 PSR 曲线:

我们用一个典型股票策略的收益样本做演示:90% 月份温和、10% 月份崩盘,得到 ,但偏度 、峰度 (左尾明显)。结果:
- 在基准 处,正态近似 PSR = 0.9999,偏度/峰度修正后降到 0.9988;
- 在基准 处,差距拉开:正态近似仍乐观地给 0.9999,修正后只剩 0.896——整整 10 个百分点被”歪尾巴”吃掉。
直觉:朴素方法默认你的收益是正态的,于是低估了极端亏损带来的不确定性;PSR 把偏度峰度显式算进去,给你一个更诚实、通常更低的概率。
三、多选偏差:从一堆噪声里挑最好的,它”看起来”必然显著#
现在进入最隐蔽的坑。假设你不是测了 1 个策略,而是测了 N 个,挑了最好的那个来汇报。即使这 N 个策略的真实夏普全是 0,被选出来的那个,它的 也一定比单个的更高——因为你挑了最大值。
把”标准化统计量” 作为比较尺度。每个真 SR=0 的策略,其 Z 近似服从标准正态。我们取 N 个里的最大 Z,重复 2500 次,看这个”选出者”的平均 Z 随 N 怎么长:

| 测试策略数 N | 1 | 10 | 50 | 100 | 500 |
|---|---|---|---|---|---|
| 选出策略的平均 Z | -0.01 | 1.58 | 2.31 | 2.60 | 3.18 |
哪怕全部是噪声,从 100 个里挑出的最佳策略,平均 Z 也到了 2.6——如果有人天真地拿它当单个策略做检验,会得到 PSR ≈ 99.5% 的”显著”结论。而 500 个里挑,Z 冲到 3.18,看起来简直是圣杯。
这就是数据窥探(data snooping)/ 多选偏差:你汇报的”最佳”,其显著性有一大半来自”我选了它”这个动作,而不是它真的更好。
四、Deflated Sharpe Ratio:把选法偏差一次性扣掉#
PSR 回答的是”单策略是否超过基准”。要扣除”我挑了 N 个里最好的”这个偏差,需要 Deflated Sharpe Ratio:
其中 是 N 个策略标准化统计量的最大值,、 是”N 个独立标准正态最大值”的矩(我们在文中用蒙特卡洛估计)。 就是你选中策略自己的标准化统计量。
一句话理解:DSR 拿你选中策略的 Z,去和”N 个纯噪声里最响那一声的期望”比较。如果你的 Z 只跟噪声里的最佳一样响,DSR ≈ 50%——也就是”它只是噪声里最响的那声”,硬币正反面而已。
我们用一个固定候选策略演示:60 个月、,其 。若只测了这 1 个,朴素 PSR = 0.9986,DSR 也 ≈ 0.9985(一致)。但如果你其实从 N 个里挑出来的:

| 你声称测过 N 个 | 1 | 10 | 50 | 100 | 200 | 500 |
|---|---|---|---|---|---|---|
| 朴素 PSR(忽略选法) | 0.9986 | 0.9986 | 0.9986 | 0.9986 | 0.9986 | 0.9986 |
| DSR(扣除选法偏差) | 0.9985 | 0.9935 | 0.9443 | 0.8691 | 0.7209 | 0.4371 |
故事一目了然:朴素 PSR 对”你搜了多少”视而不见,死守 99.86%;而 DSR 随测试数量暴涨而崩塌——当你从 500 个策略里挑时,这个看似 0.4 夏普的”赢家”,真实超过基准的概率只剩 44%,比掷硬币强不了多少。
这正是对”我测了 1000 个因子,这个最显著”最锋利的反击:显著性是真实的,还是被搜索量买来的?
五、实盘怎么用:一份自查清单#
把上面的工具落成工作流,而不是只当学术名词:
- 先看样本长度:夏普估计误差约 。月度 60 期误差 0.13, weekly 250 期误差 0.063。样本越短,越要把结论打折。
- 永远报 PSR 而非裸夏普:给定你的基准 (比如无风险或 0.5),算出 PSR。别忘了把收益偏度/峰度喂进分母——崩盘型策略的 PSR 会被大幅下修。
- 如实申报测试次数 N:你试过的参数组合、因子、标的,统统算进 N。DSR 对 N 极其敏感,瞒报 N 等于自欺。
- DSR 低于 0.95 的策略,当作未证实:这不是保守,是数学。N 越大,门槛越该高。
- 样本外 + Walk-forward 交叉验证才是最终裁判:PSR/DSR 修正的是”已见数据”的偏差,没法替你防过拟合本身。
值得强调的诚实边界:PSR/DSR 的前提是收益近似独立同分布、且你申报的 N 是真实的。它们不解决”前视偏差”(用未来信息)、“幸存者偏差”(只留活下来的股票)、“交易成本吞噬”——这些坑,DSR 救不了,得靠数据纪律。
附:可复现 Python#
下面是不依赖任何量化库、纯 numpy/scipy 的核心实现(文中数字即由此算得):
import numpy as np
from scipy import stats
def skew_kurt(r):
r = np.asarray(r, float); m = r - r.mean()
m2, m3, m4 = (m**2).mean(), (m**3).mean(), (m**4).mean()
return m3 / m2**1.5, m4 / m2**2 # 偏度, 峰度(非超额)
def psr(sr_hat, T, sr_star, skew, kurt):
denom = np.sqrt(max(1.0 - skew*sr_hat + (kurt-1)/4.0*sr_hat**2, 1e-12))
z = (sr_hat - sr_star) * np.sqrt(T) / denom
return stats.norm.cdf(z), z # 返回 (PSR, 标准化统计量)
def dsr_of(sr_hat, T, sr_star, skew, kurt, N, reps=20000, seed=20260712):
z_sel, _ = psr(sr_hat, T, sr_star, skew, kurt) # 候选策略的标准化统计量
z_sel = stats.norm.ppf(z_sel)
rng = np.random.default_rng(seed)
E_V = rng.standard_normal((reps, N)).max(axis=1).mean() # N 个噪声最大值的期望
V_V = rng.standard_normal((reps, N)).max(axis=1).var()
return stats.norm.cdf((z_sel - E_V) / np.sqrt(V_V))
# ---- 演示 ----
rng = np.random.default_rng(20260712)
r = rng.standard_normal((40000, 60)) # 真 SR=0, 4万次实验
sr_hat = r.mean(1) / r.std(1, ddof=1)
print("SE(真SR=0):", sr_hat.std(ddof=1), "≈ 1/√60 =", 1/np.sqrt(60))
sk, ku = -0.77, 3.95 # 偏度/峰度(崩盘型样本)
p_naive, _ = psr(0.5, 60, 0.3, 0, 3) # 忽略高阶矩
p_corr, _ = psr(0.5, 60, 0.3, sk, ku) # 修正后
print("PSR(0.3) 朴素=%.4f 修正=%.4f" % (p_naive, p_corr))
print("DSR(N=100) =", round(dsr_of(0.4, 60, 0.0, 0, 3, 100), 4))
print("DSR(N=500) =", round(dsr_of(0.4, 60, 0.0, 0, 3, 500), 4))python跑一遍你会看到:朴素 PSR 在 N 面前纹丝不动,而 DSR 随搜索量一路跳水——这正是”好运气”和”真信号”之间,那道数学上清清楚楚的界线。