跳转至

全市场股票一夜之间"集体假跳变":一次数据基准污染的溯源与治理

现象

我在做一组趋势策略的两年窗口验证时,其中一个"月频追涨"变体跑出了两年累计 +67.7%、年化 +30.9% 的成绩。这个数字好到不像话——好到我第一件该做的事是怀疑它,而我差一点直接把它写进结论。

真正的转折发生在检查数据时:绝大多数股票在同一个交易日出现了远超涨跌停物理界限(±21%)的"跳变",最极端的单日假跳变达到 +156%。A股单只股票一天最多涨 10%(主板),+156% 在物理上不可能。也就是说,跨这个交易日的一切收益、因子、净值计算,全部建立在一个被无声改写的价格基准上。

直觉判断的盲区

把两年 +67.7%、年化 +30.9% 摆在面前,最自然的判断是:因子有效,接下来该考虑加仓或扩参。这个判断并不愚蠢——策略逻辑逐行看没有问题,回测框架跑过多次,凭什么先怀疑数据?

它的盲区只有一个,但足够致命:默认了"数据是诚实的"。整个排查里最值钱的转折,是把问题从"策略为什么这么好"换成"这个好是不是真的"——而触发转折的,是一条物理常识:A股单日涨幅有制度上限,+156% 不可能发生。直觉不会主动去算物理界限,因为直觉默认数据没病。

至于当初写合并脚本时为什么没有"基准版本"的概念,已经无法还原——它不是某个刻意的错误决定,只是当时的视野里根本不存在"复权锚点会漂移"这个问题。局限本身不可怕,可怕的是局限落在了整个系统的地基上。

排查与关键证据

排查分三步,每步都有硬证据:

第一步,不可能事件探测。 以涨跌停物理界限作为"不可能事件"的判据:单日 |ret| 超过 ±21%,数学上不可能是正常行情。用它全市场扫描,发现跳变高度集中在同一天,且市场级占比极高——两个受影响批次里,一个约有两成股票同日假跳变,另一个接近九成。个股层面孤立出现的大幅跳变(长期停牌复牌、真实除权)是另一回事,必须区分开。

第二步,对照实验。 同一策略、同一参数,只在数据上做"污染版 vs 清洗版"对照:+67.7% 直接反转为约 -37%。收益方向都变了——所谓"策略有效"从头到尾是数据在撒谎。后来在生产数据路径上复跑两年四臂对照:四条策略全部深度负收益,而同期沪深 300 是大幅正收益。结论稳固,且和测试环境里的清洗结论互相印证。

第三步,根因定位。 顺着"为什么全市场会在同一天重锚定"往上挖,链条是:

  1. 采集器用的是前复权(qfq)价格——它不是绝对价格,而是"以最新除权日为锚的相对量",每次除权,整条历史都会被重新计算;
  2. 数据同步停摆了一段时间(一次脚本重组震断了定时任务);
  3. 恢复时做了全量补拉——新拉的数据用新锚点,本地存量是旧锚点;
  4. 合并脚本在拼接边界把两段不同锚定的历史缝在一起 → 全市场基准切换。

根因

分三层写,一层比一层重要:

  • 直接原因:不同复权锚定的数据段在合并边界拼接。
  • 语义原因:前复权价格的隐含合约是"锚点会漂移"。任何人做全量补拉、换时间窗重拉,都可能触发同样的事故。这不是某个人的失误,是 qfq 语义 + 补拉时机的必然组合。
  • 组织原因:我的数据存储里没有"基准版本"这个概念。合并脚本默认"新旧数据同源同锚",这个隐式合约从来没有被显式声明过,所以也从来没有人检查过它。

约束与取舍

治理方案是在约束下选出来的,每个取舍都有代价:

  1. 放弃标准复权,采用启发式拼接。 复权因子目录里只有几个指数片段,个股因子缺失,标准复权路线走不通。改为启发式:判据 = 单日超物理界限 + 市场级占比门控,以"个股自己的跳变行"为锚,逐股修复——缺口内确实存在市场级切换日才修,跨多个批次切换日也不会重复缩放。代价:判据有漏判误判风险,只能靠双路径互证兜底。
  2. 孤立跳变保留不修。 约 5.5 万个孤立跳变(真实除权、复牌行情)原样保留并告警。代价:留存少量真实噪声;换到:不误伤真行情。这条边界是整个方案里最重要的决定——修数据的最高原则不是"修干净",是"不把真的修成假的"。
  3. 改历史数据 = 全量备份 + 全量扫描。 修复了 4895 个文件、约 1524 万行 OHLC,pre_close/pct_chg 派生列按修复后价格重算。代价:计算量和审慎成本;换到:可回滚(备份包至今保留)。
  4. 存量修复 + 合并端防复发双修。 只修存量,下次补拉还会复发;所以合并脚本同步加固——逐行记录来源分段,落在分段边界的疑似假跳变自动拼接对齐,孤立跳变保留并告警。代价:合并逻辑变复杂,且需要合成端到端测试保护(假跳变修平、真跳变保留,两条断言都必须有)。

开发过程中还踩了三个实现坑,值得一笔带过:用 pct_change 做差分时按位置移位,被污染文件的缺口让跳变日收益恒为 NaN、检测全瞎(要先填充再移位);按文件字典序抽样会整批落在未感染批次(要随机抽样加固定种子);市场切换日误加到未感染股(要逐股门控)。

最终验证闭环:全市场复检无残留基准切换;读取端拼接逻辑自动退化为空操作(检测不到切换日就不动手);两条独立路径的修复结果逐位一致。

可复用检查清单

  1. 任何"全量补拉/重导/迁移"操作,默认假设它会改变存量数据的隐式基准——合并边界必须逐行溯源;
  2. 用物理界限(涨跌停、汇率制度、合约涨跌幅)做"不可能事件"探测器,比任何业务阈值都可靠;
  3. 个股异常先二分:"市场级同日共振"多半是基准问题,"孤立跳变"多半是真行情——修前必须逐股门控;
  4. 数据修复必须配合成端到端测试:假异常应被修平、真异常必须原样保留,两条断言缺一不可;
  5. 修复结论用两条独立路径互证(写入端 vs 读取端),单点自证不算数。