Running Backtesting for Universe of Stocks in Python with Backtrader

Pretty often you want to backtest your strategy on multiple instruments and you’re interested in how it will work together. In this article, I will show you how easy it is to do that in Python using Backtrader.

To load multiple data sources to Backtrader you can use the following code:

stocks = ['AAPL', 'MSFT', 'AMZN', 'TSLA', 'V']
for s in stocks: 
    data = bt.feeds.YahooFinanceData(dataname = s, fromdate = datetime(2010, 1, 1), todate = datetime(2020, 1, 1))
    cerebro.adddata(data, name = s)

The idea is pretty simple. In a loop you go through list of stocks you want to add to your backtest and add them 1 by 1 with the same code as for single stock.

The strategy logic is a bit more complex. You have run your signals for all the stocks in a loop and save them to an array. After that get corresponding signals and trade correct stocks. Here is an example of “__init__” function for multiple stocks:

    def __init__(self):
        self.crossovers = []
        
        for d in self.datas: 
            ma_fast = bt.ind.SMA(d, period = self.params.fast_length)
            ma_slow = bt.ind.SMA(d, period = self.params.slow_length)

            self.crossovers.append(bt.ind.CrossOver(ma_fast, ma_slow))

Here is an example of “next” function where I trade multiple stocks based on the corresponding signal:

    def next(self):
        for i, d in enumerate(self.datas):
            if not self.getposition(d).size:
                if self.crossovers[i] > 0: 
                    self.buy(data = d)
            elif self.crossovers[i] < 0: 
                self.close(data = d)

Here is the entire code:

import backtrader as bt
import backtrader.analyzers as btanalyzers
import matplotlib
from datetime import datetime

class MaCrossStrategy(bt.Strategy):

    params = (
        ('fast_length', 5),
        ('slow_length', 25)
    )
    
    def __init__(self):
        self.crossovers = []
        
        for d in self.datas: 
            ma_fast = bt.ind.SMA(d, period = self.params.fast_length)
            ma_slow = bt.ind.SMA(d, period = self.params.slow_length)

            self.crossovers.append(bt.ind.CrossOver(ma_fast, ma_slow))

    def next(self):
        for i, d in enumerate(self.datas):
            if not self.getposition(d).size:
                if self.crossovers[i] > 0: 
                    self.buy(data = d)
            elif self.crossovers[i] < 0: 
                self.close(data = d)

cerebro = bt.Cerebro()

stocks = ['AAPL', 'MSFT', 'AMZN', 'TSLA', 'V']
for s in stocks: 
    data = bt.feeds.YahooFinanceData(dataname = s, fromdate = datetime(2010, 1, 1), todate = datetime(2020, 1, 1))
    cerebro.adddata(data, name = s)


cerebro.addstrategy(MaCrossStrategy)

cerebro.broker.setcash(1000000.0)

cerebro.addsizer(bt.sizers.PercentSizer, percents = 10)

cerebro.addanalyzer(btanalyzers.SharpeRatio, _name = "sharpe")
cerebro.addanalyzer(btanalyzers.Returns,     _name = "returns")
cerebro.addanalyzer(btanalyzers.Transactions, _name = "trans")

back = cerebro.run()

cerebro.broker.getvalue()
back[0].analyzers.returns.get_analysis()['rnorm100']
back[0].analyzers.sharpe.get_analysis()
back[0].analyzers.trans.get_analysis()

2 thoughts on “Running Backtesting for Universe of Stocks in Python with Backtrader”

  1. Hello, thank for sharing this.
    However, it seems that the YahooFinance retrieving function does not work (although it still works with Empyrial). Could you please check?
    Also, could you find a way to retrieve data from Google Finance?

  2. Hello, thank for sharing this.
    However, it seems that the YahooFinance retrieving function does not work (although it still works with Empyrial). Could you please check?
    Also, could you find a way to retrieve data from Google Finance?

Leave a Comment

Your email address will not be published. Required fields are marked *