-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtester.py
143 lines (116 loc) · 5.09 KB
/
tester.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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/env python
# coding: utf-8
# In[ ]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from itertools import product
plt.style.use("seaborn")
class Backtester():
''' 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.csv", parse_dates = ["Date"], index_col = "Date")
raw = raw[raw["coin"]==self.symbol].dropna()
raw = raw.loc[self.start:self.end].copy()
raw.rename(columns={self.symbol: "price"}, inplace=True)
raw.drop(columns = ["coin", "Adj Close", "High", "Low", "Open", "Volume"], inplace = True)
raw.rename(columns = {"Close": "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 plot_field(self):
''' Plots the heatmap of performance of the trading strategy".
'''
if self.results is None:
print("Run optimize_parameters() first.")
else:
sns.set(rc={'figure.figsize':(22,22)})
df_wide = self.results_overview.pivot_table( index='SMA_L', columns='SMA_S', values='performance')
sns.heatmap(df_wide);
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