async def _user_stream_event_listener(self): """ Listens to message in _user_stream_tracker.user_stream queue. """ async for event_message in self._iter_user_event_queue(): try: endpoint = NdaxWebSocketAdaptor.endpoint_from_message( event_message) payload = NdaxWebSocketAdaptor.payload_from_message( event_message) if endpoint == CONSTANTS.ACCOUNT_POSITION_EVENT_ENDPOINT_NAME: self._process_account_position_event(payload) elif endpoint == CONSTANTS.ORDER_STATE_EVENT_ENDPOINT_NAME: self._process_order_event_message(payload) elif endpoint == CONSTANTS.ORDER_TRADE_EVENT_ENDPOINT_NAME: self._process_trade_event_message(payload) else: self.logger().debug( f"Unknown event received from the connector ({event_message})" ) except asyncio.CancelledError: raise except Exception as ex: self.logger().error( f"Unexpected error in user stream listener loop ({ex})", exc_info=True) await asyncio.sleep(5.0)
def test_sending_messages_increment_message_number(self, mock_ws): sent_messages = [] throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() mock_ws.return_value.send_json.side_effect = lambda sent_message: sent_messages.append( sent_message) adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) payload = {} self.async_run_with_timeout( adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, payload=payload, limit_id=CONSTANTS.WS_PING_ID)) self.async_run_with_timeout( adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, payload=payload, limit_id=CONSTANTS.WS_PING_ID)) self.async_run_with_timeout( adaptor.send_request(endpoint_name=CONSTANTS.WS_ORDER_BOOK_CHANNEL, payload=payload)) self.assertEqual(3, len(sent_messages)) message = sent_messages[0] self.assertEqual(1, message.get('i')) message = sent_messages[1] self.assertEqual(2, message.get('i')) message = sent_messages[2] self.assertEqual(3, message.get('i'))
def test_close(self): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) ws = AsyncMock() adaptor = NdaxWebSocketAdaptor(throttler, websocket=ws) asyncio.get_event_loop().run_until_complete(adaptor.close()) self.assertEquals(1, ws.close.await_count)
def test_close(self, mock_ws): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) self.async_run_with_timeout(adaptor.close()) self.assertEquals(1, mock_ws.return_value.close.await_count)
def test_receive_message(self): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) ws = AsyncMock() ws.recv.return_value = 'test message' adaptor = NdaxWebSocketAdaptor(throttler, websocket=ws) received_message = asyncio.get_event_loop().run_until_complete(adaptor.recv()) self.assertEqual('test message', received_message)
def test_close(self, mock_ws): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) asyncio.get_event_loop().run_until_complete(adaptor.close()) self.assertEquals(1, mock_ws.return_value.close.await_count)
def test_receive_message(self, mock_ws): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message( mock_ws.return_value, 'test message') adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) received_message = self.async_run_with_timeout(adaptor.receive()) self.assertEqual('test message', received_message.data)
def test_get_endpoint_from_raw_received_message(self, mock_ws): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() payload = {"Key1": True, "Key2": "Value2"} message = {"m": 1, "i": 1, "n": "Endpoint", "o": json.dumps(payload)} raw_message = json.dumps(message) adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) extracted_endpoint = adaptor.endpoint_from_raw_message( raw_message=raw_message) self.assertEqual("Endpoint", extracted_endpoint)
def test_listening_process_authenticates_and_subscribes_to_events( self, ws_connect_mock): messages = asyncio.Queue() ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) initial_last_recv_time = self.data_source.last_recv_time self.listening_task = asyncio.get_event_loop().create_task( self.data_source.listen_for_user_stream(asyncio.get_event_loop(), messages)) # Add the authentication response for the websocket self.mocking_assistant.add_websocket_aiohttp_message( ws_connect_mock.return_value, self._authentication_response(True)) # Add a dummy message for the websocket to read and include in the "messages" queue self.mocking_assistant.add_websocket_aiohttp_message( ws_connect_mock.return_value, json.dumps('dummyMessage')) first_received_message = asyncio.get_event_loop().run_until_complete( messages.get()) self.assertEqual('dummyMessage', first_received_message) self.assertTrue( self._is_logged('INFO', "Authenticating to User Stream...")) self.assertTrue( self._is_logged('INFO', "Successfully authenticated to User Stream.")) self.assertTrue( self._is_logged('INFO', "Successfully subscribed to user events.")) sent_messages = self.mocking_assistant.json_messages_sent_through_websocket( ws_connect_mock.return_value) self.assertEqual(2, len(sent_messages)) authentication_request = sent_messages[0] subscription_request = sent_messages[1] self.assertEqual( CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, NdaxWebSocketAdaptor.endpoint_from_raw_message( json.dumps(authentication_request))) self.assertEqual( CONSTANTS.SUBSCRIBE_ACCOUNT_EVENTS_ENDPOINT_NAME, NdaxWebSocketAdaptor.endpoint_from_raw_message( json.dumps(subscription_request))) subscription_payload = NdaxWebSocketAdaptor.payload_from_raw_message( json.dumps(subscription_request)) expected_payload = {"AccountId": self.account_id, "OMSId": self.oms_id} self.assertEqual(expected_payload, subscription_payload) self.assertGreater(self.data_source.last_recv_time, initial_last_recv_time)
def test_get_endpoint_from_raw_received_message(self): throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) ws = AsyncMock() payload = {"Key1": True, "Key2": "Value2"} message = {"m": 1, "i": 1, "n": "Endpoint", "o": json.dumps(payload)} raw_message = json.dumps(message) adaptor = NdaxWebSocketAdaptor(throttler, websocket=ws) extracted_endpoint = adaptor.endpoint_from_raw_message(raw_message=raw_message) self.assertEqual("Endpoint", extracted_endpoint)
async def _authenticate(self, ws: NdaxWebSocketAdaptor): """ Authenticates user to websocket """ try: auth_payload: Dict[ str, Any] = self._auth_assistant.get_ws_auth_payload() async with self._throttler.execute_task( CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME): await ws.send_request( CONSTANTS.AUTHENTICATE_USER_ENDPOINT_NAME, auth_payload) auth_resp = await ws.receive() auth_payload: Dict[str, Any] = ws.payload_from_raw_message( auth_resp.data) if not auth_payload["Authenticated"]: self.logger().error(f"Response: {auth_payload}", exc_info=True) raise Exception( "Could not authenticate websocket connection with NDAX") auth_user = auth_payload.get("User") self._account_id = auth_user.get("AccountId") self._oms_id = auth_user.get("OMSId") except asyncio.CancelledError: raise except Exception as ex: self.logger().error( f"Error occurred when authenticating to user stream ({ex})", exc_info=True) raise
def test_request_message_structure(self): sent_messages = [] throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) ws = AsyncMock() ws.send.side_effect = lambda sent_message: sent_messages.append(sent_message) adaptor = NdaxWebSocketAdaptor(throttler, websocket=ws) payload = {"TestElement1": "Value1", "TestElement2": "Value2"} asyncio.get_event_loop().run_until_complete(adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, payload=payload, limit_id=CONSTANTS.WS_PING_ID)) self.assertEqual(1, len(sent_messages)) message = json.loads(sent_messages[0]) self.assertEqual(0, message.get('m')) self.assertEqual(1, message.get('i')) self.assertEqual(CONSTANTS.WS_PING_REQUEST, message.get('n')) message_payload = json.loads(message.get('o')) self.assertEqual(payload, message_payload)
async def _create_websocket_connection(self) -> NdaxWebSocketAdaptor: """ Initialize WebSocket client for UserStreamDataSource """ try: ws = await websockets.connect(ndax_utils.wss_url(self._domain)) return NdaxWebSocketAdaptor(throttler=self._throttler, websocket=ws) except asyncio.CancelledError: raise except Exception as ex: self.logger().network(f"Unexpected error occurred during {CONSTANTS.EXCHANGE_NAME} WebSocket Connection " f"({ex})") raise
def test_request_message_structure(self, mock_ws): sent_messages = [] throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() mock_ws.return_value.send_json.side_effect = lambda sent_message: sent_messages.append( sent_message) adaptor = NdaxWebSocketAdaptor(throttler, websocket=mock_ws.return_value) payload = {"TestElement1": "Value1", "TestElement2": "Value2"} self.async_run_with_timeout( adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, payload=payload, limit_id=CONSTANTS.WS_PING_ID)) self.assertEqual(1, len(sent_messages)) message = sent_messages[0] self.assertEqual(0, message.get('m')) self.assertEqual(1, message.get('i')) self.assertEqual(CONSTANTS.WS_PING_REQUEST, message.get('n')) message_payload = json.loads(message.get('o')) self.assertEqual(payload, message_payload)
def test_sending_messages_increment_message_number(self): sent_messages = [] throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) ws = AsyncMock() ws.send.side_effect = lambda sent_message: sent_messages.append(sent_message) adaptor = NdaxWebSocketAdaptor(throttler, websocket=ws) payload = {} asyncio.get_event_loop().run_until_complete(adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, payload=payload, limit_id=CONSTANTS.WS_PING_ID)) asyncio.get_event_loop().run_until_complete(adaptor.send_request(endpoint_name=CONSTANTS.WS_PING_REQUEST, payload=payload, limit_id=CONSTANTS.WS_PING_ID)) asyncio.get_event_loop().run_until_complete(adaptor.send_request(endpoint_name=CONSTANTS.WS_ORDER_BOOK_CHANNEL, payload=payload)) self.assertEqual(3, len(sent_messages)) message = json.loads(sent_messages[0]) self.assertEqual(1, message.get('i')) message = json.loads(sent_messages[1]) self.assertEqual(2, message.get('i')) message = json.loads(sent_messages[2]) self.assertEqual(3, message.get('i'))
async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop, output: asyncio.Queue): """ Listen for orderbook diffs using WebSocket API. """ if not len(self._trading_pair_id_map) > 0: await self.init_trading_pair_ids(self._domain, self._throttler, self._shared_client) while True: try: ws_adaptor: NdaxWebSocketAdaptor = await self._create_websocket_connection( ) for trading_pair in self._trading_pairs: payload = { "OMSId": 1, "Symbol": convert_to_exchange_trading_pair(trading_pair), "Depth": 200 } async with self._throttler.execute_task( CONSTANTS.WS_ORDER_BOOK_CHANNEL): await ws_adaptor.send_request( endpoint_name=CONSTANTS.WS_ORDER_BOOK_CHANNEL, payload=payload) async for raw_msg in ws_adaptor.iter_messages(): payload = NdaxWebSocketAdaptor.payload_from_raw_message( raw_msg) msg_event: str = NdaxWebSocketAdaptor.endpoint_from_raw_message( raw_msg) if msg_event in [ CONSTANTS.WS_ORDER_BOOK_CHANNEL, CONSTANTS.WS_ORDER_BOOK_L2_UPDATE_EVENT ]: msg_data: List[NdaxOrderBookEntry] = [ NdaxOrderBookEntry(*entry) for entry in payload ] msg_timestamp: int = int(time.time() * 1e3) msg_product_code: int = msg_data[0].productPairCode content = {"data": msg_data} msg_trading_pair: Optional[str] = None for trading_pair, instrument_id in self._trading_pair_id_map.items( ): if msg_product_code == instrument_id: msg_trading_pair = trading_pair break if msg_trading_pair: metadata = { "trading_pair": msg_trading_pair, "instrument_id": msg_product_code, } order_book_message = None if msg_event == CONSTANTS.WS_ORDER_BOOK_CHANNEL: order_book_message: NdaxOrderBookMessage = NdaxOrderBook.snapshot_message_from_exchange( msg=content, timestamp=msg_timestamp, metadata=metadata) elif msg_event == CONSTANTS.WS_ORDER_BOOK_L2_UPDATE_EVENT: order_book_message: NdaxOrderBookMessage = NdaxOrderBook.diff_message_from_exchange( msg=content, timestamp=msg_timestamp, metadata=metadata) self._last_traded_prices[ order_book_message. trading_pair] = order_book_message.last_traded_price await output.put(order_book_message) 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.") if ws_adaptor: await ws_adaptor.close() await self._sleep(30.0)