-
Notifications
You must be signed in to change notification settings - Fork 0
/
SMABacktester.py
123 lines (102 loc) · 4.47 KB
/
SMABacktester.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from itertools import product
plt.style.use("seaborn-v0_8")
class SMABacktester():
''' Class for the vectorized backtesting of SMA-based trading strategies.
'''
def __init__(self, symbol, SMA_S, SMA_L, start, end):
'''
Parameters
----------
symbol: str
ticker symbol (instrument) to be backtested
SMA_S: int
moving window in bars (e.g. days) for shorter SMA
SMA_L: int
moving window in bars (e.g. days) for longer SMA
start: str
start date for data import
end: str
end date for data import
'''
self.symbol = symbol
self.SMA_S = SMA_S
self.SMA_L = SMA_L
self.start = start
self.end = end
self.results = None
self.get_data()
self.prepare_data()
def __repr__(self):
return "SMABacktester(symbol = {}, SMA_S = {}, SMA_L = {}, start = {}, end = {})".format(self.symbol, self.SMA_S, self.SMA_L, self.start, self.end)
def get_data(self):
''' Imports the data from forex_pairs.csv (source can be changed).
'''
raw = pd.read_csv("forex_pairs.csv", parse_dates = ["Date"], index_col = "Date")
raw = raw[self.symbol].to_frame().dropna()
raw = raw.loc[self.start:self.end].copy()
raw.rename(columns={self.symbol: "price"}, inplace=True)
raw["returns"] = np.log(raw / raw.shift(1))
self.data = raw
def prepare_data(self):
'''Prepares the data for strategy backtesting (strategy-specific).
'''
data = self.data.copy()
data["SMA_S"] = data["price"].rolling(self.SMA_S).mean()
data["SMA_L"] = data["price"].rolling(self.SMA_L).mean()
self.data = data
def set_parameters(self, SMA_S = None, SMA_L = None):
''' Updates SMA parameters and the prepared dataset.
'''
if SMA_S is not None:
self.SMA_S = SMA_S
self.data["SMA_S"] = self.data["price"].rolling(self.SMA_S).mean()
if SMA_L is not None:
self.SMA_L = SMA_L
self.data["SMA_L"] = self.data["price"].rolling(self.SMA_L).mean()
def test_strategy(self):
''' Backtests the SMA-based trading strategy.
'''
data = self.data.copy().dropna()
data["position"] = np.where(data["SMA_S"] > data["SMA_L"], 1, -1)
data["strategy"] = data["position"].shift(1) * data["returns"]
data.dropna(inplace=True)
data["creturns"] = data["returns"].cumsum().apply(np.exp)
data["cstrategy"] = data["strategy"].cumsum().apply(np.exp)
self.results = data
perf = data["cstrategy"].iloc[-1] # absolute performance of the strategy
outperf = perf - data["creturns"].iloc[-1] # out-/underperformance of strategy
return round(perf, 6), round(outperf, 6)
def plot_results(self):
''' Plots the performance of the trading strategy and compares to "buy and hold".
'''
if self.results is None:
print("Run test_strategy() first.")
else:
title = "{} | SMA_S = {} | SMA_L = {}".format(self.symbol, self.SMA_S, self.SMA_L)
self.results[["creturns", "cstrategy"]].plot(title=title, figsize=(12, 8))
def optimize_parameters(self, SMA_S_range, SMA_L_range):
''' Finds the optimal strategy (global maximum) given the SMA parameter ranges.
Parameters
----------
SMA_S_range, SMA_L_range: tuple
tuples of the form (start, end, step size)
'''
combinations = list(product(range(*SMA_S_range), range(*SMA_L_range)))
# test all combinations
results = []
for comb in combinations:
self.set_parameters(comb[0], comb[1])
results.append(self.test_strategy()[0])
best_perf = np.max(results) # best performance
opt = combinations[np.argmax(results)] # optimal parameters
# run/set the optimal strategy
self.set_parameters(opt[0], opt[1])
self.test_strategy()
# create a df with many results
many_results = pd.DataFrame(data = combinations, columns = ["SMA_S", "SMA_L"])
many_results["performance"] = results
self.results_overview = many_results
return opt, best_perf