因子投资-实验06-识别未来函数与幸存者偏差

QUANT RESEARCH NOTE LAB 06

并排构造两条财报管线:错误管线按 report_period 连接,正确管线只允许 announce_date 不晚于决策日。再比较“只保留期末仍上市股票”和历史股票池收益。目标是让两类偏差变成可失败断言,而非抽象警告。

类型动手实验
模块研究实验
阅读约 7 分钟
材料9 节 · 1 段代码

问题

并排构造两条财报管线:错误管线按 report_period 连接,正确管线只允许 announce_date 不晚于决策日。再比较“只保留期末仍上市股票”和历史股票池收益。目标是让两类偏差变成可失败断言,而非抽象警告。

输入数据

两只股票 A、B 的年报报告期均为 2025-12-31;A 在 2026-03-20 公告,B 在 2026-04-15 公告。决策日是 2026-03-31。错误表会提前使用 B。

幸存者样本有 A、B、C 三只股票,C 在第二月亏损 50% 后退市。期末名单只有 A、B;若回溯删除 C,历史等权收益被抬高。

逐步实现

错误连接条件是 report_period≤decision_date,它只检查会计期间,不检查市场知道时间。正确连接先过滤 announce_date≤decision_date,再按股票取最新公告。

幸存者测试不需要复杂回测:对每月历史 eligible 股票等权,与先筛选 end_survivor 的结果比较即可。

完整代码

PYTHON 可复现实验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import numpy as np
import pandas as pd

decision_date = pd.Timestamp("2026-03-31")
fundamentals = pd.DataFrame({
"asset": ["A", "B"],
"report_period": pd.to_datetime(["2025-12-31", "2025-12-31"]),
"announce_date": pd.to_datetime(["2026-03-20", "2026-04-15"]),
"ep": [0.08, 0.12],
})

wrong = fundamentals.loc[
fundamentals["report_period"] <= decision_date
].copy()
wrong["decision_date"] = decision_date
wrong["future_announcement"] = wrong["announce_date"] > wrong["decision_date"]

correct = fundamentals.loc[
fundamentals["announce_date"] <= decision_date
].copy()
correct["decision_date"] = decision_date
correct["future_announcement"] = (
correct["announce_date"] > correct["decision_date"]
)

returns = pd.DataFrame({
"month": [1, 1, 1, 2, 2, 2, 3, 3],
"asset": ["A", "B", "C", "A", "B", "C", "A", "B"],
"return": [0.02, 0.01, -0.05, 0.01, 0.02, -0.50, 0.03, 0.01],
"eligible": [True] * 8,
})
end_survivors = {"A", "B"}
historical = returns.loc[returns["eligible"]].groupby("month")["return"].mean()
survivor_only = (
returns.loc[returns["asset"].isin(end_survivors)]
.groupby("month")["return"].mean()
)
comparison = pd.DataFrame({
"historical": historical,
"survivor_only": survivor_only,
})

assert wrong["future_announcement"].sum() >= 1
assert correct["future_announcement"].sum() == 0
assert set(correct["asset"]) == {"A"}
assert survivor_only.mean() > historical.mean()

print(wrong[["asset", "future_announcement"]].to_string(index=False))
print(correct[["asset", "future_announcement"]].to_string(index=False))
print(comparison.round(6).to_string())

预期输出

错误管线 A、B 都出现,B 的 future_announcement=True;正确管线只有 A,未来公告计数为零。历史股票池包含 C 的退市前损失,平均收益低于只保留 A、B 的回溯样本。

断言

  1. 错误管线至少使用一个未来公告。
  2. 正确管线未来公告为零,且只保留 A。
  3. 期末幸存股票回溯收益高于历史股票池。
  4. 日期和股票键应唯一;真实管线还需断言首次公告版本与日内可用时间。

结果解释

错误表不是因为数值错,而是因为“在未来才公开的正确数值”被放到了过去。幸存者管线也不是计算错,而是研究总体被期末名单重写。两者都无法靠稳健标准误修复。

正确历史股票池并不一定收益更低;本例故意让退市股亏损以显露偏差。真实研究的断言应检查成员身份与日期,而不是规定收益方向。

常见故障

  • announce_date 只有日期,盘后公告仍假设当日可用。
  • 修订财报覆盖首次公告。
  • merge 后同一股票保留多版本。
  • 退市股收益缺失后被 dropna。
  • 指数历史成分用当前名单。
  • 用“结果应该更差”代替结构断言。

练习

  1. 加入盘中/盘后时间戳和次日开盘成交。
  2. 同一报告期加入修订版本,做 point-in-time 版本选择。
  3. 让 C 退市前上涨,验证结构断言仍通过而收益方向改变。
  4. 比较当前指数成分与历史成分回测。
  5. 将错误和正确管线封装为单元测试。

因子投资-实验06-识别未来函数与幸存者偏差
https://www.polarishaw.top/2026/07/17/因子投资-实验06-识别未来函数与幸存者偏差/
作者
Polaris
发布于
2026年7月17日
许可协议