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, )
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
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_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) 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()) # self.client._stop_feed_loop() def test_disconnect(self): async def run_test(): # Arrange with open(TEST_PATH + "res_instruments.json") as response: instruments = json.load(response) self.mock_binance_rest.markets = instruments # Act self.client.disconnect() await asyncio.sleep(0.3) # Assert self.assertFalse(self.client.is_connected()) self.loop.run_until_complete(run_test()) 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 # 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(BTCUSDT) # Assert self.assertIn(BTCUSDT, self.client.subscribed_trade_ticks) # 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 # 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.subscribe_trade_ticks(BTCUSDT) self.client.unsubscribe_trade_ticks(BTCUSDT) # Assert self.assertNotIn(BTCUSDT, self.client.subscribed_trade_ticks) # 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_rest.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()) # TODO: WIP # 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_rest.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)