-
Notifications
You must be signed in to change notification settings - Fork 0
/
IterativeBacktest.py
163 lines (138 loc) · 6.98 KB
/
IterativeBacktest.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from IterativeBase import *
class IterativeBacktest(IterativeBase):
''' Class for iterative backtesting
'''
# helper method
def go_long(self, bar, units = None, amount = None):
if self.position == -1:
self.buy_instrument(bar, units = -self.units) # if short position, go neutral first
if units:
self.buy_instrument(bar, units = units)
elif amount:
if amount == "all":
amount = self.current_balance
self.buy_instrument(bar, amount = amount) # go long
# helper method
def go_short(self, bar, units = None, amount = None):
if self.position == 1:
self.sell_instrument(bar, units = self.units) # if long position, go neutral first
if units:
self.sell_instrument(bar, units = units)
elif amount:
if amount == "all":
amount = self.current_balance
self.sell_instrument(bar, amount = amount) # go short
def test_sma_strategy(self, SMA_S, SMA_L):
'''
Backtests an SMA crossover strategy with SMA_S (short) and SMA_L (long).
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
'''
# nice printout
stm = "Testing SMA strategy | {} | SMA_S = {} & SMA_L = {}".format(self.symbol, SMA_S, SMA_L)
print("-" * 75)
print(stm)
print("-" * 75)
# reset
self.position = 0 # initial neutral position
self.trades = 0 # no trades yet
self.current_balance = self.initial_balance # reset initial capital
self.get_data() # reset dataset
# prepare data
self.data["SMA_S"] = self.data["price"].rolling(SMA_S).mean()
self.data["SMA_L"] = self.data["price"].rolling(SMA_L).mean()
self.data.dropna(inplace = True)
# sma crossover strategy
for bar in range(len(self.data)-1): # all bars (except the last bar)
if self.data["SMA_S"].iloc[bar] > self.data["SMA_L"].iloc[bar]: # signal to go long
if self.position in [0, -1]:
self.go_long(bar, amount = "all") # go long with full amount
self.position = 1 # long position
elif self.data["SMA_S"].iloc[bar] < self.data["SMA_L"].iloc[bar]: # signal to go short
if self.position in [0, 1]:
self.go_short(bar, amount = "all") # go short with full amount
self.position = -1 # short position
self.close_pos(bar+1) # close position at the last bar
def test_con_strategy(self, window = 1):
'''
Backtests a simple contrarian strategy.
window: int
time window to be considered for the strategy.
'''
# nice printout
stm = "Testing Contrarian strategy | {} | Window = {}".format(self.symbol, window)
print("-" * 75)
print(stm)
print("-" * 75)
# reset
self.position = 0 # initial neutral position
self.trades = 0 # no trades yet
self.current_balance = self.initial_balance # reset initial capital
self.get_data() # reset dataset
# prepare data
self.data["rolling_returns"] = self.data["returns"].rolling(window).mean()
self.data.dropna(inplace = True)
# Contrarian strategy
for bar in range(len(self.data)-1): # all bars (except the last bar)
if self.data["rolling_returns"].iloc[bar] <= 0: #signal to go long
if self.position in [0, -1]:
self.go_long(bar, amount = "all") # go long with full amount
self.position = 1 # long position
elif self.data["rolling_returns"].iloc[bar] > 0: #signal to go short
if self.position in [0, 1]:
self.go_short(bar, amount = "all") # go short with full amount
self.position = -1 # short position
self.close_pos(bar+1) # close position at the last bar
def test_boll_strategy(self, SMA, dev):
'''
Backtests a Bollinger Bands mean-reversion strategy.
Parameters
----------
SMA: int
moving window in bars (e.g. days) for simple moving average.
dev: int
distance for Lower/Upper Bands in Standard Deviation units
'''
# nice printout
stm = "Testing Bollinger Bands Strategy | {} | SMA = {} & dev = {}".format(self.symbol, SMA, dev)
print("-" * 75)
print(stm)
print("-" * 75)
# reset
self.position = 0 # initial neutral position
self.trades = 0 # no trades yet
self.current_balance = self.initial_balance # reset initial capital
self.get_data() # reset dataset
# prepare data
self.data["SMA"] = self.data["price"].rolling(SMA).mean()
self.data["Lower"] = self.data["SMA"] - self.data["price"].rolling(SMA).std() * dev
self.data["Upper"] = self.data["SMA"] + self.data["price"].rolling(SMA).std() * dev
self.data.dropna(inplace = True)
# Bollinger strategy
for bar in range(len(self.data)-1): # all bars (except the last bar)
if self.position == 0: # when neutral
if self.data["price"].iloc[bar] < self.data["Lower"].iloc[bar]: # signal to go long
self.go_long(bar, amount = "all") # go long with full amount
self.position = 1 # long position
elif self.data["price"].iloc[bar] > self.data["Upper"].iloc[bar]: # signal to go Short
self.go_short(bar, amount = "all") # go short with full amount
self.position = -1 # short position
elif self.position == 1: # when long
if self.data["price"].iloc[bar] > self.data["SMA"].iloc[bar]:
if self.data["price"].iloc[bar] > self.data["Upper"].iloc[bar]: # signal to go short
self.go_short(bar, amount = "all") # go short with full amount
self.position = -1 # short position
else:
self.sell_instrument(bar, units = self.units) # go neutral
self.position = 0
elif self.position == -1: # when short
if self.data["price"].iloc[bar] < self.data["SMA"].iloc[bar]:
if self.data["price"].iloc[bar] < self.data["Lower"].iloc[bar]: # signal to go long
self.go_long(bar, amount = "all") # go long with full amount
self.position = 1 # long position
else:
self.buy_instrument(bar, units = -self.units) # go neutral
self.position = 0
self.close_pos(bar+1) # close position at the last bar