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,
     )
Beispiel #3
0
 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
Beispiel #4
0
 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,
     ))
Beispiel #5
0
 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])
Beispiel #6
0
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..."
            ))