-
Notifications
You must be signed in to change notification settings - Fork 0
/
ConBacktester.py
102 lines (78 loc) · 3.49 KB
/
ConBacktester.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
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("seaborn-v0_8")
class ConBacktester():
def __init__(self, symbol, start, end, tc):
'''
Parameters
----------
symbol: str
ticker symbol (instrument) to be backtested
start: str
start date for data import
end: str
end date for data import
tc: float
proportional transaction/trading costs per trade
'''
self.symbol = symbol
self.start = start
self.end = end
self.tc = tc
self.results = None
self.get_data()
def __repr__(self):
return "ConBacktester(symbol = {}, start = {}, end = {})".format(self.symbol, self.start, self.end)
def get_data(self):
raw = pd.read_csv("intraday_pairs.csv", parse_dates = ["time"], index_col = "time")
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 test_strategy(self, window = 1):
''' Backtests the simple contrarian trading strategy.
window: int
time window (number of bars) to be considered for the strategy.
'''
self.window = window
data = self.data.copy().dropna()
data["position"] = -np.sign(data["returns"].rolling(self.window).mean())
data["strategy"] = data["position"].shift(1) * data["returns"]
data.dropna(inplace=True)
# determine the number of trades in each bar
data["trades"] = data.position.diff().fillna(0).abs()
# subtract transaction/trading costs from pre-cost return
data.strategy = data.strategy - data.trades * self.tc
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
'''
if self.results is None:
print("Run test_strategy() first.")
else:
title = "{} | Window = {} | TC = {}".format(self.symbol, self.window, self.tc)
self.results[["creturns", "cstrategy"]].plot(title=title, figsize=(12, 8))
def optimize_parameter(self, window_range):
''' Finds the optimal strategy given the window parameter range.
window_range: tuple
tuples of the form (start, end, step size)
'''
windows = range(*window_range)
results = []
for window in windows:
results.append(self.test_strategy(window)[0])
best_perf = np.max(results) # best performance
opt = windows[np.argmax(results)] # optimal parameter
# run/set the optimal strategy
self.test_strategy(opt)
# create a df with many results
many_results = pd.DataFrame(data = {"window": windows, "performance": results})
self.results_overview = many_results
return opt, best_perf