Algorithmic trading, the practice of using computer programs to execute trading strategies, has gained immense popularity. For Python enthusiasts looking to dive into this world, Backtrader stands out as a powerful and flexible open-source framework. It allows for the testing and implementation of trading ideas with historical or live data. To get started, understanding a minimal strategy template is crucial. This article dissections a fundamental Backtrader strategy, providing a clear path for beginners.
The provided code snippet offers a complete, albeit simple, example of a Backtrader strategy. Let’s break it down step-by-step:
Python
import backtrader as bt
import yfinance as yf
import matplotlib.pyplot as plt
# %matplotlib qt5 # This line is specific to Jupyter environments for interactive plots
class MyStrategy(bt.Strategy):
# define any strategy parameters here (optional)
params = (
('maperiod', 15), # example parameter: moving average period
)
def __init__(self):
# references to data feeds, indicators, etc.
self.dataclose = self.datas[0].close # Accessing the closing price of the primary data feed
# simple moving average example
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod # Using the defined 'maperiod'
)
def next(self):
# called on each new bar (data point)
if not self.position: # Check if not in the market
if self.dataclose[0] > self.sma[0]: # If close is above SMA
# enter long
self.buy()
else: # Already in the market
if self.dataclose[0] < self.sma[0]: # If close is below SMA
# exit long
self.close()
if __name__ == '__main__':
cerebro = bt.Cerebro() # Create a Cerebro engine instance
cerebro.addstrategy(MyStrategy, maperiod=20) # Add the strategy, optionally overriding params
# Fetch data using yfinance
ticker = yf.Ticker('BTC-USD') # Example: Bitcoin USD data
data_df = ticker.history(period='1y') # Fetch 1 year of historical data
# Add a data feed (Pandas DataFrame)
data_feed = bt.feeds.PandasData(dataname=data_df)
cerebro.adddata(data_feed)
cerebro.broker.setcash(100000.0) # Set initial trading capital
print('Starting Portfolio Value:', cerebro.broker.getvalue())
cerebro.run() # Execute the backtest
print('Final Portfolio Value:', cerebro.broker.getvalue())
cerebro.plot(iplot=False) # Plot the results (iplot=False for non-interactive plots)
Imports:
backtrader as bt
: This is the primary import for the
Backtrader library itself.yfinance as yf
: A popular library to download
historical market data from Yahoo Finance. This is used here to fetch
sample data for backtesting.matplotlib.pyplot as plt
: Used by Backtrader for
plotting the results of the backtest.# %matplotlib qt5
: This is a magic command for Jupyter
Notebook or IPython environments. It specifies that Matplotlib plots
should be rendered in a separate, interactive Qt window. If you’re
running a standard Python script, this line might be unnecessary or
could cause issues if the Qt backend isn’t configured.Strategy Definition
(MyStrategy(bt.Strategy)
):
bt.Strategy
.params
: This class attribute is a
tuple of tuples, allowing you to define parameters for your strategy.
These parameters can be easily tuned without modifying the core logic.
In this example, maperiod
is defined with a default value
of 15, representing the period for a moving average.__init__(self)
: This is the
constructor of your strategy. It’s called once when the strategy is
initialized.
self.datas[0]
: Backtrader can handle multiple data
feeds. self.datas[0]
refers to the first (primary) data
feed added to Cerebro.self.dataclose = self.datas[0].close
: This creates a
convenient alias to the ‘close’ price series of the primary data
feed.self.sma = bt.indicators.SimpleMovingAverage(...)
:
Here, a Simple Moving Average (SMA) indicator is instantiated. It’s
calculated on the primary data feed (self.datas[0]
) using
the period defined in self.params.maperiod
. Backtrader has
a rich library of built-in indicators.next(self)
: This is the heart of the
strategy. It’s called for each new bar (e.g., daily, hourly) of data
once all indicators have a calculated value.
if not self.position:
: This checks if the strategy
currently holds any open position (i.e., is “in the market”).
self.position
is a Backtrader object that provides
information about the current market position.if self.dataclose[0] > self.sma[0]
: This is the
entry condition for a long position. It checks if the current closing
price (self.dataclose[0]
) is greater than the current SMA
value (self.sma[0]
). The [0]
refers to the
current bar.self.buy()
: If the entry condition is met, this command
places a market order to buy the asset.else: ... if self.dataclose[0] < self.sma[0]: self.sell()
:
If the strategy is already in a position, this block checks the exit
condition. If the current closing price falls below the SMA,
self.sell()
places a market order to sell the asset
(closing the long position).Execution Block
(if __name__ == '__main__':
)
cerebro = bt.Cerebro()
: Cerebro
(Spanish for “brain”) is the central engine of Backtrader. It
orchestrates data feeds, strategies, brokers, analyzers, and more.cerebro.addstrategy(MyStrategy, maperiod=20)
:
This line adds an instance of our MyStrategy
to Cerebro.
Notice that we can override the default maperiod
(which was
15) directly here, setting it to 20 for this specific backtest.yfinance
:
ticker = yf.Ticker('BTC-USD')
: An instance of a
yfinance Ticker
object is created for Bitcoin against the
US Dollar.data_df = ticker.history(period='1y')
: The
history
method fetches historical data for the specified
period (here, ‘1y’ for one year) and returns it as a Pandas
DataFrame.data_feed = bt.feeds.PandasData(dataname=data_df)
:
Backtrader needs data in a specific format.
bt.feeds.PandasData
is a convenient way to feed data from a
Pandas DataFrame. The dataname
argument points to our
fetched DataFrame. Backtrader automatically recognizes common column
names like ‘Open’, ‘High’, ‘Low’, ‘Close’, ‘Volume’, and
‘OpenInterest’.cerebro.adddata(data_feed)
: The prepared data feed is
added to Cerebro.cerebro.broker.setcash(100000.0)
: This sets the initial
cash balance for the simulated broker.print('Starting Portfolio Value:', cerebro.broker.getvalue())
:
Prints the initial portfolio value (which will be the cash set).cerebro.run()
: This command starts the backtesting
process. Cerebro will iterate through the historical data, calling the
next()
method of the strategy for each bar.print('Final Portfolio Value:', cerebro.broker.getvalue())
:
After the backtest is complete, this prints the final value of the
portfolio.cerebro.plot(iplot=False)
: Backtrader provides a
convenient plotting facility. iplot=False
is often used for
non-Jupyter environments or when a static plot is preferred. If
iplot=True
(the default if matplotlib
is
configured for interactive backends), it would attempt to generate an
interactive plot. The plot typically shows the price data, indicators
(like our SMA), and buy/sell trades.This template implements a basic trend-following strategy using a Simple Moving Average (SMA):
This is a very straightforward strategy and serves primarily as an educational example of how to structure code within the Backtrader framework.
I added the simple strategy class into the “Backtester” app:
and run backtest:
This minimal template is a fantastic starting point. From here, you can:
By understanding this foundational template, aspiring algorithmic traders can confidently begin building and testing their own sophisticated trading strategies using the versatile Backtrader library.