From 4497ee326ceb2dfc3fe9d57b7b9ec9e8adc0cf8b Mon Sep 17 00:00:00 2001 From: Joel Grunbaum <joelgrun@gmail.com> Date: Fri, 07 Jan 2022 05:02:19 +0000 Subject: [PATCH] Added book sorting and protocol decyphering --- test.py | 36 +++--- book.py | 177 +++++++++++++++++++--------- protocol.py | 104 ++++++++++++++++- 3 files changed, 233 insertions(+), 84 deletions(-) diff --git a/book.py b/book.py index a506adb..50543ef 100644 --- a/book.py +++ b/book.py @@ -10,8 +10,8 @@ class OrderSide(Enum): """Order side enum.""" - Buy = 0, - Sell = 1 + Buy = "BUY", + Sell = "SELL" class ProductTypeEnum(Enum): @@ -37,14 +37,18 @@ side: OrderSide remaining_volume: int filled_volume: int + timestamp: float + id: str def __init__(self, price: decimal.Decimal(3), side: OrderSide, - volume: int): + volume: int, timestamp: float, id: str): """Initialise class.""" self.price = price self.side = side self.remaining_volume = volume self.filled_volume = 0 + self.timestamp = timestamp + self.id = id class Level: @@ -52,44 +56,97 @@ price: decimal.Decimal volume: int + side: OrderSide + timestamp: float + id: str - def __init__(self, price: decimal.Decimal(3), volume): + def __init__(self, order: Order): """Initialise class.""" - self.priority = int(price) - self.price = price - self.volume = volume + self.price = order.price + self.volume = order.remaining_volume + self.side = order.side + self.timestamp = order.timestamp + self.id = order.id def __lt__(self, other): """Less than comparator.""" - if (self.price < other.price): - return True + if (self.side == OrderSide.Sell): + if (self.price < other.price): + return True + elif (self.price == other.price and + self.timestamp < other.timestamp): + return True + else: + return False else: - return False + if (self.price > other.price): + return True + elif (self.price == other.price and + self.timestamp < other.timestamp): + return True + else: + return False def __gt__(self, other): """Greater than comparator.""" - if (self.price > other.price): - return True + if (self.side == OrderSide.Sell): + if (self.price > other.price): + return True + elif (self.price == other.price and + self.timestamp < other.timestamp): + return True + else: + return False else: - return False + if (self.price < other.price): + return True + elif (self.price == other.price and + self.timestamp < other.timestamp): + return True + else: + return False def __le__(self, other): """Less than or equal compatator.""" - if (self.price <= other.price): - return True + if (self.side == OrderSide.Sell): + if (self.price <= other.price): + return True + elif (self.price == other.price and + self.timestamp <= other.timestamp): + return True + else: + return False else: - return False + if (self.price >= other.price): + return True + elif (self.price == other.price and + self.timestamp <= other.timestamp): + return True + else: + return False def __ge__(self, other): """Greater than or equal comparator.""" - if (self.price >= other.price): - return True + if (self.side == OrderSide.Sell): + if (self.price >= other.price): + return True + elif (self.price == other.price and + self.timestamp <= other.timestamp): + return True + else: + return False else: - return False + if (self.price <= other.price): + return True + elif (self.price == other.price and + self.timestamp <= other.timestamp): + return True + else: + return False def __eq__(self, other): """Equalty compatator.""" - if (self.price == other.price): + if (self.price == other.price and self.timestamp == other.timestamp): return True else: return False @@ -100,7 +157,8 @@ def __str__(self): """Turn into str for printing.""" - return "Price: " + str(self.price) + ", volume: " + str(self.volume) + return ("Price: " + str(self.price) + ", volume: " + str(self.volume) + + ", time: " + str(self.timestamp) + ", id: " + self.id) class Side: @@ -111,6 +169,13 @@ def __init__(self): """Initialise class.""" self.levels = [] + + def delete(self, orderId: str): + """Delete order from side.""" + for i in self.levels: + if (i.id == orderId): + self.levels.remove(i) + heapq.heapify(self.levels) class Book: @@ -166,64 +231,58 @@ def ask(self, order: Order): """Add ask to book.""" while(len(self.bidSide.levels) > 0 and - self.bidSide.levels[0][1].price >= order.price): - if (self.bidSide.levels[0][1].volume > order.remaining_volume): - temp = self.bidSide.levels[0][1].volume - order.filled_volume += self.bidSide.levels[0][1].volume - self.bidSide.levels[0][1].volume -= order.remaining_volume + self.bidSide.levels[0].price >= order.price): + if (self.bidSide.levels[0].volume > order.remaining_volume): + temp = self.bidSide.levels[0].volume + order.filled_volume += self.bidSide.levels[0].volume + self.bidSide.levels[0].volume -= order.remaining_volume order.remaining_volume -= temp break else: - order.remaining_volume -= self.bidSide.levels[0][1].volume - order.filled_volume += self.bidSide.levels[0][1].volume + order.remaining_volume -= self.bidSide.levels[0].volume + order.filled_volume += self.bidSide.levels[0].volume heapq.heappop(self.bidSide.levels) if (order.remaining_volume > 0): - if (len(self.askSide.levels) > 0 and - self.askSide.levels[0][1].price == order.price): - self.askSide.levels[0][1].addVolume(order.remaining_volume) - else: - heapq.heappush(self.askSide.levels, - (order.price, - Level(order.price, order.remaining_volume))) + heapq.heappush(self.askSide.levels, Level(order)) def bid(self, order: Order): """Add bid to book.""" while(len(self.askSide.levels) > 0 and - self.askSide.levels[0][1].price <= order.price): - if (self.askSide.levels[0][1].volume > order.remaining_volume): - temp = self.askSide.levels[0][1].volume - order.filled_volume += self.askSide.levels[0][1].volume - self.askSide.levels[0][1].volume -= order.remaining_volume + self.askSide.levels[0].price <= order.price): + if (self.askSide.levels[0].volume > order.remaining_volume): + temp = self.askSide.levels[0].volume + order.filled_volume += self.askSide.levels[0].volume + self.askSide.levels[0].volume -= order.remaining_volume order.remaining_volume -= temp break else: - order.remaining_volume -= self.askSide.levels[0][1].volume - order.filled_volume -= self.askSide.levels[0][1].volume + order.remaining_volume -= self.askSide.levels[0].volume + order.filled_volume -= self.askSide.levels[0].volume heapq.heappop(self.askSide.levels) if (order.remaining_volume > 0): - if (len(self.bidSide.levels) > 0 and - self.bidSide.levels[0][1].price == order.price): - self.bidSide.levels[0][1].addVolume(order.remaining_volume) - else: - heapq.heappush(self.bidSide.levels, - (100 - order.price, - Level(order.price, order.remaining_volume))) + heapq.heappush(self.bidSide.levels, Level(order)) def printBook(self): """Test print book. Book is a heap so not strictly sorted.""" print("Sell side") - for i in sorted(self.askSide.levels, reverse=True): - print(i[1]) + for i in sorted(self.askSide.levels, reverse=True)[-10:]: + print(i) print("Buy side") - for i in sorted(self.bidSide.levels): - print(str(i[1])) + for i in sorted(self.bidSide.levels)[:10]: + print(str(i)) -def testBook(): +def testBook(orders: int = 10, printBook=True): """Test an example book.""" b = Book() - for i in range(1, 10): - b.addOrder(Order(i, OrderSide.Buy, 10)) - for i in range(11, 20): - b.addOrder(Order(i, OrderSide.Sell, 10)) - b.printBook() + time = 1 + for i in range(1, orders): + b.addOrder(Order(price=i, side=OrderSide.Buy, volume=10, + timestamp=time, id="a")) + time += 1 + for i in range(orders + 1, 2 * orders): + b.addOrder(Order(i, OrderSide.Sell, 10, time, "a")) + time += 1 + if (printBook): + b.printBook() + return b diff --git a/protocol.py b/protocol.py index 9d662cc..c575102 100644 --- a/protocol.py +++ b/protocol.py @@ -1,5 +1,19 @@ """.#protocol.py handling protocol.""" import book +from enum import Enum +import httpx +import json + +test = True + +HOST: str = "sytev070" +if (test): + PORT = "9005" +else: + PORT = "9000" + +USER: str = "jgrunbau" +PASS: str = "b7d630945a0854581d9f86ba147f34a5" def handleMessage(bs: {str, book.Book}, message: {str, str}): @@ -11,7 +25,12 @@ elif (message["type"] == "ADDED"): addOrder(bs, message) elif (message["type"] == "DELETED"): - pass + deleteOrder(bs, message) + elif (message["type"] == "TRADE"): + tradeOrder(bs, message) + elif (message["type"] in {"BROKER_REQUEST", "BROKER_ACK", + "BROKER_CONFIRM"}): + broker(bs, message) def announce(bs: {str, book.Book}, message: {str, str}): @@ -27,14 +46,85 @@ def settle(bs: {str, book.Book}, message: {str, str}): """Settle order.""" - bs.pop(message["product"])#.printBook() + b = bs.pop(message["product"]) + if (False): + b.printBook() + print() + if (len(b.askSide.levels) > 0): + print(b.askSide.levels[0]) + if (len(b.bidSide.levels)): + print(b.bidSide.levels[0]) + print() + def addOrder(bs: {str, book.Book}, message: {str, str}): """Order added to exchange.""" - price = message["price"] - volume = message["resting"] + message["filled"] if (message["side"] == "BUY"): - side = book.OrderSide.Buy + bs[message["product"]].bid( + book.Order(message["price"], book.OrderSide.Buy, + message["resting"], + message["timestamp"], message["id"])) else: - side = book.OrderSide.Sell - bs[message["product"]].addOrder(book.Order(price, side, volume)) + bs[message["product"]].ask( + book.Order(message["price"], book.OrderSide.Sell, + message["resting"], + message["timestamp"], message["id"])) + + +def deleteOrder(bs: {str, book.Book}, message: {str, str}): + """Delete order from market.""" + if (message["side"] == "BUY"): + bs[message["product"]].bidSide.delete(message["id"]) + else: + bs[message["product"]].askSide.delete(message["id"]) + + +def tradeOrder(bs: {str, book.Book}, message: {str, str}): + """Order traded.""" + if (message["tradeType"] == "BUY_AGGRESSOR"): + bs[message["product"]].bidSide.delete(message["passiveOrder"]) + elif (message["tradeType"] == "SELL_AGGRESSOR"): + bs[message["product"]].askSide.delete(message["passiveOrder"]) + + +def broker(bs: {str, book.Book}, message: {str, str}): + """Broker trades.""" + pass + + +def send(message): + """Send message with exchange.""" + return httpx.post(f"http://{HOST}:{PORT}/execution", + data={"message": json.dumps(message), + "username": USER, + "password": PASS}).json() + + +class OrderEnum(Enum): + """Enum for ordering from the exchange.""" + + ADD = 0, + DELETE = 1, + BROKER_REQUEST = 2, + BROKER_CONFIRM = 3 + + +def order(orderType: OrderEnum, product: str, price: float = 0, + side: book.OrderSide = book.OrderSide.Buy, + volume: int = 0, orderId: str = "", counterparty: str = ""): + """Order from exchange.""" + if (orderType == OrderEnum.ADD): + resp = send({"type": "ADD", + "product": product, + "price": price, + "side": side, + "volume": volume}) + elif (orderType == OrderEnum.DELETE): + resp = send({"type": "DELETE", + "product": product, + "id": id}) + elif (orderType == OrderEnum.BROKER_REQUEST): + pass + elif (orderType == OrderEnum.BROKER_CONFIRM): + pass + return resp diff --git a/test.py b/test.py index 788ceff..ae18f62 100644 --- a/test.py +++ b/test.py @@ -1,5 +1,4 @@ """.#test.py main module.""" -import json import time import httpx from whereismysock import all @@ -7,19 +6,16 @@ import book import protocol +test = False + HOST: str = 'sytev070' -PORT: str = '9000' +if (test): + PORT: str = '9005' +else: + PORT: str = '9000' USER: str = 'test' PASS: str = 'none' - - -def send(message): - """Send message to exchange.""" - return httpx.post(f"http://{HOST}:{PORT}/execution", - data={"message": json.dumps(message), - "username": USER, - "password": PASS}).json() def logAllIncomingMessages(): @@ -45,8 +41,6 @@ protocol.handleMessage(bs, message) -message = {"type": "ADD", "product": "F_SOH_APP0104T1700", "price": 99.0, - "side": "BUY", "volume": 1000000} # print(json.dumps(message)) # print(send(message)) # printRecoveryLog() @@ -55,9 +49,15 @@ # wstest() bs = {} -# recoverBook(bs) -# print(list(bs)) -# for i in bs: - # print(bs[i].product) - # bs[i].printBook() -book.testBook() +recoverBook(bs) +print(list(bs)) +for i in bs: + print(bs[i].product) + bs[i].printBook() + print() + if (len(bs[i].askSide.levels) > 0): + print(bs[i].askSide.levels[0]) + if (len(bs[i].bidSide.levels)): + print(bs[i].bidSide.levels[0]) + print() +# book.testBook() -- Gitblit v1.9.3