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