-
Notifications
You must be signed in to change notification settings - Fork 4
/
automated_trading_hft_portfolio.py
232 lines (192 loc) · 7.61 KB
/
automated_trading_hft_portfolio.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# hft_portfolio.py
from __future__ import print_function
import datetime
from math import floor
try:
import Queue as queue
except ImportError:
import queue
import numpy as np
import pandas as pd
from event import FillEvent, OrderEvent
from performance import create_sharpe_ratio, create_drawdowns
class PortfolioHFT(object):
"""
The PortfolioHFT class handles the positions and market
value of all instruments at a resolution of a "bar",
i.e. secondly, minutely, 5-min, 30-min, 60 min or EOD.
The positions DataFrame stores a time-index of the
quantity of positions held.
The holdings DataFrame stores the cash and total market
holdings value of each symbol for a particular
time-index, as well as the percentage change in
portfolio total across bars.
"""
def __init__(self, bars, events, start_date, initial_capital=100000.0):
"""
Initialises the portfolio with bars and an event queue.
Also includes a starting datetime index and initial capital
(USD unless otherwise stated).
Parameters:
bars - The DataHandler object with current market data.
events - The Event Queue object.
start_date - The start date (bar) of the portfolio.
initial_capital - The starting capital in USD.
"""
self.bars = bars
self.events = events
self.symbol_list = self.bars.symbol_list
self.start_date = start_date
self.initial_capital = initial_capital
self.all_positions = self.construct_all_positions()
self.current_positions = dict( (k,v) for k, v in \ [(s, 0) for s in self.symbol_list])
self.all_holdings = self.construct_all_holdings()
self.current_holdings = self.construct_current_holdings()
def construct_all_positions(self):
"""
Constructs the positions list using the start_date
to determine when the time index will begin.
"""
d = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list])
d[’datetime’] = self.start_date
return [d]
def construct_all_holdings(self):
"""
Constructs the holdings list using the start_date
to determine when the time index will begin.
"""
d = dict( (k,v) for k, v in [(s, 0.0) for s in self.symbol_list])
d[’datetime’] = self.start_date
d[’cash’] = self.initial_capital
d[’commission’] = 0.0
d[’total’] = self.initial_capital
return [d]
def update_timeindex(self, event):
"""
Adds a new record to the positions matrix for the current market data bar. This reflects the PREVIOUS bar, i.e. all
current market data at this stage is known (OHLCV).
Makes use of a MarketEvent from the events queue.
"""
latest_datetime = self.bars.get_latest_bar_datetime(self.symbol_list[0])
# Update positions
# ================
dp = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list])
dp[’datetime’] = latest_datetime
for s in self.symbol_list:
dp[s] = self.current_positions[s]
# Append the current positions
self.all_positions.append(dp)
# Update holdings
# ===============
dh = dict( (k,v) for k, v in [(s, 0) for s in self.symbol_list] )
dh[’datetime’] = latest_datetime
dh[’cash’] = self.current_holdings[’cash’]
dh[’commission’] = self.current_holdings[’commission’]
dh[’total’] = self.current_holdings[’cash’]
for s in self.symbol_list:
# Approximation to the real value
market_value = self.current_positions[s] * self.bars.get_latest_bar_value(s, "close")
dh[s] = market_value
dh[’total’] += market_value
# Append the current holdings
self.all_holdings.append(dh)
def update_positions_from_fill(self, fill):
"""
Takes a Fill object and updates the position matrix to
reflect the new position.
Parameters:
fill - The Fill object to update the positions with.
"""
# Check whether the fill is a buy or sell
fill_dir = 0
if fill.direction == ’BUY’:
fill_dir = 1
if fill.direction == ’SELL’:
fill_dir = -1
# Update positions list with new quantities
self.current_positions[fill.symbol] += fill_dir*fill.quantity
def update_holdings_from_fill(self, fill):
"""
Takes a Fill object and updates the holdings matrix to
reflect the holdings value.
Parameters:
fill - The Fill object to update the holdings with.
"""
# Check whether the fill is a buy or sell
fill_dir = 0
if fill.direction == ’BUY’:
fill_dir = 1
if fill.direction == ’SELL’:
fill_dir = -1
# Update holdings list with new quantities
fill_cost = self.bars.get_latest_bar_value(fill.symbol, "close")
cost = fill_dir * fill_cost * fill.quantity
self.current_holdings[fill.symbol] += cost
self.current_holdings[’commission’] += fill.commission
self.current_holdings[’cash’] -= (cost + fill.commission)
self.current_holdings[’total’] -= (cost + fill.commission)
def update_fill(self, event):
"""
Updates the portfolio current positions and holdings from a FillEvent.
"""
if event.type == ’FILL’:
self.update_positions_from_fill(event)
self.update_holdings_from_fill(event)
def generate_naive_order(self, signal):
"""
Simply files an Order object as a constant quantity
sizing of the signal object, without risk management or
position sizing considerations.
Parameters:
signal - The tuple containing Signal information.
"""
order = None
symbol = signal.symbol
direction = signal.signal_type
strength = signal.strength
mkt_quantity = 100
cur_quantity = self.current_positions[symbol]
order_type = ’MKT’
if direction == ’LONG’ and cur_quantity == 0:
order = OrderEvent(symbol, order_type, mkt_quantity, ’BUY’)
if direction == ’SHORT’ and cur_quantity == 0:
order = OrderEvent(symbol, order_type, mkt_quantity, ’SELL’)
if direction == ’EXIT’ and cur_quantity > 0:
order = OrderEvent(symbol, order_type, abs(cur_quantity), ’SELL’)
if direction == ’EXIT’ and cur_quantity < 0:
order = OrderEvent(symbol, order_type, abs(cur_quantity), ’BUY’)
return order
def update_signal(self, event):
"""
Acts on a SignalEvent to generate new orders
based on the portfolio logic.
"""
if event.type == ’SIGNAL’:
order_event = self.generate_naive_order(event)
self.events.put(order_event)
def create_equity_curve_dataframe(self):
"""
Creates a pandas DataFrame from the all_holdings
list of dictionaries.
"""
curve = pd.DataFrame(self.all_holdings)
curve.set_index(’datetime’, inplace=True)
curve[’returns’] = curve[’total’].pct_change()
curve[’equity_curve’] = (1.0+curve[’returns’]).cumprod()
self.equity_curve = curve
def output_summary_stats(self):
"""
Creates a list of summary statistics for the portfolio.
"""
total_return = self.equity_curve[’equity_curve’][-1]
returns = self.equity_curve[’returns’]
pnl = self.equity_curve[’equity_curve’]
sharpe_ratio = create_sharpe_ratio(returns, periods=252*60*6.5)
drawdown, max_dd, dd_duration = create_drawdowns(pnl)
self.equity_curve[’drawdown’] = drawdown
stats = [("Total Return", "%0.2f%%" % \((total_return - 1.0) * 100.0)),
("Sharpe Ratio", "%0.2f" % sharpe_ratio),
("Max Drawdown", "%0.2f%%" % (max_dd * 100.0)),
("Drawdown Duration", "%d" % dd_duration)]
self.equity_curve.to_csv(’equity.csv’)
return stats