| name | backtrader |
| description | Backtrader 开源量化回测框架,支持多数据源、多策略、多周期回测与实盘交易,纯Python实现。当用户需要开发量化策略、进行回测分析、编写交易逻辑、回测参数优化,或提及 backtrader、量化回测框架时使用。若用户仅需数据获取而无回测需求,引导使用 baostock/akshare/tushare 等数据 Skill。 |
| metadata | {"openclaw":{"emoji":"📈","requires":{"bins":["python3"]}}} |
Backtrader(开源量化回测框架)
Backtrader 是一个开源 Python 量化回测框架。采用事件驱动架构,核心组件包括:Cerebro(引擎)、Strategy(策略类)、Data Feed(数据源)、Broker(经纪商)、Indicator(内置 100+ 技术指标)、Analyzer(绩效分析)。纯 Python 实现,无外部依赖,适合离线研究。
官方文档:https://www.backtrader.com/docu/
安装
pip install backtrader
pip install backtrader[plotting]
pip install matplotlib
最简示例
import backtrader as bt
class MyStrategy(bt.Strategy):
"""简单均线策略"""
params = (('period', 20),)
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.period)
def next(self):
if self.data.close[0] > self.sma[0]:
if not self.position:
self.buy()
elif self.data.close[0] < self.sma[0]:
if self.position:
self.sell()
cerebro = bt.Cerebro()
cerebro.addstrategy(MyStrategy)
data = bt.feeds.YahooFinanceCSVData(dataname='stock_data.csv')
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)
print(f'初始资金: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'最终资金: {cerebro.broker.getvalue():.2f}')
cerebro.plot()
数据源
从Pandas DataFrame加载
import backtrader as bt
import pandas as pd
df = pd.read_csv('stock_data.csv', parse_dates=['date'], index_col='date')
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
从CSV文件加载
data = bt.feeds.GenericCSVData(
dataname='stock_data.csv',
dtformat='%Y-%m-%d',
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1
)
cerebro.adddata(data)
多股票 / 多周期
data1 = bt.feeds.PandasData(dataname=df1, name='stock1')
data2 = bt.feeds.PandasData(dataname=df2, name='stock2')
cerebro.adddata(data1)
cerebro.adddata(data2)
class MultiStockStrategy(bt.Strategy):
def __init__(self):
self.sma1 = bt.indicators.SMA(self.datas[0].close, period=20)
self.sma2 = bt.indicators.SMA(self.datas[1].close, period=20)
def next(self):
for i, d in enumerate(self.datas):
print(f'{d._name}: close={d.close[0]:.2f}')
数据重采样(分钟线转日线)
data_min = bt.feeds.GenericCSVData(dataname='1min_data.csv', timeframe=bt.TimeFrame.Minutes)
cerebro.adddata(data_min)
cerebro.resampledata(data_min, timeframe=bt.TimeFrame.Days)
策略类详解
策略参数
class MyStrategy(bt.Strategy):
params = (
('fast_period', 5),
('slow_period', 20),
('stake', 100),
)
def __init__(self):
self.fast_ma = bt.indicators.SMA(period=self.p.fast_period)
self.slow_ma = bt.indicators.SMA(period=self.p.slow_period)
def next(self):
if self.fast_ma[0] > self.slow_ma[0]:
self.buy(size=self.p.stake)
cerebro.addstrategy(MyStrategy, fast_period=10, slow_period=30)
交易方法
class MyStrategy(bt.Strategy):
def next(self):
self.buy(size=100)
self.sell(size=100)
self.order_target_size(target=500)
self.order_target_value(target=50000)
self.order_target_percent(target=0.5)
self.buy(size=100, price=10.5, exectype=bt.Order.Limit)
self.sell(size=100, price=9.0, exectype=bt.Order.Stop)
self.buy(size=100, price=10.5, pricelimit=10.8, exectype=bt.Order.StopLimit)
order = self.buy(size=100)
self.cancel(order)
self.buy(data=self.datas[1], size=200)
订单通知回调
class MyStrategy(bt.Strategy):
def notify_order(self, order):
"""订单状态变化时触发"""
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
print(f'买入执行: 价格={order.executed.price:.2f}, '
f'数量={order.executed.size}, 手续费={order.executed.comm:.2f}')
else:
print(f'卖出执行: 价格={order.executed.price:.2f}, '
f'数量={order.executed.size}, 手续费={order.executed.comm:.2f}')
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
print(f'订单失败: 状态={order.getstatusname()}')
def notify_trade(self, trade):
"""交易完成时触发(一买一卖构成完整交易)"""
if trade.isclosed:
print(f'交易完成: 毛利润={trade.pnl:.2f}, 净利润={trade.pnlcomm:.2f}')
获取数据与持仓
class MyStrategy(bt.Strategy):
def next(self):
current_close = self.data.close[0]
prev_close = self.data.close[-1]
current_volume = self.data.volume[0]
current_date = self.data.datetime.date(0)
position = self.getposition(self.data)
print(f'持仓数量: {position.size}')
print(f'平均成本: {position.price:.2f}')
cash = self.broker.getcash()
value = self.broker.getvalue()
print(f'可用资金: {cash:.2f}, 总资产: {value:.2f}')
内置技术指标
class MyStrategy(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=20)
self.ema = bt.indicators.ExponentialMovingAverage(self.data.close, period=20)
self.wma = bt.indicators.WeightedMovingAverage(self.data.close, period=20)
self.macd = bt.indicators.MACD(self.data.close)
self.rsi = bt.indicators.RSI(self.data.close, period=14)
self.boll = bt.indicators.BollingerBands(self.data.close, period=20, devfactor=2.0)
self.stoch = bt.indicators.Stochastic(self.data, period=14)
self.atr = bt.indicators.ATR(self.data, period=14)
self.crossover = bt.indicators.CrossOver(self.sma, self.ema)
券商/经纪商设置
cerebro = bt.Cerebro()
cerebro.broker.setcash(1000000.0)
cerebro.broker.setcommission(commission=0.001)
cerebro.broker.setcommission(
commission=0.0003,
margin=None,
mult=1.0
)
cerebro.broker.set_slippage_perc(perc=0.001)
cerebro.broker.set_slippage_fixed(fixed=0.02)
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
分析器
cerebro = bt.Cerebro()
cerebro.addstrategy(MyStrategy)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn')
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annual')
results = cerebro.run()
strat = results[0]
print(f"夏普比率: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.2f}")
print(f"最大回撤: {strat.analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%")
print(f"总收益率: {strat.analyzers.returns.get_analysis()['rtot']:.4f}")
trade_analysis = strat.analyzers.trades.get_analysis()
print(f"总交易次数: {trade_analysis['total']['total']}")
print(f"盈利次数: {trade_analysis['won']['total']}")
print(f"亏损次数: {trade_analysis['lost']['total']}")
参数优化
cerebro = bt.Cerebro()
cerebro.optstrategy(
MyStrategy,
fast_period=range(5, 15),
slow_period=range(20, 40, 5)
)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
cerebro.broker.setcash(100000)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
results = cerebro.run(maxcpus=4)
best_sharpe = -999
best_params = None
for result in results:
for strat in result:
sharpe = strat.analyzers.sharpe.get_analysis().get('sharperatio', 0) or 0
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = strat.params
print(f'最优参数: fast={best_params.fast_period}, slow={best_params.slow_period}')
print(f'最优夏普: {best_sharpe:.2f}')
进阶示例
更多完整策略示例(MACD+布林带组合、海龟交易、多股票轮动)见 references/advanced-strategies.md。
常见错误处理
| 错误 | 原因 | 解决方法 |
|---|
KeyError 或数据列缺失 | DataFrame 列名不符合要求 | 确保列名为小写:open, high, low, close, volume,索引为日期 |
IndexError: array index out of range | 数据量不足,指标无法计算 | 增加数据量或减少指标周期 |
| 绘图无输出/报错 | 未安装 matplotlib | pip install matplotlib |
| 参数优化结果为空 | sharpe 返回 None | 使用 or 0 处理空值(见参数优化示例) |
| 策略不触发交易 | 数据格式或逻辑问题 | 检查 DataFrame 索引是否为 DatetimeIndex,数据是否按日期排序 |
NegativeStackSize | 数据长度不足以覆盖预热期 | 增加数据量或减小指标窗口 |
使用技巧
- Backtrader 是纯本地框架,不依赖在线服务,适合离线研究。
- 数据需要用户自行准备(可配合 baostock、AKShare、Tushare 等数据源 Skill 使用)。
- 在
__init__ 中定义指标,在 next 中编写交易逻辑 — 这是核心模式。
- 使用
self.data.close[0] 访问当前值,[-1] 访问前一个值。
- 通过
optstrategy 进行参数优化支持多核并行,显著加速。
- 绘图需要安装 matplotlib;直接调用
cerebro.plot() 即可。
资源索引
规则
- 使用此 Skill 前,确认用户明确需要 backtrader 框架进行策略开发。若用户仅需要数据获取,引导使用 baostock/pywencai 等数据 Skill。
- 指标必须在
__init__ 中定义,交易逻辑必须在 next 中编写,不要在 next 中创建指标。
- DataFrame 列名必须为小写(
open, high, low, close, volume),索引必须为日期(DatetimeIndex)。
- 示例中若需真实市场数据,应提示用户配合 baostock 等数据源 Skill 获取,不要在示例中使用虚构数据路径。
- 始终在策略中处理
sharpe 为 None 的情况(参数优化时)。