Пример #1
0
class DataEngineTests(unittest.TestCase):

    def setUp(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = TestLogger(self.clock)

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

        self.data_engine = DataEngine(
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.portfolio.register_cache(self.data_engine.cache)

        self.binance_client = BacktestMarketDataClient(
            instruments=[BTCUSDT_BINANCE, ETHUSDT_BINANCE],
            name=BINANCE.value,
            engine=self.data_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.bitmex_client = BacktestMarketDataClient(
            instruments=[XBTUSD_BITMEX],
            name=BITMEX.value,
            engine=self.data_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.quandl = MockMarketDataClient(
            name="QUANDL",
            engine=self.data_engine,
            clock=self.clock,
            logger=self.logger,
        )

    def test_registered_venues(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.registered_clients)

    def test_subscribed_instruments_when_nothing_subscribed_returns_empty_list(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_instruments)

    def test_subscribed_quote_ticks_when_nothing_subscribed_returns_empty_list(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_quote_ticks)

    def test_subscribed_trade_ticks_when_nothing_subscribed_returns_empty_list(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_trade_ticks)

    def test_subscribed_bars_when_nothing_subscribed_returns_empty_list(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_bars)

    def test_register_client_successfully_adds_client(self):
        # Arrange
        # Act
        self.data_engine.register_client(self.binance_client)

        # Assert
        self.assertIn(BINANCE.value, self.data_engine.registered_clients)

    def test_deregister_client_successfully_removes_client(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        # Act
        self.data_engine.deregister_client(self.binance_client)

        # Assert
        self.assertNotIn(BINANCE.value, self.data_engine.registered_clients)

    def test_register_strategy_successfully_registered_with_strategy(self):
        # Arrange
        strategy = TradingStrategy("000")

        # Act
        strategy.register_data_engine(self.data_engine)

        # Assert
        self.assertEqual(self.data_engine.cache, strategy.data)

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

        # Assert
        self.assertEqual(0, self.data_engine.command_count)
        self.assertEqual(0, self.data_engine.data_count)
        self.assertEqual(0, self.data_engine.request_count)
        self.assertEqual(0, self.data_engine.response_count)

    def test_stop_and_resume(self):
        # Arrange
        self.data_engine.start()

        # Act
        self.data_engine.stop()
        self.data_engine.resume()
        self.data_engine.stop()
        self.data_engine.reset()

        # Assert
        self.assertEqual(0, self.data_engine.command_count)
        self.assertEqual(0, self.data_engine.data_count)
        self.assertEqual(0, self.data_engine.request_count)
        self.assertEqual(0, self.data_engine.response_count)

    def test_dispose(self):
        # Arrange
        self.data_engine.reset()

        # Act
        self.data_engine.dispose()

        # Assert
        self.assertEqual(0, self.data_engine.command_count)
        self.assertEqual(0, self.data_engine.data_count)
        self.assertEqual(0, self.data_engine.request_count)
        self.assertEqual(0, self.data_engine.response_count)

    def test_check_connected_when_client_disconnected_returns_false(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        self.binance_client.disconnect()
        self.bitmex_client.disconnect()

        # Act
        result = self.data_engine.check_connected()

        # Assert
        self.assertFalse(result)

    def test_check_connected_when_client_connected_returns_true(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        self.binance_client.connect()
        self.bitmex_client.connect()

        # Act
        result = self.data_engine.check_connected()

        # Assert
        self.assertTrue(result)

    def test_check_disconnected_when_client_disconnected_returns_true(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        # Act
        result = self.data_engine.check_disconnected()

        # Assert
        self.assertTrue(result)

    def test_check_disconnected_when_client_connected_returns_false(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        self.binance_client.connect()
        self.bitmex_client.connect()

        # Act
        result = self.data_engine.check_disconnected()

        # Assert
        self.assertFalse(result)

    def test_reset_when_already_disposed_raises_invalid_state_trigger(self):
        # Arrange
        self.data_engine.dispose()

        # Act
        # Assert
        self.assertRaises(InvalidStateTrigger, self.data_engine.reset)

    def test_dispose_when_already_disposed_raises_invalid_state_trigger(self):
        # Arrange
        self.data_engine.dispose()

        # Act
        # Assert
        self.assertRaises(InvalidStateTrigger, self.data_engine.dispose)

    def test_execute_unrecognized_message_logs_and_does_nothing(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        # Bogus message
        command = DataCommand(
            provider=BINANCE.value,
            data_type=DataType(str),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(command)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_send_request_when_no_data_clients_registered_does_nothing(self):
        # Arrange
        handler = []
        request = DataRequest(
            provider="RANDOM",
            data_type=DataType(QuoteTick, metadata={
                "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                "FromDateTime": None,
                "ToDateTime": None,
                "Limit": 1000,
            }),
            callback=handler.append,
            request_id=self.uuid_factory.generate(),
            request_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.send(request)

        # Assert
        self.assertEqual(1, self.data_engine.request_count)

    def test_send_data_request_when_data_type_unrecognized_logs_and_does_nothing(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        handler = []
        request = DataRequest(
            provider=BINANCE.value,
            data_type=DataType(str, metadata={  # str data type is invalid
                "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                "FromDateTime": None,
                "ToDateTime": None,
                "Limit": 1000,
            }),
            callback=handler.append,
            request_id=self.uuid_factory.generate(),
            request_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.send(request)

        # Assert
        self.assertEqual(1, self.data_engine.request_count)

    def test_send_data_request_with_duplicate_ids_logs_and_does_not_handle_second(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.start()

        handler = []
        uuid = self.uuid_factory.generate()  # We'll use this as a duplicate

        request1 = DataRequest(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={  # str data type is invalid
                "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                "FromDateTime": None,
                "ToDateTime": None,
                "Limit": 1000,
            }),
            callback=handler.append,
            request_id=uuid,  # Duplicate
            request_timestamp=self.clock.utc_now(),
        )

        request2 = DataRequest(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={  # str data type is invalid
                "InstrumentId": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                "FromDateTime": None,
                "ToDateTime": None,
                "Limit": 1000,
            }),
            callback=handler.append,
            request_id=uuid,  # Duplicate
            request_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.send(request1)
        self.data_engine.send(request2)

        # Assert
        self.assertEqual(2, self.data_engine.request_count)

    def test_execute_subscribe_when_data_type_unrecognized_logs_and_does_nothing(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(str),  # str data type is invalid
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_execute_subscribe_when_already_subscribed_does_not_add_and_logs(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual(2, self.data_engine.command_count)

    def test_execute_subscribe_custom_data(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.quandl)
        self.binance_client.connect()

        subscribe = Subscribe(
            provider="QUANDL",
            data_type=DataType(str, metadata={"Type": "news"}),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)
        self.assertEqual(["subscribe"], self.quandl.calls)

    def test_execute_unsubscribe_custom_data(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.quandl)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider="QUANDL",
            data_type=DataType(str, metadata={"Type": "news"}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider="QUANDL",
            data_type=DataType(str, metadata={"Type": "news"}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual(2, self.data_engine.command_count)
        self.assertEqual(["subscribe", "unsubscribe"], self.quandl.calls)

    def test_execute_unsubscribe_when_data_type_unrecognized_logs_and_does_nothing(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        handler = []
        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(type(str)),  # str data type is invalid
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_execute_unsubscribe_when_not_subscribed_logs_and_does_nothing(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(type(QuoteTick), metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_receive_response_when_no_data_clients_registered_does_nothing(self):
        # Arrange
        response = DataResponse(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick),
            data=[],
            correlation_id=self.uuid_factory.generate(),
            response_id=self.uuid_factory.generate(),
            response_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.receive(response)

        # Assert
        self.assertEqual(1, self.data_engine.response_count)

    def test_process_unrecognized_data_type_logs_and_does_nothing(self):
        # Arrange
        # Act
        self.data_engine.process("DATA!")  # Invalid

        # Assert
        self.assertEqual(1, self.data_engine.data_count)

    def test_process_data_places_data_on_queue(self):
        # Arrange
        tick = TestStubs.trade_tick_5decimal()

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual(1, self.data_engine.data_count)

    def test_execute_subscribe_instrument_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_instruments)

    def test_execute_unsubscribe_instrument_then_removes_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_instruments)

    def test_process_instrument_when_subscriber_then_sends_to_registered_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        # Act
        self.data_engine.process(ETHUSDT_BINANCE)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE], handler)

    def test_process_instrument_when_subscribers_then_sends_to_registered_handlers(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler1 = []
        subscribe1 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Instrument, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        # Act
        self.data_engine.process(ETHUSDT_BINANCE)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_instruments)
        self.assertEqual([ETHUSDT_BINANCE], handler1)
        self.assertEqual([ETHUSDT_BINANCE], handler2)

    def test_execute_subscribe_order_book_stream_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, metadata={
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 10,
                "Interval": 0,
            }),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books)

    def test_execute_subscribe_order_book_intervals_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, metadata={
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 25,
                "Interval": 10,
            }),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books)

    def test_execute_unsubscribe_order_book_stream_then_removes_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, metadata={
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 25,
                "Interval": 0,
            }),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, metadata={
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Interval": 0,
            }),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_order_books)

    def test_execute_unsubscribe_order_book_interval_then_removes_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, metadata={
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 25,
                "Interval": 10,
            }),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, metadata={
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Interval": 10,
            }),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_order_books)

    def test_process_order_book_when_one_subscriber_then_sends_to_registered_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        order_book = OrderBook(
            instrument_id=ETHUSDT_BINANCE.id,
            level=2,
            depth=25,
            price_precision=2,
            size_precision=5,
            bids=[],
            asks=[],
            update_id=0,
            timestamp=0,
        )

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, {
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 25,
                "Interval": 0,  # Streaming
            }),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        # Act
        self.data_engine.process(order_book)

        # Assert
        self.assertEqual(order_book, handler[0])

    def test_process_order_book_when_multiple_subscribers_then_sends_to_registered_handlers(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        order_book = OrderBook(
            instrument_id=ETHUSDT_BINANCE.id,
            level=2,
            depth=25,
            price_precision=2,
            size_precision=5,
            bids=[],
            asks=[],
            update_id=0,
            timestamp=0,
        )

        handler1 = []
        subscribe1 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, {
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 25,
                "Interval": 0,  # Streaming
            }),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(OrderBook, {
                "InstrumentId": ETHUSDT_BINANCE.id,
                "Level": 2,
                "Depth": 25,
                "Interval": 0,  # Streaming
            }),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        # Act
        self.data_engine.process(order_book)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_order_books)
        self.assertEqual(order_book, handler1[0])
        self.assertEqual(order_book, handler2[0])

    def test_execute_subscribe_for_quote_ticks(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks)

    def test_execute_unsubscribe_for_quote_ticks(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_quote_ticks)

    def test_process_quote_tick_when_subscriber_then_sends_to_registered_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        tick = QuoteTick(
            ETHUSDT_BINANCE.id,
            Price("100.003"),
            Price("100.003"),
            Quantity(1),
            Quantity(1),
            UNIX_EPOCH,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks)
        self.assertEqual([tick], handler)

    def test_process_quote_tick_when_subscribers_then_sends_to_registered_handlers(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler1 = []
        subscribe1 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(QuoteTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        tick = QuoteTick(
            ETHUSDT_BINANCE.id,
            Price("100.003"),
            Price("100.003"),
            Quantity(1),
            Quantity(1),
            UNIX_EPOCH,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_quote_ticks)
        self.assertEqual([tick], handler1)
        self.assertEqual([tick], handler2)

    def test_subscribe_trade_tick_then_subscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id], self.data_engine.subscribed_trade_ticks)

    def test_unsubscribe_trade_tick_then_unsubscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_trade_ticks)

    def test_process_trade_tick_when_subscriber_then_sends_to_registered_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        tick = TradeTick(
            ETHUSDT_BINANCE.id,
            Price("1050.00000"),
            Quantity(100),
            OrderSide.BUY,
            TradeMatchId("123456789"),
            UNIX_EPOCH,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([tick], handler)

    def test_process_trade_tick_when_subscribers_then_sends_to_registered_handlers(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler1 = []
        subscribe1 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(TradeTick, metadata={"InstrumentId": ETHUSDT_BINANCE.id}),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        tick = TradeTick(
            ETHUSDT_BINANCE.id,
            Price("1050.00000"),
            Quantity(100),
            OrderSide.BUY,
            TradeMatchId("123456789"),
            UNIX_EPOCH,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([tick], handler1)
        self.assertEqual([tick], handler2)

    def test_subscribe_bar_type_then_subscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True)

        handler = ObjectStorer()
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Bar, metadata={"BarType": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([bar_type], self.data_engine.subscribed_bars)

    def test_unsubscribe_bar_type_then_unsubscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True)

        handler = ObjectStorer()
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Bar, metadata={"BarType": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            provider=BINANCE.value,
            data_type=DataType(Bar, metadata={"BarType": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_bars)

    def test_process_bar_when_subscriber_then_sends_to_registered_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True)

        handler = ObjectStorer()
        subscribe = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Bar, metadata={"BarType": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe)

        bar = Bar(
            Price("1051.00000"),
            Price("1055.00000"),
            Price("1050.00000"),
            Price("1052.00000"),
            Quantity(100),
            UNIX_EPOCH,
        )

        data = BarData(bar_type, bar)

        # Act
        self.data_engine.process(data)

        # Assert
        self.assertEqual([(bar_type, bar)], handler.get_store())

    def test_process_bar_when_subscribers_then_sends_to_registered_handlers(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id, bar_spec, internal_aggregation=True)

        handler1 = ObjectStorer()
        subscribe1 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Bar, metadata={"BarType": bar_type}),
            handler=handler1.store_2,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        handler2 = ObjectStorer()
        subscribe2 = Subscribe(
            provider=BINANCE.value,
            data_type=DataType(Bar, metadata={"BarType": bar_type}),
            handler=handler2.store_2,
            command_id=self.uuid_factory.generate(),
            command_timestamp=self.clock.utc_now(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        bar = Bar(
            Price("1051.00000"),
            Price("1055.00000"),
            Price("1050.00000"),
            Price("1052.00000"),
            Quantity(100),
            UNIX_EPOCH,
        )

        data = BarData(bar_type, bar)

        # Act
        self.data_engine.process(data)

        # Assert
        self.assertEqual([(bar_type, bar)], handler1.get_store())
        self.assertEqual([(bar_type, bar)], handler2.get_store())
class DataEngineTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(self.clock)

        self.cache = TestStubs.cache()

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

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

        self.binance_client = BacktestMarketDataClient(
            client_id=ClientId(BINANCE.value),
            engine=self.data_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.bitmex_client = BacktestMarketDataClient(
            client_id=ClientId(BITMEX.value),
            engine=self.data_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.quandl = MockMarketDataClient(
            client_id=ClientId("QUANDL"),
            engine=self.data_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.data_engine.process(BTCUSDT_BINANCE)
        self.data_engine.process(ETHUSDT_BINANCE)
        self.data_engine.process(XBTUSD_BITMEX)

    def test_registered_venues(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.registered_clients)

    def test_subscribed_instruments_when_nothing_subscribed_returns_empty_list(
            self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_instruments)

    def test_subscribed_quote_ticks_when_nothing_subscribed_returns_empty_list(
            self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_quote_ticks)

    def test_subscribed_trade_ticks_when_nothing_subscribed_returns_empty_list(
            self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_trade_ticks)

    def test_subscribed_bars_when_nothing_subscribed_returns_empty_list(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual([], self.data_engine.subscribed_bars)

    def test_register_client_successfully_adds_client(self):
        # Arrange
        # Act
        self.data_engine.register_client(self.binance_client)

        # Assert
        self.assertIn(ClientId(BINANCE.value),
                      self.data_engine.registered_clients)

    def test_deregister_client_successfully_removes_client(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        # Act
        self.data_engine.deregister_client(self.binance_client)

        # Assert
        self.assertNotIn(BINANCE.value, self.data_engine.registered_clients)

    def test_register_strategy_successfully_registered_with_strategy(self):
        # Arrange
        strategy = TradingStrategy("000")

        # Act
        strategy.register_data_engine(self.data_engine)

        # Assert
        self.assertEqual(self.data_engine.cache, strategy.cache)

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

        # Assert
        self.assertEqual(0, self.data_engine.command_count)
        self.assertEqual(0, self.data_engine.data_count)
        self.assertEqual(0, self.data_engine.request_count)
        self.assertEqual(0, self.data_engine.response_count)

    def test_stop_and_resume(self):
        # Arrange
        self.data_engine.start()

        # Act
        self.data_engine.stop()
        self.data_engine.resume()
        self.data_engine.stop()
        self.data_engine.reset()

        # Assert
        self.assertEqual(0, self.data_engine.command_count)
        self.assertEqual(0, self.data_engine.data_count)
        self.assertEqual(0, self.data_engine.request_count)
        self.assertEqual(0, self.data_engine.response_count)

    def test_dispose(self):
        # Arrange
        self.data_engine.reset()

        # Act
        self.data_engine.dispose()

        # Assert
        self.assertEqual(0, self.data_engine.command_count)
        self.assertEqual(0, self.data_engine.data_count)
        self.assertEqual(0, self.data_engine.request_count)
        self.assertEqual(0, self.data_engine.response_count)

    def test_check_connected_when_client_disconnected_returns_false(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        self.binance_client.disconnect()
        self.bitmex_client.disconnect()

        # Act
        result = self.data_engine.check_connected()

        # Assert
        self.assertFalse(result)

    def test_check_connected_when_client_connected_returns_true(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        self.binance_client.connect()
        self.bitmex_client.connect()

        # Act
        result = self.data_engine.check_connected()

        # Assert
        self.assertTrue(result)

    def test_check_disconnected_when_client_disconnected_returns_true(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        # Act
        result = self.data_engine.check_disconnected()

        # Assert
        self.assertTrue(result)

    def test_check_disconnected_when_client_connected_returns_false(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.bitmex_client)

        self.binance_client.connect()
        self.bitmex_client.connect()

        # Act
        result = self.data_engine.check_disconnected()

        # Assert
        self.assertFalse(result)

    def test_reset_when_already_disposed_raises_invalid_state_trigger(self):
        # Arrange
        self.data_engine.dispose()

        # Act
        # Assert
        self.assertRaises(InvalidStateTrigger, self.data_engine.reset)

    def test_dispose_when_already_disposed_raises_invalid_state_trigger(self):
        # Arrange
        self.data_engine.dispose()

        # Act
        # Assert
        self.assertRaises(InvalidStateTrigger, self.data_engine.dispose)

    def test_execute_unrecognized_message_logs_and_does_nothing(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        # Bogus message
        command = DataCommand(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(str),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(command)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_send_request_when_no_data_clients_registered_does_nothing(self):
        # Arrange
        handler = []
        request = DataRequest(
            client_id=ClientId("RANDOM"),
            data_type=DataType(
                QuoteTick,
                metadata={
                    "instrument_id":
                    InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                    "from_datetime":
                    None,
                    "to_datetime":
                    None,
                    "limit":
                    1000,
                },
            ),
            callback=handler.append,
            request_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.send(request)

        # Assert
        self.assertEqual(1, self.data_engine.request_count)

    def test_send_data_request_when_data_type_unrecognized_logs_and_does_nothing(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        handler = []
        request = DataRequest(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                str,
                metadata={  # str data type is invalid
                    "instrument_id": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                    "from_datetime": None,
                    "to_datetime": None,
                    "limit": 1000,
                },
            ),
            callback=handler.append,
            request_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.send(request)

        # Assert
        self.assertEqual(1, self.data_engine.request_count)

    def test_send_data_request_with_duplicate_ids_logs_and_does_not_handle_second(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.start()

        handler = []
        uuid = self.uuid_factory.generate()  # We'll use this as a duplicate

        request1 = DataRequest(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                QuoteTick,
                metadata={  # str data type is invalid
                    "instrument_id": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                    "from_datetime": None,
                    "to_datetime": None,
                    "limit": 1000,
                },
            ),
            callback=handler.append,
            request_id=uuid,  # Duplicate
            timestamp_ns=self.clock.timestamp_ns(),
        )

        request2 = DataRequest(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                QuoteTick,
                metadata={  # str data type is invalid
                    "instrument_id": InstrumentId(Symbol("SOMETHING"), Venue("RANDOM")),
                    "from_datetime": None,
                    "to_datetime": None,
                    "limit": 1000,
                },
            ),
            callback=handler.append,
            request_id=uuid,  # Duplicate
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.send(request1)
        self.data_engine.send(request2)

        # Assert
        self.assertEqual(2, self.data_engine.request_count)

    def test_execute_subscribe_when_data_type_unrecognized_logs_and_does_nothing(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(str),  # str data type is invalid
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_execute_subscribe_when_already_subscribed_does_not_add_and_logs(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual(2, self.data_engine.command_count)

    def test_execute_subscribe_custom_data(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.quandl)
        self.binance_client.connect()

        subscribe = Subscribe(
            client_id=ClientId("QUANDL"),
            data_type=DataType(str, metadata={"Type": "news"}),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)
        self.assertEqual(["subscribe"], self.quandl.calls)

    def test_execute_unsubscribe_custom_data(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.data_engine.register_client(self.quandl)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId("QUANDL"),
            data_type=DataType(str, metadata={"Type": "news"}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId("QUANDL"),
            data_type=DataType(str, metadata={"Type": "news"}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual(2, self.data_engine.command_count)
        self.assertEqual(["subscribe", "unsubscribe"], self.quandl.calls)

    def test_execute_unsubscribe_when_data_type_unrecognized_logs_and_does_nothing(
        self, ):
        # Arrange
        self.data_engine.register_client(self.binance_client)

        handler = []
        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(type(str)),  # str data type is invalid
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_execute_unsubscribe_when_not_subscribed_logs_and_does_nothing(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(type(QuoteTick),
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual(1, self.data_engine.command_count)

    def test_receive_response_when_no_data_clients_registered_does_nothing(
            self):
        # Arrange
        response = DataResponse(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick),
            data=[],
            correlation_id=self.uuid_factory.generate(),
            response_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.receive(response)

        # Assert
        self.assertEqual(1, self.data_engine.response_count)

    def test_process_unrecognized_data_type_logs_and_does_nothing(self):
        # Arrange
        data = Data(0, 0)

        # Act
        self.data_engine.process(data)  # Invalid

        # Assert
        self.assertEqual(4, self.data_engine.data_count)

    def test_process_data_places_data_on_queue(self):
        # Arrange
        tick = TestStubs.trade_tick_5decimal()

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual(4, self.data_engine.data_count)

    def test_execute_subscribe_instrument_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Instrument,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_instruments)

    def test_execute_unsubscribe_instrument_then_removes_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Instrument,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Instrument,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_instruments)

    def test_process_instrument_when_subscriber_then_sends_to_registered_handler(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Instrument,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        # Act
        self.data_engine.process(ETHUSDT_BINANCE)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE], handler)

    def test_process_instrument_when_subscribers_then_sends_to_registered_handlers(
        self, ):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler1 = []
        subscribe1 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Instrument,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Instrument,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        # Act
        self.data_engine.process(ETHUSDT_BINANCE)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_instruments)
        self.assertEqual([ETHUSDT_BINANCE], handler1)
        self.assertEqual([ETHUSDT_BINANCE], handler2)

    def test_execute_subscribe_order_book_stream_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": 2,
                    "depth": 10,
                    "interval": 0,
                },
            ),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_order_books)

    def test_execute_subscribe_order_book_data_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBookData,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": 2,
                    "depth": 10,
                    "interval": 0,
                },
            ),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_order_book_data)

    def test_execute_subscribe_order_book_intervals_then_adds_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": 2,
                    "depth": 25,
                    "interval": 10,
                },
            ),
            handler=[].append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_order_books)

    def test_execute_unsubscribe_order_book_stream_then_removes_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": 2,
                    "depth": 25,
                    "interval": 0,
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "interval": 0,
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_order_books)

    def test_execute_unsubscribe_order_book_data_then_removes_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBookData,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": 2,
                    "depth": 25,
                    "interval": 0,
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBookData,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "interval": 0,
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_order_books)

    def test_execute_unsubscribe_order_book_interval_then_removes_handler(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": 2,
                    "depth": 25,
                    "interval": 10,
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                metadata={
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "interval": 10,
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_order_books)

    def test_process_order_book_snapshot_when_one_subscriber_then_sends_to_registered_handler(
        self, ):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        self.data_engine.process(
            ETHUSDT_BINANCE)  # <-- add necessary instrument for test

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                {
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": BookLevel.L2,
                    "depth": 25,
                    "interval": 0,  # Streaming
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        snapshot = OrderBookSnapshot(
            instrument_id=ETHUSDT_BINANCE.id,
            level=BookLevel.L2,
            bids=[[1000, 1]],
            asks=[[1001, 1]],
            ts_event_ns=0,
            ts_recv_ns=0,
        )

        # Act
        self.data_engine.process(snapshot)

        # Assert
        assert self.data_engine.subscribed_order_books == [ETHUSDT_BINANCE.id]
        assert handler[0].instrument_id == ETHUSDT_BINANCE.id
        assert type(handler[0]) == L2OrderBook

    def test_process_order_book_ops_then_sends_to_registered_handler(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        self.data_engine.process(
            ETHUSDT_BINANCE)  # <-- add necessary instrument for test

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                {
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": BookLevel.L2,
                    "depth": 25,
                    "interval": 0,  # Streaming
                },
            ),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        deltas = OrderBookDeltas(
            instrument_id=ETHUSDT_BINANCE.id,
            level=BookLevel.L2,
            deltas=[],
            ts_event_ns=0,
            ts_recv_ns=0,
        )

        # Act
        self.data_engine.process(deltas)

        # Assert
        assert self.data_engine.subscribed_order_books == [ETHUSDT_BINANCE.id]
        assert handler[0].instrument_id == ETHUSDT_BINANCE.id
        assert type(handler[0]) == L2OrderBook

    def test_process_order_book_when_multiple_subscribers_then_sends_to_registered_handlers(
        self, ):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        self.data_engine.process(
            ETHUSDT_BINANCE)  # <-- add necessary instrument for test

        handler1 = []
        subscribe1 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                {
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": BookLevel.L2,
                    "depth": 25,
                    "interval": 0,  # Streaming
                },
            ),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(
                OrderBook,
                {
                    "instrument_id": ETHUSDT_BINANCE.id,
                    "level": BookLevel.L2,
                    "depth": 25,
                    "interval": 0,  # Streaming
                },
            ),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        snapshot = OrderBookSnapshot(
            instrument_id=ETHUSDT_BINANCE.id,
            level=BookLevel.L2,
            bids=[[1000, 1]],
            asks=[[1001, 1]],
            ts_event_ns=0,
            ts_recv_ns=0,
        )

        # Act
        self.data_engine.process(snapshot)

        # Assert
        cached_book = self.data_engine.cache.order_book(ETHUSDT_BINANCE.id)
        assert self.data_engine.subscribed_order_books == [ETHUSDT_BINANCE.id]
        assert type(cached_book) == L2OrderBook
        assert cached_book.instrument_id == ETHUSDT_BINANCE.id
        assert handler1[0] == cached_book
        assert handler2[0] == cached_book

    def test_execute_subscribe_for_quote_ticks(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_quote_ticks)

    def test_execute_unsubscribe_for_quote_ticks(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_quote_ticks)

    def test_process_quote_tick_when_subscriber_then_sends_to_registered_handler(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        tick = QuoteTick(
            ETHUSDT_BINANCE.id,
            Price.from_str("100.003"),
            Price.from_str("100.003"),
            Quantity.from_int(1),
            Quantity.from_int(1),
            0,
            0,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_quote_ticks)
        self.assertEqual([tick], handler)

    def test_process_quote_tick_when_subscribers_then_sends_to_registered_handlers(
        self, ):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler1 = []
        subscribe1 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(QuoteTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        tick = QuoteTick(
            ETHUSDT_BINANCE.id,
            Price.from_str("100.003"),
            Price.from_str("100.003"),
            Quantity.from_int(1),
            Quantity.from_int(1),
            0,
            0,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_quote_ticks)
        self.assertEqual([tick], handler1)
        self.assertEqual([tick], handler2)

    def test_subscribe_trade_tick_then_subscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(TradeTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([ETHUSDT_BINANCE.id],
                         self.data_engine.subscribed_trade_ticks)

    def test_unsubscribe_trade_tick_then_unsubscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(TradeTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(TradeTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_trade_ticks)

    def test_process_trade_tick_when_subscriber_then_sends_to_registered_handler(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler = []
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(TradeTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        tick = TradeTick(
            ETHUSDT_BINANCE.id,
            Price.from_str("1050.00000"),
            Quantity.from_int(100),
            AggressorSide.BUY,
            "123456789",
            0,
            0,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([tick], handler)

    def test_process_trade_tick_when_subscribers_then_sends_to_registered_handlers(
        self, ):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        handler1 = []
        subscribe1 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(TradeTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler1.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        handler2 = []
        subscribe2 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(TradeTick,
                               metadata={"instrument_id": ETHUSDT_BINANCE.id}),
            handler=handler2.append,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        tick = TradeTick(
            ETHUSDT_BINANCE.id,
            Price.from_str("1050.00000"),
            Quantity.from_int(100),
            AggressorSide.BUY,
            "123456789",
            0,
            0,
        )

        # Act
        self.data_engine.process(tick)

        # Assert
        self.assertEqual([tick], handler1)
        self.assertEqual([tick], handler2)

    def test_subscribe_bar_type_then_subscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id,
                           bar_spec,
                           internal_aggregation=True)

        handler = ObjectStorer()
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Bar, metadata={"bar_type": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(subscribe)

        # Assert
        self.assertEqual([bar_type], self.data_engine.subscribed_bars)

    def test_unsubscribe_bar_type_then_unsubscribes(self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id,
                           bar_spec,
                           internal_aggregation=True)

        handler = ObjectStorer()
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Bar, metadata={"bar_type": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        unsubscribe = Unsubscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Bar, metadata={"bar_type": bar_type}),
            handler=handler.store_2,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        # Act
        self.data_engine.execute(unsubscribe)

        # Assert
        self.assertEqual([], self.data_engine.subscribed_bars)

    def test_process_bar_when_subscriber_then_sends_to_registered_handler(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id,
                           bar_spec,
                           internal_aggregation=True)

        handler = ObjectStorer()
        subscribe = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Bar, metadata={"bar_type": bar_type}),
            handler=handler.store,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe)

        bar = Bar(
            bar_type,
            Price.from_str("1051.00000"),
            Price.from_str("1055.00000"),
            Price.from_str("1050.00000"),
            Price.from_str("1052.00000"),
            Quantity.from_int(100),
            0,
            0,
        )

        # Act
        self.data_engine.process(bar)

        # Assert
        self.assertEqual([bar], handler.get_store())

    def test_process_bar_when_subscribers_then_sends_to_registered_handlers(
            self):
        # Arrange
        self.data_engine.register_client(self.binance_client)
        self.binance_client.connect()

        bar_spec = BarSpecification(1000, BarAggregation.TICK, PriceType.MID)
        bar_type = BarType(ETHUSDT_BINANCE.id,
                           bar_spec,
                           internal_aggregation=True)

        handler1 = ObjectStorer()
        subscribe1 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Bar, metadata={"bar_type": bar_type}),
            handler=handler1.store,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        handler2 = ObjectStorer()
        subscribe2 = Subscribe(
            client_id=ClientId(BINANCE.value),
            data_type=DataType(Bar, metadata={"bar_type": bar_type}),
            handler=handler2.store,
            command_id=self.uuid_factory.generate(),
            timestamp_ns=self.clock.timestamp_ns(),
        )

        self.data_engine.execute(subscribe1)
        self.data_engine.execute(subscribe2)

        bar = Bar(
            bar_type,
            Price.from_str("1051.00000"),
            Price.from_str("1055.00000"),
            Price.from_str("1050.00000"),
            Price.from_str("1052.00000"),
            Quantity.from_int(100),
            0,
            0,
        )

        # Act
        self.data_engine.process(bar)

        # Assert
        self.assertEqual([bar], handler1.get_store())
        self.assertEqual([bar], handler2.get_store())