Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: backtrader description: Event-driven backtesting with bar-by-bar execution, complex order types, multiple analyzers, and custom indicators
Backtrader
Backtrader is a Python event-driven backtesting framework that processes data bar-by-bar, simulating realistic execution with a built-in broker, order management, and position tracking. Unlike vectorized frameworks (vectorbt, pandas), backtrader walks through history one bar at a time, firing callbacks that let you implement complex order logic that depends on previous fills, partial executions, and conditional brackets.
Event-Driven vs Vectorized
| Aspect | Backtrader (event-driven) | vectorbt (vectorized) | |
|---|---|---|---|
| Execution model | Bar-by-bar callbacks | Whole-array operations | |
| Speed | Slower (Python loop) | Fast (NumPy/Numba) | |
| Order types | Market, limit, stop, stop-limit, bracket, OCO | Market only (native) | |
| Realism | Built-in broker with commission, slippage, margin | Manual slippage modeling | |
| Multi-timeframe | Native resampledata | Manual alignment | |
| Best for | Complex strategies, bracket orders, portfolio | Fast parameter sweeps, simple signals |
Use backtrader when you need:
- Bracket orders (entry + stop loss + take profit as a unit)
- Stop-limit or trailing stop orders
- Order-dependent logic (scale in after first fill, cancel if not filled in N bars)
- Multi-timeframe strategies (daily signals, hourly execution)
- Realistic commission and slippage modeling
Use vectorbt when you need:
- Fast parameter optimization over thousands of combinations
- Simple long/short signals without complex order management
- Quick prototyping and statistical analysis of results
Core Concepts
Backtrader has five core objects that interact through an event loop:
1. Cerebro (the engine)
The central orchestrator. You add strategies, data feeds, analyzers, and sizers to Cerebro, then call run().
import backtrader as btcerebro = bt.Cerebro()cerebro.addstrategy(MyStrategy, fast_period=10, slow_period=30)cerebro.adddata(data_feed)cerebro.broker.setcash(100_000)cerebro.broker.setcommission(commission=0.003) # 0.3%cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe")cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")cerebro.run()
2. Strategy (your logic)
A Strategy subclass contains all trading logic. Key methods:
__init__()— Define indicators. Runs once before backtesting starts.next()— Called on every bar. Place orders here.notify_order(order)— Called when order status changes (submitted, accepted, completed, canceled, margin, expired).notify_trade(trade)— Called when a trade opens or closes. Access P&L here.
class EMACrossover(bt.Strategy):params = (("fast_period", 10),("slow_period", 30),)def __init__(self) -> None:self.ema_fast = bt.ind.EMA(period=self.p.fast_period)self.ema_slow = bt.ind.EMA(period=self.p.slow_period)self.crossover = bt.ind.CrossOver(self.ema_fast, self.ema_slow)def next(self) -> None:if not self.position:if self.crossover > 0:self.buy()elif self.crossover < 0:self.close()
3. Data Feed
Backtrader data feeds provide OHLCV lines. The most common approach is loading from a pandas DataFrame:
import pandas as pddf = pd.DataFrame({"open": [...], "high": [...], "low": [...],"close": [...], "volume": [...],}, index=pd.DatetimeIndex([...]))data = bt.feeds.PandasData(dataname=df)cerebro.adddata(data)
For CSV files:
data = bt.feeds.GenericCSVData(dataname="ohlcv.csv",dtformat="%Y-%m-%d",openinterest=-1, # no open interest column)
4. Broker
The built-in broker simulates order execution with configurable cash, commission, and slippage.
cerebro.broker.setcash(100_000)cerebro.broker.setcommission(commission=0.003) # 0.3% per trade# Cheat-on-open: execute at the open of the signal bar (avoids lookahead)cerebro.broker.set_coo(True)
5. Analyzers
Analyzers compute performance metrics after the backtest completes.
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe",riskfreerate=0.0, annualize=True, timeframe=bt.TimeFrame.Days)cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trades")cerebro.addanalyzer(bt.analyzers.Returns, _name="returns")results = cerebro.run()strat = results[0]sharpe = strat.analyzers.sharpe.get_analysis()dd = strat.analyzers.drawdown.get_analysis()trades = strat.analyzers.trades.get_analysis()
Order Types
Backtrader supports complex order types critical for realistic crypto backtesting.
Market Order
self.buy() # market buyself.sell() # market sellself.close() # close current position
Limit Order
self.buy(exectype=bt.Order.Limit, price=95.0)self.sell(exectype=bt.Order.Limit, price=105.0)
Stop Order
Triggers a market order when price reaches the stop level:
self.sell(exectype=bt.Order.Stop, price=90.0) # stop loss
Stop-Limit Order
Triggers a limit order when price reaches the stop level:
self.buy(exectype=bt.Order.StopLimit, price=100.0, plimit=101.0)
Bracket Order
Entry + stop loss + take profit as an atomic unit. If the stop fills, the take profit is canceled (and vice versa).
self.buy_bracket(price=100.0, # entry limitstopprice=95.0, # stop losslimitprice=110.0, # take profitexectype=bt.Order.Limit,stopexec=bt.Order.Stop,limitexec=bt.Order.Limit,)
See references/strategy_patterns.md for bracket order patterns with ATR-based stops.
Position Sizing (Sizers)
Sizers determine how many units to buy/sell per order.
# Fixed sizecerebro.addsizer(bt.sizers.FixedSize, stake=100)# Percent of portfoliocerebro.addsizer(bt.sizers.PercentSizer, percents=95)# All available cashcerebro.addsizer(bt.sizers.AllInSizer, percents=95)
Custom sizer:
class RiskSizer(bt.Sizer):params = (("risk_pct", 0.02),)def _getsizing(self, comminfo, cash, data, isbuy):risk_amount = cash * self.p.risk_pctatr = self.strategy.atr[0]if atr <= 0:return 0size = risk_amount / atrreturn int(size)
Crypto Considerations
24/7 Markets
Crypto trades around the clock. When using daily bars, there are no weekends to skip. Set the session times or use sessionstart/sessionend if analyzing specific windows.
High Fees
DEX swaps on Solana typically cost 0.25-0.30% per trade. Set commission accordingly:
cerebro.broker.setcommission(commission=0.003) # 0.3% round trip per side
Fractional Sizing
Crypto allows fractional units. Backtrader supports this natively -- no special config needed.
Slippage
For realistic simulation, enable cheat-on-open and add slippage:
cerebro.broker.set_coo(True)cerebro.broker.set_slippage_perc(0.001) # 0.1% slippage
Volatile Data
Crypto OHLCV data often has extreme wicks. Use ATR-based stops rather than fixed percentage stops to adapt to volatility.
Multi-Timeframe
Backtrader can resample data to multiple timeframes within a single strategy:
data_1h = bt.feeds.PandasData(dataname=df_1h)cerebro.adddata(data_1h)# Resample 1h to dailycerebro.resampledata(data_1h, timeframe=bt.TimeFrame.Days, compression=1)
Access in strategy:
def __init__(self):self.ema_1h = bt.ind.EMA(self.datas[0], period=20) # hourlyself.ema_daily = bt.ind.EMA(self.datas[1], period=20) # daily
Custom Indicators
class SpreadIndicator(bt.Indicator):lines = ("spread", "zscore",)params = (("period", 20),)def __init__(self):mean = bt.ind.SMA(self.data, period=self.p.period)std = bt.ind.StdDev(self.data, period=self.p.period)self.lines.spread = self.data - meanself.lines.zscore = self.lines.spread / std
Plotting
Backtrader includes matplotlib-based plotting:
cerebro.plot(style="candlestick", volume=True)
For headless environments, save to file:
import matplotlibmatplotlib.use("Agg")figs = cerebro.plot(style="candlestick")figs[0][0].savefig("backtest_result.png", dpi=150)
Integration with Other Skills
- pandas-ta: Compute indicators externally, add as data feed columns. See
references/api_guide.mdfor adding extra lines. - trading-visualization: Export trade log from
notify_tradeand plot with the visualization skill. - position-sizing: Use the
position-sizingskill for Kelly or volatility-targeting sizers. - risk-management: Apply portfolio-level guardrails from the
risk-managementskill as strategy filters. - slippage-modeling: Use slippage estimates from the
slippage-modelingskill to configureset_slippage_perc.
Files
References
references/api_guide.md— Cerebro, Strategy, Broker, Analyzer, Data Feed API referencereferences/strategy_patterns.md— Reusable strategy patterns: crossover, mean reversion, multi-timeframe, custom indicators
Scripts
scripts/backtest_strategy.py— Complete EMA crossover backtest with analyzers and synthetic datascripts/bracket_orders.py— Bracket order demonstration with RSI entry and ATR-based stops
Quick Start
uv pip install backtrader pandas numpy matplotlibpython scripts/backtest_strategy.py --demopython scripts/bracket_orders.py --demo