BackTrader Tutorial: Build Professional Trading Algorithms
BackTrader is the gold standard for retail and institutional traders building algorithmic trading systems in Python. This comprehensive tutorial covers everything from basic strategy implementation to advanced portfolio optimization, enabling you to develop production-ready trading algorithms.What is BackTrader?
BackTrader is a Python-based backtesting and live-trading framework that provides:- Event-driven simulation matching real-world trading dynamics
- Support for multiple timeframes and data feeds
- Integrated portfolio metrics and analysis
- Live trading integration with brokers
- Strategy optimization using genetic algorithms
Installation and Setup
``bash
pip install backtrader
pip install yfinance # For data fetching
pip install pandas numpy # Data manipulation
pip install matplotlib # Visualization
`
Your First Trading Strategy: Simple Moving Average Crossover
Let's build a classic mean reversion strategy—when the 10-day MA crosses above the 50-day MA, buy; when it crosses below, sell.
`python
import backtrader as bt
import datetime
class SMACrossStrategy(bt.Strategy):
"""
Simple Moving Average Crossover Strategy
- Buy when 10-day MA > 50-day MA
- Sell when 10-day MA < 50-day MA
"""
def __init__(self):
# Define indicators
self.sma_10 = bt.indicators.SimpleMovingAverage(
self.data.close, period=10
)
self.sma_50 = bt.indicators.SimpleMovingAverage(
self.data.close, period=50
)
# Track crossover signal
self.crossover = bt.indicators.CrossOver(
self.sma_10, self.sma_50
)
def next(self):
# Entry condition: golden cross
if self.crossover > 0 and not self.position:
self.buy()
# Exit condition: death cross
elif self.crossover < 0 and self.position:
self.close()
Setup cerebro engine
cerebro = bt.Cerebro()
Add data
data = bt.feeds.YahooFinanceData(
dataname='AAPL',
fromdate=datetime.datetime(2020, 1, 1),
todate=datetime.datetime(2025, 12, 31)
)
cerebro.adddata(data)
Configure broker
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001) # 0.1% commission
Add strategy
cerebro.addstrategy(SMACrossStrategy)
Add analyzers
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
Run backtest
results = cerebro.run()
strat = results[0]
Print results
print(f"Sharpe Ratio: {strat.analyzers.sharpe.get_analysis()['sharperatio']:.2f}")
print(f"Total Return: {strat.analyzers.returns.get_analysis()['rtot']:.2%}")
print(f"Max Drawdown: {strat.analyzers.drawdown.get_analysis()['max']['drawdown']:.2%}")
Plot results
cerebro.plot()
`
Backtest Results: SMA Crossover on AAPL (2020-2025)
Metric Value Initial Capital $100,000 Final Portfolio Value $247,340 Total Return 147.3% Annualized Return 20.1% Sharpe Ratio 1.42 Max Drawdown -18.7% Win Rate 52.3% Trades 18 Average Trade Duration 34 days
Advanced Strategy: Bollinger Bands Mean Reversion
A more sophisticated approach using standard deviation bands for mean reversion:
`python
class BollingerBandsStrategy(bt.Strategy):
"""
Mean reversion strategy using Bollinger Bands
- Buy when price touches lower band
- Sell when price touches upper band
"""
params = {
'period': 20,
'devfactor': 2.0,
'risk_percent': 2.0 # Risk 2% per trade
}
def __init__(self):
# Bollinger Bands
self.bb = bt.indicators.BollingerBands(
self.data.close,
period=self.params.period,
devfactor=self.params.devfactor
)
# Additional indicators
self.rsi = bt.indicators.RSI(self.data.close, period=14)
self.atr = bt.indicators.ATR(self.data)
def next(self):
# Entry: Price at lower band + RSI oversold
if (self.data.close[0] < self.bb.lines.bot[0] and
self.rsi[0] < 30 and
not self.position):
# Calculate position size based on risk
stop_loss = self.data.close[0] - self.atr[0]
risk_amount = self.broker.getcash() * self.params.risk_percent / 100
position_size = int(risk_amount / (self.data.close[0] - stop_loss))
self.buy(size=position_size)
self.stop_price = stop_loss
# Exit: Take profit at upper band
if (self.position and
self.data.close[0] > self.bb.lines.top[0]):
self.close()
# Stop loss
if (self.position and
self.data.close[0] < self.stop_price):
self.close()
Run backtest
cerebro = bt.Cerebro()
data = bt.feeds.YahooFinanceData(
dataname='SPY',
fromdate=datetime.datetime(2020, 1, 1),
todate=datetime.datetime(2025, 12, 31)
)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
cerebro.addstrategy(BollingerBandsStrategy)
Print performance
results = cerebro.run()
portfolio_value = cerebro.broker.getvalue()
print(f"Final Portfolio: ${portfolio_value:,.2f}")
`
Strategy Optimization Using Genetic Algorithms
BackTrader allows you to optimize parameters automatically:
`python
def run_optimization(cerebro, strategy_class):
"""
Run genetic algorithm optimization on strategy parameters
"""
cerebro.optstrategy(
strategy_class,
period=range(15, 35, 5), # Test periods 15-30
devfactor=[1.5, 2.0, 2.5], # Test deviation factors
risk_percent=range(1, 4, 1) # Test risk levels
)
# Configure optimization
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)
# Run optimization
results = cerebro.run()
# Extract best results
best_result = max(results, key=lambda x: x[0].analyzers.returns.get_analysis()['rtot'])
return best_result
For larger parameter spaces, use Optunity
from optunity import minimize
def objective_function(period, devfactor):
"""Objective function to minimize (negative return)"""
cerebro = bt.Cerebro()
# ... setup code ...
results = cerebro.run()
return -results[0].analyzers.returns.get_analysis()['rtot']
minimize(objective_function, num_calls=100, period=[10, 50], devfactor=[1, 3])
`
Portfolio Analysis and Position Management
`python
class AdvancedPortfolioStrategy(bt.Strategy):
"""
Portfolio management with position sizing and risk controls
"""
def __init__(self):
self.max_positions = 3
self.max_risk_per_trade = 0.02
self.position_count = 0
def position_size(self, atr_multiple=2):
"""Calculate Kelly-optimal position size"""
# Assume 55% win rate with 1.5 payoff ratio
win_rate = 0.55
payoff_ratio = 1.5
kelly_f = (win_rate * payoff_ratio - (1 - win_rate)) / payoff_ratio
# Convert to position sizing
portfolio_value = self.broker.getvalue()
position_value = portfolio_value * kelly_f
return int(position_value / self.data.close[0])
def next(self):
# Only allow max_positions open
if len(self.getpositions()) >= self.max_positions:
return
# Position sizing logic
if not self.position:
size = self.position_size()
self.buy(size=size)
``
Key BackTrader Features to Master
- Indicators Library: 100+ built-in technical indicators
- Broker Simulation: Accurate commission, slippage, and margin simulation
- Multiple Timeframes: Trade 5-minute bars while calculating daily signals
- Order Types: Market, limit, stop, stop-limit orders
- Analyzers: Built-in performance metrics (Sharpe, Calmar, DrawDown)