-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOrderBook.py
161 lines (145 loc) · 5.97 KB
/
OrderBook.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
import enum
import queue
import time
from collections import defaultdict
from Trade import Trade
from Order import Order
class Side(enum.Enum):
BUY = 0
SELL = 1
def get_timestamp():
""" Microsecond timestamp """
return int(1e6 * time.time())
class OrderBook(object):
def __init__(self):
""" Orders stored as two defaultdicts of {price:[orders at price]}
Orders sent to OrderBook through OrderBook.unprocessed_orders queue
"""
self.bid_prices = []
self.bid_sizes = []
self.offer_prices = []
self.offer_sizes = []
self.bids = defaultdict(list)
self.offers = defaultdict(list)
self.unprocessed_orders = queue.Queue()
self.trades = queue.Queue()
self.order_id = 0
def new_order_id(self):
self.order_id += 1
return self.order_id
@property
def max_bid(self):
if self.bids:
return max(self.bids.keys())
else:
return 0.
@property
def min_offer(self):
if self.offers:
return min(self.offers.keys())
else:
return float('inf')
def process_order(self, incoming_order):
""" Main processing function. If incoming_order matches delegate to process_match."""
incoming_order.timestamp = get_timestamp()
incoming_order.order_id = self.new_order_id()
if incoming_order.side == Side.BUY:
if incoming_order.price >= self.min_offer and self.offers:
self.process_match(incoming_order)
else:
self.bids[incoming_order.price].append(incoming_order)
else:
if incoming_order.price <= self.max_bid and self.bids:
self.process_match(incoming_order)
else:
self.offers[incoming_order.price].append(incoming_order)
def process_match(self, incoming_order):
""" Match an incoming order against orders on the other side of the book, in price-time priority."""
levels = self.bids if incoming_order.side == Side.SELL else self.offers
prices = sorted(levels.keys(), reverse=(incoming_order.side == Side.SELL))
def price_doesnt_match(book_price):
if incoming_order.side == Side.BUY:
return incoming_order.price < book_price
else:
return incoming_order.price > book_price
for (i, price) in enumerate(prices):
if (incoming_order.size == 0) or (price_doesnt_match(price)):
break
orders_at_level = levels[price]
for (j, book_order) in enumerate(orders_at_level):
if incoming_order.size == 0:
break
trade = self.execute_match(incoming_order, book_order)
incoming_order.size = max(0, incoming_order.size-trade.size)
book_order.size = max(0, book_order.size-trade.size)
self.trades.put(trade)
levels[price] = [o for o in orders_at_level if o.size > 0]
if len(levels[price]) == 0:
levels.pop(price)
# If the incoming order has not been completely matched, add the remainder to the order book
if incoming_order.size > 0:
same_side = self.bids if incoming_order.side == Side.BUY else self.offers
same_side[incoming_order.price].append(incoming_order)
def execute_match(self, incoming_order, book_order):
trade_size = min(incoming_order.size, book_order.size)
return Trade(incoming_order.side, book_order.price, trade_size, incoming_order.order_id, book_order.order_id)
def book_summary(self):
self.bid_prices = sorted(self.bids.keys(), reverse=True)
self.offer_prices = sorted(self.offers.keys())
self.bid_sizes = [sum(o.size for o in self.bids[p]) for p in self.bid_prices]
self.offer_sizes = [sum(o.size for o in self.offers[p]) for p in self.offer_prices]
def show_book(self):
self.book_summary()
print('Sell side:')
if len(self.offer_prices) == 0:
print('EMPTY')
for i, price in reversed(list(enumerate(self.offer_prices))):
print('{0}) Price={1}, Total units={2}'.format(i+1, self.offer_prices[i], self.offer_sizes[i]))
print('Buy side:')
if len(self.bid_prices) == 0:
print('EMPTY')
for i, price in enumerate(self.bid_prices):
print('{0}) Price={1}, Total units={2}'.format(i+1, self.bid_prices[i], self.bid_sizes[i]))
print()
if __name__ == '__main__':
print('Example 1:')
ob = OrderBook()
orders = [
Order(Side.BUY, 1., 2),
Order(Side.BUY, 2., 3, 2),
Order(Side.BUY, 1., 4, 3)
]
print('We receive these orders:')
for order in orders:
print(order)
ob.unprocessed_orders.put(order)
while not ob.unprocessed_orders.empty():
ob.process_order(ob.unprocessed_orders.get())
print()
print('Resulting order book:')
ob.show_book()
# print('Example 2:')
# ob = OrderBook()
# orders = [Order(Side.BUY, 12.23, 10),
# Order(Side.BUY, 12.31, 20),
# Order(Side.SELL, 13.55, 5),
# Order(Side.BUY, 12.23, 5),
# Order(Side.BUY, 12.25, 15),
# Order(Side.SELL, 13.31, 5),
# Order(Side.BUY, 12.25, 30),
# Order(Side.SELL, 13.31, 5)]
# print('We receive these orders:')
# for order in orders:
# print(order)
# ob.unprocessed_orders.put(order)
# while not ob.unprocessed_orders.empty():
# ob.process_order(ob.unprocessed_orders.get())
# print()
# print('Resulting order book:')
# ob.show_book()
# offer_order = Order(Side.SELL, 12.25, 100)
# print('Now we get a sell order {}'.format(offer_order))
# print('This removes the first two buy orders and creates a new price level on the sell side')
# ob.unprocessed_orders.put(offer_order)
# ob.process_order(ob.unprocessed_orders.get())
# ob.show_book()