def test_get_last_price(self, mock_api, connector_creator_mock): client_config_map = ClientConfigAdapter(ClientConfigMap()) connector = BinanceExchange( client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False) connector._set_trading_pair_symbol_map(bidict({f"{self.binance_ex_trading_pair}": self.trading_pair})) connector_creator_mock.return_value = connector url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: Dict[str, Any] = { # truncated response "symbol": self.binance_ex_trading_pair, "lastPrice": "1", } mock_api.get(regex_url, body=ujson.dumps(mock_response)) result = self.async_run_with_timeout(market_price.get_last_price( exchange="binance", trading_pair=self.trading_pair, )) self.assertEqual(result, Decimal("1.0"))
def setUp(self) -> None: super().setUp() self.log_records = [] self.listening_task = None self.mocking_assistant = NetworkMockingAssistant() client_config_map = ClientConfigAdapter(ClientConfigMap()) self.connector = BinanceExchange(client_config_map=client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False, domain=self.domain) self.data_source = BinanceAPIOrderBookDataSource( trading_pairs=[self.trading_pair], connector=self.connector, api_factory=self.connector._web_assistants_factory, domain=self.domain) self.data_source.logger().setLevel(1) self.data_source.logger().addHandler(self) self.resume_test_event = asyncio.Event() self.connector._set_trading_pair_symbol_map( bidict({self.ex_trading_pair: self.trading_pair}))
def get_binance(ec): exchange = BinanceExchange( binance_api_key=creds.k, binance_api_secret=creds.s, trading_pairs=[ec.pair], ) return exchange
def setUpClass(cls): global MAINNET_RPC_URL cls.clock: Clock = Clock(ClockMode.REALTIME) connector = BinanceExchange(binance_api_key="", binance_api_secret="", trading_pairs=["ETH-USDT", "BTC-USDT"], trading_required=False) cls.market: PaperTradeExchange = PaperTradeExchange( order_book_tracker=OrderBookTracker( data_source=BinanceAPIOrderBookDataSource( trading_pairs=["ETH-USDT", "BTC-USDT"], connector=connector, api_factory=connector._api_factory), trading_pairs=["ETH-USDT", "BTC-USDT"]), target_market=BinanceExchange, exchange_name="binance", ) print( "Initializing PaperTrade execute orders market... this will take about a minute." ) cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() cls.clock.add_iterator(cls.market) cls.stack: contextlib.ExitStack = contextlib.ExitStack() cls._clock = cls.stack.enter_context(cls.clock) cls.ev_loop.run_until_complete(cls.wait_til_ready()) print("Ready.")
def validate_order_creation_request(self, order: InFlightOrder, request_call: RequestCall): request_data = dict(request_call.kwargs["data"]) self.assertEqual(self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset), request_data["symbol"]) self.assertEqual(order.trade_type.name.upper(), request_data["side"]) self.assertEqual(BinanceExchange.binance_order_type(OrderType.LIMIT), request_data["type"]) self.assertEqual(Decimal("100"), Decimal(request_data["quantity"])) self.assertEqual(Decimal("10000"), Decimal(request_data["price"])) self.assertEqual(order.client_order_id, request_data["newClientOrderId"])
def create_exchange_instance(self): client_config_map = ClientConfigAdapter(ClientConfigMap()) return BinanceExchange( client_config_map=client_config_map, binance_api_key="testAPIKey", binance_api_secret="testSecret", trading_pairs=[self.trading_pair], )
def setUpClass(cls): global MAINNET_RPC_URL cls.ev_loop = asyncio.get_event_loop() if API_MOCK_ENABLED: cls.web_app = MockWebServer.get_instance() cls.web_app.add_host_to_mock(cls.base_api_url, ["/api/v1/ping", "/api/v1/time", "/api/v1/ticker/24hr"]) cls.web_app.start() cls.ev_loop.run_until_complete(cls.web_app.wait_til_started()) cls._patcher = mock.patch("aiohttp.client.URL") cls._url_mock = cls._patcher.start() cls._url_mock.side_effect = cls.web_app.reroute_local cls._req_patcher = unittest.mock.patch.object(requests.Session, "request", autospec=True) cls._req_url_mock = cls._req_patcher.start() cls._req_url_mock.side_effect = MockWebServer.reroute_request cls.web_app.update_response("get", cls.base_api_url, "/api/v3/account", FixtureBinance.BALANCES) cls.web_app.update_response("get", cls.base_api_url, "/api/v1/exchangeInfo", FixtureBinance.MARKETS) cls.web_app.update_response("get", cls.base_api_url, "/wapi/v3/tradeFee.html", FixtureBinance.TRADE_FEES) cls.web_app.update_response("post", cls.base_api_url, "/api/v1/userDataStream", FixtureBinance.LISTEN_KEY) cls.web_app.update_response("put", cls.base_api_url, "/api/v1/userDataStream", FixtureBinance.LISTEN_KEY) cls.web_app.update_response("get", cls.base_api_url, "/api/v1/depth", FixtureBinance.LINKETH_SNAP, params={'symbol': 'LINKETH'}) cls.web_app.update_response("get", cls.base_api_url, "/api/v1/depth", FixtureBinance.ZRXETH_SNAP, params={'symbol': 'ZRXETH'}) cls.web_app.update_response("get", cls.base_api_url, "/api/v3/myTrades", {}, params={'symbol': 'ZRXETH'}) cls.web_app.update_response("get", cls.base_api_url, "/api/v3/myTrades", {}, params={'symbol': 'LINKETH'}) ws_base_url = "wss://stream.binance.com:9443/ws" cls._ws_user_url = f"{ws_base_url}/{FixtureBinance.LISTEN_KEY['listenKey']}" MockWebSocketServerFactory.start_new_server(cls._ws_user_url) MockWebSocketServerFactory.start_new_server(f"{ws_base_url}/linketh@depth/zrxeth@depth") cls._ws_patcher = unittest.mock.patch("websockets.connect", autospec=True) cls._ws_mock = cls._ws_patcher.start() cls._ws_mock.side_effect = MockWebSocketServerFactory.reroute_ws_connect cls._t_nonce_patcher = unittest.mock.patch( "hummingbot.connector.exchange.binance.binance_exchange.get_tracking_nonce") cls._t_nonce_mock = cls._t_nonce_patcher.start() cls.current_nonce = 1000000000000000 cls.clock: Clock = Clock(ClockMode.REALTIME) cls.market: BinanceExchange = BinanceExchange(API_KEY, API_SECRET, ["LINK-ETH", "ZRX-ETH"], True) print("Initializing Binance market... this will take about a minute.") cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() cls.clock.add_iterator(cls.market) cls.stack: contextlib.ExitStack = contextlib.ExitStack() cls._clock = cls.stack.enter_context(cls.clock) cls.ev_loop.run_until_complete(cls.wait_til_ready()) print("Ready.")
def _binance_connector_without_private_keys( cls, domain: str) -> 'BinanceExchange': from hummingbot.connector.exchange.binance.binance_exchange import BinanceExchange client_config_map = cls._get_client_config_map() return BinanceExchange(client_config_map=client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False, domain=domain)
def setUp(self) -> None: super().setUp() self.log_records = [] self.listening_task: Optional[asyncio.Task] = None self.mocking_assistant = NetworkMockingAssistant() self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) self.mock_time_provider = MagicMock() self.mock_time_provider.time.return_value = 1000 self.auth = BinanceAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET", time_provider=self.mock_time_provider) self.time_synchronizer = TimeSynchronizer() self.time_synchronizer.add_time_offset_ms_sample(0) client_config_map = ClientConfigAdapter(ClientConfigMap()) self.connector = BinanceExchange( client_config_map=client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False, domain=self.domain) self.connector._web_assistants_factory._auth = self.auth self.data_source = BinanceAPIUserStreamDataSource( auth=self.auth, trading_pairs=[self.trading_pair], connector=self.connector, api_factory=self.connector._web_assistants_factory, domain=self.domain ) self.data_source.logger().setLevel(1) self.data_source.logger().addHandler(self) self.resume_test_event = asyncio.Event() self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair}))
def test_orders_saving_and_restoration(self): config_path: str = "test_config" strategy_name: str = "test_strategy" sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path) order_id: Optional[str] = None recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) recorder.start() try: self.assertEqual(0, len(self.market.tracking_states)) # Try to put limit buy order for 0.02 ETH worth of ZRX, and watch for order creation event. current_bid_price: Decimal = self.market.get_price("LINK-ETH", True) bid_price: Decimal = current_bid_price * Decimal("0.8") quantize_bid_price: Decimal = self.market.quantize_order_price("LINK-ETH", bid_price) amount: Decimal = 1 quantized_amount: Decimal = self.market.quantize_order_amount("LINK-ETH", amount) if API_MOCK_ENABLED: resp = self.order_response(FixtureBinance.OPEN_BUY_ORDER, self.get_current_nonce(), "buy", "LINK-ETH") self.web_app.update_response("post", self.base_api_url, "/api/v3/order", resp) order_id = self.market.buy("LINK-ETH", quantized_amount, OrderType.LIMIT, quantize_bid_price) [order_created_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCreatedEvent)) order_created_event: BuyOrderCreatedEvent = order_created_event self.assertEqual(order_id, order_created_event.order_id) # Verify tracking states self.assertEqual(1, len(self.market.tracking_states)) self.assertEqual(order_id, list(self.market.tracking_states.keys())[0]) # Verify orders from recorder recorded_orders: List[Order] = recorder.get_orders_for_config_and_market(config_path, self.market) self.assertEqual(1, len(recorded_orders)) self.assertEqual(order_id, recorded_orders[0].id) # Verify saved market states saved_market_states: MarketState = recorder.get_market_states(config_path, self.market) self.assertIsNotNone(saved_market_states) self.assertIsInstance(saved_market_states.saved_state, dict) self.assertGreater(len(saved_market_states.saved_state), 0) # Close out the current market and start another market. self.clock.remove_iterator(self.market) for event_tag in self.events: self.market.remove_listener(event_tag, self.market_logger) self.__class__.market: BinanceExchange = BinanceExchange(API_KEY, API_SECRET, ["LINK-ETH", "ZRX-ETH"], True) for event_tag in self.events: self.market.add_listener(event_tag, self.market_logger) recorder.stop() recorder = MarketsRecorder(sql, [self.market], config_path, strategy_name) recorder.start() saved_market_states = recorder.get_market_states(config_path, self.market) self.clock.add_iterator(self.market) self.ev_loop.run_until_complete(self.wait_til_ready()) self.assertEqual(0, len(self.market.limit_orders)) self.assertEqual(0, len(self.market.tracking_states)) self.market.restore_tracking_states(saved_market_states.saved_state) self.assertEqual(1, len(self.market.limit_orders)) self.assertEqual(1, len(self.market.tracking_states)) # Cancel the order and verify that the change is saved. if API_MOCK_ENABLED: resp = self.fixture(FixtureBinance.CANCEL_ORDER, origClientOrderId=order_id, side="BUY") self.web_app.update_response("delete", self.base_api_url, "/api/v3/order", resp, params={'origClientOrderId': order_id}) self.market.cancel("LINK-ETH", order_id) self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) order_id = None self.assertEqual(0, len(self.market.limit_orders)) self.assertEqual(0, len(self.market.tracking_states)) saved_market_states = recorder.get_market_states(config_path, self.market) self.assertEqual(0, len(saved_market_states.saved_state)) finally: if order_id is not None: self.market.cancel("LINK-ETH", order_id) self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent)) recorder.stop() os.unlink(self.db_path)
class BinanceUserStreamDataSourceUnitTests(unittest.TestCase): # the level is required to receive logs from the data source logger level = 0 @classmethod def setUpClass(cls) -> None: super().setUpClass() cls.ev_loop = asyncio.get_event_loop() cls.base_asset = "COINALPHA" cls.quote_asset = "HBOT" cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" cls.ex_trading_pair = cls.base_asset + cls.quote_asset cls.domain = "com" cls.listen_key = "TEST_LISTEN_KEY" def setUp(self) -> None: super().setUp() self.log_records = [] self.listening_task: Optional[asyncio.Task] = None self.mocking_assistant = NetworkMockingAssistant() self.throttler = AsyncThrottler(rate_limits=CONSTANTS.RATE_LIMITS) self.mock_time_provider = MagicMock() self.mock_time_provider.time.return_value = 1000 self.auth = BinanceAuth(api_key="TEST_API_KEY", secret_key="TEST_SECRET", time_provider=self.mock_time_provider) self.time_synchronizer = TimeSynchronizer() self.time_synchronizer.add_time_offset_ms_sample(0) client_config_map = ClientConfigAdapter(ClientConfigMap()) self.connector = BinanceExchange( client_config_map=client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False, domain=self.domain) self.connector._web_assistants_factory._auth = self.auth self.data_source = BinanceAPIUserStreamDataSource( auth=self.auth, trading_pairs=[self.trading_pair], connector=self.connector, api_factory=self.connector._web_assistants_factory, domain=self.domain ) self.data_source.logger().setLevel(1) self.data_source.logger().addHandler(self) self.resume_test_event = asyncio.Event() self.connector._set_trading_pair_symbol_map(bidict({self.ex_trading_pair: self.trading_pair})) def tearDown(self) -> None: self.listening_task and self.listening_task.cancel() super().tearDown() def handle(self, record): self.log_records.append(record) def _is_logged(self, log_level: str, message: str) -> bool: return any(record.levelname == log_level and record.getMessage() == message for record in self.log_records) def _raise_exception(self, exception_class): raise exception_class def _create_exception_and_unlock_test_with_event(self, exception): self.resume_test_event.set() raise exception def _create_return_value_and_unlock_test_with_event(self, value): self.resume_test_event.set() return value def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret def _error_response(self) -> Dict[str, Any]: resp = { "code": "ERROR CODE", "msg": "ERROR MESSAGE" } return resp def _user_update_event(self): # Balance Update resp = { "e": "balanceUpdate", "E": 1573200697110, "a": "BTC", "d": "100.00000000", "T": 1573200697068 } return json.dumps(resp) def _successfully_subscribed_event(self): resp = { "result": None, "id": 1 } return resp @aioresponses() 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()) @aioresponses() 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) @aioresponses() 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}: " f"{self._error_response()}")) self.assertFalse(result) @aioresponses() 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) @patch("hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource" "._ping_listen_key", new_callable=AsyncMock) def test_manage_listen_key_task_loop_keep_alive_failed(self, mock_ping_listen_key): mock_ping_listen_key.side_effect = (lambda *args, **kwargs: self._create_return_value_and_unlock_test_with_event(False)) self.data_source._current_listen_key = self.listen_key # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached self.data_source._last_listen_key_ping_ts = 0 self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue(self._is_logged("ERROR", "Error occurred renewing listen key ...")) self.assertIsNone(self.data_source._current_listen_key) self.assertFalse(self.data_source._listen_key_initialized_event.is_set()) @patch("hummingbot.connector.exchange.binance.binance_api_user_stream_data_source.BinanceAPIUserStreamDataSource." "_ping_listen_key", new_callable=AsyncMock) def test_manage_listen_key_task_loop_keep_alive_successful(self, mock_ping_listen_key): mock_ping_listen_key.side_effect = (lambda *args, **kwargs: self._create_return_value_and_unlock_test_with_event(True)) # Simulate LISTEN_KEY_KEEP_ALIVE_INTERVAL reached self.data_source._current_listen_key = self.listen_key self.data_source._listen_key_initialized_event.set() self.data_source._last_listen_key_ping_ts = 0 self.listening_task = self.ev_loop.create_task(self.data_source._manage_listen_key_task_loop()) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue(self._is_logged("INFO", f"Refreshed listen key {self.listen_key}.")) self.assertGreater(self.data_source._last_listen_key_ping_ts, 0) @aioresponses() @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) 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() @aioresponses() @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) 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()) @aioresponses() @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) def test_listen_for_user_stream_connection_failed(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.side_effect = lambda *arg, **kwars: self._create_exception_and_unlock_test_with_event( Exception("TEST ERROR.")) msg_queue = asyncio.Queue() 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...")) @aioresponses() @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) 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..."))
class BinanceAPIOrderBookDataSourceUnitTests(unittest.TestCase): # logging.Level required to receive logs from the data source logger level = 0 @classmethod def setUpClass(cls) -> None: super().setUpClass() cls.ev_loop = asyncio.get_event_loop() cls.base_asset = "COINALPHA" cls.quote_asset = "HBOT" cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}" cls.ex_trading_pair = cls.base_asset + cls.quote_asset cls.domain = "com" def setUp(self) -> None: super().setUp() self.log_records = [] self.listening_task = None self.mocking_assistant = NetworkMockingAssistant() client_config_map = ClientConfigAdapter(ClientConfigMap()) self.connector = BinanceExchange(client_config_map=client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False, domain=self.domain) self.data_source = BinanceAPIOrderBookDataSource( trading_pairs=[self.trading_pair], connector=self.connector, api_factory=self.connector._web_assistants_factory, domain=self.domain) self.data_source.logger().setLevel(1) self.data_source.logger().addHandler(self) self.resume_test_event = asyncio.Event() self.connector._set_trading_pair_symbol_map( bidict({self.ex_trading_pair: self.trading_pair})) def tearDown(self) -> None: self.listening_task and self.listening_task.cancel() super().tearDown() def handle(self, record): self.log_records.append(record) def _is_logged(self, log_level: str, message: str) -> bool: return any( record.levelname == log_level and record.getMessage() == message for record in self.log_records) def _create_exception_and_unlock_test_with_event(self, exception): self.resume_test_event.set() raise exception def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): ret = self.ev_loop.run_until_complete( asyncio.wait_for(coroutine, timeout)) return ret def _successfully_subscribed_event(self): resp = {"result": None, "id": 1} return resp def _trade_update_event(self): resp = { "e": "trade", "E": 123456789, "s": self.ex_trading_pair, "t": 12345, "p": "0.001", "q": "100", "b": 88, "a": 50, "T": 123456785, "m": True, "M": True } return resp def _order_diff_event(self): resp = { "e": "depthUpdate", "E": 123456789, "s": self.ex_trading_pair, "U": 157, "u": 160, "b": [["0.0024", "10"]], "a": [["0.0026", "100"]] } return resp def _snapshot_response(self): resp = { "lastUpdateId": 1027024, "bids": [["4.00000000", "431.00000000"]], "asks": [["4.00000200", "12.00000000"]] } return resp @aioresponses() def test_get_new_order_book_successful(self, mock_api): url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self._snapshot_response() mock_api.get(regex_url, body=json.dumps(resp)) order_book: OrderBook = self.async_run_with_timeout( self.data_source.get_new_order_book(self.trading_pair)) expected_update_id = resp["lastUpdateId"] self.assertEqual(expected_update_id, order_book.snapshot_uid) bids = list(order_book.bid_entries()) asks = list(order_book.ask_entries()) self.assertEqual(1, len(bids)) self.assertEqual(4, bids[0].price) self.assertEqual(431, bids[0].amount) self.assertEqual(expected_update_id, bids[0].update_id) self.assertEqual(1, len(asks)) self.assertEqual(4.000002, asks[0].price) self.assertEqual(12, asks[0].amount) self.assertEqual(expected_update_id, asks[0].update_id) @aioresponses() def test_get_new_order_book_raises_exception(self, mock_api): url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400) with self.assertRaises(IOError): self.async_run_with_timeout( self.data_source.get_new_order_book(self.trading_pair)) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) def test_listen_for_subscriptions_subscribes_to_trades_and_order_diffs( self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) result_subscribe_trades = {"result": None, "id": 1} result_subscribe_diffs = {"result": None, "id": 2} self.mocking_assistant.add_websocket_aiohttp_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_trades)) self.mocking_assistant.add_websocket_aiohttp_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_diffs)) self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_subscriptions()) self.mocking_assistant.run_until_all_aiohttp_messages_delivered( ws_connect_mock.return_value) sent_subscription_messages = self.mocking_assistant.json_messages_sent_through_websocket( websocket_mock=ws_connect_mock.return_value) self.assertEqual(2, len(sent_subscription_messages)) expected_trade_subscription = { "method": "SUBSCRIBE", "params": [f"{self.ex_trading_pair.lower()}@trade"], "id": 1 } self.assertEqual(expected_trade_subscription, sent_subscription_messages[0]) expected_diff_subscription = { "method": "SUBSCRIBE", "params": [f"{self.ex_trading_pair.lower()}@depth@100ms"], "id": 2 } self.assertEqual(expected_diff_subscription, sent_subscription_messages[1]) self.assertTrue( self._is_logged( "INFO", "Subscribed to public order book and trade channels...")) @patch( "hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep" ) @patch("aiohttp.ClientSession.ws_connect") def test_listen_for_subscriptions_raises_cancel_exception( self, mock_ws, _: AsyncMock): mock_ws.side_effect = asyncio.CancelledError with self.assertRaises(asyncio.CancelledError): self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_subscriptions()) self.async_run_with_timeout(self.listening_task) @patch( "hummingbot.core.data_type.order_book_tracker_data_source.OrderBookTrackerDataSource._sleep" ) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) def test_listen_for_subscriptions_logs_exception_details( self, mock_ws, sleep_mock): mock_ws.side_effect = Exception("TEST ERROR.") sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( asyncio.CancelledError()) self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_subscriptions()) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue( self._is_logged( "ERROR", "Unexpected error occurred when listening to order book streams. Retrying in 5 seconds..." )) def test_subscribe_channels_raises_cancel_exception(self): mock_ws = MagicMock() mock_ws.send.side_effect = asyncio.CancelledError with self.assertRaises(asyncio.CancelledError): self.listening_task = self.ev_loop.create_task( self.data_source._subscribe_channels(mock_ws)) self.async_run_with_timeout(self.listening_task) def test_subscribe_channels_raises_exception_and_logs_error(self): mock_ws = MagicMock() mock_ws.send.side_effect = Exception("Test Error") with self.assertRaises(Exception): self.listening_task = self.ev_loop.create_task( self.data_source._subscribe_channels(mock_ws)) self.async_run_with_timeout(self.listening_task) self.assertTrue( self._is_logged( "ERROR", "Unexpected error occurred subscribing to order book trading and delta streams..." )) def test_listen_for_trades_cancelled_when_listening(self): mock_queue = MagicMock() mock_queue.get.side_effect = asyncio.CancelledError() self.data_source._message_queue[ CONSTANTS.TRADE_EVENT_TYPE] = mock_queue msg_queue: asyncio.Queue = asyncio.Queue() with self.assertRaises(asyncio.CancelledError): self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_trades(self.ev_loop, msg_queue)) self.async_run_with_timeout(self.listening_task) def test_listen_for_trades_logs_exception(self): incomplete_resp = { "m": 1, "i": 2, } mock_queue = AsyncMock() mock_queue.get.side_effect = [ incomplete_resp, asyncio.CancelledError() ] self.data_source._message_queue[ CONSTANTS.TRADE_EVENT_TYPE] = mock_queue msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_trades(self.ev_loop, msg_queue)) try: self.async_run_with_timeout(self.listening_task) except asyncio.CancelledError: pass self.assertTrue( self._is_logged( "ERROR", "Unexpected error when processing public trade updates from exchange" )) def test_listen_for_trades_successful(self): mock_queue = AsyncMock() mock_queue.get.side_effect = [ self._trade_update_event(), asyncio.CancelledError() ] self.data_source._message_queue[ CONSTANTS.TRADE_EVENT_TYPE] = mock_queue msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_trades(self.ev_loop, msg_queue)) msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) self.assertEqual(12345, msg.trade_id) def test_listen_for_order_book_diffs_cancelled(self): mock_queue = AsyncMock() mock_queue.get.side_effect = asyncio.CancelledError() self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue msg_queue: asyncio.Queue = asyncio.Queue() with self.assertRaises(asyncio.CancelledError): self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_diffs( self.ev_loop, msg_queue)) self.async_run_with_timeout(self.listening_task) def test_listen_for_order_book_diffs_logs_exception(self): incomplete_resp = { "m": 1, "i": 2, } mock_queue = AsyncMock() mock_queue.get.side_effect = [ incomplete_resp, asyncio.CancelledError() ] self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_diffs( self.ev_loop, msg_queue)) try: self.async_run_with_timeout(self.listening_task) except asyncio.CancelledError: pass self.assertTrue( self._is_logged( "ERROR", "Unexpected error when processing public order book updates from exchange" )) def test_listen_for_order_book_diffs_successful(self): mock_queue = AsyncMock() diff_event = self._order_diff_event() mock_queue.get.side_effect = [diff_event, asyncio.CancelledError()] self.data_source._message_queue[CONSTANTS.DIFF_EVENT_TYPE] = mock_queue msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_diffs( self.ev_loop, msg_queue)) msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) self.assertEqual(diff_event["u"], msg.update_id) @aioresponses() def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot( self, mock_api): url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, exception=asyncio.CancelledError) with self.assertRaises(asyncio.CancelledError): self.async_run_with_timeout( self.data_source.listen_for_order_book_snapshots( self.ev_loop, asyncio.Queue())) @aioresponses() @patch( "hummingbot.connector.exchange.binance.binance_api_order_book_data_source" ".BinanceAPIOrderBookDataSource._sleep") def test_listen_for_order_book_snapshots_log_exception( self, mock_api, sleep_mock): msg_queue: asyncio.Queue = asyncio.Queue() sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event( asyncio.CancelledError()) url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, exception=Exception) self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, msg_queue)) self.async_run_with_timeout(self.resume_test_event.wait()) self.assertTrue( self._is_logged( "ERROR", f"Unexpected error fetching order book snapshot for {self.trading_pair}." )) @aioresponses() def test_listen_for_order_book_snapshots_successful( self, mock_api, ): msg_queue: asyncio.Queue = asyncio.Queue() url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, body=json.dumps(self._snapshot_response())) self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, msg_queue)) msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get()) self.assertEqual(1027024, msg.update_id)