halo 的技术博客

返回

问题:8 个月的漂亮回测,够不够上实盘?#

假设你刚跑完一个策略:年化夏普率 1.0,回测覆盖 8 个月,权益曲线平滑向上。你盯着这个数字,心里痒——「1.0 不算顶级,但稳,可以小仓位试试?」

停。先回答一个更基础的问题:这个 1.0,你有多大把握它不是运气?

这不是抬杠。夏普率是一个估计量,它有抽样误差。你跑 8 个月得到 1.0,换一段 8 个月可能是 0.3,也可能是 1.7。真实的夏普率(如果这个策略有稳定 edge 的话)藏在这些估计的背后,你看到的只是一次带噪声的抽样。样本越短,噪声越大,你越可能把一段幸运的随机游走误认成 alpha。

问题是:短到什么程度就危险?长到什么程度才算证明?「凭感觉」在这里是致命的——因为人的直觉严重低估了统计显著性对样本长度的胃口。最小回测长度(Minimum Track Record Length, MinTRL)就是把这个「感觉」换成一个能算的数字

MinTRL 的核心:把「证明」翻译成一个不等式#

MinTRL 由 Bailey 和 López de Prado 提出,它建立在概率夏普率(PSR)之上。逻辑链条是这样的:

第一步,夏普率估计量有一个标准误。在 i.i.d. 假设下,考虑了偏度和峰度的修正版标准误是:

σ^(SR^)=1γ3SR^+γ414SR^2n1\hat{\sigma}(\widehat{SR}) = \sqrt{\frac{1 - \gamma_3 \cdot \widehat{SR} + \frac{\gamma_4 - 1}{4}\widehat{SR}^2}{n - 1}}

其中 γ₃ 是偏度、γ₄ 是峰度(正态 γ₄=3),n 是样本量。看分子那三项——这是全部玄机所在,我们下一节详拆。

第二步,PSR 问的是「真实 SR 超过某个基准 SR* 的概率」,就是把估计的超出量除以标准误、再套正态 CDF:

PSR^(SR)=Φ((SR^SR)n11γ3SR^+γ414SR^2)\widehat{PSR}(SR^*) = \Phi\left(\frac{(\widehat{SR} - SR^*)\sqrt{n-1}}{\sqrt{1 - \gamma_3 \widehat{SR} + \frac{\gamma_4-1}{4}\widehat{SR}^2}}\right)

第三步,MinTRL 把问题反过来:要让 PSR 达到目标置信度(比如 95%),n 至少要多大? 解上面的不等式得到:

MinTRL=1+[1γ3SR^+γ414SR^2](ZαSR^SR)2\text{MinTRL} = 1 + \left[1 - \gamma_3 \widehat{SR} + \frac{\gamma_4 - 1}{4}\widehat{SR}^2\right]\left(\frac{Z_\alpha}{\widehat{SR} - SR^*}\right)^2

其中 Z_α 是置信水平对应的正态分位数(95% 时 ≈ 1.645)。用 Python 写出来就是直接套公式:

import numpy as np
from scipy import stats

def mintrl_years(SR_ann, skew, exkurt, SR_star=0.0,
                 alpha=0.95, freq=252):
    """返回证明 SR>SR_star 所需的最小回测长度(年)。
    SR_ann: 年化夏普率; exkurt: 超额峰度(正态=0)。"""
    z = stats.norm.ppf(alpha)
    SR  = SR_ann  / np.sqrt(freq)   # 转成每观测(日)夏普
    SRs = SR_star / np.sqrt(freq)
    # 高阶矩修正项
    correction = 1 - skew * SR + (exkurt / 4.0) * SR**2
    n_obs = 1 + correction * (z / (SR - SRs))**2
    return n_obs / freq              # 天数换算成年
python

拆解公式:为什么低夏普和肥尾都让样本需求爆炸#

MinTRL 公式里有两个关键结构,理解它们就理解了整件事。

第一个是分母的 (SR - SR*)² 这是「信噪比」的核心。你想证明的超出量越小——比如 SR 只有 0.5、或者你的基准 SR* 定得越高——分母越小,MinTRL 越大,而且是平方级爆炸。这解释了为什么低夏普策略需要惊人长的样本:

# 正态假设下,不同夏普率的样本需求
for sr in [2.0, 1.5, 1.0, 0.5]:
    yrs = mintrl_years(sr, skew=0.0, exkurt=0.0)
    print(f"SR={sr}: 需要 {yrs:.1f} 年")

# SR=2.0: 需要 0.7 年
# SR=1.5: 需要 1.2 年
# SR=1.0: 需要 2.7 年
# SR=0.5: 需要 10.8 年   ← 平方爆炸
python

SR 从 1.0 掉到 0.5(减半),样本需求从 2.7 年暴涨到 10.8 年(翻两番)——这就是平方项的威力。这也是为什么真正稀缺的不是高夏普策略,而是有足够长历史支撑的中等夏普策略。

证明 SR>0(95%置信)所需的样本长度:夏普越低、越肥尾,需求越陡峭

第二个是分子的高阶矩修正项 1 - γ₃·SR + (γ₄-1)/4·SR² 正态收益时它退化成接近 1,但真实收益一旦负偏肥尾,它就膨胀:

  • 负偏(γ₃ < 0)-γ₃·SR 变成正的加项 → 修正项变大 → 样本需求增加。直觉上,负偏意味着「平时小赚、偶尔巨亏」,这种收益的夏普估计更不可靠,需要更多样本才能确认。
  • 肥尾(γ₄ > 3)(γ₄-1)/4·SR² 变大 → 样本需求增加。极端值让标准误膨胀,你需要更长历史才能把噪声平均掉。
# 固定 SR=1.0,看高阶矩怎么推高需求
scenarios = [
    ("正态",           0.0,  0.0),
    ("轻度肥尾",      -0.5,  3.0),
    ("强负偏肥尾",    -1.0,  5.0),
]
for name, sk, ku in scenarios:
    print(f"{name}: SR=1.0 需要 {mintrl_years(1.0, sk, ku):.2f} 年")

# 正态:        SR=1.0 需要 2.71 年
# 轻度肥尾:    SR=1.0 需要 2.80 年
# 强负偏肥尾:  SR=1.0 需要 2.89 年
python

高阶矩的代价:固定 SR=1.0,越负偏、越肥尾,同一夏普需要越长样本

对 SR=1.0 来说,肥尾的额外代价「只」是从 2.7 年涨到 2.9 年——看着不大。但注意这个惩罚随 SR² 增长,对高夏普策略(那些最容易过拟合出来的)惩罚更重;而且现实中很多「漂亮回测」恰恰是负偏肥尾的(卖波动率、网格套利、均值回归——都是「平时小赚偶尔爆亏」的形态),它们的真实样本需求比表面看到的更苛刻。

反过来看:给定样本,你的置信度爬到哪了#

MinTRL 回答「需要多长」,PSR 回答「现在够不够」。两者是同一枚硬币的两面。我们固定一个轻度肥尾的收益(偏度-0.5、超峰3),看不同夏普率的策略随样本增长,PSR 怎么爬向 95% 门槛:

def psr(SR_ann, N_days, skew, exkurt, SR_star=0.0, freq=252):
    SR  = SR_ann  / np.sqrt(freq)
    SRs = SR_star / np.sqrt(freq)
    se = np.sqrt((1 - skew*SR + (exkurt/4.0)*SR**2) / (N_days - 1))
    return stats.norm.cdf((SR - SRs) / se)

for sr in [1.5, 1.0, 0.5]:
    p1 = psr(sr, 252, -0.5, 3.0)   # 1年
    p2 = psr(sr, 504, -0.5, 3.0)   # 2年
    print(f"SR={sr}: 1年 PSR={p1:.2f}, 2年 PSR={p2:.2f}")

# SR=1.5: 1年 PSR=0.93, 2年 PSR=0.98
# SR=1.0: 1年 PSR=0.84, 2年 PSR=0.92   ← 一年还不达标
# SR=0.5: 1年 PSR=0.69, 2年 PSR=0.76   ← 两年都够不着
python

同一夏普率,样本越长置信度越高;SR=1.0 要跑到约 2 年才逼近 95% 门槛

这张图应该钉在每个量化研究员的墙上。SR=1.0 的策略跑满一整年,PSR 才 0.84——连 95% 都没到。 你那个「跑了 8 个月、夏普 1.0」的策略,统计上根本没到「证明它不是运气」的门槛。SR=0.5 更惨,跑两年 PSR 才 0.76,实务上意味着这种低夏普策略几乎无法在合理时间内证伪「纯运气」的原假设。

蒙特卡洛验证:公式不是纸上谈兵#

理论公式好看,但它靠一堆渐近近似和 i.i.d. 假设。它在真实(有限、有偏)样本里靠谱吗?我们用蒙特卡洛硬核验证一遍。

设定:真实年化 SR=1.0、收益强负偏(用左偏的 skew-normal 生成),分别抽取 1/2/3/4 年长度的样本,各跑 3000 次。每次都用样本自己估出来的 SR、偏度、峰度算 PSR,统计有多少比例的样本能通过 PSR>95%:

蒙特卡洛验证:真实 SR=1.0、强负偏肥尾,短样本极难达到 PSR>95%

结果扎心但诚实:即使策略的真实 SR 确实是 1.0(也就是它真有 edge),只跑一年的话,你也只有 25% 的概率能在数据上证明它。 四分之三的情况下,一个真正有效的策略会因为样本太短而无法通过显著性检验——统计学管这叫第二类错误(把真的当成假的)。跑到四年,通过率也才 62%。

这和 MinTRL 公式完全自洽:SR=1.0 负偏肥尾的 MinTRL 约 2.9 年,正好对应蒙特卡洛里「三年通过率过半(54%)」。理论和模拟对上了,公式可信。

怎么用:把 MinTRL 变成研究纪律#

MinTRL 不是拿来事后自我安慰的,它应该在你开发策略之前就上场:

  1. 立项前先算需求。 你想做一个预期夏普 0.8 的中频策略?先算 MinTRL——大概要 4 年多样本。如果你手上的数据只有 2 年,那这个项目从一开始就注定无法被统计验证,要么换更高夏普的思路,要么接受「证据不足」的现实,别自欺。

  2. 用它设置最小回测窗口。 别再用「跑得越久越好」这种模糊标准。根据目标夏普和收益的经验偏度峰度,算出 MinTRL,把它设成硬性的最短样本要求。达不到,不上线。

  3. 警惕「高夏普+短样本」这个最贵的组合。 它的危险在于双重欺骗:高夏普让你兴奋、短样本让噪声最大——你恰恰在最没把握的时候最自信。回测里越是漂亮的短期高夏普,越要用 MinTRL/PSR 泼冷水。


A. 实现细节: MinTRL 和 PSR 均按 Bailey-López de Prado 原始公式实现,高阶矩修正项 1 - γ₃·SR + (γ₄-1)/4·SR² 完整保留。夏普率统一按年化输入,内部除以 √252 转成日频再代入公式,输出的 MinTRL 除以 252 换算回「年」便于解读。蒙特卡洛用左偏 skew-normal(a=-4)生成收益,关键处理是用总体矩而非样本矩做标准化——否则会人为消掉夏普估计的抽样波动,导致通过率退化成 0/100 的阶跃。每个样本长度跑 3000 次,PSR 用样本自身估出的 SR/偏度/峰度计算。

B. 已知偏差: (1) 公式依赖渐近正态近似,在极短样本(几十个观测)下 PSR 本身失准,此时 MinTRL 也只能当量级参考;(2) 核心假设是收益 i.i.d.,真实策略收益常有自相关(动量正相关、均值回归负相关),正自相关会让有效样本量小于名义样本量,使公式偏乐观——实际需求比算出来的更长;(3) MinTRL 只解决「单个策略 vs 运气」的检验,防不了多重检验偏差(你试了 100 个策略挑出最好那个),后者要用去膨胀夏普率 DSR 另行处理;(4) 蒙特卡洛的偏度是特定 skew-normal 设定,换分布数字会变,但定性结论(短样本通过率低)稳健。

C. 结果解读: 最反直觉也最重要的结论是——一个真实有效的策略,也可能因为样本太短而无法被证明有效。 蒙特卡洛里真实 SR=1.0 的策略跑一年只有 25% 通过率,这不是策略不行,是证据不够。这把「回测夏普高就能上」的朴素信念彻底击碎:夏普率的点估计毫无意义,除非配上样本长度和置信度。三个量化结论钉死:SR=1.0 正态下需 2.7 年、负偏肥尾需 2.9 年;SR 减半样本需求翻两番(平方律);负偏肥尾系统性推高需求且惩罚随 SR² 加重。实务落点只有一句话——在开发前用 MinTRL 算清样本够不够,别让「高夏普+短样本」这个最贵的自欺把你送进实盘。

最小回测长度 MinTRL:多长的样本才够证明策略不是运气
https://blog.halo26812.eu.org/blog/minimum-track-record
Author halo
Published at 2026年7月27日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨