跳转到内容

财务与公告数据:时点正确性

财务数据的两个时间戳:

  • 报告期(period end):数字所属的季度末;
  • 公告日(announcement date):数字第一次对外可见的日期,通常滞后 1–4 个月。

在 tt 时刻的策略只能使用公告日 ≤t\le t 的数字——以报告期对齐,等于让 2010 年的策略"提前 4 个月"看到了一季报,是教科书式的前视偏差。

追溯修正:为什么只存最新值是错的

Section titled “追溯修正:为什么只存最新值是错的”

历史财务数据会被追溯修正(重述、审计调整、口径变更)。若数据库只保留每个报告期的最新值,会出现两种叠加的偏差:

  1. 前视:用修正后的数字代替当时市场看到的数字;
  2. 幸存者偏差:被重述"抹掉"的原始版本消失,历史看起来比当时更干净。

Point-in-time 数据库为每个事实保留完整的版本历史:

fact(conn_id="600519", field="net_profit", period="2010Q1")
├─ v1 公告日 2010-04-25 值 = 12.6 亿 ← t ∈ [2010-04-25, 修订日) 应使用这条
├─ v2 修订日 2010-08-20 值 = 12.4 亿
└─ v3 修订日 2011-03-30 值 = 12.7 亿 ← 只有"今天"的策略才能用

取数一律用 as-of 查询:给定策略时刻 tt,取公告日 ≤t\le t 的最新版本。

pit.py
import pandas as pd
def as_of_value(versions: pd.DataFrame, t: pd.Timestamp) -> pd.Series:
"""versions: 需含 announce_date / revised_date / value 列。取 t 时刻可见的最新版本。"""
visible = versions[versions["announce_date"] <= t]
return visible.sort_values("announce_date").iloc[-1] if len(visible) else None
  • 行业分类:公司的行业归属随时间变化,当前分类回溯会扭曲行业中性化;
  • 指数成分:用今天的成分名单回测历史是幸存者偏差,见回测中的典型偏差;
  • 股票状态:退市、ST、停牌的历史轨迹必须保留,否则低价股与退市股票被系统性剔除。

商用 PIT 数据库(如 Compusta­-at、Wind 点选版)昂贵;自建的最小可行方案是从公告日开始累积、永不覆盖、只追加版本。对个人研究者,A 股的公告日可从交易所公告抓取,见存储格式与查询。

  • 一切基本面因子以公告日对齐,复核脚本抽查 10 只股票的公告日 vs 使用日;
  • 永不覆盖:每次修订追加新版本;
  • 行业、成分、状态三类维表同样按 as-of 查询。
  • 以公告日而非报告期对齐
  • 保留每次修正的版本(as-of 时间戳),永不覆盖
  • 行业分类、指数成分也需要 PIT 版本