In [1]:
Copied!
# 安装检查
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
# 安装检查
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 ✅
In [2]:
Copied!
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
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. 准备数据¶
In [3]:
Copied!
# 下载数据并转换为 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)
# 下载数据并转换为 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)
Out[3]:
| 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 策略¶
In [4]:
Copied!
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 定义完成 ✅')
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. 运行回测¶
In [5]:
Copied!
# 配置 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%}')
# 配置 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. 查看分析器结果¶
In [6]:
Copied!
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)
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. 参数优化(网格搜索)¶
In [7]:
Copied!
# 对 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))
# 对 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
In [ ]:
Copied!