| name | backtrader |
| description | Backtrader open-source quantitative backtesting framework — supports multiple data sources, strategies, and timeframes for backtesting and live trading, implemented in pure Python. |
| homepage | https://github.com/mementum/backtrader |
Backtrader (Open-Source Quantitative Backtesting Framework)
Backtrader is a powerful open-source Python quantitative backtesting framework that supports multiple data sources, strategies, and timeframes for backtesting and live trading. Implemented in pure Python with no external dependencies, it features a clean and extensible architecture.
Documentation: https://www.backtrader.com/docu/
Installation
pip install backtrader
pip install backtrader[plotting]
pip install matplotlib
Core Concepts
Backtrader uses an object-oriented, event-driven architecture:
- Cerebro: The strategy engine, responsible for coordinating data, strategies, and the broker
- Strategy: The strategy class where trading logic is written
- Data Feed: Data sources, supporting CSV, Pandas, and online data
- Broker: Broker simulation, managing funds and orders
- Indicator: Technical indicators, with 100+ built-in common indicators
- Analyzer: Analyzers for calculating strategy performance metrics
- Observer: Observers that record strategy runtime status
Minimal Example
import backtrader as bt
class MyStrategy(bt.Strategy):
"""Simple moving average 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=)
()
cerebro.run()
()
cerebro.plot()
Data Sources
Loading from 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)
Loading from CSV File
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)
Multiple Stocks / Multiple Timeframes
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 Resampling (Minute Bars to Daily Bars)
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)
Strategy Class In-Depth
Strategy Parameters
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)
Trading Methods
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)
Order Notification Callbacks
class MyStrategy(bt.Strategy):
def notify_order(self, order):
"""Triggered when order status changes"""
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
print(f'Buy executed: price={order.executed.price:.2f}, '
f'size={order.executed.size}, commission={order.executed.comm:.2f}')
else:
print(f'Sell executed: price={order.executed.price:.2f}, '
f'size={order.executed.size}, commission={order.executed.comm:.2f}')
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
print(f'Order failed: status={order.getstatusname()}')
def notify_trade(self, trade):
"""Triggered when a trade is completed (a buy and sell form a complete trade)"""
if trade.isclosed:
print(f'Trade completed: gross P&L={trade.pnl:.2f}, net P&L={trade.pnlcomm:f}')
Accessing Data and Positions
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: {position.size}')
print(f'Average price: {position.price:.2f}')
cash = self.broker.getcash()
value = self.broker.getvalue()
print(f'Available cash: {cash:.2f}, Total value: {value:.2f}')
Built-in Technical Indicators
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, .ema)
Broker Settings
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)
Analyzers
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"Sharpe Ratio: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.2f}")
print(f"Max Drawdown: {strat.analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%")
print(f"Total Return: {strat.analyzers.returns.get_analysis()['rtot']:.4f}")
trade_analysis = strat.analyzers.trades.get_analysis()
print(f"Total trades: {trade_analysis['total']['total']}")
print(f"Winning trades: {trade_analysis['won']['total']}")
print(f"Losing trades: ")
Parameter Optimization
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)
if sharpe and sharpe > best_sharpe:
best_sharpe = sharpe
best_params = strat.params
print(f'Best params: fast={best_params.fast_period}, slow={best_params.slow_period}')
print(f'Best Sharpe: {best_sharpe:.2f}')
Advanced Examples
MACD + Bollinger Bands Combination Strategy
import backtrader as bt
class MACDBollStrategy(bt.Strategy):
"""MACD golden cross + Bollinger Band lower band support combination buy strategy"""
params = (
('macd_fast', 12),
('macd_slow', 26),
('macd_signal', 9),
('boll_period', 20),
('boll_dev', 2.0),
('stake', 100),
)
def __init__(self):
self.macd = bt.indicators.MACD(
self.data.close,
period_me1=self.p.macd_fast,
period_me2=self.p.macd_slow,
period_signal=self.p.macd_signal
)
self.boll = bt.indicators.BollingerBands(
self.data.close, period=self.p.boll_period, devfactor=self.p.boll_dev
)
self.macd_cross = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)
def next(self):
if not self.position:
if self.macd_cross[] > .data.close[] < .boll.mid[]:
.buy(size=.p.stake)
()
:
.data.close[] > .boll.top[] .macd_cross[] < :
.sell(size=.p.stake)
()
():
trade.isclosed:
()
cerebro = bt.Cerebro()
cerebro.addstrategy(MACDBollStrategy)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
cerebro.broker.setcash()
cerebro.broker.setcommission(commission=)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name=)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name=)
results = cerebro.run()
strat = results[]
()
()
cerebro.plot()
Turtle Trading Strategy (Complete Implementation)
import backtrader as bt
class TurtleStrategy(bt.Strategy):
"""Classic Turtle Trading Strategy — Donchian Channel breakout + ATR position sizing"""
params = (
('entry_period', 20),
('exit_period', 10),
('atr_period', 20),
('risk_pct', 0.01),
)
def __init__(self):
self.entry_high = bt.indicators.Highest(self.data.high, period=self.p.entry_period)
self.entry_low = bt.indicators.Lowest(self.data.low, period=self.p.entry_period)
self.exit_high = bt.indicators.Highest(self.data.high, period=self.p.exit_period)
self.exit_low = bt.indicators.Lowest(self.data.low, period=self.p.exit_period)
self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
self.order = None
def next(self):
if self.order:
return
atr_val = .atr[]
atr_val <= :
unit_size = (.broker.getvalue() * .p.risk_pct / atr_val)
unit_size = (unit_size, )
.position:
.data.close[] > .entry_high[-]:
.order = .buy(size=unit_size)
:
.data.close[] < .exit_low[-]:
.order = .close()
():
order.status [order.Completed]:
order.isbuy():
()
:
()
.order =
Multi-Stock Rotation Strategy
import backtrader as bt
class MomentumRotation(bt.Strategy):
"""Momentum rotation strategy — hold the top N stocks with strongest momentum each month"""
params = (
('momentum_period', 20),
('hold_num', 3),
('rebalance_days', 20),
)
def __init__(self):
self.counter = 0
self.momentums = {}
for d in self.datas:
self.momentums[d._name] = bt.indicators.RateOfChange(
d.close, period=self.p.momentum_period
)
def next(self):
self.counter += 1
if self.counter % self.p.rebalance_days != 0:
return
rankings = []
for d in self.datas:
mom = self.momentums[d._name][0]
rankings.append((d._name, d, mom))
rankings.sort(key= x: x[], reverse=)
selected = [r[] r rankings[:.p.hold_num]]
selected_names = [r[] r rankings[:.p.hold_num]]
()
d .datas:
.getposition(d).size > d selected:
.close(data=d)
selected:
per_value = .broker.getvalue() * / (selected)
d selected:
target_size = (per_value / d.close[])
current_size = .getposition(d).size
target_size > current_size:
.buy(data=d, size=target_size - current_size)
target_size < current_size:
.sell(data=d, size=current_size - target_size)
Usage Tips
- Backtrader is a purely local framework with no dependency on online services, ideal for offline research.
- Data must be prepared by the user (can be used with data sources like AKShare, Tushare, etc.).
- Define indicators in
__init__, write trading logic in next — this is the core pattern.
- Use
self.data.close[0] to access the current value, [-1] to access the previous value.
- Parameter optimization via
optstrategy supports multi-core parallelism for significant speedup.
- Plotting requires matplotlib to be installed; simply call
cerebro.plot().
- Documentation: https://www.backtrader.com/docu/
社区与支持
由 大佬量化 (Boss Quant) 维护 — 量化交易教学与策略研发团队。
微信客服: bossquant1 · Bilibili · 搜索 大佬量化 on 微信公众号 / Bilibili / 抖音