13、交叉验证与回测:时间序列交叉验证、滚动窗口回测、扩展窗口回测、回测中的常见陷阱
做量化交易的朋友,尤其是搞基差策略的,一定绕不开一个核心问题:你的策略到底靠不靠谱?
很多人跑完回测,看到曲线漂亮得不行,就急着实盘。结果呢?一进去就亏。为什么?因为回测本身就有坑。今天我就把这些坑,一个一个给你指出来。
13.1 为什么普通交叉验证不适合时间序列?
先问个问题:你用过K折交叉验证吗?
传统的K折,是把数据随机打乱,分成K份。然后拿其中K-1份训练,1份验证。这在分类、回归问题上没问题。但用在时间序列上,就出大事了。
为什么?因为时间序列有顺序。你用未来的数据去训练模型,然后用过去的数据去验证——这不就是典型的“未来函数”吗?
我刚开始做策略时,就犯过这个错。当时用了一个简单的移动平均策略,K折验证下来夏普比率2.5,兴奋得不行。结果实盘直接腰斩。后来才发现,我的验证集里包含了未来的价格信息。说白了,就是作弊了。
核心原则:时间序列的交叉验证,必须保持时间顺序。永远不能用未来的数据去预测过去。
13.2 时间序列交叉验证(Time Series Cross-Validation)
那正确的做法是什么?
时间序列交叉验证,也叫“前向链式验证”。它的逻辑很简单:
- 第一次:用第1天到第100天训练,预测第101天到第110天
- 第二次:用第1天到第110天训练,预测第111天到第120天
- 第三次:用第1天到第120天训练,预测第121天到第130天
- ……以此类推
你看,每次训练集都在扩大,验证集永远在训练集之后。这样就不会有未来信息泄露的问题。
# 时间序列交叉验证示例(伪代码)
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(data):
train_data = data[train_index]
test_data = data[test_index]
# 训练模型,验证策略
我个人习惯用5到10折。折数太少,验证结果不稳定;折数太多,计算量太大。嗯,这个平衡点需要你自己根据数据量来调。
13.3 滚动窗口回测(Rolling Window Backtest)
滚动窗口回测,是另一种常见方法。它的特点是:窗口大小固定。
比如,我固定用过去60天的数据训练,然后预测未来5天。然后窗口整体往后滚动30天,再用新的60天数据训练,预测接下来的5天。
这样做的好处是:模型能适应市场的变化。毕竟市场风格会变,你总不能指望2015年的规律在2025年还管用吧?
我的经验:滚动窗口的步长很关键。步长太小,回测次数太多,容易过拟合;步长太大,又可能错过市场风格切换的节点。我一般设步长为窗口大小的10%-20%。
13.4 扩展窗口回测(Expanding Window Backtest)
扩展窗口和滚动窗口的区别在于:窗口只扩大,不缩小。
第一次用1-100天训练,第二次用1-110天训练,第三次用1-120天训练……训练集越来越大。
这种方法适合那些需要大量历史数据才能稳定的模型。比如一些统计套利模型,数据越多,参数估计越准。
但有个问题:如果市场发生了结构性变化,早期数据反而会拖累模型。比如2015年股灾前的规律,在2016年可能就不适用了。这时候扩展窗口反而不如滚动窗口灵活。
| 方法 | 窗口变化 | 优点 | 缺点 |
|---|---|---|---|
| 时间序列交叉验证 | 逐步扩展 | 无未来信息泄露 | 计算量大 |
| 滚动窗口回测 | 固定大小,整体移动 | 适应市场变化 | 可能丢失长期规律 |
| 扩展窗口回测 | 只扩大不缩小 | 数据利用率高 | 受早期数据影响大 |
13.5 回测中的常见陷阱
这部分我得多说几句。因为很多新手,甚至一些老手,都会在这里栽跟头。
13.5.1 前视偏差(Look-Ahead Bias)
前视偏差,说白了就是用未来信息做决策。
举个例子:你在回测中,用当天的收盘价作为信号,然后当天就开仓。但实际交易中,收盘价出来之前,你根本不知道价格是多少。这就是典型的前视偏差。
我曾经见过一个策略,回测年化收益50%,实盘直接亏30%。后来一查,原来策略里用了“未来5分钟的数据”来计算指标。你说这能赚钱吗?
避坑指南:回测时,确保所有数据都是“当时能获取到的”。比如用开盘价做信号,那就只能开盘后开仓;用收盘价做信号,那就只能第二天开盘再开仓。
13.5.2 幸存者偏差(Survivorship Bias)
幸存者偏差,是指只关注活下来的品种,忽略了已经死掉的。
比如你回测期货策略,只用了当前还在交易的合约。但那些已经退市、或者被摘牌的合约呢?它们的表现可能很差。如果你把它们排除在外,回测结果就会虚高。
我记得有一次,我回测一个跨品种套利策略,结果特别好。后来才发现,我用的数据里只包含了“活到现在的品种”。而那些中途破产的品种,数据根本就没收录。这导致回测结果严重失真。
我的建议:尽量使用全样本数据,包括已经退市的品种。如果数据源不完整,至少要在报告中说明这一点。
13.5.3 过拟合(Overfitting)
过拟合,就是策略在历史数据上表现完美,但在未来数据上一塌糊涂。
怎么避免?
- 参数不要太多。参数越多,越容易过拟合。
- 使用样本外测试。把数据分成训练集和测试集,训练集上调参数,测试集上验证。
- 做敏感性分析。稍微改变参数,看看策略表现是否稳定。
13.5.4 交易成本忽略
很多新手回测时,把交易成本设得很低,甚至设为0。这太天真了。
实际交易中,滑点、手续费、冲击成本,每一项都能吃掉你的利润。尤其是高频策略,交易成本的影响更大。
我一般会按实际成本的1.5倍来估算。这样即使实盘有偏差,也不至于太惨。
13.6 知识体系总览
下面这张图,帮你理清本章的核心逻辑:
这张图把三种回测方法和常见陷阱都串起来了。你仔细看看,应该能对本章内容有个整体把握。
13.7 总结
回测不是万能的,但没有回测是万万不能的。关键是你要知道回测的局限性在哪里。
我个人习惯是:先用时间序列交叉验证做初步筛选,然后用滚动窗口做精细调参,最后用扩展窗口做稳定性测试。三个方法结合起来,能最大程度避免过拟合。
至于那些陷阱——前视偏差、幸存者偏差、交易成本——每次回测前,我都会逐条检查。宁可慢一点,也不能让策略带着bug上线。
一句话记住:回测的目的是发现错误,而不是证明正确。如果你跑完回测,发现全是优点,那大概率是你漏掉了什么。
公众号:蓝海资料掘金营,微信deep3321