4. 回放验证的核心逻辑:时间序列滚动窗口、固定窗口与扩展窗口
在金融风险计量中,VaR 模型的有效性不能仅靠一次性的历史数据拟合来证明。回放验证(Backtesting)通过模拟模型在历史不同时间点的“实时”表现,检验其预测能力。其核心在于如何切分训练集与测试集,以最真实地还原模型在未知数据上的表现。
时间序列数据具有天然的时间顺序性,因此不能使用随机切分。本章介绍三种最主流的窗口切分方法:滚动窗口、固定窗口与扩展窗口。它们决定了模型在每次回测时使用的历史数据长度与更新方式。
4.1 三种窗口切分方法的核心逻辑
| 方法 | 训练集长度 | 窗口移动方式 | 数据利用效率 | 典型应用场景 |
|---|---|---|---|---|
| 固定窗口 | 固定不变 | 整体向前滑动,丢弃最旧数据 | 较低(丢弃旧数据) | 市场结构稳定、快速适应新环境 |
| 扩展窗口 | 持续增长 | 只增加新数据,不丢弃旧数据 | 最高(保留全部历史) | 长期趋势模型、低频数据 |
| 滚动窗口 | 固定不变 | 整体向前滑动,丢弃最旧数据 | 中等(与固定窗口相同) | 波动率聚类、高频数据 |
关键区别:固定窗口与滚动窗口在数学定义上相同(训练集长度固定),但在实际工程中,固定窗口通常指“不重叠”的切分(如每年一个窗口),而滚动窗口强调“逐日/逐周”滑动,重叠度更高。
4.2 滚动窗口(Rolling Window)
滚动窗口是最常用的回放验证方法。它使用一个长度固定的训练窗口,在时间轴上逐期向前滑动。每滑动一次,丢弃最旧的一个观测值,加入最新的一个观测值,然后重新估计模型参数并预测下一期的 VaR。
- 训练集长度:固定为 \( W \) 个观测值。
- 滑动步长:通常为 1 期(日/周),也可为多期。
- 优点:模型始终基于最近的数据,能快速捕捉市场波动率变化;计算量可控。
- 缺点:丢弃了较早的历史信息,可能丢失长期趋势;窗口长度选择敏感。
工程实践:在日频 VaR 回测中,滚动窗口长度通常设为 250 个交易日(约 1 年)或 500 个交易日(约 2 年)。窗口过短会导致参数估计不稳定,过长则反应迟钝。
4.3 固定窗口(Fixed Window)
固定窗口与滚动窗口在数学形式上完全相同(训练集长度固定),但在实际回测工程中,固定窗口通常指不重叠或大步长的切分方式。例如,将 5 年的数据按年切分为 5 个不重叠的窗口,每个窗口内独立训练和测试。
- 训练集长度:固定,但窗口之间不重叠或重叠很少。
- 典型步长:等于窗口长度(不重叠)或窗口长度的一半(半重叠)。
- 优点:计算效率高;每个窗口的测试结果独立,便于横向比较。
- 缺点:样本量少时,测试结果方差大;无法捕捉窗口内的结构变化。
注意:在金融时间序列中,不重叠的固定窗口容易因窗口边界恰好处于市场转折点而导致回测结果偏差。建议至少使用半重叠窗口,或直接采用滚动窗口。
4.4 扩展窗口(Expanding Window)
扩展窗口的训练集长度随时间单调递增。每次回测时,保留全部历史数据,仅增加最新的观测值,从不丢弃旧数据。这种方法最大限度地利用了历史信息。
- 训练集长度:从初始长度 \( W_0 \) 开始,每次增加 1 期。
- 优点:参数估计的方差随样本量增加而减小;适合低频数据或长期趋势模型。
- 缺点:计算量随时间线性增长;对市场结构变化不敏感,旧数据可能引入噪声。
4.5 三种方法的对比与选择
| 对比维度 | 滚动窗口 | 固定窗口 | 扩展窗口 |
|---|---|---|---|
| 对市场突变响应 | 快 | 中等(取决于步长) | 慢 |
| 参数估计稳定性 | 中等 | 低(样本少时) | 高(样本量大) |
| 计算资源消耗 | 中等 | 低 | 高(随时间增长) |
| 适用模型 | GARCH、EWMA | 简单历史模拟 | 长期均值模型 |
| 回测结果方差 | 中等 | 高 | 低 |
选择建议:
- 高频数据(日频/分钟频):优先使用滚动窗口,窗口长度 250–500 期。
- 低频数据(月频/季频):使用扩展窗口,充分利用有限的历史数据。
- 快速验证/初步测试:使用固定窗口(不重叠),但需注意窗口边界效应。
- 监管合规回测:通常要求滚动窗口,且窗口长度需在文档中明确说明。
4.6 工程实现要点
在 Python 中实现三种窗口切分时,核心是控制训练集的起始索引与结束索引。以下是一个简化的实现框架:
import numpy as np
def rolling_window(data, window_size, step=1):
"""滚动窗口生成器"""
n = len(data)
for start in range(0, n - window_size, step):
train = data[start:start + window_size]
test = data[start + window_size]
yield train, test
def expanding_window(data, min_window, step=1):
"""扩展窗口生成器"""
n = len(data)
for end in range(min_window, n, step):
train = data[:end]
test = data[end]
yield train, test
# 使用示例
prices = np.random.randn(1000)
window_len = 250
for train, test in rolling_window(prices, window_len):
# 在此处估计 VaR 模型参数
# 计算 VaR 并与 test 比较
pass
避坑提示:
- 确保训练集与测试集严格不重叠,避免未来信息泄露。
- 在滚动窗口中,如果步长大于 1,需注意测试集之间可能存在时间间隔,影响异常事件捕获。
- 扩展窗口在数据量极大时(如 10 年以上日频数据),计算量可能过大,可考虑使用“渐进式扩展窗口”(每 N 期扩展一次)。