数据管道与存储
量化策略的好坏,取决于数据的质量。一套不会在历史数据上撒谎的数据管道长什么样:时间点正确、无幸存者偏差、清洗到位。
垃圾进,垃圾出。量化里的”垃圾”通常指三种东西:幸存者偏差、前视偏差、过期的基本面数据。一套合格的数据管道,要在它们污染你的回测之前就全部干掉。这篇文章就沿着这一个问题展开:数据在哪些环节会对回测撒谎,管道要怎么设计才能让它撒不了。
三类数据,三种撒谎方式
每条量化管道都吃进三类数据:行情、基本面、另类。它们的时序特性不同、供应商的脾气不同、坑的位置也不同——把它们不加区分地混着 join,是回测撒谎的三大常见原因之一。
行情数据——OHLCV、日内、分红、送股——是三类里最干净的,各家供应商都标准化了开高低收、成交量和公司行为。但”干净”不等于”没有坑”。第一个坑是复权方式:各家在后复权还是前复权上的做法不一样,有的只复权送股不复权分红,有的两个都复权但基准不同;不把复权方式钉死,跨次跑出来的回测结果就没法横向比。第二个坑是日内数据比日线脏得多:半天交易日缺 bar、迟到的成交、各交易所的归并规则——东部时间下午 4 点的”官方收盘价”不一定是最后一笔成交的价格,也可能是收盘集合竞价的价格。大部分多空股票策略用日线就够了,高频和统计套利才需要日内,而两者数据量差了 100 倍。第三个坑藏在成交量统计里:场外成交量(暗池、ATS)经常不进 consolidated tape,用 consolidated 成交量算”占 ADV 的比例”会把实际流动性算低,对流动性好的票误差通常有 20–40%。至于 tick 级数据——每一笔成交和报价,美股一天 1TB——不做 HFT 或超短期 alpha 的话根本用不上,别为它折腾存储。
基本面数据——财报、资产负债表、比率——是三类里最棘手的,因为”报告期”和”实际可用日”完全是两回事。这里要区分三个日期。报告日期 是数据描述的期间(例如 2024 Q4),总是季末或财年末;披露日期 是公司向 SEC 提交文件的那天,即法定披露日;可用日期 是你的供应商把数据吃进去并可查询的那天。实际中 ,而 的滞后因公司而异——大市值公用事业 2 周内就交了,小市值生物科技可能要 6 周以上。决策时点你手里的信息只有 之前的,所以 join 必须按 。相当多的策略回测漂亮、上线翻车,根因就是这一个日期用错了——具体怎么错,下一节拿完整例子拆。
另类数据是西部荒野:情绪分数、内幕交易、停车场卫星图、信用卡刷卡数据、期权流、网页流量、专利申请、供应链数据。2025 年的 alpha 越来越多来自这里,因为公开因子已经卷烂了。但它的坑比前两类加起来还多。覆盖度:大部分另类数据集只覆盖你股票池的一个子集,而缺的那部分不是随机的——情绪数据集覆盖 1000 只里的 800 只时,缺的 200 只往往是小盘、少有人研究的票,收益特征跟大盘完全不同,不跟踪每只票的覆盖时段就谈不上无幸存者偏差。质量漂移:供应商会改方法,2018 年的情绪分数和 2024 年的算法可能是两码事,有的供应商 5 年里改了 3 次以上方法还不留版本历史——模型得跟着重训,否则回测作废,买之前先问清楚。前视偏差:情绪分数经常有发布延迟,内幕交易 2 个交易日内必须申报、但供应商可能一周后才入库,数据”as of” X 日、X+7 日才真能用——你要是在 X 日按它交易,用的就是你当时根本没有的信息。最后是价格:单一数据集一年 5 万到 50 万美金是常态,收费模式还经常变。评估要不要买的正确姿势是算三个数——IC、IC IR、容量调整后的 Sharpe。三个数好看,数据就值得买;不好看,再便宜也是贵。
时间点问题:回测撒谎的最大单一来源
上面三类数据的坑各有各的形状,但杀伤力最大的是同一个:用了决策时点还没有的数据。把最经典的例子完整走一遍。
公司 X 在 2024 年 2 月 14 日发布 Q4 财报,超预期 15%。天真的回测把”Q4 财报超预期”和”2 月 14 日的股价收益”join 起来,然后宣布超预期预测了股价走势——看起来很美。现实是:财报是 2 月 14 日收盘后才发的,真正的价格反应发生在 2 月 15 日。你如果在 2 月 14 日用”今天的财报”交易,等于在消息公开之前就动了手。正确的 PIT 处理是:财报在 才可用——取决于供应商,可能是 14 日晚上或 15 日早上——所以在 2 月 15 日交易,不是 14 日。
这 1 天的偏差会在回测里凭空多出一截不存在的 Sharpe。每只票一年财报季大约出 4 次这种事,500 只票、20 年下来,就是 4 万个假信号。
管道层面对付它的办法是把时间戳做成强制的:每一条数据点都带上 (value, as_of),join 一律走 as_of 语义而不是 period 语义;谁的代码按 period(比如季末)join,管道就该给出警告。为复现学术论文而按 join 可以留一个显式覆盖开关,但要落日志。
供应商侧的问题在于:大多数供应商把”T 日发布财报”作为 join key,把 (报告期)和 (你真正能知道的时间)混为一谈。修法有三层。有 point-in-time 表的供应商——CRSP、Compustat、Bloomberg 都有 PIT 标识——直接用,注意有的叫”as-of”日期、有的叫”available”日期,问清楚。没有 PIT 表的,记录披露日期、再加一个供应商入库延迟的 buffer,buffer 多大看供应商的历史表现——有的实时,有的滞后好几天。另类数据则直接问供应商要覆盖日期和方法论历史,说不清什么时候有什么数据的,别买。说到底,不提供 point-in-time 时间戳的供应商等于在告诉你:他们不希望你做严肃回测。换一家。
这件事还可以机械化地验证:挑一个已知事件,比如苹果 2023 Q1 财报(2023 年 2 月 2 日发布),查披露日期,再看你数据集里那一行的 as_of 是不是 2 月 2 日或更晚。如果更早——比如 1 月 31 日——你的供应商在泄露未来信息。这个检查应该写进管道的 CI,在一组已知事件上跑,每次供应商数据刷新后跑一次。
存储格式
数据干净了还得放得对。存储选型由访问模式决定,三个选项覆盖 95% 的量化场景。
Parquet 是 OHLCV、基本面和大部分另类数据的默认选择。列存意味着”给我 ticker X 的所有收盘价”这种查询只从磁盘读一列,不读整行。压缩率对典型行情数据比 CSV 小 5–10 倍——1000 个 ticker、10 年日线 OHLCV,Parquet 大约 50MB,CSV 大约 500MB。字段类型是显式的,不用猜一列到底是 int64 还是 float64,这本身就能抓 bug。查询引擎(DuckDB、Polars、Spark)还能做谓词下推,查”close > 100 美元”时跳过不含匹配值的 row group。分区可以按日期、按 ticker 或两者组合,时间序列一般按日期分区,year=YYYY/month=MM/day=DD/ticker=SYM 是常见方案;压缩用 zstd 或 snappy 都行,选定一个别换来换去。
HDF5 适合另一种访问模式:非常大的时间序列数据集、需要按特定 (日期, ticker) 组合做快速随机访问。pandas 的 to_hdf 和 read_hdf 开箱即用,单文件部署也省事。代价是跟分布式计算不搭、单写限制、schema 演进别扭。单机做研究,HDF5 没问题;要上集群,换 Parquet 加 Iceberg 或 Delta。它适合原型,不适合大规模生产。
Postgres 和 ClickHouse 管的是第三种东西:元数据和查询。股票池定义、ticker 符号、行业分类、退市事件;管道运行记录、数据质量检查、新鲜度时间戳;还有临时查询——“这周发了财报的有哪些票""退市股里收盘价超过 50 美元的有哪些”。ClickHouse 对元数据上的分析型查询更快,Postgres 对运维数据更灵活——JSON 列、事务、外键。常见的搭配是元数据走 Postgres、分析查询走 ClickHouse。但别把 OHLCV 存进 Postgres,除非你有非常正当的理由:它是行存的,分析查询比 Parquet 慢 10–50 倍。
那 CSV 呢?CSV 是个格式,也能用,但做严肃量化它是错的。它没有 schema——同一列上一行是字符串下一行是 float,pandas 的隐式类型转换会在不报错的情况下产生错误结果,你可能回测发出去半年后才发觉。它慢——同样数据,解析 CSV 比读 Parquet 慢 10–50 倍,10 年回测在 CSV 上跑几分钟、在 Parquet 上几秒钟。它对缺失值含糊——"" 算缺失吗?"NA" 算吗?"null" 算吗?CSV 不说,不同解析器各行其是。它没有压缩,规模大了差距很要命。它的日期解析是噩梦——“2024-01-15” 还是 “01/15/2024” 还是 “15-Jan-2024”?时区是 UTC 还是本地?一个都不强制。管道可以兼容加载 CSV,但应该写一条 warning,而那条 warning 要当成 TODO 处理。
五个清洗步骤
存进来之前,每条行情数据管道都要跑同样的五步清洗。顺序有讲究:先把价格序列修对(复权、分红),再把股票池修对(退市),最后处理逐日的数据质量(流动性、缺口)。
第一步,应用复权,默认后复权。2 送 1 的送股发生后,送股前的历史价格要除以 2——美元价值不变,股数变了。后复权把所有历史价格按送股比例调整,序列连续,但送股前的绝对价格水平没有意义,这是分析场景的标准做法;前复权只调整送股后的价格,序列在送股日有一个跳变,适合需要实际价格水平的执行逻辑。两种混着用是回测出错的常见原因,你必须知道自己手里的是哪一种。复权也不只是送股的事:反向拆股、分拆(spin-off)、特别股息都要处理,统一走供应商提供的公司行为表。
第二步,分红调整。现金分红在除息日把股价减掉分红金额,逻辑跟送股一样:分析用后复权,执行用前复权。算因子收益时几乎总该用分红再投资的总收益序列——用价格收益会系统性地偏空高分红的票。特别股息(一次性大额分红)比较棘手,有的供应商当普通分红处理,有的当 return-of-capital,要确认供应商的处理跟你的假设一致。
第三步,退市票留下,不许悄悄丢。退市票应该带着最后已知价格和 delisted=True 标记留在历史股票池里。原因很直接:只看到活下来的票,回测的平均收益就系统性偏高。20 年里 30% 的退市率,意味着你错过了 30% 表现最差的票,幸存者偏差能让回测的账面收益虚高 1–3%/年。检查方法:你的股票池数据应该带 delisted 和 delist_date 列,没有就说明供应商在喂你幸存者偏差的数据。另外,退市票的最后已知价格经常不准——可能是最后一笔成交价而不是最后收盘价,也可能滞后好几天——清洗环节应该把可疑的退市价格标出来让人确认。
第四步,把不流动的日子标成 NaN。有的日子没成交,有的日子有成交但都是旧单,有的日子成交量只有平时的十分之一。处理原则是:价格列标 NaN,不要前向填充(那会在成交量计算里引入前视),另放一个 illiquid 布尔列方便后面过滤。阈值可配,一个合理的默认是:某天成交量低于 60 日中位成交量的 10%,且 spread 大于 60 日中位 spread 的 2 倍。为什么标记而不直接删?因为删掉不流动的日子会改变数据的时间序列性质——在流动日子有效的因子到了不流动日子未必有效。标记之后你有选择:要么包含所有日子,要么只过滤到流动日子。
第五步,前向填充缺失值,但带硬上限。真实数据有缺口——节假日、交易所宕机、供应商宕机——前向填充可以补,但比如最多填 5 天、之后标 NaN。要限的原因是:前向填 30 天的缺口是个合成数据点,不是真实数据,得让模型知道。上限太小会把太多缺口标成 NaN,太大又假装缺口不存在;5 对日线数据是合理默认,具体股票池还得调。
五步跑完,输出才配叫”时间点正确、无幸存者偏差、清洗到位”,可以往特征工程喂了。
从一次性脚本到生产管道
标准管道的形状是四段:拉原始数据、清洗、存储、校验。前三段就是上面讲过的东西串起来;第四段容易被省掉,恰恰不能省——每次产出后跑一个校验报告,看股票池规模、退市票数量、历史覆盖年限中位数、PIT 时间戳覆盖率,供应商偷偷丢了退市票或者 PIT 覆盖率掉到 95% 以下时,第一时间知道。
一次性脚本变成生产管道,还差三样东西。调度:生产环境里管道要在每晚供应商刷新后自动跑,用 cron 或工作流调度器都行。错误处理:供应商只返回了部分文件时,管道要能发现并告警——对缺日期、缺 ticker、数值异常抛显式的数据质量错误,而不是静默吞掉。schema 版本管理:供应商改 schema 管道就会挂,要有迁移机制,别靠手工改字段名续命。
在这之上还有三件运维层面的事。第一是可靠性:95% 时间能跑的管道不够,漏一次刷新就意味着用过期数据交易——所以每次运行后要有健康检查,失败要接进 Slack、PagerDuty 或邮件,漏了一天要有回填机制重跑缺失日期的清洗。第二是数据版本管理:回测依赖某个特定版本的数据,一个月后数据更新了(新增送股、基本面重述),同一个回测跑出不同结果——生产上没问题,可复现性上是灾难。解法是带 version tag 存数据,回测代码把版本钉死,想要哪天的快照就加载哪天的。第三是延迟:日线策略过夜跑就行,日内策略就很在意——那需要流式推送模式,适合实盘,不适合回测。
一个诚实的坑
如果你供应商的”退市收益”表是空的,你每年要因为幸存者偏差白白损失 1–3%。回测之前先查。
20 年回测,如果你只看当前的 S&P 500 成份股,会比在无幸存者偏差的股票池上回测好看得多——差距就来自这 20 年里被退市的 30–40% 的票。供应商假装它们不存在,你的 Sharpe 就是假的。快速测试:挑一次大型危机(2008、2020),看跌超 80% 的那些票有多少还在你的回测股票池里。如果答案是”大部分都退市了、被剔出指数了”,你就踩了幸存者偏差。
基本面是同样的道理:供应商按”报告日期”join 而不是”可用日期”的话,财报超预期、业绩指引、重述全都会给你前视偏差。管道的职责是把这两种偏差做成可见的;如果你的管道没把它们暴出来,换供应商,或者改管道。
坏数据的代价怎么算
最后提醒一句算账的纪律:粗略估算必须留在收益流单位里。说”每年有 1% 的幸存者偏差”或”1% 的前视偏差”,并不能推出 Sharpe 是多少——那要看波动率、时序、相关性、换手率,以及有偏观测是怎么进入策略的。别拿那些百分比自己脑补出 Sharpe 0.8 → 1.1 → 1.4 的故事。要真做对比,就搭一个无幸存者偏差且时间点正确的收益流,再搭一个有偏差的对照收益流,在同样的采样和成本约定下,用 各算一次年化 Sharpe,让数字自己说话。
量化机构不知不觉亏钱,就是从数据这一层开始亏的。修法是第一天就把数据基础设施做扎实。数据是地基:没有时间点正确、无幸存者偏差的覆盖,下游所有的指标——Sharpe、IC、deflated Sharpe——都不可信。下一篇讲消费这些数据的回测引擎,再下一篇讲从研究到上线的生命周期。
参考文献
- López de Prado, M. Advances in Financial Machine Learning, Ch. 2 (Financial Data Structures). Wiley, 2018. ISBN: 978-1-119-48208-6.
- Aronson, D. Evidence-Based Technical Analysis, Ch. 6 (Statistical Inference). Wiley, 2006. ISBN: 978-0-470-00874-4.
- Campbell, J., Lo, A., MacKinlay, A. The Econometrics of Financial Markets, Ch. 2-3. Princeton University Press, 1997. ISBN: 978-0-691-04301-2.