Ejemplo n.º 1
0
    def setup(self):
        # Fixture Setup
        self.loop = asyncio.get_event_loop()
        self.loop.set_debug(True)

        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(clock=self.clock)

        self.trader_id = TestIdStubs.trader_id()
        self.venue = BINANCE_VENUE
        self.account_id = AccountId(self.venue.value, "001")

        self.msgbus = MessageBus(
            trader_id=self.trader_id,
            clock=self.clock,
            logger=self.logger,
        )

        self.cache = TestComponentStubs.cache()

        self.http_client = BinanceHttpClient(  # noqa: S106 (no hardcoded password)
            loop=asyncio.get_event_loop(),
            clock=self.clock,
            logger=self.logger,
            key="SOME_BINANCE_API_KEY",
            secret="SOME_BINANCE_API_SECRET",
        )

        self.provider = BinanceSpotInstrumentProvider(
            client=self.http_client,
            logger=self.logger,
            config=InstrumentProviderConfig(load_all=True),
        )

        self.data_engine = DataEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.data_client = BinanceDataClient(
            loop=self.loop,
            client=self.http_client,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            instrument_provider=self.provider,
        )
Ejemplo n.º 2
0
    def create(
        loop: asyncio.AbstractEventLoop,
        name: str,
        config: Dict[str, Any],
        msgbus: MessageBus,
        cache: Cache,
        clock: LiveClock,
        logger: LiveLogger,
        client_cls=None,
    ) -> BinanceDataClient:
        """
        Create a new Binance data client.

        Parameters
        ----------
        loop : asyncio.AbstractEventLoop
            The event loop for the client.
        name : str
            The client name.
        config : dict
            The configuration dictionary.
        msgbus : MessageBus
            The message bus for the client.
        cache : Cache
            The cache for the client.
        clock : LiveClock
            The clock for the client.
        logger : LiveLogger
            The logger for the client.
        client_cls : class, optional
            The class to call to return a new internal client.

        Returns
        -------
        BinanceDataClient

        """
        client = get_cached_binance_http_client(
            key=config.get("api_key"),
            secret=config.get("api_secret"),
            loop=loop,
            clock=clock,
            logger=logger,
        )

        # Get instrument provider singleton
        provider = get_cached_binance_instrument_provider(client=client,
                                                          logger=logger)

        # Create client
        data_client = BinanceDataClient(
            loop=loop,
            client=client,
            msgbus=msgbus,
            cache=cache,
            clock=clock,
            logger=logger,
            instrument_provider=provider,
        )
        return data_client
Ejemplo n.º 3
0
    def setUp(self):
        # Fixture Setup
        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.trader_id = TraderId("TESTER", "001")

        # Fresh isolated loop testing pattern
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        # Setup logging
        logger = LiveLogger(
            clock=self.clock,
            name=self.trader_id.value,
            level_console=LogLevel.INFO,
            level_file=LogLevel.DEBUG,
            level_store=LogLevel.WARNING,
        )

        self.logger = LiveLogger(self.clock)

        self.portfolio = Portfolio(
            clock=self.clock,
            logger=self.logger,
        )

        self.data_engine = LiveDataEngine(
            loop=self.loop,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.mock_binance_rest = MagicMock()
        self.mock_binance_rest.name = "Binance"

        self.mock_binance_feed = MagicMock()

        self.client = BinanceDataClient(
            client_rest=self.mock_binance_rest,
            client_feed=self.mock_binance_feed,
            engine=self.data_engine,
            clock=self.clock,
            logger=logger,
        )

        self.data_engine.register_client(self.client)
Ejemplo n.º 4
0
class TestBinanceDataClient:
    def setup(self):
        # Fixture Setup
        self.loop = asyncio.get_event_loop()
        self.loop.set_debug(True)

        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(clock=self.clock)

        self.trader_id = TestStubs.trader_id()
        self.venue = BINANCE_VENUE
        self.account_id = AccountId(self.venue.value, "001")

        self.msgbus = MessageBus(
            trader_id=self.trader_id,
            clock=self.clock,
            logger=self.logger,
        )

        self.cache = TestStubs.cache()

        self.http_client = BinanceHttpClient(  # noqa: S106 (no hardcoded password)
            loop=asyncio.get_event_loop(),
            clock=self.clock,
            logger=self.logger,
            key="SOME_BINANCE_API_KEY",
            secret="SOME_BINANCE_API_SECRET",
        )

        self.provider = BinanceInstrumentProvider(
            client=self.http_client,
            logger=self.logger,
        )

        self.data_engine = DataEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.data_client = BinanceDataClient(
            loop=self.loop,
            client=self.http_client,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            instrument_provider=self.provider,
        )

    @pytest.mark.asyncio
    async def test_connect(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
                self,  # noqa (needed for mock)
                http_method: str,  # noqa (needed for mock)
                url_path: str,  # noqa (needed for mock)
                payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        # Act
        self.data_client.connect()
        await asyncio.sleep(1)

        # Assert
        assert self.data_client.is_connected

    @pytest.mark.asyncio
    async def test_disconnect(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
                self,  # noqa (needed for mock)
                http_method: str,  # noqa (needed for mock)
                url_path: str,  # noqa (needed for mock)
                payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        # Act
        self.data_client.disconnect()
        await asyncio.sleep(1)

        # Assert
        assert not self.data_client.is_connected

    @pytest.mark.asyncio
    async def test_subscribe_instruments(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
                self,  # noqa (needed for mock)
                http_method: str,  # noqa (needed for mock)
                url_path: str,  # noqa (needed for mock)
                payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        # Act
        self.data_client.subscribe_instruments()

        # Assert
        btcusdt = InstrumentId.from_str("BTCUSDT.BINANCE")
        ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE")
        assert self.data_client.subscribed_instruments() == [btcusdt, ethusdt]

    @pytest.mark.asyncio
    async def test_subscribe_instrument(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
                self,  # noqa (needed for mock)
                http_method: str,  # noqa (needed for mock)
                url_path: str,  # noqa (needed for mock)
                payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE")

        # Act
        self.data_client.subscribe_instrument(ethusdt)

        # Assert
        assert self.data_client.subscribed_instruments() == [ethusdt]

    @pytest.mark.asyncio
    async def test_subscribe_quote_ticks(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
                self,  # noqa (needed for mock)
                http_method: str,  # noqa (needed for mock)
                url_path: str,  # noqa (needed for mock)
                payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE")

        handler = []
        self.msgbus.subscribe(
            topic="data.quotes.BINANCE.ETHUSDT",
            handler=handler.append,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        # Act
        self.data_client.subscribe_quote_ticks(ethusdt)

        raw_book_tick = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.streaming",
            resource="ws_book_ticker.json",
        )

        # Assert
        self.data_client._handle_spot_ws_message(raw_book_tick)
        await asyncio.sleep(1)

        assert self.data_engine.data_count == 3
        assert len(handler) == 1  # <-- handler received tick

    @pytest.mark.asyncio
    async def test_subscribe_trade_ticks(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.responses",
            resource="spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
                self,  # noqa (needed for mock)
                http_method: str,  # noqa (needed for mock)
                url_path: str,  # noqa (needed for mock)
                payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE")

        handler = []
        self.msgbus.subscribe(
            topic="data.trades.BINANCE.ETHUSDT",
            handler=handler.append,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        # Act
        self.data_client.subscribe_trade_ticks(ethusdt)

        raw_trade = pkgutil.get_data(
            package=
            "tests.integration_tests.adapters.binance.resources.streaming",
            resource="ws_trade.json",
        )

        # Assert
        self.data_client._handle_spot_ws_message(raw_trade)
        await asyncio.sleep(1)

        assert self.data_engine.data_count == 3
        assert len(handler) == 1  # <-- handler received tick
Ejemplo n.º 5
0
    def create(
        loop: asyncio.AbstractEventLoop,
        name: str,
        config: BinanceDataClientConfig,
        msgbus: MessageBus,
        cache: Cache,
        clock: LiveClock,
        logger: LiveLogger,
    ) -> BinanceDataClient:
        """
        Create a new Binance data client.

        Parameters
        ----------
        loop : asyncio.AbstractEventLoop
            The event loop for the client.
        name : str
            The client name.
        config : BinanceDataClientConfig
            The client configuration.
        msgbus : MessageBus
            The message bus for the client.
        cache : Cache
            The cache for the client.
        clock : LiveClock
            The clock for the client.
        logger : LiveLogger
            The logger for the client.

        Returns
        -------
        BinanceDataClient

        Raises
        ------
        ValueError
            If `config.account_type` is not a valid `BinanceAccountType`.

        """
        base_url_http_default: str = _get_http_base_url(config)
        base_url_ws_default: str = _get_ws_base_url(config)

        client: BinanceHttpClient = get_cached_binance_http_client(
            loop=loop,
            clock=clock,
            logger=logger,
            key=config.api_key,
            secret=config.api_secret,
            account_type=config.account_type,
            base_url=config.base_url_http or base_url_http_default,
            is_testnet=config.testnet,
        )

        # Get instrument provider singleton
        if config.account_type.is_spot or config.account_type.is_margin:
            provider = get_cached_binance_spot_instrument_provider(
                client=client,
                logger=logger,
                account_type=config.account_type,
                config=config.instrument_provider,
            )
        else:
            provider = get_cached_binance_futures_instrument_provider(
                client=client,
                logger=logger,
                account_type=config.account_type,
                config=config.instrument_provider,
            )

        # Create client
        data_client = BinanceDataClient(
            loop=loop,
            client=client,
            msgbus=msgbus,
            cache=cache,
            clock=clock,
            logger=logger,
            instrument_provider=provider,
            account_type=config.account_type,
            base_url_ws=config.base_url_ws or base_url_ws_default,
        )
        return data_client
class BinanceDataClientTests(unittest.TestCase):

    def setUp(self):
        # Fixture Setup
        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.trader_id = TraderId("TESTER", "001")

        # Fresh isolated loop testing pattern
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

        # Setup logging
        logger = LiveLogger(
            clock=self.clock,
            name=self.trader_id.value,
            level_console=LogLevel.INFO,
            level_file=LogLevel.DEBUG,
            level_store=LogLevel.WARNING,
        )

        self.logger = LiveLogger(self.clock)

        self.portfolio = Portfolio(
            clock=self.clock,
            logger=self.logger,
        )

        self.data_engine = LiveDataEngine(
            loop=self.loop,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.mock_binance = MagicMock()
        self.mock_binance.name = "Binance"

        self.client = BinanceDataClient(
            client=self.mock_binance,
            engine=self.data_engine,
            clock=self.clock,
            logger=logger,
        )

        self.data_engine.register_client(self.client)

    def tearDown(self):
        self.loop.stop()
        self.loop.close()

    def test_connect(self):
        # Arrange
        # Act
        self.client.connect()

        # Assert
        self.assertTrue(self.client.is_connected())

    def test_disconnect(self):
        # Arrange
        self.client.connect()

        # Act
        self.client.disconnect()

        # Assert
        self.assertFalse(self.client.is_connected())

    def test_reset(self):
        # Arrange
        # Act
        self.client.reset()

        # Assert
        self.assertFalse(self.client.is_connected())

    def test_dispose(self):
        # Arrange
        # Act
        self.client.dispose()

        # Assert
        self.assertFalse(self.client.is_connected())

    def test_subscribe_instrument(self):
        # Arrange
        self.client.connect()

        # Act
        self.client.subscribe_instrument(BTCUSDT)

        # Assert
        self.assertIn(BTCUSDT, self.client.subscribed_instruments)

    # def test_subscribe_quote_ticks(self):
    #     # Arrange
    #     # Act
    #     self.client.subscribe_quote_ticks(USDJPY_SIM.symbol)
    #     self.client.connect()
    #     self.client.subscribe_quote_ticks(USDJPY_SIM.symbol)
    #
    #     # Assert
    #     self.assertTrue(True)
    #
    # def test_subscribe_trade_ticks(self):
    #     # Arrange
    #     # Act
    #     self.client.subscribe_trade_ticks(USDJPY_SIM.symbol)
    #     self.client.connect()
    #     self.client.subscribe_trade_ticks(USDJPY_SIM.symbol)
    #
    #     # Assert
    #     self.assertTrue(True)
    #
    # def test_subscribe_bars(self):
    #     # Arrange
    #     # Act
    #     self.client.subscribe_bars(TestStubs.bartype_gbpusd_1sec_mid())
    #     self.client.connect()
    #     self.client.subscribe_bars(TestStubs.bartype_gbpusd_1sec_mid())
    #
    #     # Assert
    #     self.assertTrue(True)

    def test_unsubscribe_instrument(self):
        # Arrange
        self.client.connect()

        # Act
        self.client.unsubscribe_instrument(BTCUSDT)

        # Assert
        self.assertNotIn(BTCUSDT, self.client.subscribed_instruments)

    # def test_unsubscribe_quote_ticks(self):
    #     # Arrange
    #     # Act
    #     self.client.unsubscribe_quote_ticks(USDJPY_SIM.symbol)
    #     self.client.connect()
    #     self.client.unsubscribe_quote_ticks(USDJPY_SIM.symbol)
    #
    #     # Assert
    #     self.assertTrue(True)
    #
    # def test_unsubscribe_trade_ticks(self):
    #     # Arrange
    #     # Act
    #     self.client.unsubscribe_trade_ticks(USDJPY_SIM.symbol)
    #     self.client.connect()
    #     self.client.unsubscribe_trade_ticks(USDJPY_SIM.symbol)
    #
    #     # Assert
    #     self.assertTrue(True)
    #
    # def test_unsubscribe_bars(self):
    #     # Arrange
    #     # Act
    #     self.client.unsubscribe_bars(TestStubs.bartype_usdjpy_1min_bid())
    #     self.client.connect()
    #     self.client.unsubscribe_bars(TestStubs.bartype_usdjpy_1min_bid())
    #
    #     # Assert
    #     self.assertTrue(True)

    # TODO: WIP
    def test_request_instrument(self):
        async def run_test():
            # Arrange
            with open(TEST_PATH + "res_instruments.json") as response:
                instruments = json.load(response)

            self.mock_binance.markets = instruments

            self.data_engine.start()
            await asyncio.sleep(0.3)

            # Act
            self.client.request_instrument(BTCUSDT, uuid4())

            await asyncio.sleep(0.3)

            # Assert
            # Instruments additionally requested on start
            self.assertEqual(2, self.data_engine.response_count)

            # Tear Down
            self.data_engine.stop()
            await self.data_engine.get_run_queue_task()

        self.loop.run_until_complete(run_test())

    def test_request_instruments(self):
        async def run_test():
            # Arrange
            with open(TEST_PATH + "res_instruments.json") as response:
                instruments = json.load(response)

            self.mock_binance.markets = instruments

            self.data_engine.start()
            await asyncio.sleep(0.3)

            # Act
            self.client.request_instruments(uuid4())

            await asyncio.sleep(0.3)

            # Assert
            # Instruments additionally requested on start
            self.assertEqual(2, self.data_engine.response_count)

            # Tear Down
            self.data_engine.stop()
            await self.data_engine.get_run_queue_task()

        self.loop.run_until_complete(run_test())

    def test_request_quote_ticks(self):
        # Arrange
        # Act
        self.client.request_quote_ticks(BTCUSDT, None, None, 0, uuid4())

        # Assert
        self.assertTrue(True)
Ejemplo n.º 7
0
class TestBinanceDataClient:
    def setup(self):
        # Fixture Setup
        self.loop = asyncio.get_event_loop()
        self.loop.set_debug(True)

        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(clock=self.clock)

        self.trader_id = TestIdStubs.trader_id()
        self.venue = BINANCE_VENUE
        self.account_id = AccountId(self.venue.value, "001")

        self.msgbus = MessageBus(
            trader_id=self.trader_id,
            clock=self.clock,
            logger=self.logger,
        )

        self.cache = TestComponentStubs.cache()

        self.http_client = BinanceHttpClient(  # noqa: S106 (no hardcoded password)
            loop=asyncio.get_event_loop(),
            clock=self.clock,
            logger=self.logger,
            key="SOME_BINANCE_API_KEY",
            secret="SOME_BINANCE_API_SECRET",
        )

        self.provider = BinanceSpotInstrumentProvider(
            client=self.http_client,
            logger=self.logger,
            config=InstrumentProviderConfig(load_all=True),
        )

        self.data_engine = DataEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.data_client = BinanceDataClient(
            loop=self.loop,
            client=self.http_client,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            instrument_provider=self.provider,
        )

    @pytest.mark.asyncio
    async def test_connect(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
            self,  # noqa (needed for mock)
            http_method: str,  # noqa (needed for mock)
            url_path: str,  # noqa (needed for mock)
            payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        # Act
        self.data_client.connect()
        await asyncio.sleep(1)

        # Assert
        assert self.data_client.is_connected

    @pytest.mark.asyncio
    async def test_disconnect(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
            self,  # noqa (needed for mock)
            http_method: str,  # noqa (needed for mock)
            url_path: str,  # noqa (needed for mock)
            payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        # Act
        self.data_client.disconnect()
        await asyncio.sleep(1)

        # Assert
        assert not self.data_client.is_connected

    @pytest.mark.asyncio
    async def test_subscribe_instruments(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
            self,  # noqa (needed for mock)
            http_method: str,  # noqa (needed for mock)
            url_path: str,  # noqa (needed for mock)
            payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        # Act
        self.data_client.subscribe_instruments()

        # Assert
        btcusdt = InstrumentId.from_str("BTCUSDT.BINANCE")
        ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE")
        assert self.data_client.subscribed_instruments() == [btcusdt, ethusdt]

    @pytest.mark.asyncio
    async def test_subscribe_instrument(self, monkeypatch):
        # Arrange: prepare data for monkey patch
        response1 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_wallet_trading_fee.json",
        )

        response2 = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.http_responses",
            resource="http_spot_market_exchange_info.json",
        )

        responses = [response2, response1]

        # Mock coroutine for patch
        async def mock_send_request(
            self,  # noqa (needed for mock)
            http_method: str,  # noqa (needed for mock)
            url_path: str,  # noqa (needed for mock)
            payload: Dict[str, str],  # noqa (needed for mock)
        ) -> bytes:
            return orjson.loads(responses.pop())

        # Apply mock coroutine to client
        monkeypatch.setattr(
            target=BinanceHttpClient,
            name="send_request",
            value=mock_send_request,
        )

        self.data_client.connect()
        await asyncio.sleep(1)

        ethusdt = InstrumentId.from_str("ETHUSDT.BINANCE")

        # Act
        self.data_client.subscribe_instrument(ethusdt)

        # Assert
        assert self.data_client.subscribed_instruments() == [ethusdt]

    @pytest.mark.asyncio
    async def test_subscribe_quote_ticks(self, monkeypatch):
        handler = []
        self.msgbus.subscribe(
            topic="data.quotes.BINANCE.ETHUSDT",
            handler=handler.append,
        )

        # Act
        self.data_client.subscribe_quote_ticks(ETHUSDT_BINANCE.id)

        raw_book_tick = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.ws_messages",
            resource="ws_spot_ticker_book.json",
        )

        # Assert
        self.data_client._handle_ws_message(raw_book_tick)
        await asyncio.sleep(1)

        assert self.data_engine.data_count == 1
        assert len(handler) == 1  # <-- handler received tick
        assert handler[0] == QuoteTick(
            instrument_id=ETHUSDT_BINANCE.id,
            bid=Price.from_str("4507.24000000"),
            ask=Price.from_str("4507.25000000"),
            bid_size=Quantity.from_str("2.35950000"),
            ask_size=Quantity.from_str("2.84570000"),
            ts_event=handler[0].ts_init,  # TODO: WIP
            ts_init=handler[0].ts_init,
        )

    @pytest.mark.asyncio
    async def test_subscribe_trade_ticks(self, monkeypatch):
        handler = []
        self.msgbus.subscribe(
            topic="data.trades.BINANCE.ETHUSDT",
            handler=handler.append,
        )

        # Act
        self.data_client.subscribe_trade_ticks(ETHUSDT_BINANCE.id)

        raw_trade = pkgutil.get_data(
            package="tests.integration_tests.adapters.binance.resources.ws_messages",
            resource="ws_spot_trade.json",
        )

        # Assert
        self.data_client._handle_ws_message(raw_trade)
        await asyncio.sleep(1)

        assert self.data_engine.data_count == 1
        assert len(handler) == 1  # <-- handler received tick
        assert handler[0] == TradeTick(
            instrument_id=ETHUSDT_BINANCE.id,
            price=Price.from_str("4149.74000000"),
            size=Quantity.from_str("0.43870000"),
            aggressor_side=AggressorSide.SELL,
            trade_id=TradeId("705291099"),
            ts_event=1639351062243000064,
            ts_init=handler[0].ts_init,
        )