Alpha-Online
技术手册

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 的迁移:

维度AlphaVantageAlpaca IEX(免费)Alpaca SIP
成本 / 定位免费额度极低当前可持续基线付费升级选项
限速5 calls/min,516 只需数小时无严格限速同左
增量拉取不支持,只能全量下载原生时间范围查询 + 分页同左
标的覆盖部分缺失(MMC、CTRA 等可能无数据)覆盖更完整
交易闭环纯数据,无法交易可交易同左

AlphaVantage 的限速让每日增量同步不可行。Alpaca IEX 虽有少量覆盖缺口,但免费、可持续、支持增量,且数据与交易共用同一套 API key,集成链路较短。整条链路——数据拉取、因子计算、模拟盘下单——都通过 Alpaca 完成。SIP 覆盖更完整但需付费,只作为未来升级项。

取舍的一般化:个人项目选数据源,「可持续 + 支持增量 + 能闭环到交易」比「覆盖最全」优先。覆盖缺口可以被监控和量化,不可持续的数据源没有然后。

决策二:增量同步的可靠性取决于异常路径

增量拉取的正常路径很简单:记住每只标的拉到哪天,下次从那天拉到今天。真正决定可靠性的是异常路径——部分标的在 IEX 免费源永远没有数据,朴素的「最新日期 < 今天就继续拉」逻辑会对它们无限重试,把每日任务拖成数小时(见事故档案 · 无限拉取死循环)。

解法是给水位线加状态:记录每只标的的最近检查日期与连续空数据天数,连续 N 天拉不到数据则标记停用并抛出告警。空数据从「重试的理由」变成「需要人工判断的信号」。

这个事故的严重性在于「空数据路径会拖垮任务」,不等价于「少量缺失一定显著改变策略结果」。对结果的影响要单独量化,见下面的决策四。

决策三:入库前校验,脏数据在源头拦截

入库前对每批 K 线检查三类问题:

  1. 必要列与空值:symbol、时间戳、OHLCV 任何一列缺失或含 NULL,后续计算无法继续
  2. 价格逻辑high ≥ open/closelow ≤ open/close。违反说明数据源出了问题(如拆股未调整),喂进因子会产生虚假极值
  3. 成交量非负:负成交量是无意义的脏数据

数据校验是因子计算的前置必须项。下游还有两道相关处理:停牌日 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 算子。

Alpha101 中使用 IndNeutralize 的 18 个因子

为什么用 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、缺失值处理较粗。完整清单见下方实现对照。

实现对照


内部溯源(私有仓库):空数据死循环 issue #19 · 行业中性化小组保护 #39

On this page