第22章 策略迭代与优化:参数敏感性分析、滚动窗口优化、过拟合检测与防范
做基差交易策略,最怕什么?
怕回测漂亮,实盘拉胯。
我见过太多人,拿着一个参数组合在历史数据上跑出年化50%的曲线,兴奋得睡不着。结果一上实盘,三个月亏掉一半。为什么?因为策略过拟合了。说白了,你是在「记忆」历史,而不是「学习」规律。
这一章,我们就来聊聊怎么让策略真正稳健。我会从参数敏感性分析、滚动窗口优化、过拟合检测与防范三个维度展开。嗯,都是我在实战中踩过坑之后才真正理解的东西。
一、参数敏感性分析:别让你的策略活在「刀尖」上
先问一个问题:你的策略参数,稍微变一点点,收益就崩了?
如果是,那这个策略基本不能用。
我个人习惯,在确定一组参数之前,先做敏感性分析。说白了,就是看看参数在合理范围内波动时,策略表现是否稳定。
举个例子。我做过一个螺纹钢的跨期套利策略,核心参数是「开仓价差阈值」。我把它从 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」附近有一个稳定平台。我一般会选平台中间的值,而不是峰值。为什么?因为峰值往往是噪声。
二、滚动窗口优化:让策略「跟上」市场
市场是会变的。去年有效的参数,今年可能就失效了。
怎么办?滚动窗口优化。
思路很简单:用最近一段数据(比如 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 个月窗口,结果参数天天变,交易成本直接吃掉所有利润。
三、过拟合检测与防范:别被「完美曲线」骗了
过拟合是量化交易的头号杀手。
怎么检测?我常用的方法有三个:
- 样本外测试: 把数据分成训练集和测试集,训练集上优化,测试集上验证。如果训练集 Sharpe 2.0,测试集 Sharpe 0.5,那基本就是过拟合了。
- 交叉验证: 把数据分成 K 份,轮流用 K-1 份训练,1 份验证。取 K 次结果的平均值。如果某一次结果特别差,说明策略对特定时间段敏感。
- 蒙特卡洛模拟: 随机打乱收益率序列,看看策略在随机数据上的表现。如果随机数据上也能赚钱,那策略就是在「拟合噪声」。
我个人最常用的是第三种。为什么?因为它能直接告诉你,你的策略是不是在「瞎蒙」。
来看一个具体例子。我写过一段代码,用来做蒙特卡洛过拟合检测:
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%,但实盘反而赚钱了。
嗯,这就是过拟合的代价——你追求完美,反而失去了一切。
最后说一句:策略迭代不是一锤子买卖。市场在变,你的策略也要跟着变。但变的前提是——你得知道哪些是「信号」,哪些是「噪声」。参数敏感性分析、滚动窗口优化、过拟合检测,这三板斧用好了,你的策略就能在实盘中活得更久。