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()

Leave a Reply

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