You are on page 1of 5

Quantitative Endeavors

A Leap Into the Unknown

Building a Simple Backtester

ON FEBRUARY 20, 2020MAY 14, 2021 / BY MICHAEL DOHERTY

Overview:

In this tutorial, we’re going to be discussing how to build our own backtesting
engine using the Numpy and Pandas library. We are then going to backtest a
simple sector momentum strategy and plot the performance and weights over
time.

Generally, there are two ways to go about building a backtesting engine. The
first uses an event-driven framework. In an event-driven framework, a while-
loop continuously runs, executing code as new events enter the queue. The
benefit of this kind of programming paradigm is that it minimizes look ahead
bias and more accurately mimics real life trading. The downside to this
framework is the increased complexity of code.

The second way utilizes vectorized code. Vectorized code refers to operations
that are performed on multiple components of a vector at the same time.
Vectorized backtesters are typically faster and less complex but often lack the
realism of event-driven systems.

To keep things simple, we are going to be using vectorized code to test our
strategy.

Trading Strategy Rules

The rules of this strategy are simple. Equal weight the top 3 S&P 500 sectors
as measured by their 12-month minus 2-month return rebalancing monthly.
Python Code

The first thing we need to do is create a Backtest class and initialize some
variables that we will need later on.

1 class Backtest:
2 def __init__(self, data, calendar , amount = 1000000
3 self.data = data.values
4 self.dates = data.index
5 self.tickers = data.columns
6 self.amount = amount
7 self.window = window
8 self.calendar = calendar

Then we are going to use Python’s built-in @property function to create some
functions that will allow us to retrieve our results after they have been
simulated. You can read more about the @property function here
(https://www.programiz.com/python-programming/property)

1 @property
2 def cumulative_return(self):
3 x = pd.DataFrame(data=self.cum_return, index=self.d
4 x = x.replace(0, np.nan).dropna()
5 return x
6
7 @property
8 def get_weights(self):
9 x = pd.DataFrame(data=self.weights[self.window:],
10 index=self.dates[self.window:],
11 columns=self.tickers)
12 return x

Next, we are going to use Pythons static method to create a function that we
can call to update our portfolio weights.

1 @staticmethod
2 def update_weights(returns, weights, dates):
3 num = weights * (1 + returns)
4 return np.nan_to_num(num / np.sum(num))

The last piece of code runs our trading strategy and executes our stock
selection model
1 def run(self):
2 dates = self.dates
3 self.weights = np.zeros((len(dates), self.data.shap
4 self.cum_return = np.zeros((len(dates), 1))
5 for i, day in enumerate(dates):
6 today = i # today's index
7 yesterday = today - 1 # yesterday's index
8 if today > self.window:
9 returns = self.data[today] # today's asset returns
10 if np.sum(self.weights[yesterday]) == 0.0:
11 self.cum_return[yesterday] = self.amount
12 # update the weights of my portfolio
13 self.weights[today, :] = self.update_weights(return
14 # if day is in our rebalance date run our selection
15 if day in self.calendar:
16 self.stock_selection(today)
17 portfolio_return = np.sum(self.weights[yesterday] *
18 self.cum_return[today] = (1 + portfolio_return) * (
19
20 def stock_selection(self, today):
21 a = self.data[today - self.window:today - 2,:]
22 a = np.cumprod(1 + a, 0) - 1
23 value = a[-1]
24 self.selected = value < np.sort(value)[3]
25 self.weights[today] = 0
26 self.weights[today, self.selected] = 1 / float(np.s

Now, all we have to do is instantiate our class and run our “run” method.

Let’s take a look at a performance chart and the weights over time.
So there you have it. Feel free to copy the code below and create your own
trading algorithms.

1 import pandas as pd
2 import numpy as np
3 import matplotlib.pyplot as plt
4 import matplotlib.dates as mdates
5 import matplotlib.cbook as cbook
6 class Backtest:
7 def __init__(self, data, calendar , amount = 100000
8 self.data = data.values
9 self.dates = data.index
10 self.tickers = data.columns
11 self.amount = amount
12 self.window = window
13 self.calendar = calendar
14 @property
15 def cumulative_return(self):
16 x = pd.DataFrame(data=self.cum_return, index=self.d
17 x = x.replace(0, np.nan).dropna()
18 return x
19 @property
20 def get_weights(self):
21 x = pd.DataFrame(data=self.weights[self.window:],
22 index=self.dates[self.window:],
23 columns=self.tickers)
24 return x
25
26 @staticmethod
27 def update_weights(returns, weights, dates):
28 num = weights * (1 + returns)
29 return np.nan_to_num(num / np.sum(num))
30
31 def run(self):
32 dates = self.dates
33 self.weights = np.zeros((len(dates), self.data.shap
34 self.cum_return = np.zeros((len(dates), 1))
35 for i, day in enumerate(dates):
36 today = i # today's index
37 yesterday = today - 1 # yesterday's index
38 if today > self.window:
39 returns = self.data[today] # today's asset returns
40 if np.sum(self.weights[yesterday]) == 0.0:
41 self.cum_return[yesterday] = self.amount
42 # update the weights of my portfolio
43 self.weights[today, :] = self.update_weights(return
44 # if day is in our rebalance date run our selection
45 if day in self.calendar:
46 self.stock_selection(today)
47 portfolio_return = np.sum(self.weights[yesterday] *
48 self.cum_return[today] = (1 + portfolio_return) * (
49 def stock_selection(self, today):
50 a = self.data[today - self.window:today - 2,:]
51 a = np.cumprod(1 + a, 0) - 1
52 value = a[-1]
53 self.selected = value < np.sort(value)[3]
54 self.weights[today] = 0
55 self.weights[today, self.selected] = 1 / float(np.s
56 strat1 = Backtest(sectors, data.index, window = 12)
57 strat1.run()
58 plt.style.use('fivethirtyeight')
59 fig, ax = plt.subplots(figsize=(20, 10))
60 ax.plot(strat1.cumulative_return, color = 'crimson'
61 plt.title('Simple Sector Momentom Strategy')
62 plt.show()

BLOG AT WORDPRESS.COM.

You might also like