0% found this document useful (0 votes)
103 views

Building A Simple Backtester - Quantitative Endeavor (1) .

The document discusses building a backtesting engine in Python using Numpy and Pandas libraries to backtest a simple sector momentum trading strategy. It describes taking a vectorized approach to keep the code simple, involving initializing a Backtest class, defining properties and methods to run the strategy and get performance results, and running the strategy which selects the top 3 performing sectors each month to hold equally. Sample code is provided to instantiate the class, run the strategy, and plot the cumulative returns and weights over time.

Uploaded by

Michael Peters
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
103 views

Building A Simple Backtester - Quantitative Endeavor (1) .

The document discusses building a backtesting engine in Python using Numpy and Pandas libraries to backtest a simple sector momentum trading strategy. It describes taking a vectorized approach to keep the code simple, involving initializing a Backtest class, defining properties and methods to run the strategy and get performance results, and running the strategy which selects the top 3 performing sectors each month to hold equally. Sample code is provided to instantiate the class, run the strategy, and plot the cumulative returns and weights over time.

Uploaded by

Michael Peters
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 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