1. 这节究竟解决什么实际问题?¶
4.2 backtrader 框架回测 的具体任务是:把同一研究假设放入事件驱动框架,并核对订单和分析器输出。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、仓位、交易成本与基本技术指标。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
默认固定历史样本。历史数据只能说明过去在既定假设下发生了什么,不能承诺未来收益。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 4.2 backtrader 框架回测 的输入、计算步骤、输出和局限;尤其能说明:把同一研究假设放入事件驱动框架,并核对订单和分析器输出。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
回测按照明确的成交和成本假设重放历史数据。它不能完整复现未来流动性、订单竞争和系统故障。
策略收益 = 滞后仓位 × 资产收益 − 换手 × 成本;每一项都要有明确单位。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
回测框架替你管理订单和记录,不会自动判断数据、成本或策略假设是否合理。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“把同一研究假设放入事件驱动框架,并核对订单和分析器输出。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
信号未滞后、把手续费只扣一次、忽略空仓收益、年化频率错配、将净值曲线当显著性证明。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
检查未来函数、幸存者偏差、重复测试集调参、成交可得性和失败区间;必须报告基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/04_backtesting/02_backtrader_intro_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/04_backtesting/02_backtrader_intro_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Advances in Financial Machine Learning(Lopez de Prado)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 03_performance_metrics。本节的概念会成为理解它的输入,而不是孤立的名词。
# 安装检查
try:
import backtrader as bt
print(f'backtrader 版本: {bt.__version__} ')
except ImportError:
print('请先安装: pip install backtrader')
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib
matplotlib.use('Agg') # 非交互式后端(Jupyter 兼容)
import matplotlib.pyplot as plt
backtrader 版本: 1.9.78.123
# 默认使用固定样本,保证课程与 CI 不依赖实时接口。
USE_OFFLINE_DATA = True
if USE_OFFLINE_DATA:
from pathlib import Path
import sys
docs_root = next(parent for parent in [Path.cwd(), *Path.cwd().parents]
if (parent / 'utils' / 'offline_data.py').exists())
sys.path.insert(0, str(docs_root))
from utils.offline_data import configure_yfinance_offline
configure_yfinance_offline(yf)
else:
print('在线下载为扩展步骤;请核对数据字段、时区和下载日期。')
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. backtrader 核心架构¶
Cerebro(大脑)
├── DataFeed(数据喂入)— OHLCV 行情
├── Strategy(策略)— 信号 + 买卖逻辑
├── Broker(经纪商)— 资金、仓位、手续费
└── Analyzer(分析器)— SharpeRatio, DrawDown 等
最核心的方法是 Strategy.next():每新增一根K线就会被调用一次。
2. 准备数据¶
# 下载数据并转换为 backtrader 格式
raw = yf.download('SPY', start='2018-01-01', end='2024-01-01', progress=False, auto_adjust=True)
raw = raw.droplevel(1, axis=1) if raw.columns.nlevels > 1 else raw
print(f'数据形状: {raw.shape}')
raw.tail(3)
数据形状: (1509, 5)
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| Date | |||||
| 2023-12-27 | 465.014343 | 465.160719 | 463.433430 | 463.970150 | 68000300 |
| 2023-12-28 | 465.190033 | 466.029271 | 464.770414 | 465.375452 | 77158100 |
| 2023-12-29 | 463.843231 | 465.521738 | 461.881713 | 464.994757 | 122283100 |
3. 实现 RSI 策略¶
class RSIStrategy(bt.Strategy):
"""
RSI 均值回归策略:
- RSI < 30 买入(超卖)
- RSI > 70 卖出(超买)
"""
params = (
('rsi_period', 14),
('rsi_overbought', 70),
('rsi_oversold', 30),
('stake_pct', 0.95), # 每次用 95% 资金买入
)
def __init__(self):
# 定义指标
self.rsi = bt.indicators.RelativeStrengthIndex(
self.data.close, period=self.params.rsi_period
)
self.order = None # 追踪当前挂单
def log(self, txt):
dt = self.datas[0].datetime.date(0)
print(f'[{dt}] {txt}')
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status == order.Completed:
action = '买入' if order.isbuy() else '卖出'
self.log(f'{action} @ {order.executed.price:.2f}, '
f'成本: {order.executed.value:.2f}, '
f'手续费: {order.executed.comm:.2f}')
self.order = None
def next(self):
# 已有挂单则跳过
if self.order:
return
if not self.position: # 无持仓时
if self.rsi < self.params.rsi_oversold:
# 超卖 → 买入
cash = self.broker.getcash()
size = int(cash * self.params.stake_pct / self.data.close[0])
self.order = self.buy(size=size)
else: # 有持仓时
if self.rsi > self.params.rsi_overbought:
# 超买 → 全部平仓
self.order = self.sell(size=self.position.size)
print('RSIStrategy 定义完成 ')
RSIStrategy 定义完成
4. 运行回测¶
# 配置 Cerebro
cerebro = bt.Cerebro()
cerebro.addstrategy(RSIStrategy)
# 添加数据
data_feed = bt.feeds.PandasData(dataname=raw)
cerebro.adddata(data_feed)
# 经纪商设置
cerebro.broker.setcash(100_000) # 初始资金 10 万
cerebro.broker.setcommission(0.001) # 0.1% 手续费
# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.04)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
# 运行
print(f'初始资金: {cerebro.broker.getvalue():,.2f}')
print('--- 交易记录 ---')
results = cerebro.run()
strat = results[0]
final_value = cerebro.broker.getvalue()
print('--- 回测完成 ---')
print(f'最终资产: {final_value:,.2f}')
print(f'总收益率: {(final_value / 100_000 - 1):.2%}')
初始资金: 100,000.00 --- 交易记录 --- [2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39 [2019-02-20] 卖出 @ 249.84, 成本: 94389.19, 手续费: 95.19 [2019-08-06] 买入 @ 259.52, 成本: 96280.90, 手续费: 96.28 [2019-11-08] 卖出 @ 280.68, 成本: 96280.90, 手续费: 104.13 [2020-02-26] 买入 @ 287.90, 成本: 103069.29, 手续费: 103.07 [2020-06-08] 卖出 @ 295.16, 成本: 103069.29, 手续费: 105.67 [2022-01-24] 买入 @ 408.67, 成本: 103392.39, 手续费: 103.39 [2022-08-15] 卖出 @ 404.79, 成本: 103392.39, 手续费: 102.41 [2022-09-26] 买入 @ 350.61, 成本: 103429.16, 手续费: 103.43 [2023-06-13] 卖出 @ 420.06, 成本: 103429.16, 手续费: 123.92 [2023-10-04] 买入 @ 410.22, 成本: 123067.35, 手续费: 123.07 [2023-11-21] 卖出 @ 440.46, 成本: 123067.35, 手续费: 132.14 --- 回测完成 --- 最终资产: 138,540.57 总收益率: 38.54%
5. 查看分析器结果¶
sharpe = strat.analyzers.sharpe.get_analysis()
drawdown = strat.analyzers.drawdown.get_analysis()
returns = strat.analyzers.returns.get_analysis()
trades = strat.analyzers.trades.get_analysis()
print('=' * 40)
print(' RSI 策略 — 回测结果')
print('=' * 40)
print(f'年化收益率 : {returns.get("rnorm100", 0):.2f}%')
print(f'夏普比率 : {sharpe["sharperatio"]:.2f}' if sharpe.get('sharperatio') else '夏普比率 : N/A')
print(f'最大回撤 : {drawdown.max.drawdown:.2f}%')
print(f'最大回撤天数: {drawdown.max.len} 天')
total_closed = trades.get('total', {}).get('closed', 0)
total_won = trades.get('won', {}).get('total', 0)
if total_closed > 0:
print(f'总交易次数 : {total_closed}')
print(f'胜率 : {total_won / total_closed:.2%}')
print('=' * 40)
========================================
RSI 策略 — 回测结果
========================================
年化收益率 : 5.59%
夏普比率 : 0.20
最大回撤 : 27.50%
最大回撤天数: 186 天
总交易次数 : 6
胜率 : 83.33%
========================================
6. 参数优化(网格搜索)¶
# 对 RSI 超买超卖阈值做简单优化
results_grid = []
for oversold in [25, 30, 35]:
for overbought in [65, 70, 75]:
cerebro_opt = bt.Cerebro()
cerebro_opt.addstrategy(RSIStrategy,
rsi_oversold=oversold,
rsi_overbought=overbought)
cerebro_opt.adddata(bt.feeds.PandasData(dataname=raw.copy()))
cerebro_opt.broker.setcash(100_000)
cerebro_opt.broker.setcommission(0.001)
cerebro_opt.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.04)
res = cerebro_opt.run()
sr = res[0].analyzers.sharpe.get_analysis().get('sharperatio', None)
final = cerebro_opt.broker.getvalue()
results_grid.append({
'oversold': oversold,
'overbought': overbought,
'final_value': round(final, 2),
'total_return': f'{(final/100_000 - 1):.2%}',
'sharpe': round(sr, 3) if sr else None
})
opt_df = pd.DataFrame(results_grid).sort_values('final_value', ascending=False)
print('参数优化结果(按最终资产排序):')
print(opt_df.to_string(index=False))
[2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39
[2019-02-05] 卖出 @ 245.01, 成本: 94389.19, 手续费: 93.35
[2020-02-28] 买入 @ 264.55, 成本: 91006.64, 手续费: 91.01
[2020-06-02] 卖出 @ 282.56, 成本: 91006.64, 手续费: 97.20
[2022-01-28] 买入 @ 409.28, 成本: 99864.41, 手续费: 99.86
[2022-03-30] 卖出 @ 436.80, 成本: 99864.41, 手续费: 106.58
[2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39
[2019-02-20] 卖出 @ 249.84, 成本: 94389.19, 手续费: 95.19
[2020-02-28] 买入 @ 264.55, 成本: 92593.97, 手续费: 92.59
[2020-06-08] 卖出 @ 295.16, 成本: 92593.97, 手续费: 103.31
[2022-01-28] 买入 @ 409.28, 成本: 105594.34, 手续费: 105.59
[2022-08-15] 卖出 @ 404.79, 成本: 105594.34, 手续费: 104.44
[2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39
[2019-11-18] 卖出 @ 284.08, 成本: 94389.19, 手续费: 108.23
[2020-02-28] 买入 @ 264.55, 成本: 104498.91, 手续费: 104.50
[2020-06-09] 卖出 @ 295.24, 成本: 104498.91, 手续费: 116.62
[2022-01-28] 买入 @ 409.28, 成本: 119509.87, 手续费: 119.51
[2023-06-16] 卖出 @ 429.07, 成本: 119509.87, 手续费: 125.29
[2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39
[2019-02-05] 卖出 @ 245.01, 成本: 94389.19, 手续费: 93.35
[2019-08-06] 买入 @ 259.52, 成本: 94464.28, 手续费: 94.46
[2019-10-31] 卖出 @ 277.33, 成本: 94464.28, 手续费: 100.95
[2020-02-26] 买入 @ 287.90, 成本: 100190.26, 手续费: 100.19
[2020-06-02] 卖出 @ 282.56, 成本: 100190.26, 手续费: 98.33
[2022-01-24] 买入 @ 408.67, 成本: 96445.07, 手续费: 96.45
[2022-03-30] 卖出 @ 436.80, 成本: 96445.07, 手续费: 103.08
[2022-09-26] 买入 @ 350.61, 成本: 103429.16, 手续费: 103.43
[2023-02-02] 卖出 @ 398.79, 成本: 103429.16, 手续费: 117.64
[2023-10-04] 买入 @ 410.22, 成本: 117324.20, 手续费: 117.32
[2023-11-15] 卖出 @ 437.48, 成本: 117324.20, 手续费: 125.12
[2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39
[2019-02-20] 卖出 @ 249.84, 成本: 94389.19, 手续费: 95.19
[2019-08-06] 买入 @ 259.52, 成本: 96280.90, 手续费: 96.28
[2019-11-08] 卖出 @ 280.68, 成本: 96280.90, 手续费: 104.13
[2020-02-26] 买入 @ 287.90, 成本: 103069.29, 手续费: 103.07
[2020-06-08] 卖出 @ 295.16, 成本: 103069.29, 手续费: 105.67
[2022-01-24] 买入 @ 408.67, 成本: 103392.39, 手续费: 103.39
[2022-08-15] 卖出 @ 404.79, 成本: 103392.39, 手续费: 102.41
[2022-09-26] 买入 @ 350.61, 成本: 103429.16, 手续费: 103.43
[2023-06-13] 卖出 @ 420.06, 成本: 103429.16, 手续费: 123.92
[2023-10-04] 买入 @ 410.22, 成本: 123067.35, 手续费: 123.07
[2023-11-21] 卖出 @ 440.46, 成本: 123067.35, 手续费: 132.14
[2018-10-11] 买入 @ 247.74, 成本: 94389.19, 手续费: 94.39
[2019-11-18] 卖出 @ 284.08, 成本: 94389.19, 手续费: 108.23
[2020-02-26] 买入 @ 287.90, 成本: 108251.55, 手续费: 108.25
[2020-06-09] 卖出 @ 295.24, 成本: 108251.55, 手续费: 111.01
[2022-01-24] 买入 @ 408.67, 成本: 108705.04, 手续费: 108.71
[2023-06-16] 卖出 @ 429.07, 成本: 108705.04, 手续费: 114.13
[2023-10-04] 买入 @ 410.22, 成本: 115273.08, 手续费: 115.27
[2023-12-14] 卖出 @ 459.24, 成本: 115273.08, 手续费: 129.05
[2018-02-06] 买入 @ 229.42, 成本: 93371.97, 手续费: 93.37
[2018-06-11] 卖出 @ 246.73, 成本: 93371.97, 手续费: 100.42
[2018-10-11] 买入 @ 247.74, 成本: 100830.45, 手续费: 100.83
[2019-02-05] 卖出 @ 245.01, 成本: 100830.45, 手续费: 99.72
[2019-05-14] 买入 @ 254.72, 成本: 100614.49, 手续费: 100.61
[2019-06-21] 卖出 @ 266.98, 成本: 100614.49, 手续费: 105.46
[2019-08-06] 买入 @ 259.52, 成本: 105364.00, 手续费: 105.36
[2019-10-31] 卖出 @ 277.33, 成本: 105364.00, 手续费: 112.60
[2020-02-26] 买入 @ 287.90, 成本: 111706.38, 手续费: 111.71
[2020-06-02] 卖出 @ 282.56, 成本: 111706.38, 手续费: 109.63
[2021-09-21] 买入 @ 411.48, 成本: 109452.78, 手续费: 109.45
[2021-10-22] 卖出 @ 427.12, 成本: 109452.78, 手续费: 113.61
[2022-01-20] 买入 @ 429.21, 成本: 113311.68, 手续费: 113.31
[2022-03-30] 卖出 @ 436.80, 成本: 113311.68, 手续费: 115.31
[2022-04-27] 买入 @ 395.90, 成本: 114811.43, 手续费: 114.81
[2022-08-01] 卖出 @ 389.90, 成本: 114811.43, 手续费: 113.07
[2022-09-07] 买入 @ 372.06, 成本: 112363.01, 手续费: 112.36
[2023-02-02] 卖出 @ 398.79, 成本: 112363.01, 手续费: 120.43
[2023-03-14] 买入 @ 375.37, 成本: 121621.22, 手续费: 121.62
[2023-06-05] 卖出 @ 413.26, 成本: 121621.22, 手续费: 133.90
[2023-08-18] 买入 @ 419.73, 成本: 130535.04, 手续费: 130.54
[2023-11-15] 卖出 @ 437.48, 成本: 130535.04, 手续费: 136.06
[2018-02-06] 买入 @ 229.42, 成本: 93371.97, 手续费: 93.37
[2018-07-26] 卖出 @ 252.07, 成本: 93371.97, 手续费: 102.59
[2018-10-11] 买入 @ 247.74, 成本: 103060.11, 手续费: 103.06
[2019-02-20] 卖出 @ 249.84, 成本: 103060.11, 手续费: 103.94
[2019-05-14] 买入 @ 254.72, 成本: 104435.29, 手续费: 104.44
[2019-07-05] 卖出 @ 269.98, 成本: 104435.29, 手续费: 110.69
[2019-08-06] 买入 @ 259.52, 成本: 110554.34, 手续费: 110.55
[2019-11-08] 卖出 @ 280.68, 成本: 110554.34, 手续费: 119.57
[2020-02-26] 买入 @ 287.90, 成本: 118616.06, 手续费: 118.62
[2020-06-08] 卖出 @ 295.16, 成本: 118616.06, 手续费: 121.61
[2021-09-21] 买入 @ 411.48, 成本: 121385.60, 手续费: 121.39
[2021-11-03] 卖出 @ 434.82, 成本: 121385.60, 手续费: 128.27
[2022-01-20] 买入 @ 429.21, 成本: 127475.64, 手续费: 127.48
[2022-08-15] 卖出 @ 404.79, 成本: 127475.64, 手续费: 120.22
[2022-09-07] 买入 @ 372.06, 成本: 119804.26, 手续费: 119.80
[2023-06-13] 卖出 @ 420.06, 成本: 119804.26, 手续费: 135.26
[2023-08-18] 买入 @ 419.73, 成本: 133473.12, 手续费: 133.47
[2023-11-21] 卖出 @ 440.46, 成本: 133473.12, 手续费: 140.07
[2018-02-06] 买入 @ 229.42, 成本: 93371.97, 手续费: 93.37
[2019-11-18] 卖出 @ 284.08, 成本: 93371.97, 手续费: 115.62
[2020-02-26] 买入 @ 287.90, 成本: 116312.83, 手续费: 116.31
[2020-06-09] 卖出 @ 295.24, 成本: 116312.83, 手续费: 119.28
[2021-09-21] 买入 @ 411.48, 成本: 118916.74, 手续费: 118.92
[2021-11-05] 卖出 @ 442.35, 成本: 118916.74, 手续费: 127.84
[2022-01-20] 买入 @ 429.21, 成本: 127046.43, 手续费: 127.05
[2023-06-16] 卖出 @ 429.07, 成本: 127046.43, 手续费: 127.01
[2023-08-18] 买入 @ 419.73, 成本: 125498.31, 手续费: 125.50
[2023-12-14] 卖出 @ 459.24, 成本: 125498.31, 手续费: 137.31
参数优化结果(按最终资产排序):
oversold overbought final_value total_return sharpe
35 70 147925.63 47.93% 0.297
35 75 144697.50 44.70% 0.233
35 65 143757.24 43.76% 0.258
30 70 138540.57 38.54% 0.199
30 75 134914.64 34.91% 0.143
25 75 131075.25 31.08% 0.108
30 65 130989.67 30.99% 0.110
25 65 111288.07 11.29% -0.352
25 70 109759.87 9.76% -0.325
练习¶
- 修改策略:当 RSI 从下穿越 30 后(而不是持续低于 30 时)才买入。
- 加入止损:持仓亏损超过 5% 时强制平仓。
- 使用
cerebro.optstrategy()做完整网格搜索,找到最优参数组合。
下一节 → 03_performance_metrics.ipynb