⏰ 任务调度与时序数据库

用依赖声明替代 cron 猜时间:调度器的 DAG 思维(Airflow/Prefect)、数据表的技术选型(Parquet/ClickHouse/Arctic)、每日流水线的参考蓝图。

量化系统的每日运转是一条定时流水线:行情落地 → 校验 → 因子计算 → 信号 → 订单 → 归档。「几点跑什么」的可靠性直接等于策略的可靠性——本篇讲流水线的组织方式(调度)与底下的存储选型(数据库)。

调度:从 cron 到 DAG

cron 的问题:用「猜时间」表达依赖——「17:05 跑因子计算」隐含假设「数据 17:00 前一定就绪」,一旦数据迟到,因子计算用的是残缺数据(且无人知晓)。DAG 调度(Airflow/Prefect/Dagster)的正确姿势:声明依赖而不是猜时间——

1
2
# Airflow 风格的依赖声明(伪代码)
data_landing >> data_quality_check >> factor_compute >> signal_gen >> orders

每个任务的触发条件是「上游任务成功」,而不是「到了某个钟点」。数据的迟到不再是事故(流水线自动顺延),而是可告警的异常(SLA 超时告警,见日志与监控 )。

DAG 的四个实务要点:

  1. 每个任务幂等且可重跑:任务失败重跑不能产生重复数据(写临时表 + 原子切换,见因子存储 )——幂等是「放心重跑」的前提,而重跑是流水线的日常;
  2. 质量校验是 DAG 的显式节点:校验失败 → 下游全部短路 + 告警——「宁可今天不交易,不可用坏数据交易」(检查项见数据清洗 );
  3. 回填(backfill)是一等公民:数据源修复后「重算某区间的因子」是高频操作——DAG 支持按日期区间重跑(前提是任务幂等);
  4. 个人量化的最小版:一个带依赖检查与失败告警的 Python 调度脚本(检查上游文件时间戳 → 失败则通知并退出)就能覆盖 90% 需求——cron + 防御式脚本,晚于需要再上 Airflow。

存储选型:按访问模式选库

量化数据的核心访问模式是「按日期截面读全部股票」+「按股票读全部历史」,两类负载的最佳载体不同:

存储适合特点
Parquet 文件 + 本地盘个人/小团队的一切按日期分区的宽表,Pandas/Polars 直读,零运维;文件即快照,版本管理容易
ClickHouse / DuckDB因子数多、需要切片查询列存 OLAP,长表(date, code, factor, value)亿行级秒查;DuckDB 单机零运维,ClickHouse 需要服务化
Arctic/ArcticDB(Mongo 系)时序面板为主tick/分钟级高频面板的专用形态,版本化内置
PostgreSQL / MySQL元数据、订单、账户事务型账本(订单流、持仓、审计记录)——交易账本不用 OLAP 库,要事务保证
Redis / 内存盘中实时状态行情缓存、实时信号状态、秒级刷新的监控指标

选型的两条经验:先文件后数据库(Parquet 阶段撑到因子数过百、数据量过 TB 再上 OLAP——大多数个人研究者永远停在 Parquet 就够了);账本与行情分离(订单/持仓/资金是法律意义上的记录,进事务库并备份,与行情库的故障域隔离)。

每日流水线参考蓝图

1
2
3
4
5
6
7
8
9
15:30  行情接收(收盘后)
15:45  数据落地(行情表 + 复权因子更新)
16:00  质量校验(新鲜度/缺失/极值,任一失败 → 短路+告警)
16:15  财报/公告事件增量(PIT 追加)
16:30  因子计算(增量)→ 因子库版本切换
16:50  信号生成(目标组合)
17:00  风控预检(限额校验)+ 订单生成
17:05  日报推送(净值/持仓/信号/异常)+ 心跳
(次日)开盘:订单执行 → 成交回报落账 → 当日 TCA

三个设计细节:所有节点写「运行指纹」(输入数据版本 + 代码版本 + 输出版本,见Git )——三个月后任何一天的信号可完整追溯;人工干预的入口预设计(「今日跳过交易」「手动覆盖目标持仓」要有一键且留痕的通道——总有引擎判断不了的日子);盘后流水线的总时长预算(从收盘到日报 < 2 小时),超时即优化信号(流水线太长 = 数据迟到时的应对余量太小)。

关联阅读:流水线上游数据清洗 与因子存储 ;下游策略上线 ;全链路的 CI 化DevOps 。