2. 回放验证的数据准备:历史数据清洗、数据对齐、时间窗口划分
回放验证(Backtesting)的核心在于用历史数据模拟模型在真实环境中的表现。数据准备的质量直接决定验证结论的可信度。本节聚焦三个关键环节:历史数据清洗、数据对齐与时间窗口划分,确保回放过程不引入前瞻偏差(Look-Ahead Bias)或数据泄漏。
2.1 历史数据清洗:剔除噪声与异常
原始保单与理赔数据通常包含录入错误、重复记录、缺失字段和极端异常值。清洗步骤应遵循“先结构后内容”的顺序:
- 去重:按保单号+出险日期+赔案号联合去重,保留最新修改记录。
- 缺失值处理:关键字段(如保额、费率因子、出险时间)缺失时,优先追溯源系统补全;无法补全的整条记录标记为“不可用”并记录剔除原因。
- 异常值截断:对理赔金额、保额等连续变量,使用分位数法(如99.5%分位)或业务阈值截断,避免极端值扭曲模型评估指标。
- 业务规则校验:例如出险日期不得早于保单生效日,理赔金额不得大于保额等。
精算提示: 清洗日志必须保留原始值与清洗后值的映射关系,便于审计与回溯。建议使用版本控制管理清洗脚本。
2.2 数据对齐:消除时间与口径差异
回放验证要求模型输入特征与历史观测值在时间点、聚合粒度、业务定义上完全对齐。常见对齐维度如下:
| 对齐维度 | 常见问题 | 对齐方法 |
|---|---|---|
| 时间戳对齐 | 保单生效日、出险日、报案日、结案日混用 | 统一使用“评估基准日”作为时间锚点,其余日期仅用于衍生变量 |
| 聚合粒度对齐 | 模型按保单粒度训练,但回放数据按赔案粒度汇总 | 将赔案数据按保单号+评估期聚合为保单-期记录 |
| 业务口径对齐 | 历史数据中“已赚保费”计算方式发生过变更 | 统一使用最新口径重新计算历史字段,或分段使用不同口径 |
| 特征衍生对齐 | 模型训练时使用了未来信息(如后续理赔次数) | 严格限定特征只使用评估基准日之前的信息 |
常见陷阱: 数据对齐中最容易被忽视的是“特征衍生时的未来信息”。例如用整个保单年度的理赔次数预测中期退保概率,这在回放中会高估模型效果。务必对每个特征做“时间戳穿透检查”。
2.3 时间窗口划分:训练、验证与回放期
回放验证需要将历史数据划分为三个互不重叠的时间窗口,模拟模型上线后的真实预测场景。下图展示了标准的三段式划分结构:
窗口划分的常见策略包括:
- 固定窗口法:训练期固定,回放期向后滚动。适用于业务环境相对稳定的场景。
- 扩展窗口法:每次回放时训练期向前扩展,保留更多历史信息。适用于数据量较小的场景。
- 滑动窗口法:训练期与回放期同步向前滑动,保持窗口长度不变。适用于快速变化的市场环境。
以下代码示例展示了使用 Python 实现固定窗口划分的典型逻辑:
import pandas as pd
from datetime import timedelta
def split_backtest_windows(df, date_col, train_end, test_start, test_end):
"""
固定窗口划分:训练期 [min_date, train_end]
回放期 [test_start, test_end]
"""
train = df[df[date_col] <= train_end].copy()
test = df[(df[date_col] >= test_start) & (df[date_col] < test_end)].copy()
return train, test
# 示例:训练期截止 2022-12-31,回放期 2024-01-01 至 2024-12-31
train_data, test_data = split_backtest_windows(
df=policy_data,
date_col='评估基准日',
train_end='2022-12-31',
test_start='2024-01-01',
test_end='2024-12-31'
)
2.4 数据准备流程总览
将上述三个环节串联起来,形成完整的数据准备流水线:
最佳实践: 建议将数据准备流水线封装为可重复执行的脚本或 DAG(如 Airflow),每次回放验证前重新运行,确保数据版本与模型版本一一对应。使用 YAML 配置文件管理窗口起止时间、清洗阈值等参数,避免硬编码。