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() self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) self.data_source = GateIoAPIOrderBookDataSource( self.throttler, 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_last_trade_instance_data_mock(self) -> List: last_trade_instance_data = [{ "currency_pair": f"{self.base_asset}_{self.quote_asset}", "last": "0.2959", "lowest_ask": "0.295918", "highest_bid": "0.295898", "change_percentage": "-1.72", "base_volume": "78497066.828007", "quote_volume": "23432064.936692", "high_24h": "0.309372", "low_24h": "0.286827", }] return last_trade_instance_data @staticmethod def get_order_book_data_mock() -> Dict: order_book_data = { "id": 1890172054, "current": 1630644717528, "update": 1630644716786, "asks": [["0.298705", "5020"]], "bids": [["0.298642", "2703.17"]] } return order_book_data def get_trade_data_mock(self) -> Dict: trade_data = { "time": 1606292218, "channel": "spot.trades", "event": "update", "result": { "id": 309143071, "create_time": 1606292218, "create_time_ms": "1606292218213.4578", "side": "sell", "currency_pair": f"{self.base_asset}_{self.quote_asset}", "amount": "16.4700000000", "price": "0.4705000000" } } return trade_data def get_order_book_update_mock(self) -> Dict: ob_update = { "time": 1606294781, "channel": "spot.order_book_update", "event": "update", "result": { "t": 1606294781123, "e": "depthUpdate", "E": 1606294781, "s": f"{self.base_asset}_{self.quote_asset}", "U": 48776301, "u": 48776306, "b": [ ["19137.74", "0.0001"], ], "a": [["19137.75", "0.6135"]] } } return ob_update def get_order_book_snapshot_mock(self) -> Dict: ob_snapshot = { "time": 1606295412, "channel": "spot.order_book", "event": "update", "result": { "t": 1606295412123, "lastUpdateId": 48791820, "s": f"{self.base_asset}_{self.quote_asset}", "bids": [ ["19079.55", "0.0195"], ], "asks": [ ["19080.24", "0.1638"], ] } } return ob_snapshot @aioresponses() def test_get_last_trade_instance(self, mock_api): url = f"{CONSTANTS.REST_URL}/{CONSTANTS.TICKER_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_last_trade_instance_data_mock() mock_api.get(regex_url, body=json.dumps(resp)) ret = self.async_run_with_timeout( coroutine=GateIoAPIOrderBookDataSource.get_last_traded_prices( trading_pairs=[self.trading_pair])) self.assertEqual(ret[self.trading_pair], Decimal(resp[0]["last"])) @aioresponses() def test_fetch_trading_pairs(self, mock_api): url = f"{CONSTANTS.REST_URL}/{CONSTANTS.SYMBOL_PATH_URL}" resp = [{ "id": f"{self.base_asset}_{self.quote_asset}" }, { "id": "SOME_PAIR" }] mock_api.get(url, body=json.dumps(resp)) ret = self.async_run_with_timeout( coroutine=GateIoAPIOrderBookDataSource.fetch_trading_pairs()) self.assertTrue(self.trading_pair in ret) self.assertTrue("SOME-PAIR" in ret) @patch( "hummingbot.connector.exchange.gate_io.gate_io_utils.retry_sleep_time") @aioresponses() def test_get_order_book_data_raises(self, retry_sleep_time_mock, mock_api): retry_sleep_time_mock.side_effect = lambda *args, **kwargs: 0 url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = "" for _ in range(CONSTANTS.API_MAX_RETRIES): mock_api.get(regex_url, body=json.dumps(resp), status=500) with self.assertRaises(IOError): self.async_run_with_timeout(coroutine=GateIoAPIOrderBookDataSource. get_order_book_data(self.trading_pair)) @aioresponses() def test_get_order_book_data(self, mock_api): url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_order_book_data_mock() mock_api.get(regex_url, body=json.dumps(resp)) ret = self.async_run_with_timeout( coroutine=GateIoAPIOrderBookDataSource.get_order_book_data( self.trading_pair)) self.assertEqual(resp, ret) # shallow comparison is ok @aioresponses() def test_get_new_order_book(self, mock_api): url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_order_book_data_mock() mock_api.get(regex_url, body=json.dumps(resp)) ret = self.async_run_with_timeout( coroutine=self.data_source.get_new_order_book(self.trading_pair)) self.assertTrue(isinstance(ret, OrderBook)) @patch("websockets.connect", new_callable=AsyncMock) def test_listen_for_trades(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) resp = self.get_trade_data_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp)) output_queue = asyncio.Queue() self.ev_loop.create_task( self.data_source.listen_for_trades(self.ev_loop, output_queue)) self.mocking_assistant.run_until_all_text_messages_delivered( websocket_mock=ws_connect_mock.return_value) self.assertTrue(not output_queue.empty()) self.assertTrue(isinstance(output_queue.get_nowait(), OrderBookMessage)) @patch("websockets.connect", new_callable=AsyncMock) def test_listen_for_order_book_diffs_update(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) resp = self.get_order_book_update_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp)) output_queue = asyncio.Queue() self.ev_loop.create_task( self.data_source.listen_for_order_book_diffs( self.ev_loop, output_queue)) self.mocking_assistant.run_until_all_text_messages_delivered( websocket_mock=ws_connect_mock.return_value) self.assertTrue(not output_queue.empty()) self.assertTrue(isinstance(output_queue.get_nowait(), OrderBookMessage)) @patch("websockets.connect", new_callable=AsyncMock) def test_listen_for_order_book_diffs_snapshot(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) resp = self.get_order_book_snapshot_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp)) output_queue = asyncio.Queue() self.ev_loop.create_task( self.data_source.listen_for_order_book_diffs( self.ev_loop, output_queue)) self.mocking_assistant.run_until_all_text_messages_delivered( websocket_mock=ws_connect_mock.return_value) self.assertTrue(not output_queue.empty()) self.assertTrue(isinstance(output_queue.get_nowait(), OrderBookMessage)) @aioresponses() def test_listen_for_order_book_snapshots(self, mock_api): url = f"{CONSTANTS.REST_URL}/{CONSTANTS.ORDER_BOOK_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_order_book_data_mock() mock_api.get(regex_url, body=json.dumps(resp)) output_queue = asyncio.Queue() self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, output_queue)) ret = self.async_run_with_timeout(coroutine=output_queue.get()) self.assertTrue(isinstance(ret, OrderBookMessage))
class KrakenAPIOrderBookDataSourceTest(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() self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) self.data_source = KrakenAPIOrderBookDataSource( self.throttler, 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_last_traded_prices_mock(self, last_trade_close: Decimal) -> Dict: last_traded_prices = { "error": [], "result": { f"X{self.base_asset}{self.quote_asset}": { "a": ["52609.60000", "1", "1.000"], "b": ["52609.50000", "1", "1.000"], "c": [str(last_trade_close), "0.00080000"], "v": ["1920.83610601", "7954.00219674"], "p": ["52389.94668", "54022.90683"], "t": [23329, 80463], "l": ["51513.90000", "51513.90000"], "h": ["53219.90000", "57200.00000"], "o": "52280.40000" } } } return last_traded_prices def get_depth_mock(self) -> Dict: depth = { "error": [], "result": { f"X{self.base_asset}{self.quote_asset}": { "asks": [["52523.00000", "1.199", 1616663113], ["52536.00000", "0.300", 1616663112]], "bids": [["52522.90000", "0.753", 1616663112], ["52522.80000", "0.006", 1616663109]] } } } return depth def get_public_asset_pair_mock(self) -> Dict: asset_pairs = { "error": [], "result": { f"X{self.base_asset}{self.quote_asset}": { "altname": f"{self.base_asset}{self.quote_asset}", "wsname": f"{self.base_asset}/{self.quote_asset}", "aclass_base": "currency", "base": self.base_asset, "aclass_quote": "currency", "quote": self.quote_asset, "lot": "unit", "pair_decimals": 5, "lot_decimals": 8, "lot_multiplier": 1, "leverage_buy": [2, 3, 4, 5], "leverage_sell": [2, 3, 4, 5], "fees": [ [0, 0.26], [50000, 0.24], ], "fees_maker": [ [0, 0.16], [50000, 0.14], ], "fee_volume_currency": "ZUSD", "margin_call": 80, "margin_stop": 40, "ordermin": "0.005" }, } } return asset_pairs def get_trade_data_mock(self) -> List: trade_data = [ 0, [["5541.20000", "0.15850568", "1534614057.321597", "s", "l", ""], ["6060.00000", "0.02455000", "1534614057.324998", "b", "l", ""]], "trade", f"{self.base_asset}/{self.quote_asset}" ] return trade_data @aioresponses() def test_get_last_traded_prices(self, mocked_api): url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TICKER_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) last_traded_price = Decimal("52641.10000") resp = self.get_last_traded_prices_mock( last_trade_close=last_traded_price) mocked_api.get(regex_url, body=json.dumps(resp)) ret = self.async_run_with_timeout( KrakenAPIOrderBookDataSource.get_last_traded_prices( trading_pairs=[self.trading_pair], throttler=self.throttler)) self.assertIn(self.trading_pair, ret) self.assertEqual(float(last_traded_price), ret[self.trading_pair]) @aioresponses() def test_get_new_order_book(self, mocked_api): url = f"{CONSTANTS.BASE_URL}{CONSTANTS.SNAPSHOT_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_depth_mock() mocked_api.get(regex_url, body=json.dumps(resp)) ret = self.async_run_with_timeout( self.data_source.get_new_order_book(self.trading_pair)) self.assertTrue(isinstance(ret, OrderBook)) bids_df, asks_df = ret.snapshot pair_data = resp["result"][f"X{self.base_asset}{self.quote_asset}"] first_bid_price = float(pair_data["bids"][0][0]) first_ask_price = float(pair_data["asks"][0][0]) self.assertEqual(first_bid_price, bids_df.iloc[0]["price"]) self.assertEqual(first_ask_price, asks_df.iloc[0]["price"]) @aioresponses() def test_fetch_trading_pairs(self, mocked_api): url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_public_asset_pair_mock() mocked_api.get(regex_url, body=json.dumps(resp)) resp = self.async_run_with_timeout( KrakenAPIOrderBookDataSource.fetch_trading_pairs()) self.assertTrue(len(resp) == 1) self.assertIn(self.trading_pair, resp) @patch("websockets.connect", new_callable=AsyncMock) def test_listen_for_trades(self, ws_connect_mock): ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) resp = self.get_trade_data_mock() self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp)) output_queue = asyncio.Queue() self.ev_loop.create_task( self.data_source.listen_for_trades(self.ev_loop, output_queue)) self.mocking_assistant.run_until_all_text_messages_delivered( websocket_mock=ws_connect_mock.return_value) self.assertTrue(not output_queue.empty()) msg = output_queue.get_nowait() self.assertTrue(isinstance(msg, OrderBookMessage)) first_trade_price = resp[1][0][0] self.assertEqual(msg.content["price"], first_trade_price) self.assertTrue(not output_queue.empty()) msg = output_queue.get_nowait() self.assertTrue(isinstance(msg, OrderBookMessage)) second_trade_price = resp[1][1][0] self.assertEqual(msg.content["price"], second_trade_price)
class TestAltmarketsAPIUserStreamDataSource(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() altmarkets_auth = AltmarketsAuth(api_key="someKey", secret_key="someSecret") self.data_source = AltmarketsAPIUserStreamDataSource(AsyncThrottler(Constants.RATE_LIMITS), altmarkets_auth=altmarkets_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 = { "trade": { "amount": "1.0", "created_at": 1615978645, "id": 9618578, "market": "rogerbtc", "order_id": 2324774, "price": "0.00000004", "side": "sell", "taker_type": "sell", "total": "0.00000004" } } return user_trades def get_user_orders_mock(self) -> Dict: user_orders = { "order": { "id": 9401, "market": "rogerbtc", "kind": "ask", "side": "sell", "ord_type": "limit", "price": "0.00000099", "avg_price": "0.00000099", "state": "wait", "origin_volume": "7000.0", "remaining_volume": "2810.1", "executed_volume": "4189.9", "at": 1596481983, "created_at": 1596481983, "updated_at": 1596553643, "trades_count": 272 } } return user_orders def get_user_balance_mock(self) -> Dict: user_balance = { "balance": { "currency": self.base_asset, "balance": "1032951.325075926", "locked": "1022943.325075926", } } return user_balance @patch("websockets.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_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) @patch("websockets.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 = { "success": { "message": "subscribed", "time": 1632223851, "streams": "trade" } } self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=json.dumps(resp)) resp = { "success": { "message": "unsubscribed", "time": 1632223851, "streams": "trade" } } self.mocking_assistant.add_websocket_text_message( websocket_mock=ws_connect_mock.return_value, message=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_text_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 TestKucoinAPIUserStreamDataSource(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}" cls.api_key = "someKey" cls.api_passphrase = "somePassPhrase" cls.api_secret_key = "someSecretKey" def setUp(self) -> None: super().setUp() self.throttler = AsyncThrottler(CONSTANTS.RATE_LIMITS) self.auth = KucoinAuth(self.api_key, self.api_passphrase, self.api_secret_key) self.data_source = KucoinAPIUserStreamDataSource( self.throttler, self.auth) self.mocking_assistant = NetworkMockingAssistant() 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 @staticmethod def get_listen_key_mock(): listen_key = { "code": "200000", "data": { "token": "someToken", "instanceServers": [{ "endpoint": "wss://someEndpoint", "encrypt": True, "protocol": "websocket", "pingInterval": 18000, "pingTimeout": 10000, }] } } return listen_key @aioresponses() def test_get_listen_key_raises(self, mock_api): url = CONSTANTS.BASE_PATH_URL + CONSTANTS.PRIVATE_WS_DATA_PATH_URL mock_api.post(url, status=500) with self.assertRaises(IOError): self.async_run_with_timeout(self.data_source.get_listen_key()) @aioresponses() def test_get_listen_key(self, mock_api): url = CONSTANTS.BASE_PATH_URL + CONSTANTS.PRIVATE_WS_DATA_PATH_URL resp = self.get_listen_key_mock() mock_api.post(url, body=json.dumps(resp)) ret = self.async_run_with_timeout(self.data_source.get_listen_key()) self.assertEqual(ret, resp) # shallow comparison ok @aioresponses() @patch("websockets.connect", new_callable=AsyncMock) def test_listen_to_user_stream(self, mock_api, ws_connect_mock): url = CONSTANTS.BASE_PATH_URL + CONSTANTS.PRIVATE_WS_DATA_PATH_URL resp = self.get_listen_key_mock() mock_api.post(url, body=json.dumps(resp)) ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) msg = "someMsg" msg_queue = asyncio.Queue() self.mocking_assistant.add_websocket_text_message( ws_connect_mock.return_value, json.dumps(msg)) self.ev_loop.create_task( self.data_source.listen_for_user_stream(self.ev_loop, msg_queue)) self.mocking_assistant.run_until_all_text_messages_delivered( ws_connect_mock.return_value) self.assertTrue(not msg_queue.empty()) queued = msg_queue.get_nowait() self.assertEqual(msg, queued) @aioresponses() @patch("websockets.connect", new_callable=AsyncMock) def test_listen_for_user_stream_closes_ws_on_exception( self, mock_api, ws_connect_mock): url = CONSTANTS.BASE_PATH_URL + CONSTANTS.PRIVATE_WS_DATA_PATH_URL resp = self.get_listen_key_mock() mock_api.post(url, body=json.dumps(resp)) raised_event = asyncio.Event() async def raise_exception(): raised_event.set() raise IOError ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) ws_connect_mock.return_value.recv.side_effect = raise_exception self.ev_loop.create_task( self.data_source.listen_for_user_stream(self.ev_loop, asyncio.Queue())) self.async_run_with_timeout(coroutine=raised_event.wait()) ws_connect_mock.return_value.close.assert_called()