-
Notifications
You must be signed in to change notification settings - Fork 0
/
IterativeBase.py
122 lines (109 loc) · 4.77 KB
/
IterativeBase.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
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("seaborn-v0_8")
class IterativeBase():
''' Base class for iterative (event-driven) backtesting of trading strategies.
'''
def __init__(self, symbol, start, end, amount, use_spread = True):
'''
Parameters
----------
symbol: str
ticker symbol (instrument) to be backtested
start: str
start date for data import
end: str
end date for data import
amount: float
initial amount to be invested per trade
use_spread: boolean (default = True)
whether trading costs (bid-ask spread) are included
'''
self.symbol = symbol
self.start = start
self.end = end
self.initial_balance = amount
self.current_balance = amount
self.units = 0
self.trades = 0
self.position = 0
self.use_spread = use_spread
self.get_data()
def get_data(self):
''' Imports the data from detailed.csv (source can be changed).
'''
raw = pd.read_csv("detailed.csv", parse_dates = ["time"], index_col = "time").dropna()
raw = raw.loc[self.start:self.end].copy()
raw["returns"] = np.log(raw.price / raw.price.shift(1))
self.data = raw
def plot_data(self, cols = None):
''' Plots the closing price for the symbol.
'''
if cols is None:
cols = "price"
self.data[cols].plot(figsize = (12, 8), title = self.symbol)
def get_values(self, bar):
''' Returns the date, the price and the spread for the given bar.
'''
date = str(self.data.index[bar].date())
price = round(self.data.price.iloc[bar], 5)
spread = round(self.data.spread.iloc[bar], 5)
return date, price, spread
def print_current_balance(self, bar):
''' Prints out the current (cash) balance.
'''
date, price, spread = self.get_values(bar)
print("{} | Current Balance: {}".format(date, round(self.current_balance, 2)))
def buy_instrument(self, bar, units = None, amount = None):
''' Places and executes a buy order (market order).
'''
date, price, spread = self.get_values(bar)
if self.use_spread:
price += spread/2 # ask price
if amount is not None: # use units if units are passed, otherwise calculate units
units = int(amount / price)
self.current_balance -= units * price # reduce cash balance by "purchase price"
self.units += units
self.trades += 1
print("{} | Buying {} for {}".format(date, units, round(price, 5)))
def sell_instrument(self, bar, units = None, amount = None):
''' Places and executes a sell order (market order).
'''
date, price, spread = self.get_values(bar)
if self.use_spread:
price -= spread/2 # bid price
if amount is not None: # use units if units are passed, otherwise calculate units
units = int(amount / price)
self.current_balance += units * price # increases cash balance by "purchase price"
self.units -= units
self.trades += 1
print("{} | Selling {} for {}".format(date, units, round(price, 5)))
def print_current_position_value(self, bar):
''' Prints out the current position value.
'''
date, price, spread = self.get_values(bar)
cpv = self.units * price
print("{} | Current Position Value = {}".format(date, round(cpv, 2)))
def print_current_nav(self, bar):
''' Prints out the current net asset value (nav).
'''
date, price, spread = self.get_values(bar)
nav = self.current_balance + self.units * price
print("{} | Net Asset Value = {}".format(date, round(nav, 2)))
def close_pos(self, bar):
''' Closes out a long or short position (go neutral).
'''
date, price, spread = self.get_values(bar)
print(75 * "-")
print("{} | +++ CLOSING FINAL POSITION +++".format(date))
self.current_balance += self.units * price # closing final position (works with short and long!)
self.current_balance -= (abs(self.units) * spread/2 * self.use_spread) # substract half-spread costs
print("{} | closing position of {} for {}".format(date, self.units, price))
self.units = 0 # setting position to neutral
self.trades += 1
perf = (self.current_balance - self.initial_balance) / self.initial_balance * 100
self.print_current_balance(bar)
print("{} | net performance (%) = {}".format(date, round(perf, 2) ))
print("{} | number of trades executed = {}".format(date, self.trades))
print(75 * "-")