SEC Filing Triage — a leakage audit on 11,702 real 8-Ks

A pipeline written the obvious way reports an ROC AUC of 0.888 on real SEC filings. The version that survives its own leakage audit reports 0.768 — and every one of the four bugs behind the gap produced a better number, not a crash.

泄漏审计 · 真实 SEC 数据

一个 0.888 的 AUC,全是 bug

早餐前落地四十份 8-K,分析师只有时间读五份。这个项目按市场即将做出的反应强度给它们排序——是量级,不是方向。第一版效果好得离谱,而这正是问题所在。

为什么不预测方向

预测涨跌是在跟数据更快、模型更好、资金更多的交易台对赌,安静地输是常态。哪些披露值得人读,是个能回答的问题。

ROC AUC · 天真流程

0.888

按最自然的方式写出来的版本

ROC AUC · 通过自身审计后

0.768

移除四个 bug 之后

真实 8-K 申报

11,702

193 家发行人 · 2022–2026

测试 · 泄漏审计进 CI

123

一处泄漏就让构建失败

每一个 bug 都让数字更好看

公开披露数据上跑出接近 0.9 的 AUC,本该是个了不起的发现。它其实是四个各自独立的缺陷——而且没有一个会自己出声,因为泄漏的失效方式不是崩溃,是一个让你满意的结果。

仍在泄漏审计通过
天真流程
0.518
+ purged 交叉验证
0.482
+ 滚动窗口右移
0.263
+ 时点股票池
0.263
+ 时点入场
0.345

平均精度(average precision)。每一根柱子只移除一项流程本不该拥有的特权。

阶段平均精度ROC AUC未通过的守卫
天真流程0.5180.8881
+ purged 交叉验证0.4820.8711
+ 滚动窗口右移0.2630.7911
+ 时点股票池0.2630.7911
+ 时点入场0.3450.7680

股票池那一行纹丝不动——前后都是 0.262916,小数点后六位一致。这是幸存者样本在自首:当文件里每一家发行人都还活着,时点成分股过滤器就没有东西可以移除。流程报告了自己的盲区,而不是掩盖它。

入场那一行反而上升。修正入场时点的同时也修正了结果的度量窗口,所以它既移除了不可能的成交,也让标签更干净。这正是那一档的真实成果不在分数上的原因。

那个指标抓不到的 bug

EDGAR 给你三个时间戳,只有一个是「知道的时间」。用申报日期而不是受理时间戳,分数几乎不动——而流程已经在一个早就发生过的开盘价上建了仓。

第 1 天交易时段第 2 天09:3016:0020:0009:308-K 受理,20:00盘后 —— 五分之四的申报落在交易时段之外天真:在申报日期的开盘价建仓比申报存在早 10.5 小时时点:受理之后的第一个开盘
红色箭头指向时间的反方向。整个 bug 就是这一件事。

在申报公开前就定价的成交

95.6%

天真规则下 11,681 笔中的 11,168 笔

偷看时长中位数

10.6 h

开盘价与申报时刻之间

改用时点入场之后

0

由测试固定住的不变式

正确的那个字段里还埋着第二个陷阱。EDGAR 把受理时间写成 2024-10-31T18:03:31.000Z,但那个时钟是 SEC 自己的,跑在 America/New_York 上。把这个 Z 当 UTC 解析,会让每一份申报整体前移五小时,并悄悄把盘后那一大批重新归类成盘中。

反应是怎么测的

一个标准的市场模型事件研究,中间刻意留了一道缝。

估计窗 · 120 个交易日间隔 · 20事件 · 2入场交易日非等比示意 —— 2 日事件窗若按 120 的真实比例画,只有九个像素宽。没有那道间隔,公告前的漂移会污染基线,把要测的异常本身压小。

反应强度是 |CAR| / 残差标准差——以该发行人自身噪声的标准差为单位,所以一只沉闷的公用事业股和一只生物科技股落在同一把尺子上。落进前十分位的申报算「值得读」,这个阈值在全样本上算一次,绝不在折内重新估计。

特征,以及藏在里面的一个泄漏决策

是哪一类消息

8-K item 代码。由申报人自己填写,申报落地那一刻就可读。

什么时候落地

盘前、盘中、盘后,还是非交易日。发布时点是发行人自己的选择,本身带信息。

有多不寻常

与该发行人自己历史申报的余弦距离。套话模板得分接近零。

发行人状态

滚动波动率、相对成交量、发文频率——全部截止到入场前一个交易日。

文本新颖度用的是无状态的 HashingVectorizer,不是 TF-IDF——这是个泄漏决策,不是性能决策。TF-IDF 必须先 fit,而在全语料上 fit 出来的向量器,携带着那些当时还没被写出来的文档的词频信息。

通过审计的模型值多少

优于随机读五份

1.56×

15.6% 对 10% 的基准率

每个交易日的申报数中位数

9

769 个交易日拥挤到足以排序

样本外评分的申报数

9,729

purged walk-forward · 5 折

有用。但不是交易策略,也没有声称是。这两句话之间的距离,就是这个项目的大部分内容。

其余的申报去哪了

摄入 11,702 份,评分 9,729 份。差额逐项列出,而不是留给读者去做减法——因为一个没人解释的下降,和一个 bug 无法区分。有一条测试断言这本账必须平。

去向申报数
样本外评分9,729
walk-forward 保留为纯训练集1,945
事件窗超出价格数据末端15
事件窗内缺 K 线7
入场交易日无 K 线6

最大的那一行是这个切分方式的诚实代价:walk-forward 只测试第 1…n 折,所以最早的那个时间块永远只是训练数据。shuffle K-fold 能给全部 11,681 份打分——代价是训练时看了未来。

这个项目不声称什么

不预测收益。 方向从未被建模。仓库里任何地方都不出现策略收益、夏普比率或 P&L。

真实数据这条路上,幸存者偏差没有被控制。 股票池是 200 支手挑的、今天仍然存在的大盘股——这正是上面时点股票池那一行纹丝不动的原因。那一类泄漏改在合成语料上测量,那里成分股有进有出。股票池文件在旁车文件里记录了这个局限,ingest 把它带进运行的 provenance,报告在横幅里写明。

只有日线数据。 入场规则——决策时刻之后的第一个开盘——是这个数据精度下最保守的约定。

全部数字来自一次真实的 EDGAR 拉取:11,702 份 8-K,193 家发行人,2022–2026,价格来自 yfinance。源码、方法论与泄漏专题见 GitHub.