第22章 策略迭代与优化:参数敏感性分析、滚动窗口优化、过拟合检测与防范

做基差交易策略,最怕什么?

怕回测漂亮,实盘拉胯。

我见过太多人,拿着一个参数组合在历史数据上跑出年化50%的曲线,兴奋得睡不着。结果一上实盘,三个月亏掉一半。为什么?因为策略过拟合了。说白了,你是在「记忆」历史,而不是「学习」规律。

这一章,我们就来聊聊怎么让策略真正稳健。我会从参数敏感性分析、滚动窗口优化、过拟合检测与防范三个维度展开。嗯,都是我在实战中踩过坑之后才真正理解的东西。

一、参数敏感性分析:别让你的策略活在「刀尖」上

先问一个问题:你的策略参数,稍微变一点点,收益就崩了?

如果是,那这个策略基本不能用。

我个人习惯,在确定一组参数之前,先做敏感性分析。说白了,就是看看参数在合理范围内波动时,策略表现是否稳定。

核心思路: 固定其他参数,让目标参数在 [min, max] 区间内步进,观察 Sharpe、最大回撤、年化收益的变化。

举个例子。我做过一个螺纹钢的跨期套利策略,核心参数是「开仓价差阈值」。我把它从 20 到 60 步进,步长 5,结果如下:

阈值 年化收益 最大回撤 Sharpe
20 8.2% 12.1% 0.68
25 11.5% 9.8% 1.17
30 13.8% 7.5% 1.84
35 14.2% 7.2% 1.97
40 13.5% 8.1% 1.67
45 11.0% 10.5% 1.05
50 7.8% 13.2% 0.59

看到没?阈值在 30-40 之间,表现都还不错。但到了 20 或 50,直接崩了。这说明什么?说明策略在「阈值=35」附近有一个稳定平台。我一般会选平台中间的值,而不是峰值。为什么?因为峰值往往是噪声。

我的经验: 做敏感性分析时,别只看 Sharpe。把最大回撤和收益稳定性也画出来。如果一个参数变化 10%,回撤翻倍,那这个参数就是「雷区」。

二、滚动窗口优化:让策略「跟上」市场

市场是会变的。去年有效的参数,今年可能就失效了。

怎么办?滚动窗口优化。

思路很简单:用最近一段数据(比如 6 个月)来优化参数,然后用优化后的参数跑接下来一段时间(比如 1 个月)。然后窗口向前滚动,重复这个过程。

我给你们看一段伪代码,理解起来更直观:

# 伪代码:滚动窗口优化
window_size = 126  # 6个月交易日
step_size = 21     # 1个月

for start in range(0, len(data) - window_size, step_size):
    train_data = data[start : start + window_size]
    test_data  = data[start + window_size : start + window_size + step_size]
    
    # 在训练集上优化参数
    best_params = optimize(train_data)
    
    # 在测试集上验证
    performance = backtest(test_data, best_params)
    
    # 记录结果
    results.append(performance)

这样做的好处很明显:参数会随着市场状态自适应调整。但注意,别把窗口设得太短。我试过用 3 个月窗口,结果参数天天变,交易成本直接吃掉所有利润。

避坑指南: 我曾经在一个股指期货策略上,把窗口设成 60 天,步长 10 天。结果优化出来的参数每两周就大变一次。实盘跑了一个月,手续费比利润还高。后来我把窗口拉到 180 天,步长 20 天,才稳定下来。记住:滚动窗口不是越短越好,要给策略足够的「学习时间」。

三、过拟合检测与防范:别被「完美曲线」骗了

过拟合是量化交易的头号杀手。

怎么检测?我常用的方法有三个:

  1. 样本外测试: 把数据分成训练集和测试集,训练集上优化,测试集上验证。如果训练集 Sharpe 2.0,测试集 Sharpe 0.5,那基本就是过拟合了。
  2. 交叉验证: 把数据分成 K 份,轮流用 K-1 份训练,1 份验证。取 K 次结果的平均值。如果某一次结果特别差,说明策略对特定时间段敏感。
  3. 蒙特卡洛模拟: 随机打乱收益率序列,看看策略在随机数据上的表现。如果随机数据上也能赚钱,那策略就是在「拟合噪声」。

我个人最常用的是第三种。为什么?因为它能直接告诉你,你的策略是不是在「瞎蒙」。

来看一个具体例子。我写过一段代码,用来做蒙特卡洛过拟合检测:

import numpy as np

def monte_carlo_overfit_test(strategy_returns, n_simulations=1000):
    """
    蒙特卡洛过拟合检测
    strategy_returns: 策略的实际日收益率序列
    """
    actual_sharpe = np.mean(strategy_returns) / np.std(strategy_returns) * np.sqrt(252)
    
    shuffled_sharpes = []
    for _ in range(n_simulations):
        # 打乱收益率顺序
        shuffled = np.random.permutation(strategy_returns)
        sharpe = np.mean(shuffled) / np.std(shuffled) * np.sqrt(252)
        shuffled_sharpes.append(sharpe)
    
    # 计算实际Sharpe在随机分布中的百分位
    percentile = np.sum(np.array(shuffled_sharpes) >= actual_sharpe) / n_simulations
    
    return actual_sharpe, shuffled_sharpes, percentile

如果 percentile 小于 5%,说明你的策略在随机数据上几乎不可能达到这个表现,那策略大概率是有效的。反之,如果 percentile 大于 20%,那就要小心了——你的策略可能只是在「记住」历史。

核心原则: 一个真正稳健的策略,在样本外和随机数据上的表现,不应该和样本内有太大差距。如果差距很大,那就不是策略,是「幻觉」。

四、如何防范过拟合?我的实战清单

说了这么多检测方法,最后给一份我自己的防范清单:

  • 参数越少越好。 每增加一个参数,过拟合风险指数级上升。我一般控制在 3-5 个以内。
  • 逻辑优先,优化其次。 先确保策略有经济学逻辑支撑,再用数据验证。不要反过来。
  • 惩罚复杂模型。 在优化目标函数里加入参数数量的惩罚项,比如 AIC、BIC 准则。
  • 多周期验证。 在牛、熊、震荡市分别测试。如果一个策略只在牛市中赚钱,那它可能只是「做多」策略。
  • 实盘模拟。 在正式实盘前,先用模拟账户跑 1-2 个月。看看实盘滑点和冲击成本对策略的影响。

我记得有一次,一个学员拿着一个「完美」的跨品种套利策略来找我。回测曲线 45 度向上,最大回撤不到 3%。我让他做了一次蒙特卡洛检测,结果 percentile 是 35%。也就是说,随机打乱收益率后,有 35% 的概率还能得到同样的 Sharpe。这明显是过拟合了。后来我们去掉两个参数,重新优化,虽然回测收益降了 20%,但实盘反而赚钱了。

嗯,这就是过拟合的代价——你追求完美,反而失去了一切。

策略迭代与优化核心流程 参数敏感性分析 滚动窗口优化 过拟合检测与防范 步进测试 稳定性评估 选择平台区 窗口划分 滚动优化 自适应调整 样本外测试 交叉验证 蒙特卡洛模拟 稳健的基差交易策略

最后说一句:策略迭代不是一锤子买卖。市场在变,你的策略也要跟着变。但变的前提是——你得知道哪些是「信号」,哪些是「噪声」。参数敏感性分析、滚动窗口优化、过拟合检测,这三板斧用好了,你的策略就能在实盘中活得更久。