🏠
AI量化交易 · 第9章
目录
Chapter 09

Backtrader

用对话的方式理解回测框架——你不需要成为专家,只需要知道怎么和AI一起使用它

本章学习目标

  1. 理解Backtrader的核心架构和关键概念
  2. 掌握Cerebro引擎、Data Feed、Strategy的协作方式
  3. 能够用Backtrader运行一个最简单的买入并持有回测
  4. 了解Backtrader与Zipline、VectorBT的优劣对比
  5. 学会用AI辅助编写和调试Backtrader策略

为什么需要回测框架

想象一下,你有一个交易想法:"当5日均线上穿20日均线时买入,下穿时卖出。"听起来简单,对吧?但如果你要验证这个想法在历史上是否有效,你需要做很多事情:获取历史数据、计算移动平均线、模拟每一笔交易的执行、考虑手续费和滑点、计算最终的收益曲线……

如果你手动做这些,一个五年的回测可能需要几天时间,而且极易出错。更可怕的是,你很难保证自己的计算没有bug——一个小小的逻辑错误就可能让结果完全失真。

这就是回测框架存在的意义。它把回测过程中所有重复的、机械的工作封装起来,让你只需要关注最核心的东西:策略逻辑。Backtrader就是Python世界中最优秀的回测框架之一,它优雅、灵活、文档丰富,而且完全开源免费。

核心理念:回测框架就像一位尽职尽责的会计。你告诉它"什么时候买卖",它帮你记录每一笔交易、计算盈亏、绘制曲线。你不需要关心账本怎么记,只需要关心决策对不对。

Backtrader的架构:一场精心编排的交响乐

Backtrader的设计理念非常优雅。它的核心是一个叫Cerebro(拉丁语"大脑")的引擎,负责协调所有组件的工作。你可以把Cerebro想象成一个乐团的指挥,而Data Feed、Strategy、Indicators、Observers、Analyzers则是乐团中的不同乐器组。

graph TD A[Cerebro引擎] --> B[Data Feed
历史数据] A --> C[Strategy
策略逻辑] A --> D[Indicators
技术指标] A --> E[Observers
观察者] A --> F[Analyzers
分析器] C --> G{买卖信号} G --> H[Broker
模拟交易] H --> I[结果输出] style A fill:#1e3a5f,stroke:#f97316,color:#e8ecf4 style C fill:#1e3a5f,stroke:#06b6d4,color:#e8ecf4 style I fill:#10b981,stroke:#06b6d4,color:#e8ecf4

Backtrader的五大核心组件

让我们来认识Backtrader家族的主要成员。点击下方卡片,了解每个组件的职责。

Backtrader核心组件
Cerebro 大脑
整个回测系统的指挥官
Data Feed 数据源
为策略提供历史价格数据
Strategy 策略
你的交易逻辑写在这里
Indicators 指标
内置的技术分析工具
Broker 经纪商
模拟交易执行和资金管理
Analyzers 分析器
计算夏普比率、最大回撤等指标
Cerebro — 大脑

Cerebro是Backtrader的核心引擎。它的工作包括:加载数据、初始化策略、按时间顺序驱动回测、协调各个组件之间的通信、收集和输出结果。

在代码中,你首先创建一个Cerebro实例,然后向它添加数据、策略、分析器,最后调用cerebro.run()执行回测。所有的复杂性都被封装在这个简单的接口后面。
Data Feed — 数据源

Data Feed负责把历史数据喂给策略。Backtrader内置了从CSV文件、Yahoo Finance、Pandas DataFrame等多种数据源读取数据的能力。

每根K线(Bar)包含开盘价、最高价、最低价、收盘价、成交量。在策略中,你可以通过self.data.close[0]访问当前收盘价,通过self.data.close[-1]访问上一根K线的收盘价。
Strategy — 策略

策略是你实现交易逻辑的地方。你需要继承bt.Strategy类,并重写两个核心方法:

__init__(self):初始化指标和变量
next(self):每根K线到达时执行一次

next()中,你根据当前的市场状态和指标值做出买卖决策,通过self.buy()self.sell()下单。
Indicators — 技术指标

Backtrader内置了上百种技术指标:移动平均线、MACD、RSI、布林带、ATR……你只需要一行代码就能使用它们。

例如:self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=20)

指标会自动随着数据推进而更新,在next()中你随时可以通过self.sma[0]获取当前值。
Broker — 模拟经纪商

Broker负责模拟交易执行。它会跟踪你的现金和持仓,处理订单的成交,扣除手续费和佣金。

你可以通过cerebro.broker.setcash(100000)设置初始资金,通过cerebro.broker.setcommission(commission=0.001)设置佣金率。Backtrader默认考虑了成交量限制——如果你下的单超过了该K线的成交量,只会部分成交。
Analyzers — 分析器

回测结束后,你需要评价策略的表现。Analyzers就是干这个的。

Backtrader内置了多种分析器:SharpeRatio(夏普比率)、DrawDown(回撤分析)、Returns(收益分析)、TradeAnalyzer(交易统计)等。回测完成后,你可以通过cerebro.run()返回的结果对象获取这些指标。

你的第一个Backtrader回测

理论讲得够多了,让我们动手写代码。下面是一个完整的"买入并持有"策略——历史上涨幅最大的策略之一(虽然它没有任何技术含量,但作为一个起点再合适不过)。

代码分步演示
import backtrader as bt
import backtrader.feeds as btfeed
import datetime
首先导入Backtrader核心库。backtrader是主模块,backtrader.feeds包含各种数据读取工具,datetime用于处理日期。
class BuyAndHold(bt.Strategy):
    """最简单的买入并持有策略"""
    
    def __init__(self):
        """初始化:设置订单状态跟踪"""
        self.order = None  # 跟踪当前订单
    
    def next(self):
        """每根K线到达时执行"""
        # 如果有未完成的订单,不操作
        if self.order:
            return
        
        # 如果没有持仓,买入
        if not self.position:
            self.order = self.buy()
            print(f"买入 @ {self.data.close[0]:.2f}")
    
    def notify_order(self, order):
        """订单状态更新时调用"""
        if order.status in [order.Completed]:
            if order.isbuy():
                print(f"买入执行: {order.executed.price:.2f}")
            self.order = None
策略类继承自bt.Strategy__init__初始化状态变量,next()是核心逻辑——每根K线到达时检查是否有持仓,没有就买入。notify_order在订单成交时通知我们。
# 创建Cerebro引擎
cerebro = bt.Cerebro()

# 设置初始资金
cerebro.broker.setcash(100000.0)

# 设置佣金(千分之一)
cerebro.broker.setcommission(commission=0.001)

# 加载数据(以CSV为例)
data = btfeed.YahooFinanceData(
    dataname='AAPL',
    fromdate=datetime.datetime(2020, 1, 1),
    todate=datetime.datetime(2023, 12, 31),
    reverse=False
)
cerebro.adddata(data)

# 添加策略
cerebro.addstrategy(BuyAndHold)

# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
创建Cerebro实例后,我们设置初始资金、佣金,添加数据源和策略,再加上三个分析器:夏普比率、回撤、收益率。这里用的是Yahoo Finance的免费数据源。
# 打印初始资金
print(f"初始资金: {cerebro.broker.getvalue():.2f}")

# 运行回测
results = cerebro.run()

# 打印最终资金
print(f"最终资金: {cerebro.broker.getvalue():.2f}")
print(f"总收益率: {(cerebro.broker.getvalue()/100000 - 1)*100:.2f}%")

# 获取分析结果
strat = results[0]
print(f"\n夏普比率: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.3f}")
print(f"最大回撤: {strat.analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%")
cerebro.run()执行回测,返回策略实例列表。我们通过cerebro.broker.getvalue()获取最终资产价值,通过分析器获取风险收益指标。
# 绘制结果
cerebro.plot(style='candlestick', volume=False)

# 输出交易统计
trade_analysis = strat.analyzers.getbyname('tradeanalyzer')
if trade_analysis:
    trades = trade_analysis.get_analysis()
    print(f"\n总交易次数: {trades.total.total}")
    print(f"盈利交易: {trades.won.total}")
    print(f"亏损交易: {trades.lost.total}")
Backtrader内置了绘图功能,可以生成专业的回测图表,包含价格走势、买卖标记、资金曲线等。你也可以用分析器获取更详细的交易统计信息。

上面的分步演示把一个完整的回测拆解成了五个步骤。即使你是第一次见Backtrader代码,也能大致理解每一部分在做什么。这五个步骤的模式——导入、定义策略、创建引擎、运行、分析——是几乎所有Backtrader回测的通用模板。

把策略变复杂一点:双均线交叉

"买入并持有"太简单了,让我们加入一点技术含量。下面是一个基于双均线交叉的策略:当短期均线上穿长期均线时买入,下穿时卖出。

class DualMAStrategy(bt.Strategy):
    """双均线交叉策略"""
    
    params = (
        ('short_period', 5),
        ('long_period', 20),
    )
    
    def __init__(self):
        self.order = None
        # 计算两条移动平均线
        self.sma_short = bt.indicators.SMA(
            self.data.close, period=self.p.short_period)
        self.sma_long = bt.indicators.SMA(
            self.data.close, period=self.p.long_period)
        # 计算交叉信号
        self.crossover = bt.indicators.CrossOver(
            self.sma_short, self.sma_long)
    
    def next(self):
        if self.order:
            return
        
        # 金叉且没有持仓 -> 买入
        if self.crossover > 0 and not self.position:
            self.order = self.buy()
        
        # 死叉且有持仓 -> 卖出
        elif self.crossover < 0 and self.position:
            self.order = self.sell()
    
    def notify_order(self, order):
        if order.status in [order.Completed]:
            action = "买入" if order.isbuy() else "卖出"
            print(f"{action} @ {order.executed.price:.2f}, "
                  f"数量: {order.size}")
            self.order = None

# 使用这个策略
# cerebro.addstrategy(DualMAStrategy, short_period=10, long_period=30)

这个策略虽然简单,但已经包含了量化策略的核心要素:信号生成(均线交叉)、仓位管理(有持仓就不买,没持仓就不卖)、订单管理(跟踪订单状态避免重复下单)。

双均线策略在趋势明显的市场中表现不错,但在震荡市中会反复被打脸——频繁的金叉死叉产生大量亏损交易。这就是为什么我们需要回测:看看这个策略在不同市场环境下的真实表现,而不是凭想象认为它"应该有效"。

—— 来自第3章的市场状态分析

三大回测框架对比

Backtrader虽然优秀,但并不是唯一的选择。让我们快速对比一下Python世界中最流行的三个回测框架,帮你做出适合自己的选择。

特性 Backtrader Zipline VectorBT
学习曲线 中等 较陡 较陡
执行速度 中等 中等 极快
事件驱动 否(向量化)
实时交易 支持 有限 有限
社区活跃度 中(已停止维护)
最适合 初学者到中高级 学术研究 参数优化/大规模回测

对于初学者,我的推荐很明确:从Backtrader开始。它的文档最友好,社区最活跃,学习资源最丰富。当你需要对大量参数组合进行快速回测时,可以再学习VectorBT作为补充。Zipline虽然曾经是Quantopian的官方框架,但自从Quantopian关闭后已经基本停止维护,不建议新用户投入。

Backtrader的常见陷阱

作为一个用了Backtrader多年的用户,我想分享几个新手最容易踩的坑。提前知道这些,能帮你节省大量的调试时间。

遇到这些问题时,不要独自挣扎。把错误信息或困惑描述给AI,它通常能很快帮你定位问题。Backtrader的社区论坛也是一个很好的求助渠道,但AI的响应速度显然更快。

与AI一起练习

练习1:让AI把文字策略翻译成Backtrader代码

Prompt模板:

请把我的文字策略翻译成完整的Backtrader代码:

策略描述:[填写你的策略,如"RSI低于30时买入,高于70时卖出"]

要求:
1. 包含完整的导入、策略类定义、Cerebro设置
2. 使用Yahoo Finance数据(股票代码:[填写])
3. 回测时间范围:[填写]
4. 初始资金:[填写]
5. 包含夏普比率和最大回撤分析
6. 添加注释解释每一部分的作用

你需要做的:把AI生成的代码复制到Colab中运行,观察结果。然后修改参数,看看不同设置下的表现差异。

练习2:让AI解释回测结果

Prompt模板:

我运行了一个Backtrader回测,得到了以下结果:

- 初始资金: [填写]
- 最终资金: [填写]
- 夏普比率: [填写]
- 最大回撤: [填写]
- 总交易次数: [填写]
- 胜率: [填写]

请帮我分析:
1. 这个策略的整体表现如何?
2. 夏普比率和最大回撤说明了什么?
3. 如果这是实盘策略,我需要注意哪些风险?
4. 有什么改进方向?

你需要做的:认真阅读AI的分析,特别留意它指出的风险点。很多初学者看到正收益就兴奋,忽略了AI警告的潜在问题。

练习3:对比两个策略的回测结果

Prompt模板:

我回测了两个策略,结果如下:

策略A(买入并持有):
[填写各项指标]

策略B(双均线交叉):
[填写各项指标]

请详细对比两个策略的优劣,并回答:
1. 从风险收益比角度,哪个更好?
2. 什么市场环境下策略A会赢?什么环境下策略B会赢?
3. 如果要把两个策略组合,你会怎么设计?

你需要做的:这个练习帮你建立"对比分析"的思维习惯。量化交易不是找到一个"最好"的策略,而是找到在不同环境下表现互补的策略组合。

动手实践:运行你的第一个完整回测

下面是一段可以直接运行的Backtrader回测代码。把它复制到Google Colab中,按步骤执行,你将获得你的第一份回测报告。

# Backtrader完整回测示例:双均线策略
# 运行环境:Google Colab

# 第一步:安装库
!pip install backtrader yfinance -q

import backtrader as bt
import yfinance as yf
import datetime
import pandas as pd

# 第二步:定义策略
class SmaCrossStrategy(bt.Strategy):
    params = (('fast', 10), ('slow', 30),)
    
    def __init__(self):
        self.order = None
        self.fast_ma = bt.indicators.SMA(self.data.close, 
                                         period=self.p.fast)
        self.slow_ma = bt.indicators.SMA(self.data.close, 
                                         period=self.p.slow)
        self.crossover = bt.indicators.CrossOver(self.fast_ma, 
                                                  self.slow_ma)
    
    def next(self):
        if self.order:
            return
        if not self.position:
            if self.crossover > 0:
                self.order = self.order_target_percent(target=0.95)
        elif self.crossover < 0:
            self.order = self.close()
    
    def notify_order(self, order):
        if order.status in [order.Completed]:
            self.order = None

# 第三步:准备数据
def get_data(symbol, start, end):
    df = yf.download(symbol, start=start, end=end, progress=False)
    df.columns = ['close', 'high', 'low', 'open', 'volume']
    return bt.feeds.PandasData(dataname=df)

# 第四步:运行回测
cerebro = bt.Cerebro()
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)

data = get_data('AAPL', '2020-01-01', '2023-12-31')
cerebro.adddata(data)
cerebro.addstrategy(SmaCrossStrategy, fast=10, slow=30)

cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe',
                    riskfreerate=0.02)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd')
cerebro.addanalyzer(bt.analyzers.Returns, _name='ret')

print(f"初始资金: ${cerebro.broker.getvalue():,.2f}")
results = cerebro.run()
print(f"最终资金: ${cerebro.broker.getvalue():,.2f}")

# 第五步:输出分析
strat = results[0]
print(f"\n夏普比率: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.3f}")
print(f"最大回撤: {strat.analyzers.dd.get_analysis()['max']['drawdown']:.2f}%")
print(f"年化收益: {strat.analyzers.ret.get_analysis()['rnorm100']:.2f}%")

# 绘制图表(Colab中需要特殊处理)
try:
    cerebro.plot(style='candlestick', iplot=False)
except:
    print("\n图表已在后台生成。在本地Jupyter环境中可以显示交互式图表。")
运行步骤
  1. 打开 Google Colab 并新建笔记本
  2. 把上面的代码复制到单元格中
  3. 按 Shift + Enter 逐步运行(或者 Runtime -> Run all)
  4. 观察输出结果,特别是夏普比率和最大回撤
  5. 修改 fastslow 参数,重新运行,对比不同参数下的结果
  6. 把股票代码换成你感兴趣的其他股票(如 TSLAMSFTBABA

本章思考题

知识测验
1. Backtrader中,策略的核心逻辑应该写在哪个方法里?
  • A. __init__
  • B. next
  • C. notify_order
  • D. run
答案是B。next()方法在每根K线到达时执行一次,是策略决策的核心。__init__只执行一次用于初始化,notify_order处理订单通知,run是Cerebro的方法不是策略的。
2. 以下哪个指标衡量的是"每承担一单位风险,获得多少超额收益"?
  • A. 最大回撤
  • B. 总收益率
  • C. 夏普比率
  • D. 胜率
答案是C。夏普比率 = (策略收益率 - 无风险利率) / 收益率标准差,衡量的是风险调整后的收益。最大回撤衡量的是最坏情况下的亏损幅度,胜率是盈利交易的比例。
3. 在Backtrader中,为什么需要跟踪订单状态(self.order)?
  • A. 避免在同一根K线上重复下单
  • B. 这是可选的,不跟踪也没问题
  • C. 为了计算佣金
  • D. 为了绘制订单标记
答案是A。如果不跟踪订单状态,策略可能在同一根K线上多次下单,导致仓位失控。在事件驱动的回测框架中,订单从提交到成交有一段延迟,需要等待确认后再发新单。
深度思考题
  1. 你运行了双均线策略的回测。如果回测结果非常好,你有多大信心认为它在未来也会表现好?列出至少三个可能让回测结果"过于乐观"的因素。
  2. Backtrader是事件驱动(event-driven)的回测框架,而VectorBT是向量化(vectorized)的。这两种方式各有什么优缺点?在什么场景下应该选择哪一种?
  3. 如果你要用Backtrader回测一个A股策略,你会遇到哪些特有的挑战?(提示:涨跌停、T+1、停牌等)

下一章预告

恭喜你!到这里为止,你已经掌握了量化交易最核心的基础设施:市场认知、风险管理、策略设计、回测验证。但这只是开始。

第10章将带你进入实战阶段——用AI辅助开发你的第一个完整策略。我们会从想法生成开始,到数据获取、策略编写、回测优化、风险控制,走完整个策略开发的完整流程。这一次,AI不只是你的老师,更是你的编程搭档。

你已经准备好了。让我们一起进入策略开发实战。

觉得有收获?分享给同样对量化交易感兴趣的朋友

上一章
你的第一个量化环境——不需要安装任何东西
搭建零配置的在线量化开发环境,运行第一个Python代码。
下一章
AI辅助策略开发实战
用AI辅助开发你的第一个完整策略,走完策略开发全流程。

如果这本书对你有所启发,欢迎支持我继续创作

赞赏二维码