-
Notifications
You must be signed in to change notification settings - Fork 1
/
slob.py
178 lines (147 loc) · 4.83 KB
/
slob.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
import uuid
class Event(list):
def __call__(self, *args, **kwargs):
for f in self:
f(*args, **kwargs)
def __repr__(self):
return "Event(%s)" % list.__repr__(self)
class Order(object):
def __init__(self, limit, quantity, buy):
self.cb_cancel = Event()
self.cb_reduce = Event()
self.cb_fill = Event()
self.id = uuid.uuid4().hex
self.limit = limit
self.quantity = quantity
self.buy = buy
self.next = None
self.prev = None
@property
def sell(self):
return not self.buy
def _eliminate(self):
if self.quantity == 0:
if self.next:
self.next.prev = self.prev
if self.prev:
self.prev.next = self.next
def cancel(self):
self.cb_cancel(self)
self.quantity = 0
self._eliminate()
def reduce(self, amount):
if amount > self.quantity:
raise Exception("Reduce amount can not exceed order quantity")
self.cb_reduce(self, amount)
self.quantity -= amount
self._eliminate()
def fill(self, amount):
if amount > self.quantity:
raise Exception("Fill amount can not exceed order quantity")
self.cb_fill(self, amount)
self.quantity -= amount
self._eliminate()
class PriceLevel(object):
def __init__(self, price):
self.cb_eliminate = Event()
self.price = price
self.volume = 0
self.head = None
self.tail = None
def add(self, order):
self.volume += order.quantity
order.cb_cancel.append(self._cancel)
order.cb_reduce.append(self._reduce)
order.cb_fill.append(self._fill)
if self.head is None:
self.head = order
self.tail = order
else:
self.tail.next = order
order.prev = self.tail
self.tail = order
def remove(self, order):
if self.head == order:
self.head = order.next
if self.head is None:
self.eliminate()
def _cancel(self, order):
self.volume -= order.quantity
self.remove(order)
def _reduce(self, order, amount):
self.volume -= amount
if order.quantity == amount:
self.remove(order)
def _fill(self, order, amount):
self.volume -= amount
if order.quantity == amount:
self.remove(order)
def eliminate(self):
self.cb_eliminate(self)
class OrderContainer(object):
def __init__(self, asc):
self.asc = asc
self.prices = []
self.price_levels = {}
def _new_price(self, price):
self.prices.append(price)
self.prices.sort(reverse=self.asc)
def add(self, order):
price = order.limit
if price in self.prices:
self.price_levels.get(price).add(order)
else:
self._new_price(price)
price_level = PriceLevel(price)
price_level.cb_eliminate.append(self._eliminate)
price_level.add(order)
self.price_levels.update({price: price_level})
def market_price_level(self):
market_price = self.prices[0]
return market_price, self.price_levels[market_price]
def depth(self):
depth = {}
for price, price_level in self.price_levels.items():
depth.update({price: price_level.volume})
return depth
def _eliminate(self, pricelevel):
price = pricelevel.price
self.prices.remove(price)
del self.price_levels[price]
class OrderBook(object):
def __init__(self):
self.orders = {}
self.bids = OrderContainer(asc=True)
self.asks = OrderContainer(asc=False)
def add_order(self, order):
self.orders.update({order.id: order})
if order.buy:
if not match(order, self.asks):
self.bids.add(order)
elif order.sell:
if not match(order, self.bids):
self.asks.add(order)
def get_order(self, orderid):
return self.orders.get(orderid)
def depth(self):
return {"bids": self.bids.depth(), "asks": self.asks.depth()}
def match(self, order, container):
while True:
try:
price, level = container.market_price_level()
except IndexError:
# no market price level
return False
if (order.buy and order.limit < price) or \
(order.sell and order.limit > price):
# market price not good enough
return False
while level.head is not None:
if order.quantity == 0:
return True
match_orders(order, level.head)
def match_orders(order1, order2):
quantity = min(order1.quantity, order2.quantity)
order1.fill(quantity)
order2.fill(quantity)
return quantity