🏠
AI量化交易 · 第二十三章
目录
CHAPTER 23

过拟合

当回测战神遇到实盘,为什么策略总会失效

本章导航

  1. 学习目标
  2. 故事引入:曲线拟合的怪物
  3. 核心知识:过拟合的数学本质与检测方法
  4. AI实践:用AI检测策略是否过拟合
  5. 动手练习:CSCV过拟合检测代码
  6. 本章测验
  7. 下一章预告

学习目标

过拟合是量化策略的头号杀手。它不像未来函数那样容易被代码审查发现,也不像成本低估那样可以通过简单计算修正。过拟合是一种统计学意义上的幻觉——你看到的规律,可能只是随机噪声的巧合排列。

本章结束后,你将能够:

故事引入:曲线拟合的怪物

19世纪的数学家高斯已经告诉我们,给定N个点,总能找到一条N-1次多项式完美穿过所有点。这意味着如果你允许模型足够复杂,它可以在训练数据上达到100%的准确率——但对新数据毫无预测能力。

2016年,某量化私募发布了一款AI选股产品,号称用深度学习训练了上亿参数。回测显示,2010-2015年年化收益超过80%。产品募资5亿元,2016年实盘运行——结果当年亏损35%,2017年继续亏损20%,基金清盘。

事后分析发现,这个深度学习模型在训练过程中记住了大量历史噪声:某次特定月份的小盘股轮动、某几年的行业周期性规律、甚至某些股票的除权除息日模式。这些模式在未来再也没有重现。

用奥卡姆剃刀的精神来说:如无必要,勿增实体。一个用3个参数就能解释的现象,绝不要用30个参数去拟合。

——威廉·奥卡姆,约1285-1349

核心知识:过拟合的数学本质与检测方法

偏差-方差权衡(Bias-Variance Tradeoff)

任何预测模型的误差都可以分解为三部分:

总误差 = 偏差² + 方差 + 不可约误差

过拟合的本质就是方差过大——模型记住了训练数据的特有噪声,把这些噪声当成了规律。当遇到新数据时,这些噪声不复存在,模型表现断崖式下跌。

多重比较问题(Multiple Comparison Problem)

假设你测试了100个不同的策略变体,每个变体在5年回测中有5%的概率纯粹因运气而显得显著(p<0.05)。那么, purely by chance,你期望能看到大约5个看起来显著的策略。

更可怕的是,你通常会只汇报最好的那个,而读者根本不知道你还测试了另外99个。这就是量化交易中的出版偏差(Publication Bias)——你只看到幸存者,看不到背后的尸体堆。

flowchart TD
    A[提出策略假设] --> B[回测验证]
    B --> C{结果显著?}
    C -->|是| D[发表/实盘]
    C -->|否| E[调整参数]
    E --> F[再次回测]
    F --> C
    D --> G[样本外验证]
    G -->|失败| H[过拟合!]
    G -->|成功| I[可能稳健]
    

CSCV(Combinatorially Symmetric Cross-Validation)

CSCV是Marcos Lopez de Prado提出的检测策略过拟合概率的方法。核心思想是:

  1. 将历史数据分为S个子集(如10个子集)。
  2. 在所有可能的组合中,选出一半子集作为训练集,另一半作为测试集。
  3. 对每个划分,在训练集上优化策略参数,在测试集上评估表现。
  4. 如果训练集最优策略在测试集上表现远差于训练集,说明存在过拟合。

CSCV输出一个过拟合概率(PBO, Probability of Backtest Overfitting)。PBO < 0.1表示策略很可能稳健,PBO > 0.5表示策略很可能过拟合。

import numpy as np
import pandas as pd
from itertools import combinations

def cscv_pbo(returns_matrix, n_splits=10):
    """
    CSCV方法计算过拟合概率(PBO)
    returns_matrix: DataFrame, 每列是一个策略变体的日收益序列
    """
    n_strategies = returns_matrix.shape[1]
    T = len(returns_matrix)
    
    # 将收益序列分成n_splits段
    splits = np.array_split(returns_matrix, n_splits)
    
    omega = 0  # 过拟合次数计数
    n_total = 0
    
    # 所有可能的训练/测试划分(各取一半)
    all_indices = list(range(n_splits))
    for train_idx in combinations(all_indices, n_splits // 2):
        test_idx = [i for i in all_indices if i not in train_idx]
        
        # 拼接训练集和测试集
        train_returns = pd.concat([splits[i] for i in train_idx])
        test_returns = pd.concat([splits[i] for i in test_idx])
        
        # 在训练集上选择夏普比率最高的策略
        train_sharpe = train_returns.mean() / train_returns.std() * np.sqrt(252)
        best_idx = train_sharpe.idxmax()
        
        # 计算该策略在测试集上的排名
        test_sharpe = test_returns.mean() / test_returns.std() * np.sqrt(252)
        test_rank = test_sharpe.rank(ascending=False)[best_idx]
        
        # 如果训练集最优策略在测试集上的排名位于后50%,计为过拟合
        if test_rank > n_strategies / 2:
            omega += 1
        n_total += 1
    
    pbo = omega / n_total if n_total > 0 else 0
    return pbo

# 示例:生成10个策略变体的收益序列
np.random.seed(42)
returns = pd.DataFrame({
    f'strategy_{i}': np.random.normal(0.0003, 0.02, 2520)
    for i in range(10)
})
# 人为让strategy_0在训练期表现更好(添加噪声趋势)
returns['strategy_0'] += np.linspace(0, 0.001, 2520)

pbo = cscv_pbo(returns, n_splits=10)
print(f"过拟合概率 PBO: {pbo:.2%}")
if pbo > 0.5:
    print("警告:策略很可能过拟合!")
else:
    print("策略看起来比较稳健。")

参数敏感性分析

另一个直观的过拟合检测方法是参数敏感性分析。画出策略收益随关键参数变化的曲线:

与AI一起练习

练习1:让AI分析你的策略参数数量

我的策略有以下可调参数:
- 短期均线周期(5-60日)
- 长期均线周期(60-250日)
- 止损比例(1%-10%)
- 仓位比例(10%-100%)
- 入场阈值(0.5-3个标准差)

请帮我:
1. 计算参数空间的总组合数
2. 评估在10年日频数据上优化这些参数的过拟合风险
3. 建议哪些参数可以固定、哪些可以简化

练习2:让AI实现CSCV

请用Python实现一个完整的CSCV(组合对称交叉验证)框架:
- 输入:N个策略变体的日收益序列(DataFrame)
- 输出:PBO(过拟合概率)、Logit曲线、最优策略在训练集和测试集的排名分布
- 要求:支持自定义分割数(S),绘制可视化图表
- 额外:解释PBO的计算逻辑和统计含义

练习3:让AI设计抗过拟合策略

我想设计一个对过拟合免疫的量化策略框架。
请帮我:
1. 提出3种降低过拟合风险的方法(如正则化、简化模型、贝叶斯优化等)
2. 用Python演示其中一种方法(如LASSO正则化在多因子权重中的应用)
3. 讨论为什么简单策略往往比复杂策略更稳健

动手练习:过拟合检测实战

"""过拟合检测实战:网格搜索 + 参数敏感性分析"""
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

# 模拟策略收益:双均线交叉
def simulate_strategy_returns(short_window, long_window, noise_level=0.02):
    np.random.seed(42)
    price = 100 * np.exp(np.cumsum(np.random.normal(0.0002, 0.01, 1000)))
    short_ma = pd.Series(price).rolling(short_window).mean()
    long_ma = pd.Series(price).rolling(long_window).mean()
    signal = (short_ma > long_ma).astype(int).diff().fillna(0)
    returns = signal.shift(1) * np.diff(price, prepend=price[0]) / price
    returns += np.random.normal(0, noise_level, len(returns))
    return returns.dropna()

# 网格搜索
results = []
for short in range(5, 61, 5):
    for long in range(short+10, 251, 10):
        r = simulate_strategy_returns(short, long)
        sharpe = r.mean() / r.std() * np.sqrt(252)
        results.append({'short': short, 'long': long, 'sharpe': sharpe})

df_results = pd.DataFrame(results)
best = df_results.loc[df_results['sharpe'].idxmax()]
print(f"最优参数: 短期={best['short']}, 长期={best['long']}")
print(f"最优夏普: {best['sharpe']:.2f}")

# 参数敏感性:固定长期均线,看短期均线变化
sensitivity = df_results[df_results['long'] == 120].sort_values('short')
print("
参数敏感性(长期=120):")
print(sensitivity[['short', 'sharpe']].head(10))

# 判断:如果最优参数附近的夏普差异很大,说明过拟合风险高
near_best = df_results[
    (df_results['short'].between(best['short']-10, best['short']+10)) &
    (df_results['long'].between(best['long']-20, best['long']+20))
]
sharpe_std = near_best['sharpe'].std()
print(f"
最优参数附近夏普标准差: {sharpe_std:.3f}")
if sharpe_std > 0.5:
    print("警告:参数敏感,过拟合风险高!")
else:
    print("参数较稳健。")

本章测验

测测你的理解
1. 过拟合的本质是什么?
  • A. 模型太简单,无法捕捉规律
  • B. 模型太复杂,记住了训练数据的噪声
  • C. 数据量太大
  • D. 计算资源不足
过拟合是模型方差过大的表现。复杂模型把训练数据中的随机噪声也当作规律学习,导致在新数据上表现差。
2. CSCV方法中的PBO(过拟合概率)高于多少时,策略应被视为过拟合?
  • A. 10%
  • B. 50%
  • C. 90%
  • D. 100%
PBO > 0.5意味着训练集最优策略在测试集上有一半以上的概率排名后50%,说明策略很可能过拟合。一般认为PBO < 0.1才比较安全。
3. 以下哪种做法最能降低过拟合风险?
  • A. 减少可调参数数量,使用更简单的模型
  • B. 增加更多技术指标
  • C. 在历史数据上反复优化直到收益最高
  • D. 缩短回测时间窗口
奥卡姆剃刀原则在量化交易中同样适用。更少的参数意味着更低的方差和更强的泛化能力。简单策略在样本外往往表现更稳定。

下一章预告

我们已经学会了如何检测过拟合,但还有一个更核心的问题:如何在策略设计阶段就避免过拟合?答案是Walk-Forward分析——一种模拟实盘滚动优化的回测方法。

第24章将带你深入Walk-Forward分析的世界。你将学会如何设置训练/测试窗口、如何评估 Walk-Forward 的效率、以及为什么它比传统的全样本优化更接近真实交易场景。

回测优化是开卷考试,Walk-Forward才是闭卷测试。

觉得有收获?分享给同样对量化交易感兴趣的朋友

上一章
回测的局限性
为什么99%的策略回测结果都无法在实盘复制
下一章
Walk-Forward分析
滚动优化与样本外验证的正确打开方式

如果这本书对你有所启发,欢迎支持我继续创作

赞赏二维码