forked from DrAshBooth/PyLOB
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathorderbook.py
More file actions
275 lines (256 loc) · 11.4 KB
/
orderbook.py
File metadata and controls
275 lines (256 loc) · 11.4 KB
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
#orderbook
import sys
import math
#import io #for python3 and remove StringsIO to BytesIO?
from collections import deque # a faster insert/pop queue
from cStringIO import StringIO #might not be needed due since I added import io
from ordertree import OrderTree
class OrderBook(object):
def __init__(self, tick_size = 0.0001):
self.tape = deque(maxlen=None) # Index [0] is most recent trade
self.bids = OrderTree()
self.asks = OrderTree()
self.lastTick = None
self.lastTimestamp = 0
self.tickSize = tick_size
self.time = 0
self.nextQuoteID = 0
# Clips the price according to the ticksize. May not make sense if not a currency
def clipPrice(self, price):
return round(price, int(math.log10(1 / self.tickSize)))
def updateTime(self):
self.time += 1
def processOrder(self, quote, fromData, verbose):
orderType = quote['type']
orderInBook = None
if fromData:
self.time = quote['timestamp']
else:
self.updateTime()
quote['timestamp'] = self.time
if quote['qty'] <= 0:
sys.exit('processLimitOrder() given order of qty <= 0')
if not fromData: self.nextQuoteID += 1
if orderType=='market':
trades = self.processMarketOrder(quote, verbose)
elif orderType=='limit':
quote['price'] = self.clipPrice(quote['price'])
trades, orderInBook = self.processLimitOrder(quote, fromData, verbose)
else:
sys.exit("orderType for processOrder() is neither 'market' nor 'limit'")
return trades, orderInBook
def processOrderList(self, side, orderlist,
qtyStillToTrade, quote, verbose):
'''
Takes an order list (stack of orders at one price) and
an incoming order and matches appropriate trades given
the orders quantity.
'''
trades = []
qtyToTrade = qtyStillToTrade
while len(orderlist) > 0 and qtyToTrade > 0:
headOrder = orderlist.getHeadOrder()
tradedPrice = headOrder.price
counterparty = headOrder.tid
if qtyToTrade < headOrder.qty:
tradedQty = qtyToTrade
# Amend book order; Do the transaction
newBookQty = headOrder.qty - qtyToTrade
headOrder.updateQty(newBookQty, headOrder.timestamp)
# Incoming done with
qtyToTrade = 0
elif qtyToTrade == headOrder.qty:
tradedQty = qtyToTrade
if side == 'bid':
# Hit the bid
self.bids.removeOrderById(headOrder.idNum)
else:
# Lift the ask
self.asks.removeOrderById(headOrder.idNum)
# Incoming done with
qtyToTrade = 0
else: # quantity to trade is larger than the head order
tradedQty = headOrder.qty
if side == 'bid':
# Hit the bid
self.bids.removeOrderById(headOrder.idNum)
else:
# Lift the ask
self.asks.removeOrderById(headOrder.idNum)
# We need to keep eating into volume at this price
qtyToTrade -= tradedQty
if verbose: print('>>> TRADE \nt=%d $%f n=%d p1=%d p2=%d' %
(self.time, tradedPrice, tradedQty,
counterparty, quote['tid']))
# print ("TRADE: Time - %d, Price - %f, Quantity - %d, TradeID - %d, Matching TradeID - %d" %
# (self.time, traded_price, traded_quantity, counter_party, quote['trade_id']))
transactionRecord = {'timestamp': self.time,
'price': tradedPrice,
'qty': tradedQty,
'time': self.time}
if side == 'bid':
transactionRecord['party1'] = [counterparty,
'bid',
headOrder.idNum]
transactionRecord['party2'] = [quote['tid'],
'ask',
None]
else:
transactionRecord['party1'] = [counterparty,
'ask',
headOrder.idNum]
transactionRecord['party2'] = [quote['tid'],
'bid',
None]
self.tape.append(transactionRecord)
trades.append(transactionRecord)
return qtyToTrade, trades
def processMarketOrder(self, quote, verbose):
trades = []
qtyToTrade = quote['qty']
side = quote['side']
if side == 'bid':
while qtyToTrade > 0 and self.asks:
bestPriceAsks = self.asks.minPriceList()
qtyToTrade, newTrades = self.processOrderList('ask',
bestPriceAsks,
qtyToTrade,
quote, verbose)
trades += newTrades
elif side == 'ask':
while qtyToTrade > 0 and self.bids:
bestPriceBids = self.bids.maxPriceList()
qtyToTrade, newTrades = self.processOrderList('bid',
bestPriceBids,
qtyToTrade,
quote, verbose)
trades += newTrades
else:
sys.exit('processMarketOrder() received neither "bid" nor "ask"')
return trades
def processLimitOrder(self, quote, fromData, verbose):
orderInBook = None
trades = []
qtyToTrade = quote['qty']
side = quote['side']
price = quote['price']
if side == 'bid':
while (self.asks and
price >= self.asks.minPrice() and # >= prevents bug that lets best price be equal when only =
qtyToTrade > 0):
bestPriceAsks = self.asks.minPriceList()
qtyToTrade, newTrades = self.processOrderList('ask',
bestPriceAsks,
qtyToTrade,
quote, verbose)
trades += newTrades
# If volume remains, update/add to book with new quantity
if qtyToTrade > 0:
if not fromData:
quote['idNum'] = self.nextQuoteID
quote['qty'] = qtyToTrade
self.bids.insertOrder(quote)
orderInBook = quote
elif side == 'ask':
while (self.bids and
price <= self.bids.maxPrice() and # <= prevents bug that lets best price be equal when only =
qtyToTrade > 0):
bestPriceBids = self.bids.maxPriceList()
qtyToTrade, newTrades = self.processOrderList('bid',
bestPriceBids,
qtyToTrade,
quote, verbose)
trades += newTrades
# If volume remains, add to book
if qtyToTrade > 0:
if not fromData:
quote['idNum'] = self.nextQuoteID #idNum is order_id
quote['qty'] = qtyToTrade
self.asks.insertOrder(quote)
orderInBook = quote
else:
sys.exit('processLimitOrder() given neither bid nor ask')
return trades, orderInBook
def cancelOrder(self, side, idNum, time = None):
if time:
self.time = time
else:
self.updateTime()
if side == 'bid':
if self.bids.orderExists(idNum):
self.bids.removeOrderById(idNum)
elif side == 'ask':
if self.asks.orderExists(idNum):
self.asks.removeOrderById(idNum)
else:
sys.exit('cancelOrder() given neither bid nor ask')
def modifyOrder(self, idNum, orderUpdate, time=None):
if time:
self.time = time
else:
self.updateTime()
side = orderUpdate['side']
orderUpdate['idNum'] = idNum
orderUpdate['timestamp'] = self.time
if side == 'bid':
if self.bids.orderExists(orderUpdate['idNum']):
self.bids.updateOrder(orderUpdate)
elif side == 'ask':
if self.asks.orderExists(orderUpdate['idNum']):
self.asks.updateOrder(orderUpdate)
else:
sys.exit('modifyOrder() given neither bid nor ask')
def getVolumeAtPrice(self, side, price):
price = self.clipPrice(price)
if side =='bid':
vol = 0
if self.bids.priceExists(price):
vol = self.bids.getPrice(price).volume
return vol
elif side == 'ask':
vol = 0
if self.asks.priceExists(price):
vol = self.asks.getPrice(price).volume
return vol
else:
sys.exit('getVolumeAtPrice() given neither bid nor ask')
def getBestBid(self):
return self.bids.maxPrice()
def getWorstBid(self):
return self.bids.minPrice()
def getBestAsk(self):
return self.asks.minPrice()
def getWorstAsk(self):
return self.asks.maxPrice()
def tapeDump(self, fname, fmode, tmode):
dumpfile = open(fname, fmode)
for tapeitem in self.tape:
dumpfile.write('%s, %s, %s\n' % (tapeitem['time'],
tapeitem['price'],
tapeitem['qty']))
dumpfile.close()
if tmode == 'wipe':
self.tape = []
def __str__(self):
fileStr = StringIO()
fileStr.write("------ Bids -------\n")
if self.bids != None and len(self.bids) > 0:
for k, v in self.bids.priceTree.items(reverse=True): #for key, value...
fileStr.write('%s' % v)
fileStr.write("\n------ Asks -------\n")
if self.asks != None and len(self.asks) > 0:
for k, v in self.asks.priceTree.items():
fileStr.write('%s' % v)
fileStr.write("\n------ Trades ------\n")
if self.tape != None and len(self.tape) > 0:
num = 0
for entry in self.tape:
if num < 5: # get last 5 entries
fileStr.write(str(entry['qty']) + " @ " +
str(entry['price']) +
" (" + str(entry['timestamp']) + ")\n")
num += 1
else:
break
fileStr.write("\n")
return fileStr.getvalue()