Joel Grunbaum
2022-01-06 5d61a1e5c5942901cc02f87b664ad6304cf79213
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
13     Buy = 0,
14     Sell = 1
15
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
40
41     def __init__(self, price: decimal.Decimal(3), side: OrderSide,
42                  volume: int):
43         """Initialise class."""
44         self.price = price
45         self.side = side
46         self.remaining_volume = volume
47         self.filled_volume = 0
48
49
50 class Level:
51     """Level class."""
52
53     price: decimal.Decimal
54     volume: int
55
56     def __init__(self, price: decimal.Decimal(3), volume):
57         """Initialise class."""
58         self.priority = int(price)
59         self.price = price
60         self.volume = volume
61
62     def __lt__(self, other):
63         """Less than comparator."""
64         if (self.price < other.price):
65             return True
66         else:
67             return False
68
69     def __gt__(self, other):
70         """Greater than comparator."""
71         if (self.price > other.price):
72             return True
73         else:
74             return False
75
76     def __le__(self, other):
77         """Less than or equal compatator."""
78         if (self.price <= other.price):
79             return True
80         else:
81             return False
82
83     def __ge__(self, other):
84         """Greater than or equal comparator."""
85         if (self.price >= other.price):
86             return True
87         else:
88             return False
89
90     def __eq__(self, other):
91         """Equalty compatator."""
92         if (self.price == other.price):
93             return True
94         else:
95             return False
96
97     def addVolume(self, volume: int):
98         """Add volume to price level."""
99         self.volume += volume
100
101     def __str__(self):
102         """Turn into str for printing."""
103         return "Price: " + str(self.price) + ", volume: " + str(self.volume)
104
105
106 class Side:
107     """Side class."""
108
109     levels: [Level]
110
111     def __init__(self):
112         """Initialise class."""
113         self.levels = []
114
115
116 class Book:
117     """Book class."""
118
119     bidSide: Side
120     askSide: Side
121     productType: ProductTypeEnum
122     product: str
123     stationId: str
124     unit: str
125     expiry: time.struct_time
126     aggFee: decimal.Decimal
127     pasFee: decimal.Decimal
128     broFee: decimal.Decimal
129
130     def __init__(self, productType: str = "TEST", product: str = "a",
131                  stationId: str = "b", unit: str = "c",
132                  expiry: str = time.strftime("%Y-%m-%d %H:%M%z",
133                                              time.localtime(0)),
134                  aggFee: decimal.Decimal = 1, pasFee: decimal.Decimal = -1,
135                  broFee: decimal.Decimal = 2):
136         """Initialise module."""
137         self.bidSide = Side()
138         self.askSide = Side()
139         if productType == "FUTURE":
140             self.productType = ProductTypeEnum.FUTURE
141         elif productType == "SPREAD":
142             self.productType = ProductTypeEnum.SPREAD
143         elif productType == "CALL":
144             self.productType = ProductTypeEnum.CALL
145         elif productType == "PUT":
146             self.productType = ProductTypeEnum.PUT
147         elif productType == "TEST":
148             self.productType = ProductTypeEnum.TEST
149         else:
150             raise UnknownProductTypeError(productType)
151         self.product = product
152         self.stationId = stationId
153         self.unit = unit
154         self.expiry = time.strptime(expiry, "%Y-%m-%d %H:%M%z")
155         self.aggFee = aggFee
156         self.pasFee = pasFee
157         self.broFee = broFee
158
159     def addOrder(self, order: Order):
160         """Add order to book."""
161         if (order.side == OrderSide.Buy):
162             self.bid(order)
163         else:
164             self.ask(order)
165
166     def ask(self, order: Order):
167         """Add ask to book."""
168         while(len(self.bidSide.levels) > 0 and
169               self.bidSide.levels[0][1].price >= order.price):
170             if (self.bidSide.levels[0][1].volume > order.remaining_volume):
171                 temp = self.bidSide.levels[0][1].volume
172                 order.filled_volume += self.bidSide.levels[0][1].volume
173                 self.bidSide.levels[0][1].volume -= order.remaining_volume
174                 order.remaining_volume -= temp
175                 break
176             else:
177                 order.remaining_volume -= self.bidSide.levels[0][1].volume
178                 order.filled_volume += self.bidSide.levels[0][1].volume
179                 heapq.heappop(self.bidSide.levels)
180         if (order.remaining_volume > 0):
181             if (len(self.askSide.levels) > 0 and
182                     self.askSide.levels[0][1].price == order.price):
183                 self.askSide.levels[0][1].addVolume(order.remaining_volume)
184             else:
185                 heapq.heappush(self.askSide.levels,
186                                (order.price,
187                                 Level(order.price, order.remaining_volume)))
188
189     def bid(self, order: Order):
190         """Add bid to book."""
191         while(len(self.askSide.levels) > 0 and
192               self.askSide.levels[0][1].price <= order.price):
193             if (self.askSide.levels[0][1].volume > order.remaining_volume):
194                 temp = self.askSide.levels[0][1].volume
195                 order.filled_volume += self.askSide.levels[0][1].volume
196                 self.askSide.levels[0][1].volume -= order.remaining_volume
197                 order.remaining_volume -= temp
198                 break
199             else:
200                 order.remaining_volume -= self.askSide.levels[0][1].volume
201                 order.filled_volume -= self.askSide.levels[0][1].volume
202                 heapq.heappop(self.askSide.levels)
203         if (order.remaining_volume > 0):
204             if (len(self.bidSide.levels) > 0 and
205                     self.bidSide.levels[0][1].price == order.price):
206                 self.bidSide.levels[0][1].addVolume(order.remaining_volume)
207             else:
208                 heapq.heappush(self.bidSide.levels,
209                                (100 - order.price,
210                                 Level(order.price, order.remaining_volume)))
211
212     def printBook(self):
213         """Test print book. Book is a heap so not strictly sorted."""
214         print("Sell side")
215         for i in sorted(self.askSide.levels, reverse=True):
216             print(i[1])
217         print("Buy side")
218         for i in sorted(self.bidSide.levels):
219             print(str(i[1]))
220
221
222 def testBook():
223     """Test an example book."""
224     b = Book()
225     for i in range(1, 10):
226         b.addOrder(Order(i, OrderSide.Buy, 10))
227     for i in range(11, 20):
228         b.addOrder(Order(i, OrderSide.Sell, 10))
229     b.printBook()