def test_update_order_fills_request_parameters(self, mock_api): self.exchange._set_current_timestamp(0) self.exchange._last_poll_timestamp = -1 url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = [] mock_api.get(regex_url, body=json.dumps(mock_response)) self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) request = self._all_executed_requests(mock_api, url)[0] self.validate_auth_credentials_present(request) request_params = request.kwargs["params"] self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) self.assertNotIn("startTime", request_params) self.exchange._set_current_timestamp(1640780000) self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) self.exchange._last_trades_poll_binance_timestamp = 10 self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) request = self._all_executed_requests(mock_api, url)[1] self.validate_auth_credentials_present(request) request_params = request.kwargs["params"] self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) self.assertEqual(10 * 1e3, request_params["startTime"])
def test_listen_for_user_stream_iter_message_throws_exception(self, mock_api, mock_ws): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "listenKey": self.listen_key } mock_api.post(regex_url, body=json.dumps(mock_response)) msg_queue: asyncio.Queue = asyncio.Queue() mock_ws.return_value = self.mocking_assistant.create_websocket_mock() mock_ws.return_value.receive.side_effect = (lambda *args, **kwargs: self._create_exception_and_unlock_test_with_event( Exception("TEST ERROR"))) mock_ws.close.return_value = None self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue) ) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds..."))
def test_private_rest_url(self): path_url = "/TEST_PATH" domain = "com" expected_url = CONSTANTS.REST_URL.format( domain) + CONSTANTS.PRIVATE_API_VERSION + path_url self.assertEqual(expected_url, web_utils.private_rest_url(path_url, domain))
def test_get_listen_key_log_exception(self, mock_api): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.post(regex_url, status=400, body=json.dumps(self._error_response())) with self.assertRaises(IOError): self.async_run_with_timeout(self.data_source._get_listen_key())
def test_ping_listen_key_successful(self, mock_api): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, body=json.dumps({})) self.data_source._current_listen_key = self.listen_key result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) self.assertTrue(result)
def configure_http_error_order_status_response( self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=401, callback=callback) return url
def configure_partially_filled_order_status_response( self, order: InFlightOrder, mock_api: aioresponses, callback: Optional[Callable] = lambda *args, **kwargs: None) -> str: url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) response = self._order_status_request_partially_filled_mock_response(order=order) mock_api.get(regex_url, body=json.dumps(response), callback=callback) return url
def test_update_time_synchronizer_raises_cancelled_error(self, mock_api): url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, exception=asyncio.CancelledError) self.assertRaises( asyncio.CancelledError, self.async_run_with_timeout, self.exchange._update_time_synchronizer())
def test_update_order_fills_from_trades_with_repeated_fill_triggers_only_one_event(self, mock_api): self.exchange._set_current_timestamp(1640780000) self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) url = web_utils.private_rest_url(CONSTANTS.MY_TRADES_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) trade_fill_non_tracked_order = { "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), "id": 30000, "orderId": 99999, "orderListId": -1, "price": "4.00000100", "qty": "12.00000000", "quoteQty": "48.000012", "commission": "10.10000000", "commissionAsset": "BNB", "time": 1499865549590, "isBuyer": True, "isMaker": False, "isBestMatch": True } mock_response = [trade_fill_non_tracked_order, trade_fill_non_tracked_order] mock_api.get(regex_url, body=json.dumps(mock_response)) self.exchange.add_exchange_order_ids_from_market_recorder( {str(trade_fill_non_tracked_order["orderId"]): "OID99"}) self.async_run_with_timeout(self.exchange._update_order_fills_from_trades()) request = self._all_executed_requests(mock_api, url)[0] self.validate_auth_credentials_present(request) request_params = request.kwargs["params"] self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) self.assertEqual(1, len(self.order_filled_logger.event_log)) fill_event: OrderFilledEvent = self.order_filled_logger.event_log[0] self.assertEqual(float(trade_fill_non_tracked_order["time"]) * 1e-3, fill_event.timestamp) self.assertEqual("OID99", fill_event.order_id) self.assertEqual(self.trading_pair, fill_event.trading_pair) self.assertEqual(TradeType.BUY, fill_event.trade_type) self.assertEqual(OrderType.LIMIT, fill_event.order_type) self.assertEqual(Decimal(trade_fill_non_tracked_order["price"]), fill_event.price) self.assertEqual(Decimal(trade_fill_non_tracked_order["qty"]), fill_event.amount) self.assertEqual(0.0, fill_event.trade_fee.percent) self.assertEqual([ TokenAmount(trade_fill_non_tracked_order["commissionAsset"], Decimal(trade_fill_non_tracked_order["commission"]))], fill_event.trade_fee.flat_fees) self.assertTrue(self.is_logged( "INFO", f"Recreating missing trade in TradeFill: {trade_fill_non_tracked_order}" ))
def test_ping_listen_key_log_exception(self, mock_api): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.put(regex_url, status=400, body=json.dumps(self._error_response())) self.data_source._current_listen_key = self.listen_key result: bool = self.async_run_with_timeout(self.data_source._ping_listen_key()) self.assertTrue(self._is_logged("WARNING", f"Failed to refresh the listen key {self.listen_key}: {self._error_response()}")) self.assertFalse(result)
def test_get_listen_key_successful(self, mock_api): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "listenKey": self.listen_key } mock_api.post(regex_url, body=json.dumps(mock_response)) result: str = self.async_run_with_timeout(self.data_source._get_listen_key()) self.assertEqual(self.listen_key, result)
def test_update_time_synchronizer_failure_is_logged(self, mock_api): request_sent_event = asyncio.Event() url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) response = {"code": -1121, "msg": "Dummy error"} mock_api.get(regex_url, body=json.dumps(response), callback=lambda *args, **kwargs: request_sent_event.set()) self.async_run_with_timeout(self.exchange._update_time_synchronizer()) self.assertTrue(self.is_logged("NETWORK", "Error getting server time."))
def test_update_time_synchronizer_successfully(self, mock_api, seconds_counter_mock): request_sent_event = asyncio.Event() seconds_counter_mock.side_effect = [0, 0, 0] self.exchange._time_synchronizer.clear_time_offset_ms_samples() url = web_utils.private_rest_url(CONSTANTS.SERVER_TIME_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) response = {"serverTime": 1640000003000} mock_api.get(regex_url, body=json.dumps(response), callback=lambda *args, **kwargs: request_sent_event.set()) self.async_run_with_timeout(self.exchange._update_time_synchronizer()) self.assertEqual(response["serverTime"] * 1e-3, self.exchange._time_synchronizer.time())
def test_listen_for_user_stream_does_not_queue_empty_payload(self, mock_api, mock_ws): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "listenKey": self.listen_key } mock_api.post(regex_url, body=json.dumps(mock_response)) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, "") msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue) ) self.mocking_assistant.run_until_all_aiohttp_messages_delivered(mock_ws.return_value) self.assertEqual(0, msg_queue.qsize())
def test_listen_for_user_stream_get_listen_key_successful_with_user_update_event(self, mock_api, mock_ws): url = web_utils.private_rest_url(path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "listenKey": self.listen_key } mock_api.post(regex_url, body=json.dumps(mock_response)) mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message(mock_ws.return_value, self._user_update_event()) msg_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(msg_queue) ) msg = self.async_run_with_timeout(msg_queue.get()) self.assertEqual(json.loads(self._user_update_event()), msg) mock_ws.return_value.ping.assert_called()
def trading_rules_url(self): url = web_utils.private_rest_url(CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain) return url
def balance_url(self): url = web_utils.private_rest_url(CONSTANTS.ACCOUNTS_PATH_URL, domain=self.exchange._domain) return url
def test_update_order_status_when_failed(self, mock_api): self.exchange._set_current_timestamp(1640780000) self.exchange._last_poll_timestamp = (self.exchange.current_timestamp - self.exchange.UPDATE_ORDER_STATUS_MIN_INTERVAL - 1) self.exchange.start_tracking_order( order_id="OID1", exchange_order_id="100234", trading_pair=self.trading_pair, order_type=OrderType.LIMIT, trade_type=TradeType.BUY, price=Decimal("10000"), amount=Decimal("1"), ) order = self.exchange.in_flight_orders["OID1"] url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) order_status = { "symbol": self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), "orderId": int(order.exchange_order_id), "orderListId": -1, "clientOrderId": order.client_order_id, "price": "10000.0", "origQty": "1.0", "executedQty": "0.0", "cummulativeQuoteQty": "0.0", "status": "REJECTED", "timeInForce": "GTC", "type": "LIMIT", "side": "BUY", "stopPrice": "0.0", "icebergQty": "0.0", "time": 1499827319559, "updateTime": 1499827319559, "isWorking": True, "origQuoteOrderQty": "10000.000000" } mock_response = order_status mock_api.get(regex_url, body=json.dumps(mock_response)) self.async_run_with_timeout(self.exchange._update_order_status()) request = self._all_executed_requests(mock_api, url)[0] self.validate_auth_credentials_present(request) request_params = request.kwargs["params"] self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_params["symbol"]) self.assertEqual(order.client_order_id, request_params["origClientOrderId"]) failure_event: MarketOrderFailureEvent = self.order_failure_logger.event_log[0] self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp) self.assertEqual(order.client_order_id, failure_event.order_id) self.assertEqual(order.order_type, failure_event.order_type) self.assertNotIn(order.client_order_id, self.exchange.in_flight_orders) self.assertTrue( self.is_logged( "INFO", f"Order {order.client_order_id} has failed. Order Update: OrderUpdate(trading_pair='{self.trading_pair}'," f" update_timestamp={order_status['updateTime'] * 1e-3}, new_state={repr(OrderState.FAILED)}, " f"client_order_id='{order.client_order_id}', exchange_order_id='{order.exchange_order_id}', " "misc_updates=None)") )
def order_creation_url(self): url = web_utils.private_rest_url(CONSTANTS.ORDER_PATH_URL, domain=self.exchange._domain) return url
def network_status_url(self): url = web_utils.private_rest_url(CONSTANTS.PING_PATH_URL, domain=self.exchange._domain) return url