1. 课程导入
在金融研究尤其是量化策略开发中,数据是策略的“燃料”,但也是最容易被误用的环节。许多回测曲线漂亮、实盘却迅速失效的策略,根源往往不在模型本身,而在于研究阶段引入了未来信息,或训练数据与验证数据之间发生了泄漏。本课程围绕“防未来函数”与“防数据泄漏”两条主线,帮助研究者建立一套可落地、可审计的数据使用规范。
一、学习目标
完成本课程后,你应当能够独立完成以下任务:
- 识别金融时间序列中常见的未来函数形态,包括前视偏差、幸存者偏差、财报披露时点错配等;
- 理解数据泄漏在特征工程、标准化、缺失值填充、超参数搜索等环节的发生机制;
- 掌握基于时间切分的训练集、验证集、测试集划分方法,并能正确使用滚动窗口与扩展窗口;
- 能够为因子计算、标签构造、回测撮合编写符合“当时可得信息”约束的代码;
- 建立数据血缘与实验记录意识,使研究过程可复现、可复盘、可审计。
核心判断标准:在任意一个历史时点 t,策略只能使用 t 时刻及之前已经真实可获得的数据。任何依赖 t 之后信息的计算,无论多隐蔽,都属于未来函数或数据泄漏。
二、核心概念
本课程反复出现的关键概念需要先建立清晰边界。
| 概念 | 含义 | 典型后果 |
|---|---|---|
| 未来函数 | 在 t 时刻的计算中使用了 t 之后才产生或才披露的数据 | 回测虚高,实盘无法复现 |
| 数据泄漏 | 训练阶段接触到了验证集或测试集的信息 | 模型评估过于乐观,泛化能力差 |
| 前视偏差 | 使用当前成分股、当前财务口径回溯历史 | 幸存者偏差、指数成分漂移失真 |
| 时点对齐 | 确保特征、标签、交易信号在时间轴上严格对齐 | 避免“用收盘价预测开盘价”类错误 |
| 滚动回测 | 按时间推进反复训练与验证,模拟真实研究节奏 | 更接近实盘,但计算成本更高 |
其中,未来函数与数据泄漏经常同时出现,但来源不同:前者多发生在单条样本内部的时间逻辑,后者多发生在样本集合之间的信息隔离。二者都需要在代码层面显式约束。
三、整体框架
课程按照“认知—识别—防御—验证—复盘”的路径展开,共 30 章,可划分为五个模块。
- 基础认知模块:建立时间语义、数据可得性、回测可信度的基本判断框架。
- 未来函数识别模块:覆盖行情、财报、公告、指数成分、宏观数据等场景中的典型陷阱。
- 数据泄漏防御模块:讲解特征工程、标准化、缺失值处理、交叉验证中的隔离原则。
- 工程实现模块:通过代码模板、配置约束、日志审计,把规范固化到研究流程中。
- 案例复盘模块:用真实策略片段演示错误如何产生、如何定位、如何修复。
学习建议:每章至少准备一份自己的历史研究代码,边学边对照检查。防未来函数不是理论问题,而是代码审查问题。
四、典型场景与误区
先看一个常见场景。研究者用日频数据构造标签,代码如下:
# 错误示例:用当日收盘价计算未来收益,却把当日收盘特征也放进去
df['future_return'] = df['close'].shift(-1) / df['close'] - 1
df['ma5'] = df['close'].rolling(5).mean()
X = df[['ma5', 'volume', 'pe']]
y = df['future_return']
这段代码本身没有直接错误,但若 pe 使用的是当日收盘后才知道的财报数据,或者 ma5 在 t 日盘中就使用了 t 日收盘价,就会产生未来函数。更隐蔽的是,若在标准化时使用全样本均值方差,则验证集信息泄漏进训练集。
高频误区:“只要用了 shift 就没有未来函数”是错误认知。shift 只解决单列错位,不解决数据披露时点、样本外标准化、超参数选择泄漏等问题。
另一个典型误区是幸存者偏差。若今天获取股票池,再回溯过去五年数据,已经退市或曾被 ST 的股票会被系统性剔除,回测收益会被高估。正确做法是使用历史时点的成分股名单或全市场历史快照。
五、方法流程与工程约束
为了把上述原则落到代码中,建议采用统一的研究流程:
- 明确每个字段的“可得时间戳”,而不仅是“数据时间戳”;
- 按时间切分训练、验证、测试集,禁止随机打乱;
- 所有统计量只在训练窗口内拟合,再应用到验证与测试;
- 标签构造与特征构造分别检查是否跨越信息边界;
- 回测撮合使用下一可交易时点,避免用当日收盘信号当日成交;
- 记录每次实验的数据版本、时间窗口与参数,便于复盘。
# 推荐的时间切分示意
train = df[df['date'] < '2021-01-01']
valid = df[(df['date'] >= '2021-01-01') & (df['date'] < '2022-01-01')]
test = df[df['date'] >= '2022-01-01']
# 标准化只在训练集拟合
scaler.fit(train[features])
train_scaled = scaler.transform(train[features])
valid_scaled = scaler.transform(valid[features])
一句话原则:任何在 t 时刻不可获得的信息,都不能进入 t 时刻的决策路径;任何验证集或测试集的信息,都不能进入训练过程。