Running a Massive Backtest on 1M Bars in Python with Backtrader

Python is a very powerful language for backtesting and quantitative analysis. You’re free to use any data sources you want, you can use millions of raws in your backtesting easily. In this article, I show an example of running backtesting over 1 million 1 minute bars from Binance.

Including libraries:

import requests 
import backtrader as bt
import backtrader.analyzers as btanalyzers
import json 
import pandas as pd
import datetime as dt
import matplotlib.pyplot as plt
import qgrid

Function to get data from Binance:

def get_binance_bars(symbol, interval, startTime, endTime):

    url = "https://api.binance.com/api/v3/klines"

    startTime = str(int(startTime.timestamp() * 1000))
    endTime = str(int(endTime.timestamp() * 1000))
    limit = '1000'

    req_params = {"symbol" : symbol, 'interval' : interval, 'startTime' : startTime, 'endTime' : endTime, 'limit' : limit}

    df = pd.DataFrame(json.loads(requests.get(url, params = req_params).text))

    if (len(df.index) == 0):
        return None
    
    df = df.iloc[:, 0:6]
    df.columns = ['datetime', 'open', 'high', 'low', 'close', 'volume']

    df.open      = df.open.astype("float")
    df.high      = df.high.astype("float")
    df.low       = df.low.astype("float")
    df.close     = df.close.astype("float")
    df.volume    = df.volume.astype("float")
   
    df['adj_close'] = df['close']
    
    df.index = [dt.datetime.fromtimestamp(x / 1000.0) for x in df.datetime]

    return df

Using this code you can get all 1m bars from the 1st of January 2019. It works pretty fast, to get 1M 1minute bars it takes only around 4 minutes.

df_list = []
last_datetime = dt.datetime(2019, 1, 1)
while True:
    new_df = get_binance_bars('ETHUSDT', '1m', last_datetime, dt.datetime.now())
    if new_df is None:
        break
    df_list.append(new_df)
    last_datetime = max(new_df.index) + dt.timedelta(0, 1)

df = pd.concat(df_list)
df.shape

Here is a simple backtrader strategy for MA Cross.

class MaCrossStrategy(bt.Strategy):

    def __init__(self):
        ma_fast = bt.ind.SMA(period = 10)
        ma_slow = bt.ind.SMA(period = 50)
        
        self.crossover = bt.ind.CrossOver(ma_fast, ma_slow)

    def next(self):
        if not self.position:
            if self.crossover > 0: 
                self.buy()
        elif self.crossover < 0: 
            self.close()

Creating engine, adding strategies and analyzers:

cerebro = bt.Cerebro()

data = bt.feeds.PandasData(dataname = df)
cerebro.adddata(data)

cerebro.addstrategy(MaCrossStrategy)
cerebro.broker.setcash(1000000.0)

cerebro.addsizer(bt.sizers.PercentSizer, percents = 50)
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name = "sharpe")
cerebro.addanalyzer(btanalyzers.Transactions, _name = "trans")

Running the engine:

back = cerebro.run()

It will take only around 8 minutes to run backtest on 1M rows.

After running the engine you can compute metrics on your backtesting the usual way:

cerebro.broker.getvalue() # Ending balance
back[0].analyzers.sharpe.get_analysis() # Sharpe
len(back[0].analyzers.trans.get_analysis()) # Number of Trades

You can also plot backtesting results using the standard backtrader method. It will be plotted in around a minute and will be a bit messy but it will work:

cerebro.plot()

Leave a Reply

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