def setup(self):
        # Fixture Setup
        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(self.clock)

        self.trader_id = TraderId("TESTER", "000")
        self.account_id = TestStubs.account_id()

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S", "001"),
            clock=self.clock,
        )

        self.random_order_factory = OrderFactory(
            trader_id=TraderId("RANDOM", "042"),
            strategy_id=StrategyId("S", "042"),
            clock=self.clock,
        )

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

        self.analyzer = PerformanceAnalyzer()

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

        self.database = BypassExecutionDatabase(trader_id=self.trader_id,
                                                logger=self.logger)
        self.engine = LiveExecutionEngine(
            loop=self.loop,
            database=self.database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.instrument_provider = InstrumentProvider()
        self.instrument_provider.add(AUDUSD_SIM)
        self.instrument_provider.add(GBPUSD_SIM)

        self.client = MockLiveExecutionClient(
            name=SIM.value,
            account_id=self.account_id,
            engine=self.engine,
            instrument_provider=self.instrument_provider,
            clock=self.clock,
            logger=self.logger,
        )

        self.engine.register_client(self.client)
예제 #2
0
    def setup(self):
        # Fixture Setup
        self.loop = asyncio.get_event_loop()
        self.loop.set_debug(True)

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

        self.trader_id = TestStubs.trader_id()

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S-001"),
            clock=self.clock,
        )

        self.random_order_factory = OrderFactory(
            trader_id=TraderId("RANDOM-042"),
            strategy_id=StrategyId("S-042"),
            clock=self.clock,
        )

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

        self.cache = TestStubs.cache()

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

        self.data_engine = LiveDataEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_engine = LiveExecutionEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.risk_engine = LiveRiskEngine(
            loop=self.loop,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.instrument_provider = InstrumentProvider()
        self.instrument_provider.add(AUDUSD_SIM)
        self.instrument_provider.add(GBPUSD_SIM)

        self.client = MockLiveExecutionClient(
            loop=self.loop,
            client_id=ClientId(SIM.value),
            venue_type=VenueType.ECN,
            account_id=TestStubs.account_id(),
            account_type=AccountType.CASH,
            base_currency=USD,
            instrument_provider=self.instrument_provider,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )
        self.portfolio.update_account(TestStubs.event_cash_account_state())
        self.exec_engine.register_client(self.client)

        self.cache.add_instrument(AUDUSD_SIM)
예제 #3
0
class TestLiveExecutionEngine:
    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(self.clock)

        self.trader_id = TestStubs.trader_id()

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S-001"),
            clock=self.clock,
        )

        self.random_order_factory = OrderFactory(
            trader_id=TraderId("RANDOM-042"),
            strategy_id=StrategyId("S-042"),
            clock=self.clock,
        )

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

        self.cache = TestStubs.cache()

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

        self.data_engine = LiveDataEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_engine = LiveExecutionEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.risk_engine = LiveRiskEngine(
            loop=self.loop,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.instrument_provider = InstrumentProvider()
        self.instrument_provider.add(AUDUSD_SIM)
        self.instrument_provider.add(GBPUSD_SIM)

        self.client = MockLiveExecutionClient(
            loop=self.loop,
            client_id=ClientId(SIM.value),
            venue_type=VenueType.ECN,
            account_id=TestStubs.account_id(),
            account_type=AccountType.CASH,
            base_currency=USD,
            instrument_provider=self.instrument_provider,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )
        self.portfolio.update_account(TestStubs.event_cash_account_state())
        self.exec_engine.register_client(self.client)

        self.cache.add_instrument(AUDUSD_SIM)

    def teardown(self):
        self.exec_engine.dispose()

    @pytest.mark.asyncio
    async def test_start_when_loop_not_running_logs(self):
        # Arrange, Act
        self.exec_engine.start()

        # Assert
        assert True  # No exceptions raised
        self.exec_engine.stop()

    @pytest.mark.asyncio
    async def test_message_qsize_at_max_blocks_on_put_command(self):
        # Arrange
        # Deregister test fixture ExecutionEngine from msgbus)
        self.msgbus.deregister(endpoint="ExecEngine.execute",
                               handler=self.exec_engine.execute)
        self.msgbus.deregister(endpoint="ExecEngine.process",
                               handler=self.exec_engine.process)

        self.exec_engine = LiveExecutionEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            config=LiveExecEngineConfig(qsize=1),
        )

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.exec_engine.execute(submit_order)
        self.exec_engine.execute(submit_order)
        await asyncio.sleep(0.1)

        # Assert
        assert self.exec_engine.qsize() == 1
        assert self.exec_engine.command_count == 0

    @pytest.mark.asyncio
    async def test_message_qsize_at_max_blocks_on_put_event(self):
        # Arrange
        # Deregister test fixture ExecutionEngine from msgbus)
        self.msgbus.deregister(endpoint="ExecEngine.execute",
                               handler=self.exec_engine.execute)
        self.msgbus.deregister(endpoint="ExecEngine.process",
                               handler=self.exec_engine.process)

        self.exec_engine = LiveExecutionEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            config=LiveExecEngineConfig(qsize=1),
        )

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        event = TestStubs.event_order_submitted(order)

        # Act
        self.exec_engine.execute(submit_order)
        self.exec_engine.process(event)  # Add over max size
        await asyncio.sleep(0.1)

        # Assert
        assert self.exec_engine.qsize() == 1
        assert self.exec_engine.command_count == 0

    @pytest.mark.asyncio
    async def test_start(self):
        # Arrange, Act
        self.exec_engine.start()
        await asyncio.sleep(0.1)

        # Assert
        assert self.exec_engine.is_running

        # Tear Down
        self.exec_engine.stop()

    @pytest.mark.asyncio
    async def test_kill_when_running_and_no_messages_on_queues(self):
        # Arrange, Act
        self.exec_engine.start()
        await asyncio.sleep(0)
        self.exec_engine.kill()

        # Assert
        assert self.exec_engine.is_stopped

    @pytest.mark.asyncio
    async def test_kill_when_not_running_with_messages_on_queue(self):
        # Arrange, Act
        self.exec_engine.kill()

        # Assert
        assert self.exec_engine.qsize() == 0

    @pytest.mark.asyncio
    async def test_execute_command_places_command_on_queue(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.exec_engine.execute(submit_order)
        await asyncio.sleep(0.1)

        # Assert
        assert self.exec_engine.qsize() == 0
        assert self.exec_engine.command_count == 1

        # Tear Down
        self.exec_engine.stop()

    @pytest.mark.asyncio
    async def test_reconcile_state_with_no_active_orders(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Act
        await self.exec_engine.reconcile_state(timeout_secs=10)
        self.exec_engine.stop()
        await asyncio.sleep(0.1)

        # Assert
        assert True  # No exceptions raised

    @pytest.mark.asyncio
    async def test_reconcile_state_when_report_agrees_reconciles(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.execute(submit_order)
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.ACCEPTED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        self.client.add_order_status_report(report)

        await asyncio.sleep(0.1)  # Allow processing time

        # Act
        result = await self.exec_engine.reconcile_state(timeout_secs=10)
        self.exec_engine.stop()

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_canceled_reconciles(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.execute(submit_order)
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.CANCELED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        self.client.add_order_status_report(report)

        await asyncio.sleep(0.1)  # Allow processing time

        # Act
        result = await self.exec_engine.reconcile_state(timeout_secs=10)
        self.exec_engine.stop()

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_expired_reconciles(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.execute(submit_order)
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.EXPIRED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        self.client.add_order_status_report(report)

        await asyncio.sleep(0.1)  # Allow processing time

        # Act
        result = await self.exec_engine.reconcile_state(timeout_secs=10)
        self.exec_engine.stop()

        # Assert
        assert result

    @pytest.mark.skip(reason="reimplement reconciliation")
    @pytest.mark.asyncio
    async def test_reconcile_state_when_partially_filled_reconciles(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.execute(submit_order)
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.PARTIALLY_FILLED,
            filled_qty=Quantity.from_int(70000),
            ts_init=0,
        )

        trade1 = ExecutionReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),
            venue_position_id=None,
            execution_id=ExecutionId("1"),
            last_qty=Quantity.from_int(50000),
            last_px=Price.from_str("1.00000"),
            commission=Money(5.00, USD),
            liquidity_side=LiquiditySide.MAKER,
            ts_event=0,
            ts_init=0,
        )

        trade2 = ExecutionReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),
            venue_position_id=None,
            execution_id=ExecutionId("2"),
            last_qty=Quantity.from_int(20000),
            last_px=Price.from_str("1.00000"),
            commission=Money(2.00, USD),
            liquidity_side=LiquiditySide.MAKER,
            ts_event=0,
            ts_init=0,
        )

        self.client.add_order_status_report(report)
        self.client.add_trades_list(VenueOrderId("1"), [trade1, trade2])

        await asyncio.sleep(0.1)  # Allow processing time

        # Act
        result = await self.exec_engine.reconcile_state(timeout_secs=10)
        self.exec_engine.stop()

        # Assert
        assert result

    @pytest.mark.skip(reason="reimplement reconciliation")
    @pytest.mark.asyncio
    async def test_reconcile_state_when_filled_reconciles(self):
        # Arrange
        self.exec_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.execute(submit_order)
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.FILLED,
            filled_qty=Quantity.from_int(100000),
            ts_init=0,
        )

        trade1 = ExecutionReport(
            execution_id=ExecutionId("1"),
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),
            venue_position_id=None,
            last_qty=Quantity.from_int(50000),
            last_px=Price.from_str("1.00000"),
            commission=Money(5.00, USD),
            liquidity_side=LiquiditySide.MAKER,
            ts_event=0,
            ts_init=0,
        )

        trade2 = ExecutionReport(
            execution_id=ExecutionId("2"),
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),
            venue_position_id=None,
            last_qty=Quantity.from_int(50000),
            last_px=Price.from_str("1.00000"),
            commission=Money(2.00, USD),
            liquidity_side=LiquiditySide.MAKER,
            ts_event=0,
            ts_init=0,
        )

        self.client.add_order_status_report(report)
        self.client.add_trades_list(VenueOrderId("1"), [trade1, trade2])

        await asyncio.sleep(0.1)  # Allow processing time

        # Act
        result = await self.exec_engine.reconcile_state(timeout_secs=10)
        self.exec_engine.stop()

        # Assert
        assert result
class TestLiveExecutionEngine:
    def setup(self):
        # Fixture Setup
        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(self.clock)

        self.trader_id = TraderId("TESTER", "000")
        self.account_id = TestStubs.account_id()

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S", "001"),
            clock=self.clock,
        )

        self.random_order_factory = OrderFactory(
            trader_id=TraderId("RANDOM", "042"),
            strategy_id=StrategyId("S", "042"),
            clock=self.clock,
        )

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

        self.analyzer = PerformanceAnalyzer()

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

        self.database = BypassExecutionDatabase(trader_id=self.trader_id,
                                                logger=self.logger)
        self.engine = LiveExecutionEngine(
            loop=self.loop,
            database=self.database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.instrument_provider = InstrumentProvider()
        self.instrument_provider.add(AUDUSD_SIM)
        self.instrument_provider.add(GBPUSD_SIM)

        self.client = MockLiveExecutionClient(
            name=SIM.value,
            account_id=self.account_id,
            engine=self.engine,
            instrument_provider=self.instrument_provider,
            clock=self.clock,
            logger=self.logger,
        )

        self.engine.register_client(self.client)

    def teardown(self):
        self.engine.dispose()
        self.loop.stop()
        self.loop.close()

    def test_start_when_loop_not_running_logs(self):
        # Arrange
        # Act
        self.engine.start()

        # Assert
        assert True  # No exceptions raised
        self.engine.stop()

    def test_get_event_loop_returns_expected_loop(self):
        # Arrange
        # Act
        loop = self.engine.get_event_loop()

        # Assert
        assert loop == self.loop

    def test_message_qsize_at_max_blocks_on_put_command(self):
        # Arrange
        self.engine = LiveExecutionEngine(
            loop=self.loop,
            database=self.database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
            config={"qsize": 1},
        )

        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            self.clock,
            self.logger,
        )

        self.engine.register_strategy(strategy)

        order = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity(100000),
        )

        submit_order = SubmitOrder(
            order.instrument_id,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.engine.execute(submit_order)
        self.engine.execute(submit_order)

        # Assert
        assert self.engine.qsize() == 1
        assert self.engine.command_count == 0

    def test_message_qsize_at_max_blocks_on_put_event(self):
        # Arrange
        self.engine = LiveExecutionEngine(
            loop=self.loop,
            database=self.database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
            config={"qsize": 1},
        )

        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            self.clock,
            self.logger,
        )

        self.engine.register_strategy(strategy)

        order = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity(100000),
        )

        submit_order = SubmitOrder(
            order.instrument_id,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        event = TestStubs.event_order_submitted(order)

        # Act
        self.engine.execute(submit_order)
        self.engine.process(event)  # Add over max size

        # Assert
        assert self.engine.qsize() == 1
        assert self.engine.command_count == 0

    def test_start(self):
        async def run_test():
            # Arrange
            # Act
            self.engine.start()
            await asyncio.sleep(0.1)

            # Assert
            assert self.engine.state == ComponentState.RUNNING

            # Tear Down
            self.engine.stop()

        self.loop.run_until_complete(run_test())

    def test_kill_when_running_and_no_messages_on_queues(self):
        async def run_test():
            # Arrange
            # Act
            self.engine.start()
            await asyncio.sleep(0)
            self.engine.kill()

            # Assert
            assert self.engine.state == ComponentState.STOPPED

        self.loop.run_until_complete(run_test())

    def test_kill_when_not_running_with_messages_on_queue(self):
        async def run_test():
            # Arrange
            # Act
            self.engine.kill()

            # Assert
            assert self.engine.qsize() == 0

        self.loop.run_until_complete(run_test())

    def test_execute_command_places_command_on_queue(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.market(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
            )

            submit_order = SubmitOrder(
                order.instrument_id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            # Act
            self.engine.execute(submit_order)
            await asyncio.sleep(0.1)

            # Assert
            assert self.engine.qsize() == 0
            assert self.engine.command_count == 1

            # Tear Down
            self.engine.stop()

        self.loop.run_until_complete(run_test())

    def test_handle_position_opening_with_position_id_none(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.market(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
            )

            event = TestStubs.event_order_submitted(order)

            # Act
            self.engine.process(event)
            await asyncio.sleep(0.1)

            # Assert
            assert self.engine.qsize() == 0
            assert self.engine.event_count == 1

            # Tear Down
            self.engine.stop()

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_with_no_active_orders(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            # Act
            await self.engine.reconcile_state()
            self.engine.stop()

            # Assert
            assert True  # No exceptions raised

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_report_agrees_reconciles(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            self.engine.process(TestStubs.event_order_accepted(order))

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.ACCEPTED,
                filled_qty=Quantity(0),
                timestamp_ns=0,
            )

            self.client.add_order_status_report(report)

            await asyncio.sleep(0.1)  # Allow processing time

            # Act
            result = await self.engine.reconcile_state()
            self.engine.stop()

            # Assert
            assert result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_cancelled_reconciles(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            self.engine.process(TestStubs.event_order_accepted(order))

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.CANCELLED,
                filled_qty=Quantity(0),
                timestamp_ns=0,
            )

            self.client.add_order_status_report(report)

            await asyncio.sleep(0.1)  # Allow processing time

            # Act
            result = await self.engine.reconcile_state()
            self.engine.stop()

            # Assert
            assert result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_expired_reconciles(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            self.engine.process(TestStubs.event_order_accepted(order))

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.EXPIRED,
                filled_qty=Quantity(0),
                timestamp_ns=0,
            )

            self.client.add_order_status_report(report)

            await asyncio.sleep(0.01)

            # Act
            result = await self.engine.reconcile_state()
            self.engine.stop()

            # Assert
            assert result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_partially_filled_reconciles(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            self.engine.process(TestStubs.event_order_accepted(order))

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.PARTIALLY_FILLED,
                filled_qty=Quantity(70000),
                timestamp_ns=0,
            )

            trade1 = ExecutionReport(
                execution_id=ExecutionId("1"),
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),
                last_qty=Decimal(50000),
                last_px=Decimal("1.00000"),
                commission_amount=Decimal("5.0"),
                commission_currency="USD",
                liquidity_side=LiquiditySide.MAKER,
                execution_ns=0,
                timestamp_ns=0,
            )

            trade2 = ExecutionReport(
                execution_id=ExecutionId("2"),
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),
                last_qty=Decimal(20000),
                last_px=Decimal("1.00000"),
                commission_amount=Decimal("2.0"),
                commission_currency="USD",
                liquidity_side=LiquiditySide.MAKER,
                execution_ns=0,
                timestamp_ns=0,
            )

            self.client.add_order_status_report(report)
            self.client.add_trades_list(VenueOrderId("1"), [trade1, trade2])

            await asyncio.sleep(0.01)

            # Act
            result = await self.engine.reconcile_state()
            self.engine.stop()

            # Assert
            assert result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_filled_reconciles(self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            self.engine.process(TestStubs.event_order_accepted(order))

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.FILLED,
                filled_qty=Quantity(100000),
                timestamp_ns=0,
            )

            trade1 = ExecutionReport(
                execution_id=ExecutionId("1"),
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),
                last_qty=Decimal(50000),
                last_px=Decimal("1.00000"),
                commission_amount=Decimal("5.0"),
                commission_currency="USD",
                liquidity_side=LiquiditySide.MAKER,
                execution_ns=0,
                timestamp_ns=0,
            )

            trade2 = ExecutionReport(
                execution_id=ExecutionId("2"),
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),
                last_qty=Decimal(50000),
                last_px=Decimal("1.00000"),
                commission_amount=Decimal("2.0"),
                commission_currency="USD",
                liquidity_side=LiquiditySide.MAKER,
                execution_ns=0,
                timestamp_ns=0,
            )

            self.client.add_order_status_report(report)
            self.client.add_trades_list(VenueOrderId("1"), [trade1, trade2])

            await asyncio.sleep(0.01)

            # Act
            result = await self.engine.reconcile_state()
            self.engine.stop()

            # Assert
            assert result

        self.loop.run_until_complete(run_test())
class TestLiveExecutionClient:
    def setup(self):
        # Fixture Setup
        # Fresh isolated loop testing pattern
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)

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

        self.trader_id = TraderId("TESTER", "000")
        self.account_id = TestStubs.account_id()

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S", "001"),
            clock=self.clock,
        )

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

        self.analyzer = PerformanceAnalyzer()

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

        database = BypassExecutionDatabase(trader_id=self.trader_id,
                                           logger=self.logger)
        self.engine = LiveExecutionEngine(
            loop=self.loop,
            database=database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.client = MockLiveExecutionClient(
            name=SIM.value,
            account_id=self.account_id,
            engine=self.engine,
            instrument_provider=InstrumentProvider(),
            clock=self.clock,
            logger=self.logger,
        )

        self.engine.register_client(self.client)

    def teardown(self):
        self.client.dispose()

    def test_reconcile_state_given_no_order_and_not_in_cache_returns_false(
            self):
        async def run_test():
            # Arrange
            report = OrderStatusReport(
                client_order_id=ClientOrderId("O-123456"),
                venue_order_id=VenueOrderId("1"),
                order_state=OrderState.FILLED,
                filled_qty=Quantity(100000),
                timestamp_ns=0,
            )

            # Act
            result = await self.client.reconcile_state(
                report, order=None)  # <- order won't be in cache

            # Assert
            assert not result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_order_completed_returns_true_with_warning1(
            self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.stop_market(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            await asyncio.sleep(0)  # Process queue
            self.engine.process(TestStubs.event_order_accepted(order))
            await asyncio.sleep(0)  # Process queue
            self.engine.process(TestStubs.event_order_cancelled(order))
            await asyncio.sleep(0)  # Process queue

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.CANCELLED,
                filled_qty=Quantity(0),
                timestamp_ns=0,
            )

            # Act
            result = await self.client.reconcile_state(report, order)

            # Assert
            assert result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_when_order_completed_returns_true_with_warning2(
            self):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            await asyncio.sleep(0)  # Process queue
            self.engine.process(TestStubs.event_order_accepted(order))
            await asyncio.sleep(0)  # Process queue
            self.engine.process(TestStubs.event_order_filled(
                order, AUDUSD_SIM))
            await asyncio.sleep(0)  # Process queue

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.FILLED,
                filled_qty=Quantity(100000),
                timestamp_ns=0,
            )

            # Act
            result = await self.client.reconcile_state(report, order)

            # Assert
            assert result

        self.loop.run_until_complete(run_test())

    def test_reconcile_state_with_filled_order_when_trades_not_given_returns_false(
        self, ):
        async def run_test():
            # Arrange
            self.engine.start()

            strategy = TradingStrategy(order_id_tag="001")
            strategy.register_trader(
                TraderId("TESTER", "000"),
                self.clock,
                self.logger,
            )

            self.engine.register_strategy(strategy)

            order = strategy.order_factory.limit(
                AUDUSD_SIM.id,
                OrderSide.BUY,
                Quantity(100000),
                Price("1.00000"),
            )

            submit_order = SubmitOrder(
                AUDUSD_SIM.id,
                self.trader_id,
                self.account_id,
                strategy.id,
                PositionId.null(),
                order,
                self.uuid_factory.generate(),
                self.clock.timestamp_ns(),
            )

            self.engine.execute(submit_order)
            self.engine.process(TestStubs.event_order_submitted(order))
            await asyncio.sleep(0)  # Process queue
            self.engine.process(TestStubs.event_order_accepted(order))
            await asyncio.sleep(0)  # Process queue

            report = OrderStatusReport(
                client_order_id=order.client_order_id,
                venue_order_id=VenueOrderId("1"),  # <-- from stub event
                order_state=OrderState.FILLED,
                filled_qty=Quantity(100000),
                timestamp_ns=0,
            )

            # Act
            result = await self.client.reconcile_state(report, order)

            # Assert
            assert not result

        self.loop.run_until_complete(run_test())
예제 #6
0
    def setup(self):
        # Fixture Setup
        self.clock = LiveClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(self.clock)

        self.trader_id = TraderId("TESTER-000")

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S-001"),
            clock=self.clock,
        )

        self.random_order_factory = OrderFactory(
            trader_id=TraderId("RANDOM-042"),
            strategy_id=StrategyId("S-042"),
            clock=self.clock,
        )

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

        self.cache = TestStubs.cache()

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

        self.exec_engine = LiveExecutionEngine(
            loop=self.loop,
            portfolio=self.portfolio,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.risk_engine = LiveRiskEngine(
            loop=self.loop,
            exec_engine=self.exec_engine,
            portfolio=self.portfolio,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.instrument_provider = InstrumentProvider()
        self.instrument_provider.add(AUDUSD_SIM)
        self.instrument_provider.add(GBPUSD_SIM)

        self.client = MockLiveExecutionClient(
            client_id=ClientId(SIM.value),
            venue_type=VenueType.ECN,
            account_id=TestStubs.account_id(),
            account_type=AccountType.CASH,
            base_currency=USD,
            engine=self.exec_engine,
            instrument_provider=self.instrument_provider,
            clock=self.clock,
            logger=self.logger,
        )

        # Wired up components
        self.exec_engine.register_risk_engine(self.risk_engine)
        self.exec_engine.register_client(self.client)
class TestLiveExecutionClient:
    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 = LiveLogger(self.loop, self.clock)

        self.trader_id = TestStubs.trader_id()

        self.order_factory = OrderFactory(
            trader_id=self.trader_id,
            strategy_id=StrategyId("S-001"),
            clock=self.clock,
        )

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

        self.cache = TestStubs.cache()

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

        self.data_engine = LiveDataEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_engine = LiveExecutionEngine(
            loop=self.loop,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.risk_engine = LiveRiskEngine(
            loop=self.loop,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.client = MockLiveExecutionClient(
            loop=self.loop,
            client_id=ClientId(SIM.value),
            venue_type=VenueType.ECN,
            account_id=TestStubs.account_id(),
            account_type=AccountType.CASH,
            base_currency=USD,
            instrument_provider=InstrumentProvider(),
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )
        self.portfolio.update_account(TestStubs.event_cash_account_state())
        self.exec_engine.register_client(self.client)

        # Prepare components
        self.cache.add_instrument(AUDUSD_SIM)

    def teardown(self):
        self.client.dispose()

    @pytest.mark.asyncio
    async def test_reconcile_state_given_no_order_and_not_in_cache_returns_false(
            self):
        # Arrange
        report = OrderStatusReport(
            client_order_id=ClientOrderId("O-123456"),
            venue_order_id=VenueOrderId("1"),
            order_status=OrderStatus.FILLED,
            filled_qty=Quantity.from_int(100000),
            ts_init=0,
        )

        # Act
        result = await self.client.reconcile_state(
            report, order=None)  # <- order won't be in the cache

        # Assert
        assert not result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_order_rejected_generates_event(self):
        # Arrange
        self.exec_engine.start()
        self.risk_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order)
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        await asyncio.sleep(0)  # Process queue

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.REJECTED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        # Act
        result = self.client.reconcile_state(report, order)

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_order_expired_generates_event(self):
        # Arrange
        self.exec_engine.start()
        self.risk_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order)
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        await asyncio.sleep(0)  # Process queue

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.EXPIRED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        # Act
        result = self.client.reconcile_state(report, order)

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_order_canceled_generates_event(self):
        # Arrange
        self.exec_engine.start()
        self.risk_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order)
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        await asyncio.sleep(0)  # Process queue

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.CANCELED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        # Act
        result = self.client.reconcile_state(report, order)

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_order_completed_returns_true_with_warning1(
            self):
        # Arrange
        self.exec_engine.start()
        self.risk_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order)
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_canceled(order))
        await asyncio.sleep(0)  # Process queue

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.CANCELED,
            filled_qty=Quantity.zero(),
            ts_init=0,
        )

        # Act
        result = await self.client.reconcile_state(report, order)

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_when_order_completed_returns_true_with_warning2(
            self):
        # Arrange
        self.exec_engine.start()
        self.risk_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order)
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(
            TestStubs.event_order_filled(order, AUDUSD_SIM))
        await asyncio.sleep(0)  # Process queue

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.FILLED,
            filled_qty=Quantity.from_int(100000),
            ts_init=0,
        )

        # Act
        result = await self.client.reconcile_state(report, order)

        # Assert
        assert result

    @pytest.mark.asyncio
    async def test_reconcile_state_with_filled_order_when_trades_not_given_returns_false(
        self, ):
        # Arrange
        self.exec_engine.start()
        self.risk_engine.start()

        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order = strategy.order_factory.limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

        submit_order = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order)
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        await asyncio.sleep(0)  # Process queue
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        await asyncio.sleep(0)  # Process queue

        report = OrderStatusReport(
            client_order_id=order.client_order_id,
            venue_order_id=VenueOrderId("1"),  # <-- from stub event
            order_status=OrderStatus.FILLED,
            filled_qty=Quantity.from_int(100000),
            ts_init=0,
        )

        # Act
        result = await self.client.reconcile_state(report, order)

        # Assert
        assert not result