async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): """ *required Subscribe to diff channel via web socket, and keep the connection open for incoming messages :param ev_loop: ev_loop to execute this function in :param output: an async queue where the incoming messages are stored """ while True: try: trading_pairs = self._trading_pairs tp_map_mrktid = await self.get_map_marketid() marketsDict = dict(zip(tp_map_mrktid.values(), tp_map_mrktid.keys())) marketIds = [] for tp in trading_pairs: marketIds.append(tp_map_mrktid[tp]) async with websockets.connect(constants.WSS_URL) as ws: ws: websockets.WebSocketClientProtocol = ws subscribe_request: Dict[str, Any] = { "type": "subscribe", "channelId": "order_book", "marketIds": marketIds, } await ws.send(ujson.dumps(subscribe_request)) async for raw_msg in self._inner_messages(ws): msg = ujson.loads(raw_msg) msg_type: str = msg.get("type", None) if msg_type is None: raise ValueError(f"Eterbase Websocket message does not contain a type - {msg}") elif msg_type == "error": raise ValueError(f"Eterbase Websocket received error message - {msg['message']}") elif msg_type == "pong": self.logger().debug("Eterbase websocket received event pong - {msg}") elif msg_type == "ob_snapshot": order_book_message: OrderBookMessage = EterbaseOrderBook.snapshot_message_from_exchange(msg, pd.Timestamp.now("UTC").timestamp()) output.put_nowait(order_book_message) elif msg_type == "ob_update": msg["trading_pair"] = marketsDict[msg["marketId"]] order_book_message: OrderBookMessage = EterbaseOrderBook.diff_message_from_exchange(msg, pd.Timestamp.now("UTC").timestamp()) output.put_nowait(order_book_message) else: raise ValueError(f"Unrecognized Eterbase Websocket message received - {msg}") except asyncio.CancelledError: raise except Exception: self.logger().network( "Unexpected error with WebSocket connection.", exc_info=True, app_warning_msg="Unexpected error with WebSocket connection. Retrying in 30 seconds. " "Check network connection." ) await asyncio.sleep(30.0)
async def get_new_order_book(self, trading_pair: str) -> OrderBook: async with aiohttp.ClientSession() as client: td_map_id: Dict[str, str] = await self.get_map_marketid() snapshot: Dict[str, any] = await self.get_snapshot(client, trading_pair) snapshot_timestamp: float = time.time() snapshot_msg: OrderBookMessage = EterbaseOrderBook.snapshot_message_from_exchange( snapshot, snapshot_timestamp, metadata={"trading_pair": trading_pair, "market_id": td_map_id[trading_pair]} ) order_book: OrderBook = self.order_book_create_function() active_order_tracker: EterbaseActiveOrderTracker = EterbaseActiveOrderTracker() bids, asks = active_order_tracker.convert_snapshot_message_to_order_book_row(snapshot_msg) order_book.apply_snapshot(bids, asks, snapshot_msg.update_id) return order_book
async def listen_for_order_book_snapshots(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): """ *required Fetches order book snapshots for each trading pair, and use them to update the local order book :param ev_loop: ev_loop to execute this function in :param output: an async queue where the incoming messages are stored """ while True: try: trading_pairs: List[str] = self._trading_pairs async with aiohttp.ClientSession() as client: for trading_pair in trading_pairs: try: snapshot: Dict[str, any] = await self.get_snapshot( client, trading_pair) snapshot_timestamp: float = time.time() snapshot_msg: OrderBookMessage = EterbaseOrderBook.snapshot_message_from_exchange( snapshot, snapshot_timestamp, metadata={"product_id": trading_pair}) output.put_nowait(snapshot_msg) self.logger().debug( f"Saved order book snapshot for {trading_pair}" ) # Be careful not to go above API rate limits. await asyncio.sleep(5.0) except asyncio.CancelledError: raise except Exception: self.logger().network( "Unexpected error with WebSocket connection.", exc_info=True, app_warning_msg= "Unexpected error with WebSocket connection. Retrying in 5 seconds. " "Check network connection.") await asyncio.sleep(5.0) this_hour: pd.Timestamp = pd.Timestamp.utcnow().replace( minute=0, second=0, microsecond=0) next_hour: pd.Timestamp = this_hour + pd.Timedelta(hours=1) delta: float = next_hour.timestamp() - time.time() await asyncio.sleep(delta) except asyncio.CancelledError: raise except Exception: self.logger().error("Unexpected error.", exc_info=True) await asyncio.sleep(5.0)
def test_diff_message_not_found(self): test_active_order_tracker = self.order_book_tracker._active_order_trackers[test_trading_pair] test_order_book: OrderBook = EterbaseOrderBook() # receive match message that is not in active orders (should be ignored) match_msg_to_ignore: Dict[str, Any] = { "type": "o_fill", "tradeId": 10, "orderId": "ac928c66-ca53-498f-9c13-a110027a60e8", "timestamp": int(datetime.now().timestamp() * 1000), "marketId": 51, "qty": "5.23512", "price": "400.23", "side": 1 } ignore_msg: EterbaseOrderBookMessage = test_order_book.diff_message_from_exchange(match_msg_to_ignore) open_ob_row: OrderBookRow = test_active_order_tracker.convert_diff_message_to_order_book_row(ignore_msg) self.assertEqual(open_ob_row, ([], []))
def setUpClass(cls): cls.order_book_tracker: EterbaseOrderBookTracker = EterbaseOrderBookTracker( trading_pairs=[test_trading_pair]) cls.order_book_tracker.order_books[ test_trading_pair] = EterbaseOrderBook()
def test_diff_msg_get_added_to_order_book(self): test_active_order_tracker = self.order_book_tracker._active_order_trackers["ETHEUR"] price = "200" order_id = "test_order_id" market_id = 51 size = "1.50" remaining_size = "1.00" # Test open message diff raw_open_message = { "type": "o_placed", "timestamp": datetime.now().timestamp() * 1000, "marketId": market_id, "orderId": order_id, "limitPrice": price, "qty": size, "oType": 2, "side": 1 } open_message = EterbaseOrderBook.diff_message_from_exchange(raw_open_message) self.order_book_tracker._order_book_diff_stream.put_nowait(open_message) self.run_parallel(asyncio.sleep(5)) test_order_book_row = test_active_order_tracker.active_bids[Decimal(price)] self.assertEqual(test_order_book_row[order_id]["remaining_size"], size) # Test match message diff match_size = "0.50" raw_match_message = { "type": "o_fill", "tradeId": 10, "orderId": order_id, "timestamp": datetime.now().timestamp() * 1000, "marketId": market_id, "qty": match_size, "remainingQty": remaining_size, "price": price, "side": 1 } match_message = EterbaseOrderBook.diff_message_from_exchange(raw_match_message) self.order_book_tracker._order_book_diff_stream.put_nowait(match_message) self.run_parallel(asyncio.sleep(5)) test_order_book_row = test_active_order_tracker.active_bids[Decimal(price)] self.assertEqual(Decimal(test_order_book_row[order_id]["remaining_size"]), Decimal(remaining_size)) # Test done message diff raw_done_message = { "type": "o_closed", "timestamp": datetime.now().timestamp() * 1000, "marketId": market_id, "limitPrice": price, "orderId": order_id, "reason": "FILLED", "qty": match_size, "remainingQty": "1.00", "side": 1 } done_message = EterbaseOrderBook.diff_message_from_exchange(raw_done_message) self.order_book_tracker._order_book_diff_stream.put_nowait(done_message) self.run_parallel(asyncio.sleep(5)) self.assertTrue(Decimal(price) not in test_active_order_tracker.active_bids)