跳到主要内容

数据快照与避免前视偏差(Point-in-Time)

直觉

模块 0 提过前视偏差——这里从数据角度再讲一遍:很多数据在「事后」会被修订(财报先出预披露、后修正;公司被并购后从数据库消失)。如果回测时直接用「今天看到的数据」,等于偷看了未来。Point-in-Time(PIT)数据就是给每个数据点打上「何时可得」的时间戳,回测只用当时已公开的版本。

两类前视

  1. 特征前视:用「包含当日」的信息生成当日特征(如用当日收盘算当日均线再当日交易);
  2. 修订/幸存者前视:用了后来才修订或后来才筛选过的数据。
「人话」解释:特征前视怎么躲?

记住一条铁律:当日只能用「昨日及之前」的信息。生成特征后,再整体 .shift(1) 一下,确保「今日用昨日特征」。 我们的回测引擎 quant.vector_backtest 内部正是用 持仓 = 信号.shift(1) 实现这点(见 引擎验证)。

可运行案例:前视特征 vs 安全特征

下面演示两种「20 日均线」特征:一种泄漏(用了当日收盘),一种安全shift(1))。泄漏版「预测」当期收益时相关性强得离谱——这正是它不可信的原因。

import numpy as np
import pandas as pd

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
close = df['close']
ret = np.log(close / close.shift(1))

ma20 = close.rolling(20).mean()

# 特征 = 价格偏离均线的程度
feature_leaky = (close / ma20 - 1)            # 含当日收盘 → 泄漏
feature_safe  = feature_leaky.shift(1)        # 只用到昨日的均线 → 安全

d = pd.DataFrame({'ret': ret, 'leaky': feature_leaky, 'safe': feature_safe}).dropna()
print("当期收益 与 特征 的同期相关(越高越'像能预测'):")
print(f"  泄漏版特征 corr = {d['ret'].corr(d['leaky']):+.3f}   ← 虚高, 不可信")
print(f"  安全版特征 corr = {d['ret'].corr(d['safe']):+.3f}   ← 真实可用信号强度")

print("\n→ 差距来自: 泄漏版用'今日收盘'算'今日特征', 再与'今日收益'对比, 当然高相关。")
print("   shift(1) 后, 用'昨日特征'预测'今日收益', 才是诚实的评估。")

动手改一改

把均线窗口从 20 改成 5,再对比泄漏版与安全版的相关——窗口越短,泄漏版的「虚假预测力」往往越夸张,更能凸显 shift(1) 的必要。

小结

  • 前视分特征前视(当日用了当日信息)和修订/幸存者前视
  • 防特征前视:生成特征后 .shift(1),只用「昨日的」信息;
  • PIT 数据给每个数据点打「可得时间戳」,是严肃回测的基础设施。