1. 数据接入层 ODS
数据层的设计原则:可持续的单一数据源、增量同步、入库前校验。标记缺失并告警,防止脏数据流入下游。
作用:从 Alpaca 增量拉取美股纳斯达克 500+ 标的的日 K 线,经质量校验后写入 kline_data,并补全行业分类供因子中性化使用。
输入与输出的契约
输入是外部数据源的日 K 线 API 与股票池清单(data/symbol/nasdaq500+.txt,516 只静态标的)。输出是三样东西:
| 输出 | 内容 | 下游用途 |
|---|---|---|
| 原始 K 线 | OHLCV + vwap,按标的增量追加 | 预处理层的唯一输入 |
| 行业分类 | 每只标的的 GICS sector | 因子层的行业中性化 |
| 同步水位线 | 每只标的拉到哪天、连续空数据几天 | 增量拉取的断点续传与异常标记 |
这一层对下游的承诺:入库的每根 K 线通过基本合法性校验;拉不到的数据被记录和告警,而不是静默缺失。
决策一:美股 + Alpaca,数据与交易一个入口
Alpha101 基于美股开发,当前系统沿用同一市场;美股在数据 API 与交易基础设施上相对成熟(T+0、无涨跌停、支持碎股、NYSE 日历规律)。更重要的是数据源的教训——项目实际走过 AlphaVantage → Alpaca 的迁移:
| 维度 | AlphaVantage | Alpaca IEX(免费) | Alpaca SIP |
|---|---|---|---|
| 成本 / 定位 | 免费额度极低 | 当前可持续基线 | 付费升级选项 |
| 限速 | 5 calls/min,516 只需数小时 | 无严格限速 | 同左 |
| 增量拉取 | 不支持,只能全量下载 | 原生时间范围查询 + 分页 | 同左 |
| 标的覆盖 | 全 | 部分缺失(MMC、CTRA 等可能无数据) | 覆盖更完整 |
| 交易闭环 | 纯数据,无法交易 | 可交易 | 同左 |
AlphaVantage 的限速让每日增量同步不可行。Alpaca IEX 虽有少量覆盖缺口,但免费、可持续、支持增量,且数据与交易共用同一套 API key,集成链路较短。整条链路——数据拉取、因子计算、模拟盘下单——都通过 Alpaca 完成。SIP 覆盖更完整但需付费,只作为未来升级项。
取舍的一般化:个人项目选数据源,「可持续 + 支持增量 + 能闭环到交易」比「覆盖最全」优先。覆盖缺口可以被监控和量化,不可持续的数据源没有然后。
决策二:增量同步的可靠性取决于异常路径
增量拉取的正常路径很简单:记住每只标的拉到哪天,下次从那天拉到今天。真正决定可靠性的是异常路径——部分标的在 IEX 免费源永远没有数据,朴素的「最新日期 < 今天就继续拉」逻辑会对它们无限重试,把每日任务拖成数小时(见事故档案 · 无限拉取死循环)。
解法是给水位线加状态:记录每只标的的最近检查日期与连续空数据天数,连续 N 天拉不到数据则标记停用并抛出告警。空数据从「重试的理由」变成「需要人工判断的信号」。
这个事故的严重性在于「空数据路径会拖垮任务」,不等价于「少量缺失一定显著改变策略结果」。对结果的影响要单独量化,见下面的决策四。
决策三:入库前校验,脏数据在源头拦截
入库前对每批 K 线检查三类问题:
- 必要列与空值:symbol、时间戳、OHLCV 任何一列缺失或含 NULL,后续计算无法继续
- 价格逻辑:
high ≥ open/close且low ≤ open/close。违反说明数据源出了问题(如拆股未调整),喂进因子会产生虚假极值 - 成交量非负:负成交量是无意义的脏数据
数据校验是因子计算的前置必须项。下游还有两道相关处理:停牌日 vwap=0 由预处理层修复,落库后的八项 DQC 兜底(见数据预处理)。三道检查各管一段,源头这一道最便宜——问题数据根本不入库。
复权用 adjustment='all',保证拆股 / 分红后的价格序列连续;原始输入必须按时间戳排序,否则时序因子结果无效。
决策四:接受一个有偏的股票池,先跑通再扩展
516 只标的是一个有偏的截面,这是已知并接受的取舍:
- 大盘成长偏斜:纳斯达克 500 天然偏向科技/成长,排除了小盘股、传统价值股
- 幸存者偏差:只包含当前上市的成分股,已退市标的不在其中。窗口较短会降低这类偏差的量级,但不能说「基本不存在」
- 时间覆盖:2025 年至今约 1 年数据,未覆盖完整牛熊周期
为什么还这么做:项目当前处于「先跑通再扩展」的阶段,516 只 + 单库 SQLite 已足够验证因子 → 回测 → 实盘的完整链路。扩展到全市场、引入退市数据在路线图 M6+。
同理,IEX 的覆盖缺失也按「监控而非夸大」处理。风险有两类:运维风险(空数据拖慢任务,已由水位线解决)和结果风险(缺失标的刚好频繁进入 Top N 才会放大为组合偏差)。
当前库快照(截至 2026-06-30):kline_data 覆盖 516 个 symbol、processed_kline_data 覆盖 516 个;sync_status 共 516 行、其中 516 行 active,连续空数据最大值为 6 天。
这说明 IEX 缺失目前更像需要被监控的常态噪声,而不是已经观测到的主导风险。后续评估应做两组对照:排除缺失/短历史标的后重跑 Top N 重合率、换手率、净值曲线;再按行业/市值分组看缺失是否集中。
行业中性化的数据基础:sector 而非 industry
定义:对每个交易日,在每个行业组内减去组内均值。
x_neutralized = x - mean(x | 同一天, 同一行业)为什么要做:假设科技股整体涨 3%,你持有的科技股涨 4%,未中性化的因子值是 4%,但剔除行业效应后的 alpha 只有 1%。不做中性化,因子值可能主要反映行业 beta。Alpha101 中有 18 个因子内含 IndNeutralize 算子。

为什么用 sector 而非 industry:500 只池子里 GICS sector 约 11 类,每组约 45 只标的,统计上够用。industry 约 140 类,每组平均仅约 3 只——样本量不足时中性化退化(单成员组被强制变 0,双成员组退化成完全对称的 ±值)。组内不足 2 只时回退为全市场去均值。
常见错误
- 无覆盖标的触发增量拉取无限重试,每日任务拖成数小时 → 事故档案 · 无限拉取死循环
volume=0 & close>0未区分「停牌」与「交易中断」→ VWAP 计算失真并污染因子排名(由预处理层与 DQC 处理)
当前状态
Alpaca IEX + 全复权增量拉取 516 只静态标的,已上线,含入库校验、幂等入库、水位线防空数据死循环和后置 DQC。仍未闭环的是:拉取参数(feed / adjustment / asof)未入库导致历史行无法严格复现、股票池和行业分类不是 point-in-time、缺失值处理较粗。完整清单见下方实现对照。
实现对照
下一层:2. 数据预处理 DWD