← 返回目录
p1.7动手⏱ 约 17 分钟

PIT 数据与财报时点

一种不会被任何价格检查抓到的未来函数

🔎 最后验证 2026-07📚 来源:财报时点与提前期收益于 2026-07-31 在 ECS 实测(12 只沪深300 成分股,288 份季报,2019~2024),输出见 code/outputs/stdout.txt🧰 baostock、pandas 3.0.5、Python 3.12.13
为什么学这个

你的价格数据干净得无可挑剔:八项体检全过,日历一天不差,未来函数两把尺子都测过,一个 shift 都不缺。

然后你写了这样一行:

factor = fundamentals.set_index("statDate")["roe"]     # 按报告期对齐

实测:在这只样本股上,这一行让你的因子在 47.8% 的交易日里,握着一个当时还没有公告的数字

平均而言,你提前 54 天知道了业绩。年报是 102.5 天

而在这段提前期里,股价平均波动 8.25%——一年四份报告,如果每次都站对方向,那是年化 33% 的"免费"超额。

这就是 PIT 问题。它不会被任何价格检查抓到,因为价格本身完全没有问题。

💡 打个比方

考试成绩是 6 月 20 日考完的,但成绩单 7 月 15 日才寄到家。

如果你在复盘"我妈知道成绩后的反应",却把她的反应记在 6 月 20 日那天——你就凭空给了她 25 天的先知

那 25 天里发生的每一件事,都被错误地归因了。

一、报告期 ≠ 披露日

财务数据有两个日期,任何一个数据源都会同时给你:

  • statDate 报告期:数据描述的是截至哪一天的经营状况(如 2024-03-31)
  • pubDate 披露日:这份报告实际公告的日子(如 2024-04-28)

实测 12 只沪深300 成分股、288 份季报(2019–2024):

平均滞后 54.0 天 | 中位 31 天 | 最短 22 天 | 最长 121 天
分位:P25 29 天 | P75 66 天 | P95 118 天

按报告期分季度看,差异极大:

报告期份数平均滞后最长
Q1(截至 3-31)7228.3 天30 天
Q2(截至 6-30)7256.2 天62 天
Q3(截至 9-30)7229.2 天31 天
Q4(截至 12-31)72102.5 天121 天

年报滞后最久——年报披露截止日是次年 4 月 30 日,所以 12 月 31 日的数据,你可能要到四个月后才真正拿得到。

如果你按 statDate 对齐,就等于在这 102 天里,提前握着全市场的年报业绩。

二、这个泄漏值多少钱

光说"不对"没有说服力。我们把它换算成钱。

对每一份报告,计算 statDate 到 pubDate 之间的股价涨跌(后复权):

可计算样本 288 份
提前期平均涨跌 +0.36% | 中位 -1.01%
绝对值平均 8.25% ← 这才是泄漏的信息量
区间最大 +114.2% | 最小 -34.2%

注意为什么要看绝对值:平均涨跌只有 +0.36%,看起来无关紧要。但泄漏的价值不在方向,在信息量——你提前知道业绩好坏,就能选择做多或做空。

每份报告的提前期平均波动 8.25%,一年四份。若每次都站对方向,年化约 33% 的"免费"超额。

⚠️ 避坑

这 33% 不是策略收益,而是「信息提前量」本身的价值。

它意味着:如果你的回测里有 PIT 泄漏,那么你看到的超额收益中,有相当一部分不是策略的能力,而是这 33% 的一个分成

更麻烦的是:这类泄漏做出来的回测,曲线会非常漂亮而且很稳——因为它每个季度都能"预知"一次业绩。稳定的超额比疯狂的超额更容易骗到人,包括骗到你自己。

🔧 动手做:量一量"提前知道业绩"这个未来函数(5 分钟)

python pit_leak.py。它统计财报的报告期 → 披露日滞后。

你会看到(实测):12 只样本股 288 份季报,报告期到真正披露平均滞后 54 天,中位 31 天,年报最长 121 天(Q4 平均 102.5 天)。如果你的因子按报告期对齐(而不是披露日),你就等于提前 54 天用上了当时还没公布的业绩

想明白:这是一种任何价格检查都抓不到的未来函数——价格全对、shift 也做了,但你用的财务数字在那个时间点根本还没公开。修法不是"记得对齐",是让错误对齐在数据层就不可能:每个财务值都带上它的披露日,取数时只允许取"披露日 ≤ 当前"的。基本面因子,先问:你对齐的是报告期还是披露日?

三、同一个因子,两种对齐

拿一只样本股的 ROE 因子,两种对齐方式各铺一条时间序列:

wrong.loc[idx >= r["statDate"]] = val    # ❌ 报告期当天就"知道"
right.loc[idx >= r["pubDate"]]  = val    # ✅ 披露日之后才知道

实测结果:

两种对齐取值不同的交易日:888 天 / 1856 天 (47.8%)

将近一半的交易日,两个版本的因子值是不一样的。

不是偶尔差一点,是接近一半的时间,错误版本握着一个当时还没公告的数字。

四、修法:让错误的对齐在数据层就不可能发生

单靠"记得用 pubDate"是不够的——这正是 p0.3 那条教训:人记不住。工程上的做法是把生效日写进数据模型:

CREATE TABLE fundamentals (
    symbol VARCHAR,
    stat_date DATE,           -- 报告期:只用于说明这份数据描述的是哪一期
    effective_date DATE,      -- 生效日 = pubDate:下游只允许用这个 join
    roe DOUBLE, revenue DOUBLE, ...,
    source VARCHAR, fetched_at TIMESTAMP,
    PRIMARY KEY (symbol, stat_date)
);

三条纪律:

  1. 入库时就把 pubDate 写成 effective_date,不要留给下游去理解
  2. 下游 join 一律用 effective_date,stat_date 只用于展示和分组
  3. 同一份报告有修正版时(如更正公告),按新的 pubDate 再插一条,而不是覆盖——因为"当时你看到的"确实是旧版本

第三条是完整 PIT 的核心,也是最容易被忽略的:真正的 PIT 数据库,记录的不是"这个数字是多少",而是"在每一个时刻,你能看到的这个数字是多少"。

❓ 测验
你的多因子选股回测年化 25%,曲线非常稳,每个季度都有正超额。因子里包含 ROE、净利润同比等财务指标。最该先查什么?
✏️ 填空
财务数据入库时,应把披露日写成 ___ 日,下游 join 一律用它,报告期只用于展示与分组。

📌 免责:本课为技术教学,样本为沪深300 成分股按代码序取前 12 只(非精选),仅用于统计演示,不构成任何投资建议。

✅ 小结

P1 章最硬的一节结束。四个数字:

  • 54 天:报告期到披露日的平均滞后(中位 31,最长 121)
  • 102.5 天:年报的平均滞后——你可能四个月后才真正拿到 12 月 31 日的数据
  • 8.25%:提前期内股价的平均绝对波动 → 年化约 33% 的"免费"超额
  • 47.8%:两种对齐下因子取值不同的交易日占比

一条修法:把 pubDate 写成 effective_date,下游只允许按它 join;有修正版就再插一条,不覆盖——因为"当时你看到的"确实是旧版本。

下一节是 P1 的收尾:绘图工程。听起来最软,但它承担一个硬任务——让数据的问题自己跳出来。这一章讲的所有坑,除权跳空、停牌占位、日历断档,在正确的图上都是一眼可见的;而在一张默认参数的折线图上,它们全都藏得很好。

下一节 → 绘图工程:让图自己说出问题
🔎 来源与核验· 5 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「12 只沪深300 成分股 288 份季报(2019~2024):报告期→披露日平均滞后 54.0 天,中位 31 天,最短 22 天,最长 121 天;P95 为 118 天」
📚 本节 code/pit_leak.py,2026-07-31 于 ECS 实测(baostock query_profit_data 的 statDate/pubDate),输出见 code/outputs/stdout.txt✓ 已核验 2026-07
「分季度平均滞后:Q1 28.3 天、Q2 56.2 天、Q3 29.2 天、Q4(年报)102.5 天,年报最长 121 天」
📚 同上实测✓ 已核验 2026-07
「statDate→pubDate 提前期内股价(后复权)绝对涨跌平均 8.25%,均值 +0.36%,中位 -1.01%,区间 -34.2%~+114.2%;按一年 4 份估算,信息提前量的年化价值约 33%」
📚 同上实测。注:33% 为「每次都站对方向」的上界估算,非可实现收益✓ 已核验 2026-07
「同一只样本股的 ROE 因子,按 statDate 与按 pubDate 对齐,有 888/1856 个交易日(47.8%)取值不同」
📚 同上实测✓ 已核验 2026-07
「A 股年报披露截止日为次年 4 月 30 日」
📚 交易所信息披露规则(公开)✓ 已核验 2026-07
提前期收益统计为历史模拟,用于量化数据对齐错误的影响,不代表任何可实现的收益。
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · p1.8
绘图工程:让数据的问题自己跳出来
继续读下一节 →