第 8 章 · 03 backtrader 事件驱动


文档摘要

第 8 章 · 03 backtrader 事件驱动 本节摘要:本节把回测的逼真度拉上一个台阶——用 backtrader 做事件驱动回测。与上一节「信号乘收益」的向量化不同,backtrader 逐根 K 线喂给你的策略,策略发订单给经纪商,经纪商撮合、扣手续费、记账,像真实交易一样按事件顺序推进。本节讲清四件事:backtrader 的 Cerebro(大脑)架构由哪些对象组成;如何自定义数据加载器( )、自定义手续费( )、自定义策略( );策略如何在 里按预测排名调仓、在 里记账;以及如何把结果接到 pyfolio 出绩效报告。读完本节,你能用 backtrader 把一个多空策略跑成事件驱动的回测。 内容来源:原项目 ,汉化并套用体系化模板。

第 8 章 · 03 backtrader 事件驱动

本节摘要:本节把回测的逼真度拉上一个台阶——用 backtrader 做事件驱动回测。与上一节「信号乘收益」的向量化不同,backtrader 逐根 K 线喂给你的策略,策略发订单给经纪商,经纪商撮合、扣手续费、记账,像真实交易一样按事件顺序推进。本节讲清四件事:backtrader 的 Cerebro(大脑)架构由哪些对象组成;如何自定义数据加载器(SignalData)、自定义手续费(FixedCommisionScheme)、自定义策略(MLStrategy);策略如何在 next() 里按预测排名调仓、在 notify_order() 里记账;以及如何把结果接到 pyfolio 出绩效报告。读完本节,你能用 backtrader 把一个多空策略跑成事件驱动的回测。

内容来源:原项目 08_ml4t_workflow/03_backtesting_with_backtrader.ipynb,汉化并套用体系化模板。

⚠️ 学习提示:事件驱动回测比向量化慢一个数量级,但更接近实盘。它会把滑点、手续费、订单未成交一个个扣回去,策略夏普通常会显著衰减——这是好事,说明你在看更接近真相的数字。

学习目标

阅读完本节,你应当能够:

  1. 列举 backtrader Cerebro 架构的核心对象及其职责。
  2. 区分向量化回测事件驱动回测的适用场景。
  3. 写出 bt.Strategy 子类的 next()notify_order() 方法。
  4. addanalyzer(PyFolio) 把回测结果接进 pyfolio
  5. 理解订单提交、撮合、记账的事件顺序。

一、Cerebro 架构:大脑如何调度

backtrader 由 Daniel Rodriguez 自 2015 年起开发,是 Python 生态里最流行的本地回测库之一。它的核心是 Cerebro(西班牙语「大脑」),把回测的关键角色抽象成可扩展的 Python 对象,由一个 Cerebro 实例统一调度。

对象 职责 本节示例
Cerebro 总调度,加载数据、运行策略、收结果 cerebro = bt.Cerebro()
Data Feeds 喂入 OHLCV + 自定义列 SignalData(dataname=df)
Strategy 你的策略逻辑,逐根 K 线被调用 MLStrategy(bt.Strategy)
Broker 接收订单、撮合、扣手续费、记账 cerebro.broker.setcash(10000)
Analyzers 计算绩效指标 addanalyzer(PyFolio)

💡 核心心法:backtrader 的「事件驱动」体现在「策略代码被引擎逐根 K 线调用」——每一根 K 线到来都是一个事件,你的 next() 在那个事件里读最新数据、做决策、发订单。这跟实盘里「每收到一个 tick 都重新评估」的逻辑一致,所以更接近真实。

二、自定义数据与手续费

SignalData:扩展 OHLCV 加一列预测

backtrader 默认只认 OHLCV,但我们的 ML 策略还需要一列 predicted(模型预测)。继承 PandasData 加一条 lines:

OHLCV = ['open', 'high', 'low', 'close', 'volume'] class SignalData(PandasData): cols = OHLCV + ['predicted'] lines = tuple(cols) # 新增一条叫 predicted 的「线」 params = {c: -1 for c in cols} # -1 表示「列名与 lines 同名」 params.update({'datetime': None}) params = tuple(params.items())

lines 是 backtrader 的核心概念——每条 line 就是一条按时间索引的数值序列(类似 pandas 的 Series)。OHLCV 各是一条 line,我们再加一条 predicted,策略就能在每根 K 线上读到当天的预测值。

FixedCommisionScheme:每股固定佣金

backtrader 的手续费体系高度可定制。这里实现一个简单的「每股固定 2 分钱」方案:

class FixedCommisionScheme(bt.CommInfoBase): params = (('commission', .02), ('stocklike', True), ('commtype', bt.CommInfoBase.COMM_FIXED),) def _getcommission(self, size, price, pseudoexec): return abs(size) * self.p.commission

stocklike=True 表示这是股票(非期货,无保证金逻辑),COMM_FIXED 表示按手数固定收费。_getcommission 接成交手数与价格,返回实际费用。

三、MLStrategy:策略的核心逻辑

策略继承 bt.Strategy,关键参数 n_positions(目标持仓数)和 min_positions(最少持仓数,低于它就不调):

class MLStrategy(bt.Strategy): params = (('n_positions', 10), ('min_positions', 5), ('verbose', False), ('log_file', 'backtest.csv'))

next():每根 K 线被调用一次

next() 是策略的心脏。典型逻辑是:每根 K 线到来时,从所有 data feed 收集预测值、排名、决定哪些该买哪些该卖,然后发 order_target 订单把每只股票调到目标权重。order_target 是 backtrader 的便捷接口——你告诉它「想让这只股票占总资产的 X%」,它自己算需要买/卖多少股。

notify_order():订单状态回调

订单发出后会有生命周期:Submitted → Accepted → Completed / Canceled / Margin / Rejected。notify_order 在状态变化时被调用:

def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: return if order.status in [order.Completed]: p = order.executed.price if order.isbuy(): self.log(f'{order.data._name},BUY executed,{p:.2f}') elif order.issell(): self.log(f'{order.data._name},SELL executed,{p:.2f}') elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log(f'{order.data._name},FAIL')

注意几个关键状态:

状态 含义 策略应做什么
Submitted / Accepted 已提交/已接受,未成交 直接 return,不打日志
Completed 已成交 记账、打日志
Margin 现金不足被拒 通常意味着仓位超限
Rejected 经纪商拒绝 参数或资金问题

⚠️ order.Margin 的含义:当现金不足以支撑订单时,broker 会拒绝并返回 Margin 状态。这是 backtrader 告诉你「策略想买的比钱多」的方式——在向量化回测里你永远看不到这个,因为那里假设资金无限。

四、装配 Cerebro 并跑起来

把数据、策略、分析器装到 Cerebro 里运行:

cerebro = bt.Cerebro() cash = 10000 cerebro.broker.setcash(cash) # 给每只股票加一个数据 feed for ticker in tickers: df = data.loc[idx[ticker, :], :].droplevel('ticker', axis=0) df.index.name = 'datetime' cerebro.adddata(SignalData(dataname=df), name=ticker) # 加分析器和策略 cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio') cerebro.addstrategy(MLStrategy, n_positions=25, min_positions=20, verbose=True, log_file='bt_log.csv') results = cerebro.run() ending_value = cerebro.broker.getvalue()

最后 cerebro.broker.getvalue() 给出期末组合净值,results 含分析器输出的 pyfolio 输入。整套流程是「装数据 → 装策略 → 装分析器 → run → 取结果」五步。

💡 为什么不用 cerebro.plot():notebook 特意禁用了 cerebro.plot(),因为每只股票单独画一条会刷屏。实务里通常直接用 pyfolio 出标准绩效报告,更清晰。

五、向量化 vs 事件驱动:何时用哪个

维度 向量化 事件驱动(backtrader)
速度 快(秒级) 慢(分钟级)
逼真度 低(无撮合) 高(逐根 K 线)
滑点/手续费 难以建模 原生支持
订单未成交 不存在 会发生
适合阶段 信号初筛 上线前评估
代码量 十行 百行起步

工作流上,先用向量化筛掉明显没希望的信号,再用 backtrader 把幸存者放到事件驱动里压测——这是上一节与本节的协同关系。

本节要点回顾

  1. Cerebro 架构:Cerebro 是总调度,协调 Data Feeds、Strategy、Broker、Analyzers,逐根 K 线推进。
  2. 事件驱动:next() 每根 K 线被调一次,策略发订单给 Broker,Broker 撮合并回调 notify_order()
  3. SignalData:继承 PandasData 加一条 predicted line,让策略每根 K 线能读到模型预测。
  4. 订单生命周期:Submitted → Accepted → Completed / Canceled / Margin / Rejected,Margin 是「钱不够」的信号。
  5. pyfolio 对接:addanalyzer(PyFolio) 把回测输出直接接进 pyfolio 出标准绩效报告。
  6. 与向量化协同:向量化做初筛,事件驱动做终评,两者分工而非互斥。

下一节,我们看 Zipline 回测集成——用 Quantopian 出品的工业级框架,把 ML 训练与回测端到端串起来,完成 ML4T 的完整闭环。


发布者: 作者: 灏天文库 转发
评论区 (0)
U