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()
Hi
I get following error message, if use
cerebro.broker.getvalue() # Ending balance
back[0].analyzers.sharpe.get_analysis() # Sharpe
len(back[0].analyzers.trans.get_analysis()) # Number of Trades
cerebro.plot()
TypeError: super(type, obj): obj must be an instance or subtype of type
Hi
I get following error message, if use
cerebro.broker.getvalue() # Ending balance
back[0].analyzers.sharpe.get_analysis() # Sharpe
len(back[0].analyzers.trans.get_analysis()) # Number of Trades
cerebro.plot()
TypeError: super(type, obj): obj must be an instance or subtype of type