4. 数据准备与窗口切分工程

滚动窗口回测的可信度,首先取决于进入窗口之前的数据是否干净、对齐、无偏。策略逻辑再精巧,如果行情序列存在重复时间戳、停牌缺口未处理、或股票池里混入了“未来才知道会退市”的标的,回测结果就只是数据缺陷的投影。本节围绕真实行情数据,完成从原始数据到可复用窗口切分方案的完整管道设计。

一、数据清洗:先定义“什么算脏数据”

以日频A股行情为例,原始数据通常包含日期、代码、开高低收、成交量、成交额、复权因子等字段。清洗的第一步不是写代码,而是明确校验规则。常见问题包括:同一代码同一日期重复记录、价格为零或负、最高价低于最低价、成交量非整数、复权因子缺失。建议在管道入口设置硬性校验,任何一条不满足即拒绝该批次数据并记录日志,而不是静默丢弃。

校验规则示例(日频行情):
1. (date, code) 唯一
2. low <= open, close <= high
3. low > 0 且 volume >= 0
4. 复权因子 > 0 且非空
5. 单日涨跌幅绝对值 <= 涨跌停限制 + 容差

复权处理需要统一口径。回测中若使用前复权价格,历史价格会随新除权事件变化,导致同一窗口在不同运行时间得到不同数值。工程上更稳妥的做法是:存储后复权价格用于计算收益,同时保留原始价格与复权因子,确保任意时点可重建。这样窗口切分方案不会因数据更新而漂移。

二、对齐与缺失处理:交易日历是基准

多标的回测中,各股票停牌、上市时间不同,直接按各自日期序列拼接会造成错位。正确做法是以交易所交易日历为主轴,将所有标的对齐到统一日期索引,缺失位置显式标记为NaN,而不是前向填充。前向填充会制造“停牌期间仍有价格变动”的假象,严重扭曲波动率和换手率计算。

缺失值的处理策略应分场景:对于收益计算,停牌日收益记为零、复牌日使用复牌价对停牌前收盘价计算累计收益;对于因子计算,若某标的在窗口内缺失比例超过阈值(例如20%),则该标的在该窗口内不参与选股,而不是用均值填补。阈值本身应作为参数写入配置,便于后续敏感性分析。

对齐后的数据应保留一张“有效性掩码”矩阵,形状与价格矩阵一致,标记每个单元格是真实观测还是缺失。后续窗口切分和因子计算都读取该掩码,避免在多个环节重复判断。

三、幸存者偏差检查:股票池必须“时点化”

幸存者偏差是回测中最隐蔽的偏差来源。若用当前仍在交易的股票列表回溯历史,等于提前剔除了退市、暂停上市和重大重组失败的标的,收益会被系统性高估。检查方法是:为每个标的维护上市日期、退市日期、暂停上市区间,构建“时点股票池”——在任意历史日期t,只允许使用当时已上市且未退市的标的。

工程实现上,可将股票池表示为区间表:代码、起始日期、结束日期、状态。窗口切分时,对训练段和测试段的每个交易日分别求交集,得到该日可用标的集合。若某标的在测试段中途退市,其退市前数据仍应保留在池中,退市后自动移出。这样回测的每一笔交易都只使用当时真实可交易的对象。

数据管道与滚动窗口切分方案 原始行情数据 日频OHLCV + 复权因子 标的元数据 上市/退市/停牌区间 交易日历 交易所统一日期轴 清洗与对齐 唯一性 / 价格合法性校验 后复权价格 + 原始价格并存 对齐至交易日历,缺失标NaN 生成有效性掩码矩阵 缺失比例超阈值则剔除 时点股票池区间表 幸存者偏差检查通过 滚动窗口切分 t0 tN 训练段 W1 测试 训练段 W2 测试 训练段 W3 测试 步长 = 测试段长度,训练段可重叠 每窗口独立读取掩码与时点股票池 可复用输出物 窗口清单表 窗口ID/训练起止/测试起止 数据快照 价格/掩码/股票池版本号 切分配置 训练长度/测试长度/步长

四、窗口切分规则:训练段与测试段的边界设计

窗口切分的核心参数有三个:训练段长度、测试段长度、滚动步长。以日频数据为例,一种常见配置是训练段500个交易日、测试段60个交易日、步长60个交易日,即训练段与测试段不重叠、窗口之间首尾相接。若希望训练样本更充分,可让训练段重叠,例如步长设为20个交易日,但必须保证测试段之间不重叠,否则同一段行情会被重复评估,夸大策略稳定性。

切分时必须遵守“训练段结束日 < 测试段开始日”的严格时序约束,且两者之间建议留出隔离带(embargo),长度至少覆盖策略持仓周期或标签计算窗口。例如策略使用未来5日收益作为标签,则训练段与测试段之间应空出5个交易日,防止标签泄漏。隔离带内的数据不参与训练也不参与测试,仅作为缓冲。

参数示例取值作用与约束
训练段长度500个交易日需覆盖至少一个完整市场周期片段
测试段长度60个交易日不宜过短,否则评估方差过大
滚动步长60个交易日等于测试段长度时测试段不重叠
隔离带5个交易日不小于标签计算窗口,防泄漏
缺失比例阈值20%窗口内标的缺失超此值则剔除

五、可复用窗口切分方案与数据管道

将上述规则固化为一份窗口清单表,是管道可复用的关键。清单表每行对应一个窗口,字段包括:窗口ID、训练开始日、训练结束日、测试开始日、测试结束日、隔离带起止、数据快照版本号。回测引擎只读取该表,不自行推算日期,从而保证不同策略、不同运行批次使用完全一致的切分口径。

窗口清单表示例(片段):
window_id | train_start | train_end  | test_start | test_end   | embargo_days | snapshot_ver
W001      | 2018-01-02  | 2019-12-31 | 2020-01-08 | 2020-04-01 | 5            | v2024.03
W002      | 2018-04-02  | 2020-03-31 | 2020-04-08 | 2020-07-01 | 5            | v2024.03
W003      | 2018-07-02  | 2020-06-30 | 2020-07-08 | 2020-09-30 | 5            | v2024.03

数据管道则按“抽取—校验—对齐—掩码—时点池—切分—快照”的顺序组织,每个环节输出带版本号的中间产物。快照版本号与窗口清单绑定,确保任何时候重跑都能复现同一份数据状态。当原始数据发生除权调整或补充退市记录时,生成新版本快照,旧窗口清单仍指向旧版本,避免历史回测结果被无声改写。

常见工程陷阱:在测试段内使用全样本计算的标准化参数(如全历史均值方差)做特征缩放,等同于把未来信息泄漏进测试段。正确做法是每个窗口内仅用训练段数据拟合缩放参数,再应用到测试段。

完成以上步骤后,进入回测的每一段行情都具备明确的时间边界、干净的数值、真实的标的范围和可追溯的数据版本。窗口切分方案不再是散落在脚本里的日期硬编码,而是一份可审计、可复用、可版本化的工程资产,为后续参数稳定性评估提供一致的数据底座。