Joel Grunbaum
2022-01-13 90107c504e5b7d1bea2c93a43c7d07b640350355
commit | author | age
5d61a1 1 """.#book.py package for handling book."""
JG 2 import decimal
3 import heapq
4 from enum import Enum
5 import time
6
7 decimal.getcontext().prec = 3
8
9
10 class OrderSide(Enum):
11     """Order side enum."""
12
4497ee 13     Buy = "BUY",
JG 14     Sell = "SELL"
5d61a1 15
JG 16
17 class ProductTypeEnum(Enum):
18     """Product type enum."""
19
20     TEST = 0,
21     FUTURE = 1,
22     SPREAD = 2,
23     CALL = 3,
24     PUT = 4
25
26
27 class UnknownProductTypeError(Exception):
28     """Exception for unknown product type."""
29
30     pass
31
32
33 class Order:
34     """Order class."""
35
36     price: decimal.Decimal
37     side: OrderSide
38     remaining_volume: int
39     filled_volume: int
4497ee 40     timestamp: float
JG 41     id: str
5d61a1 42
JG 43     def __init__(self, price: decimal.Decimal(3), side: OrderSide,
4497ee 44                  volume: int, timestamp: float, id: str):
5d61a1 45         """Initialise class."""
JG 46         self.price = price
47         self.side = side
48         self.remaining_volume = volume
49         self.filled_volume = 0
4497ee 50         self.timestamp = timestamp
JG 51         self.id = id
5d61a1 52
JG 53
54 class Level:
55     """Level class."""
56
57     price: decimal.Decimal
58     volume: int
4497ee 59     side: OrderSide
JG 60     timestamp: float
61     id: str
5d61a1 62
4497ee 63     def __init__(self, order: Order):
5d61a1 64         """Initialise class."""
4497ee 65         self.price = order.price
JG 66         self.volume = order.remaining_volume
67         self.side = order.side
68         self.timestamp = order.timestamp
69         self.id = order.id
5d61a1 70
JG 71     def __lt__(self, other):
72         """Less than comparator."""
4497ee 73         if (self.side == OrderSide.Sell):
JG 74             if (self.price < other.price):
75                 return True
76             elif (self.price == other.price and
77                   self.timestamp < other.timestamp):
78                 return True
79             else:
80                 return False
5d61a1 81         else:
4497ee 82             if (self.price > other.price):
JG 83                 return True
84             elif (self.price == other.price and
85                   self.timestamp < other.timestamp):
86                 return True
87             else:
88                 return False
5d61a1 89
JG 90     def __gt__(self, other):
91         """Greater than comparator."""
4497ee 92         if (self.side == OrderSide.Sell):
JG 93             if (self.price > other.price):
94                 return True
95             elif (self.price == other.price and
96                   self.timestamp < other.timestamp):
97                 return True
98             else:
99                 return False
5d61a1 100         else:
4497ee 101             if (self.price < other.price):
JG 102                 return True
103             elif (self.price == other.price and
104                   self.timestamp < other.timestamp):
105                 return True
106             else:
107                 return False
5d61a1 108
JG 109     def __le__(self, other):
110         """Less than or equal compatator."""
4497ee 111         if (self.side == OrderSide.Sell):
JG 112             if (self.price <= other.price):
113                 return True
114             elif (self.price == other.price and
115                   self.timestamp <= other.timestamp):
116                 return True
117             else:
118                 return False
5d61a1 119         else:
4497ee 120             if (self.price >= other.price):
JG 121                 return True
122             elif (self.price == other.price and
123                   self.timestamp <= other.timestamp):
124                 return True
125             else:
126                 return False
5d61a1 127
JG 128     def __ge__(self, other):
129         """Greater than or equal comparator."""
4497ee 130         if (self.side == OrderSide.Sell):
JG 131             if (self.price >= other.price):
132                 return True
133             elif (self.price == other.price and
134                   self.timestamp <= other.timestamp):
135                 return True
136             else:
137                 return False
5d61a1 138         else:
4497ee 139             if (self.price <= other.price):
JG 140                 return True
141             elif (self.price == other.price and
142                   self.timestamp <= other.timestamp):
143                 return True
144             else:
145                 return False
5d61a1 146
JG 147     def __eq__(self, other):
148         """Equalty compatator."""
4497ee 149         if (self.price == other.price and self.timestamp == other.timestamp):
5d61a1 150             return True
JG 151         else:
152             return False
153
154     def addVolume(self, volume: int):
155         """Add volume to price level."""
156         self.volume += volume
157
158     def __str__(self):
159         """Turn into str for printing."""
4497ee 160         return ("Price: " + str(self.price) + ", volume: " + str(self.volume) +
JG 161                 ", time: " + str(self.timestamp) + ", id: " + self.id)
5d61a1 162
JG 163
164 class Side:
165     """Side class."""
166
167     levels: [Level]
168
169     def __init__(self):
170         """Initialise class."""
171         self.levels = []
4497ee 172
JG 173     def delete(self, orderId: str):
174         """Delete order from side."""
175         for i in self.levels:
176             if (i.id == orderId):
177                 self.levels.remove(i)
178                 heapq.heapify(self.levels)
5d61a1 179
JG 180
181 class Book:
182     """Book class."""
183
184     bidSide: Side
185     askSide: Side
186     productType: ProductTypeEnum
187     product: str
188     stationId: str
189     unit: str
190     expiry: time.struct_time
191     aggFee: decimal.Decimal
192     pasFee: decimal.Decimal
193     broFee: decimal.Decimal
194
195     def __init__(self, productType: str = "TEST", product: str = "a",
196                  stationId: str = "b", unit: str = "c",
197                  expiry: str = time.strftime("%Y-%m-%d %H:%M%z",
198                                              time.localtime(0)),
199                  aggFee: decimal.Decimal = 1, pasFee: decimal.Decimal = -1,
200                  broFee: decimal.Decimal = 2):
201         """Initialise module."""
202         self.bidSide = Side()
203         self.askSide = Side()
204         if productType == "FUTURE":
205             self.productType = ProductTypeEnum.FUTURE
206         elif productType == "SPREAD":
207             self.productType = ProductTypeEnum.SPREAD
208         elif productType == "CALL":
209             self.productType = ProductTypeEnum.CALL
210         elif productType == "PUT":
211             self.productType = ProductTypeEnum.PUT
212         elif productType == "TEST":
213             self.productType = ProductTypeEnum.TEST
214         else:
215             raise UnknownProductTypeError(productType)
216         self.product = product
217         self.stationId = stationId
218         self.unit = unit
219         self.expiry = time.strptime(expiry, "%Y-%m-%d %H:%M%z")
220         self.aggFee = aggFee
221         self.pasFee = pasFee
222         self.broFee = broFee
223
224     def addOrder(self, order: Order):
225         """Add order to book."""
226         if (order.side == OrderSide.Buy):
227             self.bid(order)
228         else:
229             self.ask(order)
230
231     def ask(self, order: Order):
232         """Add ask to book."""
233         while(len(self.bidSide.levels) > 0 and
4497ee 234               self.bidSide.levels[0].price >= order.price):
JG 235             if (self.bidSide.levels[0].volume > order.remaining_volume):
236                 temp = self.bidSide.levels[0].volume
237                 order.filled_volume += self.bidSide.levels[0].volume
238                 self.bidSide.levels[0].volume -= order.remaining_volume
5d61a1 239                 order.remaining_volume -= temp
JG 240                 break
241             else:
4497ee 242                 order.remaining_volume -= self.bidSide.levels[0].volume
JG 243                 order.filled_volume += self.bidSide.levels[0].volume
5d61a1 244                 heapq.heappop(self.bidSide.levels)
JG 245         if (order.remaining_volume > 0):
4497ee 246             heapq.heappush(self.askSide.levels, Level(order))
5d61a1 247
JG 248     def bid(self, order: Order):
249         """Add bid to book."""
250         while(len(self.askSide.levels) > 0 and
4497ee 251               self.askSide.levels[0].price <= order.price):
JG 252             if (self.askSide.levels[0].volume > order.remaining_volume):
253                 temp = self.askSide.levels[0].volume
254                 order.filled_volume += self.askSide.levels[0].volume
255                 self.askSide.levels[0].volume -= order.remaining_volume
5d61a1 256                 order.remaining_volume -= temp
JG 257                 break
258             else:
4497ee 259                 order.remaining_volume -= self.askSide.levels[0].volume
JG 260                 order.filled_volume -= self.askSide.levels[0].volume
5d61a1 261                 heapq.heappop(self.askSide.levels)
JG 262         if (order.remaining_volume > 0):
4497ee 263             heapq.heappush(self.bidSide.levels, Level(order))
5d61a1 264
JG 265     def printBook(self):
266         """Test print book. Book is a heap so not strictly sorted."""
267         print("Sell side")
4497ee 268         for i in sorted(self.askSide.levels, reverse=True)[-10:]:
JG 269             print(i)
5d61a1 270         print("Buy side")
4497ee 271         for i in sorted(self.bidSide.levels)[:10]:
JG 272             print(str(i))
5d61a1 273
JG 274
4497ee 275 def testBook(orders: int = 10, printBook=True):
5d61a1 276     """Test an example book."""
JG 277     b = Book()
4497ee 278     time = 1
JG 279     for i in range(1, orders):
280         b.addOrder(Order(price=i, side=OrderSide.Buy, volume=10,
281                          timestamp=time, id="a"))
282         time += 1
283     for i in range(orders + 1, 2 * orders):
284         b.addOrder(Order(i, OrderSide.Sell, 10, time, "a"))
285         time += 1
286     if (printBook):
287         b.printBook()
288     return b