第 8 章 · 04 Zipline 回测集成 本节摘要:本节把 ML4T 工作流推到工业级——用 Quantopian 维护的 Zipline 做端到端回测。Zipline 不只是回测引擎,它是一整套「数据-因子-下单-绩效」的基础设施:用 bundle 存点在时 OHLCV、用 TradingCalendar 对齐交易所时区、用 Pipeline API 声明式地算因子、用 调度整套流程。
本节摘要:本节把 ML4T 工作流推到工业级——用 Quantopian 维护的 Zipline 做端到端回测。Zipline 不只是回测引擎,它是一整套「数据-因子-下单-绩效」的基础设施:用 bundle 存点在时 OHLCV、用 TradingCalendar 对齐交易所时区、用 Pipeline API 声明式地算因子、用
run_algorithm调度整套流程。本节讲清四件事:bundle 与自定义 bundle 如何让你把自有数据接进来;Pipeline API 的 Factor/Filter/Classifier 如何让因子计算向量化提速;initialize/before_trading_start/handle_data/schedule_function的事件调度模型;以及如何用CustomFactor把「在回测中训练 ML 模型」嵌进 Pipeline,实现真正的端到端 ML4T。读完本节,你能在 Zipline 里跑通一个从原始数据到 pyfolio 绩效的完整闭环。
内容来源:原项目
08_ml4t_workflow/04_ml4t_workflow_with_zipline/,汉化并套用体系化模板。
⚠️ 学习提示:Zipline 1.3(2018 年 7 月)是本书使用的最后版本,需要打补丁(参见
installation)。它强依赖国库券曲线和 S&P500 基准,跑之前要先把SPY_benchmark.csv放到.zipline目录,否则会因缺基准报错。
阅读完本节,你应当能够:
extension.py 四步流程。initialize/before_trading_start/handle_data 的调用时机。CustomFactor 把 ML 训练嵌进回测做端到端 ML4T。Zipline 是 Quantopian 自 2012 年起开源的事件驱动回测系统,也是其在线平台的生产引擎。它比 backtrader 更「重」——但换来的是规模化(可同时处理数千只证券)与防前视(数据按点在时 point-in-time 对齐)。
| 组件 | 职责 |
|---|---|
| Bundle | bcolz 列式存储 + SQLite 元数据,支持拆股/分红点在时调整 |
| TradingCalendar | 交易所时区、开盘收盘、节假日 |
| Pipeline API | 声明式计算横截面因子,内部向量化加速 |
| Algorithm API | initialize/handle_data 等回调驱动交易逻辑 |
💡 核心心法:Zipline 的关键不是「快」,而是「防前视 + 规模化」。它把数据、因子、订单分别交给专门的子系统,每个子系统都为「别让未来泄漏到当前」做了设计——这是它能被生产级对冲基金使用的核心原因。
Bundle 是 Zipline 的主数据存储,用 bcolz 列式格式压缩存储 OHLCV,SQLite 存元数据。最大优点是存了拆股和分红信息,Zipline 会根据你回测的时间段自动计算点在时调整——这意味着你回测 2015 年的某天,看到的是「那时那天的真实价格」,而不是被后来拆股回填过的价格。
要把自有数据(如本书用的 AlgoSeek 分钟级 NASDAQ100)接进 Zipline,需要:
algoseek.h5。load_equities()(元数据)、ticker_generator()、data_generator(),组合成 algoseek_to_bundle() 返回 ingest() 函数。~/.zipline/extension.py 里用 register() 注册 bundle 名,用 sys.path.append 让 Zipline 能找到 ingest 函数,并指定自定义日历。注册后用 zipline ingest -b algoseek 把数据写进 bundle,用 ZIPLINE_ROOT 环境变量可改默认存储位置(默认在用户主目录的 .zipline)。
⚠️ Bundle 只存 OHLCV:它不存基本面、另类数据。要喂额外特征,用
fetch_csv()(适合日频)或DataFrameLoader(适合 Pipeline 内的日频特征)。分钟级额外特征只能靠fetch_csv,这是 Pipeline API 的限制。
Pipeline API 是 Zipline 区别于 backtrader 的另一大杀器。你声明「我要哪些因子、怎么过滤」,Zipline 在整个回测期间向量化地算这些因子,而不是每个事件单独算——既保留了事件驱动架构,又获得了向量化速度。
Pipeline 用三类构件:
| 构件 | 作用 | 示例 |
|---|---|---|
| Factor | 输入历史 bar 数组,输出每只证券一个值 | SimpleMovingAverage,自定义 MLSignal |
| Filter | 输出布尔值,用于筛选证券 | top(10).bottom(10) |
| Classifier | 输出类别,用于分组 | 按行业、按市值分桶 |
一个典型 Pipeline:加载 lasso 预测作为 MLSignal 因子,用 top(10) 选预测最高的 10 只做多、bottom(10) 选最低的 10 只做空,每边至少 5 只。这套声明式逻辑让因子研究与下单逻辑解耦,改因子不动下单代码。
TradingAlgorithm 实现了 Algorithm API,操作与某交易日历对齐的 BarData。关键回调:
| 方法 | 调用时机 | 典型用途 |
|---|---|---|
initialize(context, data) |
模拟开始时一次 | 注册 Pipeline、设佣金滑点、初始化 context |
before_trading_start(context, data) |
每日开盘前 | 选当日交易标的 |
handle_data(context, data) |
每个交易频率 | 下单、调仓 |
schedule_function |
自定义时间 | 每周/每月再平衡 |
context 是状态字典(贯穿整个回测),data 提供点在时的当前与历史数据。整个算法通过 run_algorithm() 启动,返回的 DataFrame 直接兼容 pyfolio 出绩效报告。
最关键的能力是——Zipline 允许在回测过程中训练 ML 模型,而不是预先训好把预测喂进去。notebook ml4t_with_zipline.ipynb 演示了这套真正的端到端流程:
class MLLiquidityReturn(CustomFactor): """在 Pipeline 内训练模型并产出预测""" def compute(self, today, assets, out, returns, *inputs): # 用今天之前的数据训练,预测今天 model.fit(X_train, y_train) out[:] = model.predict(X_latest)
CustomFactor 接收收益与特征作为输入,在 compute 里训练模型并输出预测——这是「随时间向前滚动训练」的实现方式。第 7 章用过的技术指标(动量、波动、滞后收益等)作为特征,目标是粗略复现那章的日收益预测。
💡 避免前视的训练纪律:
compute(today, ...)里训练数据必须严格截断到today之前,绝不能用today当天的收益当标签。Zipline 的 Pipeline 在调度上帮你对齐,但特征工程的细节仍要你自己把关——把训练集与预测集的边界画清楚,是端到端 ML4T 最容易出错的地方。
ingest() → 在 extension.py 注册 → 必要时自定义 TradingCalendar。initialize(一次)/before_trading_start(每日盘前)/handle_data(每频率)/schedule_function(自定义)。CustomFactor.compute() 内训练模型,实现回测中滚动训练,但要严守「训练只用过去」的纪律。run_algorithm() 输出直接接 pyfolio,完成从原始数据到标准绩效报告的完整链路。下一章,我们进入树模型与梯度提升——从决策树讲起,看非线性模型如何捕捉线性模型抓不到的因子交互,并落到日本股票多空策略。