Core Concepts¶
This chapter explains the fundamental concepts of Backtrader. Understanding these concepts is essential for effective strategy development.
小技巧
For more detailed explanations, see the Author's Blog.
Understanding Lines¶
Lines are the fundamental data structure in Backtrader, similar to columns in an Excel spreadsheet. Each Line represents a time series where each point corresponds to a specific time (bar).
Think of it like Excel:
A class (Strategy, Indicator) is like a workbook
Each Line is like a column in the workbook
Each bar is like a row in the column
# Access current bar value (index 0)
current_close = self.data.close[0]
# Access previous bar value (index -1)
previous_close = self.data.close[-1]
# Access 5 bars ago
close_5_bars_ago = self.data.close[-5]
# Get multiple values as array
last_10_closes = self.data.close.get(size=10)
备注
Index [0] is the current bar, negative indices look back in time.
Positive indices [1], [2] look forward (only valid in indicators during __init__).
Data Feeds¶
Data feeds provide OHLCV data:
Open: Opening price
High: Highest price
Low: Lowest price
Close: Closing price
Volume: Trading volume
OpenInterest: Open interest (futures)
# Access data lines
self.data.open[0]
self.data.high[0]
self.data.low[0]
self.data.close[0]
self.data.volume[0]
self.data.datetime[0] # Float representation
Timeframes¶
Supported timeframes:
TimeFrame.TicksTimeFrame.SecondsTimeFrame.MinutesTimeFrame.DaysTimeFrame.WeeksTimeFrame.MonthsTimeFrame.Years
Indicators¶
Indicators calculate values from data:
class MyStrategy(bt.Strategy):
def __init__(self):
# Indicator automatically calculates for each bar
self.sma = bt.indicators.SMA(self.data.close, period=20)
def next(self):
# Use indicator value
if self.data.close[0] > self.sma[0]:
self.buy()
Orders¶
Order types:
Market: Execute at current price
Limit: Execute at specified price or better
Stop: Execute when price reaches trigger
StopLimit: Combination of stop and limit
# Market order
self.buy()
# Limit order
self.buy(exectype=bt.Order.Limit, price=100.0)
# Stop order
self.buy(exectype=bt.Order.Stop, price=105.0)
# Stop-limit order
self.buy(exectype=bt.Order.StopLimit, price=105.0, plimit=106.0)
Positions¶
A position represents holdings in an asset:
# Check if we have a position
if self.position:
print(f'Size: {self.position.size}')
print(f'Price: {self.position.price}')
# Check position for specific data
pos = self.getposition(self.data)
Strategy Lifecycle¶
Backtrader uses an event-driven model. The author compares it to human life stages:
Method |
Life Stage |
Description |
|---|---|---|
|
Conception |
Initialize indicators and variables |
|
Birth |
Called once at beginning, usually empty |
|
Childhood |
Called before indicators have enough data |
|
Coming of Age |
Called once when minimum period is reached |
|
Adulthood |
Main trading logic, called for each bar |
|
Death |
Called at end, output results here |
class MyStrategy(bt.Strategy):
def __init__(self):
'''Initialize indicators and variables'''
self.sma = bt.indicators.SMA(period=20)
self.order = None
def start(self):
'''Called once at beginning'''
self.log('Strategy starting')
def prenext(self):
'''Called before indicators have enough data.
Useful for debugging or early logic.'''
pass
def nextstart(self):
'''Called once when minimum period is reached'''
self.next() # Often just calls next()
def next(self):
'''Main trading logic - called for each bar'''
if self.data.close[0] > self.sma[0]:
self.buy()
def stop(self):
'''Called at end - output results'''
self.log(f'Final Value: {self.broker.getvalue():.2f}')
Notification Events¶
def notify_order(self, order):
'''Called when order status changes'''
if order.status == order.Submitted:
return # Order submitted to broker
if order.status == order.Accepted:
return # Order accepted by broker
if order.status == order.Completed:
if order.isbuy():
self.log(f'BUY @ {order.executed.price:.2f}')
else:
self.log(f'SELL @ {order.executed.price:.2f}')
elif order.status == order.Canceled:
self.log('Order canceled')
elif order.status == order.Margin:
self.log('Insufficient margin')
elif order.status == order.Rejected:
self.log('Order rejected')
def notify_trade(self, trade):
'''Called when trade is opened or closed'''
if trade.isclosed:
self.log(f'TRADE CLOSED - PnL: {trade.pnl:.2f}, Net: {trade.pnlcomm:.2f}')
if trade.isopen:
self.log(f'TRADE OPENED @ {trade.price:.2f}')
def notify_cashvalue(self, cash, value):
'''Called when cash or value changes'''
pass # Rarely used
Cerebro - The Brain¶
Cerebro is the central engine that orchestrates everything:
cerebro = bt.Cerebro()
# 1. Add data
data = bt.feeds.GenericCSVData(dataname='data.csv')
cerebro.adddata(data, name='AAPL')
# 2. Add strategy with parameters
cerebro.addstrategy(MyStrategy, period=20, stake=100)
# 3. Add analyzers
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
# 4. Add observers (for plotting)
cerebro.addobserver(bt.observers.Broker)
cerebro.addobserver(bt.observers.Trades)
cerebro.addobserver(bt.observers.BuySell)
# 5. Set broker parameters
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.001)
# 6. Run
results = cerebro.run()
# 7. Get results
strat = results[0]
print(f"Sharpe: {strat.analyzers.sharpe.get_analysis()}")
# 8. Plot
cerebro.plot()
Key Cerebro Parameters¶
Parameter |
Default |
Description |
|---|---|---|
preload |
True |
Pre-load data into memory (faster backtesting) |
runonce |
True |
Calculate indicators vectorized (faster) |
live |
False |
Live trading mode (disables preload and runonce) |
maxcpus |
None |
CPUs for optimization (None = all) |
stdstats |
True |
Add default observers (Broker, Trades, BuySell) |
See Also¶
Strategies - Strategy development
Data Feeds - Data loading
Indicators - Technical indicators