Backtesting with Backtrader

Backtrader is a Python framework for writing and testing trading strategies.

Table of contents

Create a Strategy

Invest in assets when their 50-day moving average crosses above their 200-day moving average. Sell when the 50-day moving average crosses below the 200-day moving average.

In [1]:
import backtrader as bt
import math
import datetime
import backtrader.strategies as btstrats
import backtrader.analyzers as btanalyzers
import backtrader.indicators as btind
import backtrader.feeds as btfeeds
import IPython

%matplotlib inline

# Create a Stratey
class TestStrategy(bt.Strategy):
    params = dict(ticker='MSFT')

    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close
        
        self.ma50 = btind.SMA(self.data.close, period=50, plotname='50-day MA')
        self.ma200 = btind.SMA(self.data.close, period=200, plotname='200-day MA')
        
        self.crossover = btind.CrossOver(self.ma50, self.ma200)

    def next(self):
        # if we don't own shares of the security
        # and ma50 crossed above ma200
        if (self.position.size == 0) & (self.crossover > 0):
            # buy
            dollars_to_invest = self.broker.cash
            # Round down to nearest integer to calculate number of shares.
            # Assume I can buy at the closing price
            self.size = math.floor(dollars_to_invest / self.data.close)
            print(f'Buy {self.size} shares of {self.params.ticker} at ${self.data.close[0]} per share.')
            self.buy(size=self.size)
        
        # if we own shares of the security
        # and ma50 crossed below ma200
        if (self.position.size > 0) & (self.crossover < 0):
            print(f'Sell {self.size} shares of {self.params.ticker} at ${self.data.close[0]} per share.')
            self.close()
        

Create Cerebro Engine

Set initial cash to $100k.

In [2]:
cerebro = bt.Cerebro()
cerebro.broker.set_cash(100000.0)

Load and inject Data Feed

Retrieve Microsoft stock data from Jan 1, 2005 to July 31, 2020.

In [3]:
MSFT = bt.feeds.YahooFinanceData(
    dataname = 'MSFT',
    fromdate=datetime.datetime(2005, 1, 1),
    todate=datetime.datetime(2020, 7, 31),
    reverse=False
)

cerebro.adddata(MSFT)
Out[3]:
<backtrader.feeds.yahoo.YahooFinanceData at 0x29ac16b0ee0>

Inject Strategy

Add strategy to the Cerebro engine.

In [4]:
cerebro.addstrategy(TestStrategy)
Out[4]:
0

Analyzer

Use the Sharpe ratio analyzer from backtrader.analyzers to the Cerebro engine.

In [5]:
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='SharpeRatio')

Run Cerebro engine

In [6]:
strategies = cerebro.run()
Buy 4955 shares of MSFT at $20.18 per share.
Sell 4955 shares of MSFT at $21.59 per share.
Buy 4053 shares of MSFT at $26.16 per share.
Sell 4053 shares of MSFT at $22.02 per share.
Buy 4979 shares of MSFT at $18.13 per share.
Buy 4017 shares of MSFT at $22.47 per share.
Sell 4017 shares of MSFT at $21.0 per share.
Buy 4012 shares of MSFT at $21.29 per share.
Sell 4012 shares of MSFT at $20.67 per share.
Buy 3803 shares of MSFT at $21.96 per share.
Buy 3217 shares of MSFT at $25.96 per share.
Buy 1995 shares of MSFT at $41.86 per share.
Sell 1995 shares of MSFT at $45.88 per share.
Buy 1762 shares of MSFT at $52.5 per share.
Buy 853 shares of MSFT at $108.38 per share.

Retrieve the Sharpe Ratio from the analyzer

In [7]:
print('Sharpe Ratio:', strategies[0].analyzers.SharpeRatio.get_analysis())
Sharpe Ratio: OrderedDict([('sharperatio', 0.23432531000971024)])

Visualize results

In [8]:
cerebro.plot(width=28, height=28)[0][0]
Out[8]: