def setUp(self) -> None: super().setUp() self.log_records = [] self.listening_task: Optional[asyncio.Task] = None self.mocking_assistant = NetworkMockingAssistant() self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) self.mock_time_provider = MagicMock() self.mock_time_provider.time.return_value = 1000 self.auth = GateIoAuth(api_key=self.api_key, secret_key=self.api_secret_key, 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 = GateIoExchange(client_config_map=client_config_map, gate_io_api_key="", gate_io_secret_key="", trading_pairs=[], trading_required=False) self.connector._web_assistants_factory._auth = self.auth self.data_source = GateIoAPIUserStreamDataSource( self.auth, trading_pairs=[self.trading_pair], connector=self.connector, api_factory=self.connector._web_assistants_factory) self.data_source.logger().setLevel(1) self.data_source.logger().addHandler(self) self.connector._set_trading_pair_symbol_map( bidict({self.ex_trading_pair: self.trading_pair}))
def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource: return GateIoAPIUserStreamDataSource( auth=self._auth, trading_pairs=self._trading_pairs, connector=self, api_factory=self._web_assistants_factory, domain=self.domain, )
def data_source(self) -> UserStreamTrackerDataSource: """ *required Initializes a user stream data source (user specific order diffs from live socket stream) :return: OrderBookTrackerDataSource """ if not self._data_source: self._data_source = GateIoAPIUserStreamDataSource( gate_io_auth=self._gate_io_auth, trading_pairs=self._trading_pairs) return self._data_source
def __init__(self, gate_io_auth: Optional[GateIoAuth] = None, trading_pairs: Optional[List[str]] = None, api_factory: Optional[WebAssistantsFactory] = None): self._api_factory = api_factory self._gate_io_auth: GateIoAuth = gate_io_auth self._trading_pairs: List[str] = trading_pairs or [] super().__init__(data_source=GateIoAPIUserStreamDataSource( gate_io_auth=self._gate_io_auth, trading_pairs=self._trading_pairs, api_factory=self._api_factory, ))
def setUp(self) -> None: super().setUp() self.mocking_assistant = NetworkMockingAssistant() gate_io_auth = GateIoAuth(api_key="someKey", secret_key="someSecret") self.data_source = GateIoAPIUserStreamDataSource(gate_io_auth, trading_pairs=[self.trading_pair])
class TestGateIoAPIUserStreamDataSource(unittest.TestCase): @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}" def setUp(self) -> None: super().setUp() self.mocking_assistant = NetworkMockingAssistant() gate_io_auth = GateIoAuth(api_key="someKey", secret_key="someSecret") self.data_source = GateIoAPIUserStreamDataSource(gate_io_auth, trading_pairs=[self.trading_pair]) def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret def get_user_trades_mock(self) -> Dict: user_trades = { "time": 1637764970, "channel": "spot.usertrades", "event": "update", "result": [ { "id": 2217816329, "user_id": 5774224, "order_id": "96780687179", "currency_pair": "ETH_USDT", "create_time": 1637764970, "create_time_ms": "1637764970928.48", "side": "buy", "amount": "0.005", "role": "maker", "price": "4191.1", "fee": "0.000009", "fee_currency": "ETH", "point_fee": "0", "gt_fee": "0", "text": "t-HBOT-B-EHUT1637764969004024", } ], } return user_trades def get_user_orders_mock(self) -> Dict: user_orders = { "time": 1605175506, "channel": "spot.orders", "event": "update", "result": [ { "id": "30784435", "user": 123456, "text": "t-abc", "create_time": "1605175506", "create_time_ms": "1605175506123", "update_time": "1605175506", "update_time_ms": "1605175506123", "event": "put", "currency_pair": f"{self.base_asset}_{self.quote_asset}", "type": "limit", "account": "spot", "side": "sell", "amount": "1", "price": "10001", "time_in_force": "gtc", "left": "1", "filled_total": "0", "fee": "0", "fee_currency": "USDT", "point_fee": "0", "gt_fee": "0", "gt_discount": True, "rebated_fee": "0", "rebated_fee_currency": "USDT", } ], } return user_orders def get_user_balance_mock(self) -> Dict: user_balance = { "time": 1605248616, "channel": "spot.balances", "event": "update", "result": [ { "timestamp": "1605248616", "timestamp_ms": "1605248616123", "user": "******", "currency": self.base_asset, "change": "100", "total": "1032951.325075926", "available": "1022943.325075926", } ], } return user_balance @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) def test_listen_for_user_stream_user_trades(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() output_queue = asyncio.Queue() self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, output_queue)) resp = self.get_user_trades_mock() self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(resp)) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertEqual(ret, resp) resp = self.get_user_orders_mock() self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(resp)) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertEqual(ret, resp) resp = self.get_user_balance_mock() self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(resp)) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertEqual(ret, resp) @patch("aiohttp.client.ClientSession.ws_connect", new_callable=AsyncMock) def test_listen_for_user_stream_skips_subscribe_unsubscribe_messages_updates_last_recv_time(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() resp = {"time": 1632223851, "channel": "spot.usertrades", "event": "subscribe", "result": {"status": "success"}} self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(resp)) resp = { "time": 1632223851, "channel": "spot.usertrades", "event": "unsubscribe", "result": {"status": "success"}, } self.mocking_assistant.add_websocket_aiohttp_message(ws_connect_mock.return_value, json.dumps(resp)) output_queue = asyncio.Queue() self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, output_queue)) self.mocking_assistant.run_until_all_aiohttp_messages_delivered(ws_connect_mock.return_value) self.assertTrue(output_queue.empty()) np.testing.assert_allclose([time.time()], self.data_source.last_recv_time, rtol=1)
class TestGateIoAPIOrderBookDataSource(unittest.TestCase): @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}" def setUp(self) -> None: super().setUp() self.mocking_assistant = NetworkMockingAssistant() gate_io_auth = GateIoAuth(api_key="someKey", secret_key="someSecret") self.data_source = GateIoAPIUserStreamDataSource(gate_io_auth, trading_pairs=[self.trading_pair]) def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret def get_user_trades_mock(self) -> Dict: user_trades = { "time": 1605176741, "channel": "spot.usertrades", "event": "update", "result": [ { "id": 5736713, "user_id": 1000001, "order_id": "30784428", "currency_pair": f"{self.base_asset}_{self.quote_asset}", "create_time": 1605176741, "create_time_ms": "1605176741123.456", "side": "sell", "amount": "1.00000000", "role": "taker", "price": "10000.00000000", "fee": "0.00200000000000", "point_fee": "0", "gt_fee": "0", "text": "apiv4" } ] } return user_trades def get_user_orders_mock(self) -> Dict: user_orders = { "time": 1605175506, "channel": "spot.orders", "event": "update", "result": [ { "id": "30784435", "user": 123456, "text": "t-abc", "create_time": "1605175506", "create_time_ms": "1605175506123", "update_time": "1605175506", "update_time_ms": "1605175506123", "event": "put", "currency_pair": f"{self.base_asset}_{self.quote_asset}", "type": "limit", "account": "spot", "side": "sell", "amount": "1", "price": "10001", "time_in_force": "gtc", "left": "1", "filled_total": "0", "fee": "0", "fee_currency": "USDT", "point_fee": "0", "gt_fee": "0", "gt_discount": True, "rebated_fee": "0", "rebated_fee_currency": "USDT" } ] } return user_orders def get_user_balance_mock(self) -> Dict: user_balance = { "time": 1605248616, "channel": "spot.balances", "event": "update", "result": [ { "timestamp": "1605248616", "timestamp_ms": "1605248616123", "user": "******", "currency": self.base_asset, "change": "100", "total": "1032951.325075926", "available": "1022943.325075926" } ] } return user_balance @patch("websockets.connect", new_callable=AsyncMock) def test_listen_for_user_stream(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() output_queue = asyncio.Queue() self.ev_loop.create_task(self.data_source.listen_for_user_stream(self.ev_loop, output_queue)) resp = self.get_user_trades_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp) ) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertEqual(ret, resp) resp = self.get_user_orders_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp) ) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertEqual(ret, resp) resp = self.get_user_balance_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp) ) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertEqual(ret, resp)
class TestGateIoAPIUserStreamDataSource(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 = f"{cls.base_asset}_{cls.quote_asset}" cls.api_key = "someKey" cls.api_secret_key = "someSecretKey" def setUp(self) -> None: super().setUp() self.log_records = [] self.listening_task: Optional[asyncio.Task] = None self.mocking_assistant = NetworkMockingAssistant() self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) self.mock_time_provider = MagicMock() self.mock_time_provider.time.return_value = 1000 self.auth = GateIoAuth(api_key=self.api_key, secret_key=self.api_secret_key, 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 = GateIoExchange(client_config_map=client_config_map, gate_io_api_key="", gate_io_secret_key="", trading_pairs=[], trading_required=False) self.connector._web_assistants_factory._auth = self.auth self.data_source = GateIoAPIUserStreamDataSource( self.auth, trading_pairs=[self.trading_pair], connector=self.connector, api_factory=self.connector._web_assistants_factory) self.data_source.logger().setLevel(1) self.data_source.logger().addHandler(self) 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 async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): ret = self.ev_loop.run_until_complete( asyncio.wait_for(coroutine, timeout)) return ret @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) @patch( "hummingbot.connector.exchange.gate_io.gate_io_api_user_stream_data_source.GateIoAPIUserStreamDataSource" "._time") def test_listen_for_user_stream_subscribes_to_orders_and_balances_events( self, time_mock, ws_connect_mock): time_mock.return_value = 1000 ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) result_subscribe_orders = { "time": 1611541000, "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, "event": "subscribe", "error": None, "result": { "status": "success" } } result_subscribe_trades = { "time": 1611541000, "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, "event": "subscribe", "error": None, "result": { "status": "success" } } result_subscribe_balance = { "time": 1611541000, "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, "event": "subscribe", "error": None, "result": { "status": "success" } } self.mocking_assistant.add_websocket_aiohttp_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_orders)) 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_balance)) output_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(output=output_queue)) 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(3, len(sent_subscription_messages)) expected_orders_subscription = { "time": int(self.mock_time_provider.time()), "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, "event": "subscribe", "payload": [self.ex_trading_pair], "auth": { "KEY": self.api_key, "SIGN": '005d2e6996fa7783459453d36ff871d8d5cfe225a098f37ac234543811c79e3c' # noqa: mock 'db8f41684f3ad9491f65c15ed880ce7baee81f402eb1df56b1bba188c0e7838c', # noqa: mock "method": "api_key" }, } self.assertEqual(expected_orders_subscription, sent_subscription_messages[0]) expected_trades_subscription = { "time": int(self.mock_time_provider.time()), "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, "event": "subscribe", "payload": [self.ex_trading_pair], "auth": { "KEY": self.api_key, "SIGN": '0f34bf79558905d2b5bc7790febf1099d38ff1aa39525a077db32bcbf9135268' # noqa: mock 'caf23cdf2d62315841500962f788f7c5f4c3f4b8a057b2184366687b1f74af69', # noqa: mock "method": "api_key" } } self.assertEqual(expected_trades_subscription, sent_subscription_messages[1]) expected_balances_subscription = { "time": int(self.mock_time_provider.time()), "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, "event": "subscribe", "auth": { "KEY": self.api_key, "SIGN": '90f5e732fc586d09c4a1b7de13f65b668c7ce90678b30da87aa137364bac0b97' # noqa: mock '16b34219b689fb754e821872933a0e12b1d415867b9fbb8ec441bc86e77fb79c', # noqa: mock "method": "api_key" } } self.assertEqual(expected_balances_subscription, sent_subscription_messages[2]) self.assertTrue( self._is_logged( "INFO", "Subscribed to private order changes and balance updates channels..." )) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) @patch( "hummingbot.connector.exchange.gate_io.gate_io_api_user_stream_data_source.GateIoAPIUserStreamDataSource" "._time") def test_listen_for_user_stream_skips_subscribe_unsubscribe_messages( self, time_mock, ws_connect_mock): time_mock.return_value = 1000 ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) result_subscribe_orders = { "time": 1611541000, "channel": CONSTANTS.USER_ORDERS_ENDPOINT_NAME, "event": "subscribe", "error": None, "result": { "status": "success" } } result_subscribe_trades = { "time": 1611541000, "channel": CONSTANTS.USER_TRADES_ENDPOINT_NAME, "event": "subscribe", "error": None, "result": { "status": "success" } } result_subscribe_balance = { "time": 1611541000, "channel": CONSTANTS.USER_BALANCE_ENDPOINT_NAME, "event": "subscribe", "error": None, "result": { "status": "success" } } self.mocking_assistant.add_websocket_aiohttp_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(result_subscribe_orders)) 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_balance)) output_queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_user_stream(output=output_queue)) self.mocking_assistant.run_until_all_aiohttp_messages_delivered( ws_connect_mock.return_value) self.assertTrue(output_queue.empty()) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) def test_listen_for_user_stream_does_not_queue_pong_payload(self, mock_ws): mock_pong = { "time": 1545404023, "channel": CONSTANTS.PONG_CHANNEL_NAME, "event": "", "error": None, "result": None } mock_ws.return_value = self.mocking_assistant.create_websocket_mock() self.mocking_assistant.add_websocket_aiohttp_message( mock_ws.return_value, json.dumps(mock_pong)) 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()) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) @patch( "hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep" ) def test_listen_for_user_stream_connection_failed(self, sleep_mock, mock_ws): mock_ws.side_effect = Exception("TEST ERROR.") sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution msg_queue = asyncio.Queue() try: self.async_run_with_timeout( self.data_source.listen_for_user_stream(msg_queue)) except asyncio.CancelledError: pass self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds..." )) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) @patch( "hummingbot.core.data_type.user_stream_tracker_data_source.UserStreamTrackerDataSource._sleep" ) def test_listen_for_user_stream_iter_message_throws_exception( self, sleep_mock, mock_ws): msg_queue: asyncio.Queue = asyncio.Queue() mock_ws.return_value = self.mocking_assistant.create_websocket_mock() mock_ws.return_value.receive.side_effect = Exception("TEST ERROR") sleep_mock.side_effect = asyncio.CancelledError # to finish the task execution try: self.async_run_with_timeout( self.data_source.listen_for_user_stream(msg_queue)) except asyncio.CancelledError: pass self.assertTrue( self._is_logged( "ERROR", "Unexpected error while listening to user stream. Retrying after 5 seconds..." ))