async def _subscribe_channels(self, ws: WSAssistant): try: symbols = ",".join([await self._connector.exchange_symbol_associated_to_pair(trading_pair=pair) for pair in self._trading_pairs]) trades_payload = { "id": web_utils.next_message_id(), "type": "subscribe", "topic": f"/market/match:{symbols}", "privateChannel": False, "response": False, } subscribe_trade_request: WSJSONRequest = WSJSONRequest(payload=trades_payload) order_book_payload = { "id": web_utils.next_message_id(), "type": "subscribe", "topic": f"/market/level2:{symbols}", "privateChannel": False, "response": False, } subscribe_orderbook_request: WSJSONRequest = WSJSONRequest(payload=order_book_payload) await ws.send(subscribe_trade_request) await ws.send(subscribe_orderbook_request) self._last_ws_message_sent_timestamp = self._time() self.logger().info("Subscribed to public order book and trade channels...") except asyncio.CancelledError: raise except Exception: self.logger().exception("Unexpected error occurred subscribing to order book trading and delta streams...") raise
async def _subscribe_channels(self, ws: WSAssistant): """ Subscribes to order events and balance events. :param ws: the websocket assistant used to connect to the exchange """ try: orders_change_payload = { "id": web_utils.next_message_id(), "type": "subscribe", "topic": "/spotMarket/tradeOrders", "privateChannel": True, "response": False, } subscribe_order_change_request: WSRequest = WSRequest(payload=orders_change_payload) balance_payload = { "id": web_utils.next_message_id(), "type": "subscribe", "topic": "/account/balance", "privateChannel": True, "response": False, } subscribe_balance_request: WSRequest = WSRequest(payload=balance_payload) await ws.send(subscribe_order_change_request) await ws.send(subscribe_balance_request) self.logger().info("Subscribed to private order changes and balance updates channels...") except asyncio.CancelledError: raise except Exception: self.logger().exception("Unexpected error occurred subscribing to user streams...") raise
async def _subscribe_channels(self, ws: WSAssistant): """ Subscribes to the trade events and diff orders events through the provided websocket connection. :param ws: the websocket assistant used to connect to the exchange """ try: symbols = ",".join([ await self.exchange_symbol_associated_to_pair( trading_pair=pair, domain=self._domain, api_factory=self._api_factory, throttler=self._throttler, time_synchronizer=self._time_synchronizer) for pair in self._trading_pairs ]) trades_payload = { "id": web_utils.next_message_id(), "type": "subscribe", "topic": f"/market/match:{symbols}", "privateChannel": False, "response": False, } subscribe_trade_request: WSRequest = WSRequest( payload=trades_payload) order_book_payload = { "id": web_utils.next_message_id(), "type": "subscribe", "topic": f"/market/level2:{symbols}", "privateChannel": False, "response": False, } subscribe_orderbook_request: WSRequest = WSRequest( payload=order_book_payload) await ws.send(subscribe_trade_request) await ws.send(subscribe_orderbook_request) self.logger().info( "Subscribed to public order book and trade channels...") except asyncio.CancelledError: raise except Exception: self.logger().exception( "Unexpected error occurred subscribing to order book trading and delta streams..." ) raise
async def listen_for_subscriptions(self): """ Connects to the trade events and order diffs websocket endpoints and listens to the messages sent by the exchange. Each message is stored in its own queue. """ ws = None while True: try: connection_info = await web_utils.api_request( path=CONSTANTS.PUBLIC_WS_DATA_PATH_URL, api_factory=self._api_factory, throttler=self._throttler, domain=self._domain, method=RESTMethod.POST, ) ws_url = connection_info["data"]["instanceServers"][0][ "endpoint"] ping_interval = int( int(connection_info["data"]["instanceServers"][0] ["pingInterval"]) * 0.8 * 1e-3) token = connection_info["data"]["token"] ws: WSAssistant = await self._api_factory.get_ws_assistant() await ws.connect(ws_url=f"{ws_url}?token={token}", message_timeout=ping_interval) await self._subscribe_channels(ws) self._last_ws_message_sent_timestamp = self._time() while True: try: seconds_until_next_ping = ping_interval - ( self._time() - self._last_ws_message_sent_timestamp) await asyncio.wait_for( self._process_ws_messages(websocket_assistant=ws), timeout=seconds_until_next_ping) except asyncio.TimeoutError: payload = { "id": web_utils.next_message_id(), "type": "ping", } ping_request = WSRequest(payload=payload) self._last_ws_message_sent_timestamp = self._time() await ws.send(request=ping_request) except asyncio.CancelledError: raise except Exception: self.logger().error( "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds...", exc_info=True, ) await self._sleep(5.0) finally: ws and await ws.disconnect()
async def listen_for_user_stream(self, output: asyncio.Queue): """ Connects to the user private channel in the exchange using a websocket connection. With the established connection, listens to all balance events and order updates provided by the exchange and stores them in the output queue :param output: The queue where all received events should be stored """ ws: Optional[WSAssistant] = None while True: try: connection_info = await web_utils.api_request( path=CONSTANTS.PRIVATE_WS_DATA_PATH_URL, api_factory=self._api_factory, throttler=self._throttler, domain=self._domain, method=RESTMethod.POST, is_auth_required=True, ) ws_url = connection_info["data"]["instanceServers"][0]["endpoint"] ping_interval = int(int(connection_info["data"]["instanceServers"][0]["pingInterval"]) * 0.8 * 1e-3) token = connection_info["data"]["token"] ws = await self._get_ws_assistant() await ws.connect(ws_url=f"{ws_url}?token={token}", message_timeout=ping_interval) await ws.ping() # to update last_recv_timestamp await self._subscribe_channels(ws) self._last_ws_message_sent_timestamp = self._time() while True: try: seconds_until_next_ping = ping_interval - (self._time() - self._last_ws_message_sent_timestamp) await asyncio.wait_for(self._process_ws_messages(websocket_assistant=ws, output=output), timeout=seconds_until_next_ping) except asyncio.TimeoutError: payload = { "id": web_utils.next_message_id(), "type": "ping", } ping_request = WSRequest(payload=payload) self._last_ws_message_sent_timestamp = self._time() await ws.send(request=ping_request) except asyncio.CancelledError: raise except Exception: self.logger().exception( "Unexpected error occurred when listening to user streams. Retrying in 5 seconds...") await self._sleep(5.0) finally: ws and await ws.disconnect()
async def _process_websocket_messages(self, websocket_assistant: WSAssistant): while True: try: seconds_until_next_ping = self._ping_interval - (self._time() - self._last_ws_message_sent_timestamp) await asyncio.wait_for(super()._process_websocket_messages(websocket_assistant=websocket_assistant), timeout=seconds_until_next_ping) except asyncio.TimeoutError: payload = { "id": web_utils.next_message_id(), "type": "ping", } ping_request = WSJSONRequest(payload=payload) self._last_ws_message_sent_timestamp = self._time() await websocket_assistant.send(request=ping_request)