-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbroker.py
366 lines (283 loc) · 12.1 KB
/
broker.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
"""
TODO: describe the purpose of this file
"""
# import pandas as pd
import utils as ut
from _config import *
# ==================================================================================================
# global variables
# ==================================================================================================
BROKER_COLUMNS = ['Date','TotalCash','TotalValue']
ORDER_COLUMNS = ['Date','Type','Size','Status']
POSITION_COLUMNS = ['Date','Position','Price','Cost','Type','Status','Unrealized','Realized']
''' moved to _config.py
DEBUG = True
'''
# ==================================================================================================
# broker functions
# ==================================================================================================
def initialize_broker(initial_date, cash, value):
if DEBUG:
print(f'init broker ---- date: {initial_date}, TotalCash: {cash}, TotalValue: {value}')
broker = ut.initialize_df(BROKER_COLUMNS)
orders = ut.initialize_df(ORDER_COLUMNS)
positions = ut.initialize_df(POSITION_COLUMNS)
broker.loc[len(broker)] = [initial_date, cash, value]
# ----------------------------------------------------------------------------------------------
if DEBUG:
print('BROKER')
print('BROKER COLUMNS', BROKER_COLUMNS)
print(broker.head())
print()
# ----------------------------------------------------------------------------------------------
print('ORDERS')
print('ORDER COLUMNS', ORDER_COLUMNS)
print(orders.head())
print()
# ----------------------------------------------------------------------------------------------
print('POSITIONS')
print('POSITION COLUMNS', POSITION_COLUMNS)
print(positions.head())
print()
return broker, orders, positions
# ==================================================================================================
# core functions
# ==================================================================================================
def execute_order(broker, positions, execution_date, current_price, order_size, order_type):
"""TODO: a summary fo what this function does
TODO: add a detailed description if necessary
Parameters
----------
TODO: update the list of parameters
df : pandas.DataFrame (required)
A OHLC dataframe containing the pricing data related to this order.
i : int (required)
'i' is the row in 'df' which contains price info relevant for this order.
size : int (required)
The size of the order / number of shares to be bought or sold.
otype : str (required)
The type of order to execute: "buy", "sell", or "close".
Returns
-------
TODO: update the return value
"""
# ----------------------------------------------------------------------------------------------
# TODO: we can probably add more comments in this code
# TODO: we probably want to refactor this code because the 'buy' and 'sell' execution is
# completely different from the 'close' execution
# TODO: include 'commision' so we can account for additional loss per trade execution
# ----------------------------------------------------------------------------------------------
# REPLACE ME -- this comment and print() should be a log statement
# display some details about this order execution
if DEBUG:
print(f'execute order -- type: {order_type}, date: {execution_date}, price: {current_price}, size: {order_size}')
# DELETE ME -- these were the prior details I printed
# print('execute order =', otype, '; size =', size, '; i =', i)
# DELETE ME -- I've stopped specifying an index for the dataframe
# get the date from the provided dataframe
# date = df.index.values[i]
# date = df.Date[i]
# TODO: do we want to keep this comment around?
"""This function does two things
1. it executes a 'buy' or 'sell' order - ie. it adds an 'open' position to the positions dataframe.
2. it executes a 'close' order - ie. it modifies the 'open' position in the positions dataframe so that it's 'closed'
"""
# ----------------------------------------------------------------------------------------------
# execute the 'buy' or 'sell' by adding a new 'long' or 'short' position
if (order_type=='buy') or (order_type=='sell'):
# set the position size and position type based on the order type
if order_type=='buy':
position=order_size
position_type='long'
else:
position=order_size * -1
position_type='short'
# create the position data
cost = current_price * order_size
status = 'open'
unrealized = 0.0
realized = 0.0
data = [
execution_date,
position,
current_price,
cost,
position_type,
status,
unrealized,
realized
]
# REPLACE ME -- this comment and print() should be a log statement
# display the order data we just created
if DEBUG:
print(f'execute order -- created position data = [date: {execution_date}, position: {position}, price: {current_price}, cost: {cost}, type: {position_type}, status: {status}, unrealized: {unrealized}, realized: {realized}]')
# print(f'execute order -- data = {data}')
# DELETE ME -- save the new position in a dataframe
# position = pd.DataFrame(
# [data],
# columns=POSITION_COLUMNS
# )
# DELETE ME -- I've stopped specifying an index for the dataframe
# position.set_index('Date',inplace=True)
# DELETE ME -- concatenate the new order to the orders dataframe
# positions = pd.concat([positions, position])
# insert the new position at the end of the current dataframe
positions.loc[len(positions)] = data
if DEBUG:
print(f"execute order -- appended position [data]")
# -1 == last row in the dataframe
# 1 == TotalCash colum
# 2 == TotalValue column
# update the brokerage account's cash with the cost of this purchase
broker.iloc[-1].TotalCash -= cost
if DEBUG:
print(f"execute order -- updated broker -- TotalCash: {broker.iloc[-1].TotalCash}")
# we don't need to update the brokerage account's value because the value of the new
# position offsets the cash draw down
# broker.iloc[-1, 2] = broker.iloc[-1, 1] + cost
return broker, positions
# ----------------------------------------------------------------------------------------------
# execute the 'close' order
elif (order_type=='close'):
"""Steps Required to Close a Position
1. update Value based on the current price of the stock
2. set Position = 0 (because we 'sold'/'bought' all the stock)
3.
Reminder: each position contains these fields
columns=['Date','Position','Price','Cost','Type','Status','Unrealized','Realized']
"""
# filter for all 'open' positions
open_positions = positions.loc[positions.Status=='open']
open_pos_len = len(open_positions)
if DEBUG:
print(f'execute order -- filtered open positions')
print(open_positions)
if open_pos_len == 1 :
if DEBUG:
print(f'execute order -- 1 open position')
elif open_pos_len > 1 :
#### THIS SHOULD NEVER HAPPEN ###
if DEBUG:
print(f'execute order -- {open_pos_len} open positions')
assert(f'{open_pos_len} open positions - THIS SHOULD NEVER HAPPEN!')
else :
#### THIS SHOULD NEVER HAPPEN ###
if DEBUG:
print(f'execute order -- {open_pos_len} open positions')
assert(f'{open_pos_len} open positions - THIS SHOULD NEVER HAPPEN!')
# storing the index for the open position
idx = open_positions.index[0]
order_size = abs(positions.Position[idx])
# 1 == Position column
positions.iloc[idx,1] = 0 # <-- close the full position
# 6 == Unrealized column
positions.iloc[idx,6] = 0.0 # <-- Unrealized becomes zero
# 7 == Realized column
# 3 == Cost column
# the Realized(gain) = (Price * Size) - Cost
positions.iloc[idx,7] = (current_price * order_size) - positions.iloc[idx].Cost
# 5 == Status column
positions.iloc[idx,5] = 'closed'
# -1 == last row in the dataframe
# 1 == TotalCash colum
# 2 == TotalValue column
# update the brokerage account's TotalCash and TotalValue from the proceeds of this transaction
broker.iloc[-1,1] += positions.Cost[idx] + positions.Realized[idx]
broker.iloc[-1,2] = broker.iloc[-1].TotalCash
if DEBUG:
print(f'execute order -- updated broker -- TotalCash: {broker.iloc[-1].TotalCash}, TotalValue: {broker.iloc[-1].TotalValue}')
# DELETE ME -- NOTE: using .Status[#] creates a copy of the dataframe, so if we assign a value there with "=" then we get a pandas warning printed to the screen
# positions.Status[idx] = 'closed'
# DELETE ME ^^^^
return broker, positions
else:
assert(f'unknown execute order type: {order_type}')
return broker, positions
def process_orders(broker, orders, positions, stocks, stock_idx):
"""The function 'processes' each open order in order to 'fill' that order.
This function filters the 'orders' dataframe for open orders. For every 'open' order, it calls the execute_order() function, and passes 'df' the current stock dataframe, 'i' the active row in that dataframe, the 'size' of the order, and the 'type' of that order. After that we then consider the order 'filled'.
Parameters
----------
df : pandas.DataFrame (required)
i : int (required)
Returns
-------
None
"""
"""Steps Required to Close a Position
1. filter the orders dataframe for 'open' orders
2. call execute_order() for each order, passing appropriate information
3. set the order 'status' field to 'filled'
Reminders...
- Positions require these fields
columns=['Date','Position','Price','Cost','Type','Status','Unrealized','Realized']
- execute_orders require these fields
positions, execution_date, current_price, order_size, order_type
"""
# get a copy of all open orders
open_orders = orders.loc[orders.Status=='open']
# if there's no open orders, then return as there's nothing more to do
if len(open_orders) < 1 :
# print(f'no orders to process')
return broker, orders, positions
# REPLACE ME -- this comment and print() should be a log statement
# display some details about this order execution
if DEBUG:
print(f'process order -- stock idx: {stock_idx}, date: {stocks.Date[stock_idx]}, price: {stocks.Open[stock_idx]}, # orders: {len(open_orders)}')
# display(open_orders)
# REPLACE ME --
for i in range(0, len(open_orders)) :
# get order index
idx = open_orders.index[i]
# REPLACE ME -- this comment and print() should be a log statement
# print the Type of order we're processing
if DEBUG:
print(f'process order -- orders idx: {idx}, date: {stocks.Date[stock_idx]}, price: {stocks.Open[stock_idx]}, size: {orders.Size[idx]}, type: {orders.Type[idx]}')
broker, positions = execute_order(broker, positions, stocks.Date[stock_idx], stocks.Open[stock_idx], orders.Size[idx], orders.Type[idx])
# after we execute the order we then set the current order's status to 'filled'
# 3 = Status column
orders.iloc[idx,3] = 'filled'
# display(orders)
return broker, orders, positions
def create_order(orders, order_date, order_type, order_size=1):
"""Creates an order and adds it to the orders dataframe provided.
TODO: add a detailed description if necessary
Parameters
----------
orders : <pandas.DataFrame>, required
The dataframe where add new orders.
order_date : <float>, required
The 'datetime' <float> when the order was created.
order_type : <str>, required
Type of order: "buy", "sell", or "close".
order_size : <int>, required
Size of the order (ie. number of shares to purchase). Fractional shares are not supported.
Defauls to "DEFAULT_ORDER_SIZE".
Returns
-------
orders : <Pandas Dataframe>
The dataframe which contains our new order.
"""
# REPLACE ME -- this comment and print() should be a log statement
# display the order type for this order
if DEBUG:
print(f'create order --- {order_type}')
# combine the data required for the order
data = [
order_date,
order_type,
order_size,
'open'
]
# REPLACE ME -- this comment and print() should be a log statement
# display the order data we just created
if DEBUG:
print(f'create order --- data = {data}')
# add the new order as a row at the bottom of the orders dataframe
orders.loc[len(orders)] = data
if DEBUG:
print(f'create order --- appended [data]')
# REPLACE ME -- this comment and print() should be a log statement
# display the updated orders dataframe
# display(orders)
return orders