Esempio n. 1
0
class TestRiskEngine:

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

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

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

        self.database = MockExecutionDatabase(trader_id=self.trader_id, logger=self.logger)
        self.exec_engine = ExecutionEngine(
            database=self.database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_client = MockExecutionClient(
            self.venue,
            self.account_id,
            self.exec_engine,
            self.clock,
            self.logger,
        )

        self.risk_engine = RiskEngine(
            exec_engine=self.exec_engine,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
            config={},
        )

        self.exec_engine.register_client(self.exec_client)
        self.exec_engine.register_risk_engine(self.risk_engine)

    def test_registered_clients_returns_expected_list(self):
        # Arrange
        # Act
        result = self.risk_engine.registered_clients

        # Assert
        assert result == [Venue('SIM')]

    def test_set_block_all_orders_changes_flag_value(self):
        # Arrange
        # Act
        self.risk_engine.set_block_all_orders()

        # Assert
        assert self.risk_engine.block_all_orders

    def test_given_random_command_logs_and_continues(self):
        # Arrange
        random = TradingCommand(
            self.venue,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.risk_engine.execute(random)

    def test_given_random_event_logs_and_continues(self):
        # Arrange
        random = Event(
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.exec_engine.process(random)

    def test_submit_order_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_client.calls == ['connect', 'submit_order']

    def test_submit_bracket_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        bracket = strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price("1.00000"),
            take_profit=Price("1.00010"),
        )

        submit_bracket = SubmitBracketOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.exec_client.calls == ['connect', 'submit_bracket_order']

    def test_submit_order_when_block_all_orders_true_then_denies_order(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        self.risk_engine.set_block_all_orders()

        # Act
        self.exec_engine.execute(submit_order)

        # Assert
        assert self.exec_client.calls == ['connect']
        assert self.exec_engine.event_count == 1

    def test_amend_order_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        submit = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        amend = AmendOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            order.cl_ord_id,
            order.quantity,
            Price("1.00010"),
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.risk_engine.execute(submit)

        # Act
        self.risk_engine.execute(amend)

        # Assert
        assert self.exec_client.calls == ['connect', 'submit_order', 'amend_order']

    def test_cancel_order_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        submit = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        cancel = CancelOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            order.cl_ord_id,
            order.id,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.risk_engine.execute(submit)

        # Act
        self.risk_engine.execute(cancel)

        # Assert
        assert self.exec_client.calls == ['connect', 'submit_order', 'cancel_order']

    def test_submit_bracket_when_block_all_orders_true_then_denies_order(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        bracket = strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price("1.00000"),
            take_profit=Price("1.00010"),
        )

        submit_bracket = SubmitBracketOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.risk_engine.set_block_all_orders()

        # Act
        self.exec_engine.execute(submit_bracket)

        # Assert
        assert self.exec_client.calls == ['connect']
        assert self.exec_engine.event_count == 3
Esempio n. 2
0
class TraderTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        clock = TestClock()
        logger = Logger(clock)

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

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

        self.data_engine = DataEngine(
            portfolio=self.portfolio,
            clock=clock,
            logger=logger,
            config={"use_previous_close": False},
        )

        self.portfolio.register_cache(self.data_engine.cache)
        self.analyzer = PerformanceAnalyzer()

        self.exec_db = BypassExecutionDatabase(
            trader_id=trader_id,
            logger=logger,
        )

        self.exec_engine = ExecutionEngine(
            database=self.exec_db,
            portfolio=self.portfolio,
            clock=clock,
            logger=logger,
        )

        self.exchange = SimulatedExchange(
            venue=Venue("SIM"),
            oms_type=OMSType.HEDGING,
            is_frozen_account=False,
            starting_balances=[Money(1_000_000, USD)],
            exec_cache=self.exec_engine.cache,
            instruments=[USDJPY_SIM],
            modules=[],
            fill_model=FillModel(),
            clock=clock,
            logger=logger,
        )

        self.data_client = BacktestMarketDataClient(
            instruments=[USDJPY_SIM],
            client_id=ClientId("SIM"),
            engine=self.data_engine,
            clock=clock,
            logger=logger,
        )

        self.data_engine.register_client(self.data_client)

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            account_id=account_id,
            engine=self.exec_engine,
            clock=clock,
            logger=logger,
        )

        self.risk_engine = RiskEngine(
            exec_engine=self.exec_engine,
            portfolio=self.portfolio,
            clock=clock,
            logger=logger,
        )

        self.exec_engine.register_risk_engine(self.risk_engine)
        self.exec_engine.register_client(self.exec_client)

        strategies = [
            TradingStrategy("001"),
            TradingStrategy("002"),
        ]

        self.trader = Trader(
            trader_id=trader_id,
            strategies=strategies,
            portfolio=self.portfolio,
            data_engine=self.data_engine,
            exec_engine=self.exec_engine,
            risk_engine=self.risk_engine,
            clock=clock,
            logger=logger,
        )

    def test_initialize_trader(self):
        # Arrange
        # Act
        trader_id = self.trader.id

        # Assert
        self.assertEqual(TraderId("TESTER", "000"), trader_id)
        self.assertEqual(IdTag("000"), trader_id.tag)
        self.assertEqual(ComponentState.INITIALIZED, self.trader.state)
        self.assertEqual(2, len(self.trader.strategy_states()))

    def test_get_strategy_states(self):
        # Arrange
        # Act
        status = self.trader.strategy_states()

        # Assert
        self.assertTrue(StrategyId("TradingStrategy", "001") in status)
        self.assertTrue(StrategyId("TradingStrategy", "002") in status)
        self.assertEqual("INITIALIZED",
                         status[StrategyId("TradingStrategy", "001")])
        self.assertEqual("INITIALIZED",
                         status[StrategyId("TradingStrategy", "002")])
        self.assertEqual(2, len(status))

    def test_change_strategies(self):
        # Arrange
        strategies = [
            TradingStrategy("003"),
            TradingStrategy("004"),
        ]

        # Act
        self.trader.initialize_strategies(strategies, warn_no_strategies=True)

        # Assert
        self.assertTrue(strategies[0].id in self.trader.strategy_states())
        self.assertTrue(strategies[1].id in self.trader.strategy_states())
        self.assertEqual(2, len(self.trader.strategy_states()))

    def test_trader_detects_duplicate_identifiers(self):
        # Arrange
        strategies = [
            TradingStrategy("000"),
            TradingStrategy("000"),
        ]

        # Act
        self.assertRaises(
            ValueError,
            self.trader.initialize_strategies,
            strategies,
            True,
        )

    def test_start_a_trader(self):
        # Arrange
        # Act
        self.trader.start()

        strategy_states = self.trader.strategy_states()

        # Assert
        self.assertEqual(ComponentState.RUNNING, self.trader.state)
        self.assertEqual("RUNNING",
                         strategy_states[StrategyId("TradingStrategy", "001")])
        self.assertEqual("RUNNING",
                         strategy_states[StrategyId("TradingStrategy", "002")])

    def test_stop_a_running_trader(self):
        # Arrange
        self.trader.start()

        # Act
        self.trader.stop()

        strategy_states = self.trader.strategy_states()

        # Assert
        self.assertEqual(ComponentState.STOPPED, self.trader.state)
        self.assertEqual("STOPPED",
                         strategy_states[StrategyId("TradingStrategy", "001")])
        self.assertEqual("STOPPED",
                         strategy_states[StrategyId("TradingStrategy", "002")])
Esempio n. 3
0
class ExecutionEngineTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = TestLogger(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=TestClock(),
        )

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

        self.analyzer = PerformanceAnalyzer()

        database = BypassExecutionDatabase(trader_id=self.trader_id,
                                           logger=self.logger)
        self.exec_engine = ExecutionEngine(
            database=database,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.cache = self.exec_engine.cache
        self.exec_engine.process(TestStubs.event_account_state())

        self.venue = Venue("SIM")
        self.exec_client = MockExecutionClient(
            self.venue,
            self.account_id,
            self.exec_engine,
            self.clock,
            self.logger,
        )

        self.exec_engine.register_client(self.exec_client)

    def test_registered_venues_returns_expected(self):
        # Arrange
        # Act
        result = self.exec_engine.registered_venues

        # Assert
        self.assertEqual([Venue("SIM")], result)

    def test_deregister_client_removes_client(self):
        # Arrange
        # Act
        self.exec_engine.deregister_client(self.exec_client)

        # Assert
        self.assertEqual([], self.exec_engine.registered_venues)

    def test_register_strategy(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            self.trader_id,
            self.clock,
            self.logger,
        )

        # Act
        self.exec_engine.register_strategy(strategy)

        # Assert
        self.assertIn(strategy.id, self.exec_engine.registered_strategies)

    def test_deregister_strategy(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            self.clock,
            self.logger,
        )

        self.exec_engine.register_strategy(strategy)

        # Act
        self.exec_engine.deregister_strategy(strategy)

        # Assert
        self.assertNotIn(strategy.id, self.exec_engine.registered_strategies)

    def test_reset_retains_registered_strategies(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            self.clock,
            self.logger,
        )

        self.exec_engine.register_strategy(
            strategy)  # Also registers with portfolio

        # Act
        self.exec_engine.reset()

        # Assert
        self.assertIn(strategy.id, self.exec_engine.registered_strategies)

    def test_integrity_check_calls_check_on_cache(self):
        # Arrange
        # Act
        self.exec_engine.integrity_check()

        # Assert
        self.assertTrue(True)  # No exceptions raised

    def test_submit_order(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        # Act
        self.exec_engine.execute(submit_order)

        # Assert
        self.assertIn(submit_order, self.exec_client.commands)
        self.assertTrue(self.cache.order_exists(order.cl_ord_id))

    def test_handle_order_fill_event(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        self.exec_engine.execute(submit_order)

        # Act
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        self.exec_engine.process(
            TestStubs.event_order_filled(order, AUDUSD_SIM))

        expected_position_id = PositionId(
            "O-19700101-000000-000-001-1")  # Stubbed from order id?

        # Assert
        self.assertTrue(self.cache.position_exists(expected_position_id))
        self.assertTrue(self.cache.is_position_open(expected_position_id))
        self.assertFalse(self.cache.is_position_closed(expected_position_id))
        self.assertEqual(Position,
                         type(self.cache.position(expected_position_id)))
        self.assertIn(expected_position_id, self.cache.position_ids())
        self.assertNotIn(
            expected_position_id,
            self.cache.position_closed_ids(strategy_id=strategy.id))
        self.assertNotIn(expected_position_id,
                         self.cache.position_closed_ids())
        self.assertIn(expected_position_id,
                      self.cache.position_open_ids(strategy_id=strategy.id))
        self.assertIn(expected_position_id, self.cache.position_open_ids())
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())

    def test_handle_position_opening_with_position_id_none(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        self.exec_engine.execute(submit_order)

        # Act
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        self.exec_engine.process(
            TestStubs.event_order_filled(order, AUDUSD_SIM))

        expected_id = PositionId(
            "O-19700101-000000-000-001-1")  # Stubbed from order id

        # Assert
        self.assertTrue(self.cache.position_exists(expected_id))
        self.assertTrue(self.cache.is_position_open(expected_id))
        self.assertFalse(self.cache.is_position_closed(expected_id))
        self.assertEqual(Position, type(self.cache.position(expected_id)))
        self.assertIn(expected_id, self.cache.position_ids())
        self.assertNotIn(
            expected_id,
            self.cache.position_closed_ids(strategy_id=strategy.id))
        self.assertNotIn(expected_id, self.cache.position_closed_ids())
        self.assertIn(expected_id,
                      self.cache.position_open_ids(strategy_id=strategy.id))
        self.assertIn(expected_id, self.cache.position_open_ids())
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())

    def test_add_to_existing_position_on_order_fill(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, AUDUSD_SIM))

        expected_position_id = PositionId(
            "O-19700101-000000-000-001-1")  # Stubbed from order id?

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            expected_position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, AUDUSD_SIM,
                                         expected_position_id))

        # Assert
        self.assertTrue(
            self.cache.position_exists(
                TestStubs.event_order_filled(
                    order1,
                    AUDUSD_SIM,
                ).position_id))
        self.assertTrue(self.cache.is_position_open(expected_position_id))
        self.assertFalse(self.cache.is_position_closed(expected_position_id))
        self.assertEqual(Position,
                         type(self.cache.position(expected_position_id)))
        self.assertEqual(
            0, len(self.cache.positions_closed(strategy_id=strategy.id)))
        self.assertEqual(0, len(self.cache.positions_closed()))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy.id)))
        self.assertEqual(1, len(self.cache.positions_open()))
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())

    def test_close_position_on_order_fill(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        order2 = strategy.order_factory.stop_market(
            AUDUSD_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
            Price("1.00000"),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id = PositionId("P-1")

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, AUDUSD_SIM, position_id))

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, AUDUSD_SIM, position_id))

        # # Assert
        self.assertTrue(self.cache.position_exists(position_id))
        self.assertFalse(self.cache.is_position_open(position_id))
        self.assertTrue(self.cache.is_position_closed(position_id))
        self.assertEqual(position_id, self.cache.position(position_id).id)
        self.assertEqual(position_id,
                         self.cache.positions(strategy_id=strategy.id)[0].id)
        self.assertEqual(position_id, self.cache.positions()[0].id)
        self.assertEqual(
            0, len(self.cache.positions_open(strategy_id=strategy.id)))
        self.assertEqual(0, len(self.cache.positions_open()))
        self.assertEqual(
            position_id,
            self.cache.positions_closed(strategy_id=strategy.id)[0].id)
        self.assertEqual(position_id, self.cache.positions_closed()[0].id)
        self.assertNotIn(position_id,
                         self.cache.position_open_ids(strategy_id=strategy.id))
        self.assertNotIn(position_id, self.cache.position_open_ids())
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(0, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())

    def test_multiple_strategy_positions_opened(self):
        # Arrange
        self.exec_engine.start()

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

        strategy2 = TradingStrategy(order_id_tag="002")
        strategy2.register_trader(
            TraderId("TESTER", "000"),
            self.clock,
            self.logger,
        )

        self.exec_engine.register_strategy(strategy1)
        self.exec_engine.register_strategy(strategy2)

        order1 = strategy1.order_factory.stop_market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        order2 = strategy2.order_factory.stop_market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy1.id,
            PositionId.null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy2.id,
            PositionId.null(),
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position1_id = PositionId('P-1')
        position2_id = PositionId('P-2')

        # Act
        self.exec_engine.execute(submit_order1)
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, AUDUSD_SIM, position1_id))
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, AUDUSD_SIM, position2_id))

        # Assert
        self.assertTrue(self.cache.position_exists(position1_id))
        self.assertTrue(self.cache.position_exists(position2_id))
        self.assertTrue(self.cache.is_position_open(position1_id))
        self.assertTrue(self.cache.is_position_open(position2_id))
        self.assertFalse(self.cache.is_position_closed(position1_id))
        self.assertFalse(self.cache.is_position_closed(position2_id))
        self.assertEqual(Position, type(self.cache.position(position1_id)))
        self.assertEqual(Position, type(self.cache.position(position2_id)))
        self.assertIn(position1_id,
                      self.cache.position_ids(strategy_id=strategy1.id))
        self.assertIn(position2_id,
                      self.cache.position_ids(strategy_id=strategy2.id))
        self.assertIn(position1_id, self.cache.position_ids())
        self.assertIn(position2_id, self.cache.position_ids())
        self.assertEqual(2, len(self.cache.position_open_ids()))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy1.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertEqual(2, len(self.cache.positions_open()))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy1.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertIn(position1_id,
                      self.cache.position_open_ids(strategy_id=strategy1.id))
        self.assertIn(position2_id,
                      self.cache.position_open_ids(strategy_id=strategy2.id))
        self.assertIn(position1_id, self.cache.position_open_ids())
        self.assertIn(position2_id, self.cache.position_open_ids())
        self.assertNotIn(
            position1_id,
            self.cache.position_closed_ids(strategy_id=strategy1.id))
        self.assertNotIn(
            position2_id,
            self.cache.position_closed_ids(strategy_id=strategy2.id))
        self.assertNotIn(position1_id, self.cache.position_closed_ids())
        self.assertNotIn(position2_id, self.cache.position_closed_ids())
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(2, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())

    def test_multiple_strategy_positions_one_active_one_closed(self):
        # Arrange
        self.exec_engine.start()

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

        strategy2 = TradingStrategy(order_id_tag="002")
        strategy2.register_trader(
            TraderId("TESTER", "000"),
            self.clock,
            self.logger,
        )

        self.exec_engine.register_strategy(strategy1)
        self.exec_engine.register_strategy(strategy2)

        order1 = strategy1.order_factory.stop_market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        order2 = strategy1.order_factory.stop_market(
            AUDUSD_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
            Price("1.00000"),
        )

        order3 = strategy2.order_factory.stop_market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy1.id,
            PositionId.null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id1 = PositionId('P-1')

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy1.id,
            position_id1,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        submit_order3 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy2.id,
            PositionId.null(),
            order3,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id2 = PositionId('P-2')

        # Act
        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, AUDUSD_SIM, position_id1))

        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, AUDUSD_SIM, position_id1))

        self.exec_engine.execute(submit_order3)
        self.exec_engine.process(TestStubs.event_order_submitted(order3))
        self.exec_engine.process(TestStubs.event_order_accepted(order3))
        self.exec_engine.process(
            TestStubs.event_order_filled(order3, AUDUSD_SIM, position_id2))

        # Assert
        # Already tested .is_position_active and .is_position_closed above
        self.assertTrue(self.cache.position_exists(position_id1))
        self.assertTrue(self.cache.position_exists(position_id2))
        self.assertIn(position_id1,
                      self.cache.position_ids(strategy_id=strategy1.id))
        self.assertIn(position_id2,
                      self.cache.position_ids(strategy_id=strategy2.id))
        self.assertIn(position_id1, self.cache.position_ids())
        self.assertIn(position_id2, self.cache.position_ids())
        self.assertEqual(
            0, len(self.cache.positions_open(strategy_id=strategy1.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertEqual(1, len(self.cache.positions_open()))
        self.assertEqual(1, len(self.cache.positions_closed()))
        self.assertEqual(2, len(self.cache.positions()))
        self.assertNotIn(
            position_id1,
            self.cache.position_open_ids(strategy_id=strategy1.id))
        self.assertIn(position_id2,
                      self.cache.position_open_ids(strategy_id=strategy2.id))
        self.assertNotIn(position_id1, self.cache.position_open_ids())
        self.assertIn(position_id2, self.cache.position_open_ids())
        self.assertIn(position_id1,
                      self.cache.position_closed_ids(strategy_id=strategy1.id))
        self.assertNotIn(
            position_id2,
            self.cache.position_closed_ids(strategy_id=strategy2.id))
        self.assertIn(position_id1, self.cache.position_closed_ids())
        self.assertNotIn(position_id2, self.cache.position_closed_ids())
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())

    def test_flip_position_on_opposite_filled_same_position_sell(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            AUDUSD_SIM.symbol,
            OrderSide.SELL,
            Quantity(150000),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id = PositionId("P-000-AUD/USD.SIM-1")

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, AUDUSD_SIM, position_id))

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, AUDUSD_SIM, position_id))

        position_id_flipped = PositionId("P-000-AUD/USD.SIM-1F")

        # Assert
        position_flipped = self.cache.position(position_id_flipped)
        self.assertEqual(-50000, position_flipped.relative_quantity)
        self.assertTrue(self.cache.position_exists(position_id))
        self.assertTrue(self.cache.position_exists(position_id_flipped))
        self.assertTrue(self.cache.is_position_closed(position_id))
        self.assertTrue(self.cache.is_position_open(position_id_flipped))
        self.assertIn(position_id, self.cache.position_ids())
        self.assertIn(position_id,
                      self.cache.position_ids(strategy_id=strategy.id))
        self.assertIn(position_id_flipped, self.cache.position_ids())
        self.assertIn(position_id_flipped,
                      self.cache.position_ids(strategy_id=strategy.id))
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())

    def test_flip_position_on_opposite_filled_same_position_buy(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            AUDUSD_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(150000),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id = PositionId("P-000-AUD/USD.SIM-1")

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, AUDUSD_SIM, position_id))

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, AUDUSD_SIM, position_id))

        position_id_flipped = PositionId("P-000-AUD/USD.SIM-1F")

        # Assert
        position_flipped = self.cache.position(position_id_flipped)
        self.assertEqual(50000, position_flipped.relative_quantity)
        self.assertTrue(self.cache.position_exists(position_id))
        self.assertTrue(self.cache.position_exists(position_id_flipped))
        self.assertTrue(self.cache.is_position_closed(position_id))
        self.assertTrue(self.cache.is_position_open(position_id_flipped))
        self.assertIn(position_id, self.cache.position_ids())
        self.assertIn(position_id,
                      self.cache.position_ids(strategy_id=strategy.id))
        self.assertIn(position_id_flipped, self.cache.position_ids())
        self.assertIn(position_id_flipped,
                      self.cache.position_ids(strategy_id=strategy.id))
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())
Esempio n. 4
0
class TestSimulatedExchangeContingencyAdvancedOrders:
    def setup(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(
            clock=self.clock,
            level_stdout=LogLevel.INFO,
        )

        self.trader_id = TestIdStubs.trader_id()

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

        self.cache = TestComponentStubs.cache()

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

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

        self.exec_engine = ExecutionEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

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

        self.exchange = SimulatedExchange(
            venue=FTX,
            oms_type=OMSType.NETTING,
            account_type=AccountType.MARGIN,
            base_currency=None,  # Multi-asset wallet
            starting_balances=[Money(200, ETH),
                               Money(1_000_000, USD)],
            default_leverage=Decimal(100),
            leverages={},
            is_frozen_account=False,
            instruments=[ETHUSD_FTX],
            modules=[],
            fill_model=FillModel(),
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            latency_model=LatencyModel(0),
        )

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Wire up components
        self.exec_engine.register_client(self.exec_client)
        self.exchange.register_client(self.exec_client)

        self.cache.add_instrument(ETHUSD_FTX)

        # Create mock strategy
        self.strategy = MockStrategy(
            bar_type=TestDataStubs.bartype_usdjpy_1min_bid())
        self.strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Start components
        self.exchange.reset()
        self.data_engine.start()
        self.exec_engine.start()
        self.strategy.start()

    def test_submit_bracket_market_buy_accepts_sl_and_tp(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_market(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Assert
        assert bracket.orders[0].status == OrderStatus.FILLED
        assert bracket.orders[1].status == OrderStatus.ACCEPTED
        assert bracket.orders[2].status == OrderStatus.ACCEPTED

    def test_submit_bracket_market_sell_accepts_sl_and_tp(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_market(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.SELL,
            quantity=ETHUSD_FTX.make_qty(10.000),
            stop_loss=ETHUSD_FTX.make_price(3150.0),
            take_profit=ETHUSD_FTX.make_price(3050.0),
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Assert
        assert bracket.orders[0].status == OrderStatus.FILLED
        assert bracket.orders[1].status == OrderStatus.ACCEPTED
        assert bracket.orders[2].status == OrderStatus.ACCEPTED

    def test_submit_bracket_limit_buy_has_sl_tp_pending(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3090.0),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)
        #
        # # Assert
        assert bracket.orders[0].status == OrderStatus.ACCEPTED
        assert bracket.orders[1].status == OrderStatus.SUBMITTED
        assert bracket.orders[2].status == OrderStatus.SUBMITTED

    def test_submit_bracket_limit_sell_has_sl_tp_pending(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.SELL,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3100.0),
            stop_loss=ETHUSD_FTX.make_price(3150.0),
            take_profit=ETHUSD_FTX.make_price(3050.0),
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)
        #
        # # Assert
        assert bracket.orders[0].status == OrderStatus.ACCEPTED
        assert bracket.orders[1].status == OrderStatus.SUBMITTED
        assert bracket.orders[2].status == OrderStatus.SUBMITTED

    def test_submit_bracket_limit_buy_fills_then_triggers_sl_and_tp(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3100.0),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Assert
        assert bracket.orders[0].status == OrderStatus.FILLED
        assert bracket.orders[1].status == OrderStatus.ACCEPTED
        assert bracket.orders[2].status == OrderStatus.ACCEPTED
        assert len(self.exchange.get_open_orders()) == 2
        assert bracket.orders[1] in self.exchange.get_open_orders()
        assert bracket.orders[2] in self.exchange.get_open_orders()

    def test_submit_bracket_limit_sell_fills_then_triggers_sl_and_tp(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.SELL,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3050.0),
            stop_loss=ETHUSD_FTX.make_price(3150.0),
            take_profit=ETHUSD_FTX.make_price(3000.0),
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Assert
        assert bracket.orders[0].status == OrderStatus.FILLED
        assert bracket.orders[1].status == OrderStatus.ACCEPTED
        assert bracket.orders[2].status == OrderStatus.ACCEPTED
        assert len(self.exchange.get_open_orders()) == 2
        assert bracket.orders[1] in self.exchange.get_open_orders()
        assert bracket.orders[2] in self.exchange.get_open_orders()

    def test_reject_bracket_entry_then_rejects_sl_and_tp(self):
        # Arrange: Prepare market
        tick = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.SELL,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3050.0),  # <-- in the market
            stop_loss=ETHUSD_FTX.make_price(3150.0),
            take_profit=ETHUSD_FTX.make_price(3000.0),
            post_only=True,  # <-- will reject placed into the market
        )

        # Act
        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Assert
        assert bracket.orders[0].status == OrderStatus.REJECTED
        assert bracket.orders[1].status == OrderStatus.REJECTED
        assert bracket.orders[2].status == OrderStatus.REJECTED
        assert len(self.exchange.get_open_orders()) == 0
        assert bracket.orders[1] not in self.exchange.get_open_orders()
        assert bracket.orders[2] not in self.exchange.get_open_orders()

    def test_filling_bracket_sl_cancels_tp_order(self):
        # Arrange: Prepare market
        tick1 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3100.0),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        tick2 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3150.0),
            ask=ETHUSD_FTX.make_price(3151.0),
            bid_size=ETHUSD_FTX.make_qty(10.000),
            ask_size=ETHUSD_FTX.make_qty(10.000),
            ts_event=0,
            ts_init=0,
        )

        # Act
        self.exchange.process_tick(tick2)

        # Assert
        assert bracket.orders[0].status == OrderStatus.FILLED
        assert bracket.orders[1].status == OrderStatus.CANCELED
        assert bracket.orders[2].status == OrderStatus.FILLED
        assert len(self.exchange.get_open_orders()) == 0
        assert len(self.exchange.cache.positions_open()) == 0

    def test_filling_bracket_tp_cancels_sl_order(self):
        # Arrange: Prepare market
        tick1 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3100.0),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Act
        tick2 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3150.0),
            ask=ETHUSD_FTX.make_price(3151.0),
            bid_size=ETHUSD_FTX.make_qty(10.000),
            ask_size=ETHUSD_FTX.make_qty(10.000),
            ts_event=0,
            ts_init=0,
        )

        self.exchange.process_tick(tick2)

        # Assert
        assert bracket.orders[0].status == OrderStatus.FILLED
        assert bracket.orders[1].status == OrderStatus.CANCELED
        assert bracket.orders[2].status == OrderStatus.FILLED
        assert len(self.exchange.get_open_orders()) == 0
        assert len(self.exchange.cache.positions_open()) == 0

    def test_partial_fill_bracket_tp_updates_sl_order(self):
        # Arrange: Prepare market
        tick1 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3100.0),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        en = bracket.orders[0]
        sl = bracket.orders[1]
        tp = bracket.orders[2]

        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Act
        tick2 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3150.0),
            ask=ETHUSD_FTX.make_price(3151.0),
            bid_size=ETHUSD_FTX.make_qty(5.000),
            ask_size=ETHUSD_FTX.make_qty(5.1000),
            ts_event=0,
            ts_init=0,
        )

        self.exchange.process_tick(tick2)

        # Assert
        assert en.status == OrderStatus.FILLED
        assert sl.status == OrderStatus.ACCEPTED
        assert tp.status == OrderStatus.PARTIALLY_FILLED
        assert sl.quantity == Quantity.from_int(5)
        assert tp.leaves_qty == Quantity.from_int(5)
        assert tp.quantity == Quantity.from_int(10)
        assert len(self.exchange.get_open_orders()) == 2
        assert len(self.exchange.cache.positions_open()) == 1

    def test_modifying_bracket_tp_updates_sl_order(self):
        # Arrange: Prepare market
        tick1 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        bracket = self.strategy.order_factory.bracket_limit(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            entry=ETHUSD_FTX.make_price(3100.0),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        en = bracket.orders[0]
        sl = bracket.orders[1]
        tp = bracket.orders[2]

        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Act
        self.strategy.modify_order(
            order=sl,
            quantity=Quantity.from_int(5),
            trigger_price=sl.trigger_price,
        )
        self.exchange.process(0)

        # Assert
        assert en.status == OrderStatus.FILLED
        assert sl.status == OrderStatus.ACCEPTED
        assert tp.status == OrderStatus.ACCEPTED
        assert sl.quantity == Quantity.from_int(5)
        assert tp.quantity == Quantity.from_int(5)
        assert len(self.exchange.get_open_orders()) == 2
        assert len(self.exchange.cache.positions_open()) == 1

    def test_closing_position_cancels_bracket_ocos(self):
        # Arrange: Prepare market
        tick1 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        bracket = self.strategy.order_factory.bracket_market(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        en = bracket.orders[0]
        sl = bracket.orders[1]
        tp = bracket.orders[2]

        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Act
        self.strategy.flatten_position(
            self.strategy.cache.position(en.position_id))
        self.exchange.process(0)

        # Assert
        assert en.status == OrderStatus.FILLED
        assert sl.status == OrderStatus.CANCELED
        assert tp.status == OrderStatus.CANCELED
        assert len(self.exchange.get_open_orders()) == 0
        assert len(self.exchange.cache.positions_open()) == 0

    def test_partially_filling_position_updates_bracket_ocos(self):
        # Arrange: Prepare market
        tick1 = QuoteTick(
            instrument_id=ETHUSD_FTX.id,
            bid=ETHUSD_FTX.make_price(3090.2),
            ask=ETHUSD_FTX.make_price(3090.5),
            bid_size=ETHUSD_FTX.make_qty(15.100),
            ask_size=ETHUSD_FTX.make_qty(15.100),
            ts_event=0,
            ts_init=0,
        )

        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        bracket = self.strategy.order_factory.bracket_market(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.BUY,
            quantity=ETHUSD_FTX.make_qty(10.000),
            stop_loss=ETHUSD_FTX.make_price(3050.0),
            take_profit=ETHUSD_FTX.make_price(3150.0),
        )

        en = bracket.orders[0]
        sl = bracket.orders[1]
        tp = bracket.orders[2]

        self.strategy.submit_order_list(bracket)
        self.exchange.process(0)

        # Act
        reduce_order = self.strategy.order_factory.market(
            instrument_id=ETHUSD_FTX.id,
            order_side=OrderSide.SELL,
            quantity=ETHUSD_FTX.make_qty(5.000),
        )
        self.strategy.submit_order(
            reduce_order,
            position_id=self.cache.position_for_order(en.client_order_id).id,
        )
        self.exchange.process(0)

        # Assert
        assert en.status == OrderStatus.FILLED
        assert sl.status == OrderStatus.ACCEPTED
        assert tp.status == OrderStatus.ACCEPTED
        assert sl.quantity == ETHUSD_FTX.make_qty(5.000)
        assert tp.quantity == ETHUSD_FTX.make_qty(5.000)
        assert len(self.exchange.get_open_orders()) == 2
        assert len(self.exchange.cache.positions_open()) == 1
class TradingStrategyTests(unittest.TestCase):

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

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

        self.data_engine = DataEngine(
            tick_capacity=1000,
            bar_capacity=1000,
            portfolio=self.portfolio,
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )

        self.data_engine.set_use_previous_close(False)

        self.analyzer = PerformanceAnalyzer()

        trader_id = TraderId('TESTER', '000')
        account_id = TestStubs.account_id()

        self.exec_db = BypassExecutionDatabase(
            trader_id=trader_id,
            logger=self.logger,
        )

        self.exec_engine = ExecutionEngine(
            database=self.exec_db,
            portfolio=self.portfolio,
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )

        usdjpy = InstrumentLoader.default_fx_ccy(TestStubs.symbol_usdjpy_fxcm())

        self.market = SimulatedMarket(
            venue=Venue("FXCM"),
            oms_type=OMSType.HEDGING,
            generate_position_ids=True,
            exec_cache=self.exec_engine.cache,
            instruments={usdjpy.symbol: usdjpy},
            config=BacktestConfig(),
            fill_model=FillModel(),
            commission_model=GenericCommissionModel(),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_client = BacktestExecClient(
            market=self.market,
            account_id=account_id,
            engine=self.exec_engine,
            logger=self.logger,
        )

        self.exec_engine.register_client(self.exec_client)
        self.market.register_client(self.exec_client)
        self.exec_engine.process(TestStubs.event_account_state())

        self.market.process_tick(TestStubs.quote_tick_3decimal(usdjpy.symbol))  # Prepare market

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

        self.strategy.register_data_engine(self.data_engine)
        self.strategy.register_execution_engine(self.exec_engine)

        print("\n")

    def test_strategy_equality(self):
        # Arrange
        strategy1 = TradingStrategy(order_id_tag="001")
        strategy2 = TradingStrategy(order_id_tag="AUD/USD-001")
        strategy3 = TradingStrategy(order_id_tag="AUD/USD-002")

        # Act
        result1 = strategy1 == strategy1
        result2 = strategy1 == strategy2
        result3 = strategy2 == strategy3
        result4 = strategy1 != strategy1
        result5 = strategy1 != strategy2
        result6 = strategy2 != strategy3

        # Assert
        self.assertTrue(result1)
        self.assertFalse(result2)
        self.assertFalse(result3)
        self.assertFalse(result4)
        self.assertTrue(result5)
        self.assertTrue(result6)

    def test_strategy_is_hashable(self):
        # Arrange
        # Act
        result = self.strategy.__hash__()

        # Assert
        # If this passes then result must be an int
        self.assertTrue(result != 0)

    def test_strategy_str_and_repr(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="GBP/USD-MM")

        # Act
        result1 = str(strategy)
        result2 = repr(strategy)

        # Assert
        self.assertEqual("TradingStrategy(TradingStrategy-GBP/USD-MM)", result1)
        self.assertTrue(result2.startswith("<TradingStrategy(TradingStrategy-GBP/USD-MM) object at"))
        self.assertTrue(result2.endswith(">"))

    def test_get_strategy_id(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual(StrategyId("TradingStrategy", "001"), self.strategy.id)

    def test_get_current_time(self):
        # Arrange
        # Act
        result = self.strategy.clock.utc_now()

        # Assert
        self.assertEqual(pytz.utc, result.tzinfo)

    def test_initialization(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()
        strategy = TestStrategy1(bar_type)

        # Act
        # Assert
        self.assertFalse(strategy.indicators_initialized())

    def test_get_tick_count_for_unknown_symbol_returns_zero(self):
        # Arrange
        # Act
        result = self.strategy.quote_tick_count(AUDUSD_FXCM)

        # Assert
        self.assertEqual(0, result)

    def test_get_ticks_for_unknown_symbol_raises_exception(self):
        # Arrange
        # Act
        # Assert
        self.assertRaises(KeyError, self.strategy.quote_ticks, AUDUSD_FXCM)

    def test_get_bar_count_for_unknown_bar_type_returns_zero(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()

        # Act
        result = self.strategy.bar_count(bar_type)

        # Assert
        self.assertEqual(0, result)

    def test_get_bars_for_unknown_bar_type_raises_exception(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()

        # Act
        # Assert
        self.assertRaises(KeyError, self.strategy.bars, bar_type)

    def test_bars(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()
        bar = Bar(
            Price("1.00001"),
            Price("1.00004"),
            Price("1.00002"),
            Price("1.00003"),
            Quantity(100000),
            datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc),
        )

        self.data_engine.handle_bar(bar_type, bar)

        # Act
        result = self.strategy.bars(bar_type)

        # Assert
        self.assertTrue(bar, result[0])

    def test_getting_bar_for_unknown_bar_type_raises_exception(self):
        # Arrange
        unknown_bar_type = TestStubs.bartype_gbpusd_1sec_mid()

        # Act
        # Assert
        self.assertRaises(KeyError, self.strategy.bar, unknown_bar_type, 0)

    def test_getting_bar_at_out_of_range_index_raises_exception(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()
        bar = Bar(
            Price("1.00001"),
            Price("1.00004"),
            Price("1.00002"),
            Price("1.00003"),
            Quantity(100000),
            datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc),
        )

        self.data_engine.handle_bar(bar_type, bar)

        # Act
        # Assert
        self.assertRaises(IndexError, self.strategy.bar, bar_type, -2)

    def test_get_bar(self):
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()
        bar = Bar(
            Price("1.00001"),
            Price("1.00004"),
            Price("1.00002"),
            Price("1.00003"),
            Quantity(100000),
            datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc),
        )

        self.data_engine.handle_bar(bar_type, bar)

        # Act
        result = self.strategy.bar(bar_type, 0)

        # Assert
        self.assertEqual(bar, result)

    def test_getting_tick_with_unknown_tick_type_raises_exception(self):
        # Act
        # Assert
        self.assertRaises(KeyError, self.strategy.quote_tick, AUDUSD_FXCM, 0)

    def test_get_quote_tick(self):
        tick = QuoteTick(
            AUDUSD_FXCM,
            Price("1.00000"),
            Price("1.00001"),
            Quantity(1),
            Quantity(1),
            datetime(2018, 1, 1, 19, 59, 1, 0, pytz.utc),
        )

        self.data_engine.handle_quote_tick(tick)

        # Act
        result = self.strategy.quote_tick(tick.symbol, 0)

        # Assert
        self.assertEqual(tick, result)

    def test_get_trade_tick(self):
        tick = TradeTick(
            AUDUSD_FXCM,
            Price("1.00000"),
            Quantity(10000),
            Maker.BUYER,
            MatchId("123456789"),
            datetime(2018, 1, 1, 19, 59, 1, 0, pytz.utc),
        )

        self.data_engine.handle_trade_tick(tick)

        # Act
        result = self.strategy.trade_tick(tick.symbol, 0)

        # Assert
        self.assertEqual(tick, result)

    def test_start_strategy(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.data_engine.register_strategy(strategy)
        self.exec_engine.register_strategy(strategy)

        result1 = strategy.state()

        # Act
        strategy.start()
        result2 = strategy.state()

        # Assert
        self.assertEqual(ComponentState.INITIALIZED, result1)
        self.assertEqual(ComponentState.RUNNING, result2)
        self.assertTrue("custom start logic" in strategy.object_storer.get_store())

    def test_stop_strategy(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.data_engine.register_strategy(strategy)
        self.exec_engine.register_strategy(strategy)

        # Act
        strategy.start()
        strategy.stop()

        # Assert
        self.assertEqual(ComponentState.STOPPED, strategy.state())
        self.assertTrue("custom stop logic" in strategy.object_storer.get_store())

    def test_reset_strategy(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()

        bar = Bar(
            Price("1.00001"),
            Price("1.00004"),
            Price("1.00002"),
            Price("1.00003"),
            Quantity(100000),
            datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc),
        )

        strategy.handle_bar(bar_type, bar)

        # Act
        strategy.reset()

        # Assert
        self.assertEqual(ComponentState.INITIALIZED, strategy.state())
        self.assertEqual(0, strategy.ema1.count)
        self.assertEqual(0, strategy.ema2.count)
        self.assertTrue("custom reset logic" in strategy.object_storer.get_store())

    def test_register_indicator_with_strategy(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )

        # Act
        result = strategy.registered_indicators()

        # Assert
        self.assertEqual([strategy.ema1, strategy.ema2], result)

    def test_register_strategy_with_exec_client(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )

        # Act
        self.exec_engine.register_strategy(strategy)

        # Assert
        self.assertIsNotNone(strategy.execution)

    def test_stopping_a_strategy_cancels_a_running_time_alert(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.data_engine.register_strategy(strategy)
        self.exec_engine.register_strategy(strategy)

        alert_time = datetime.now(pytz.utc) + timedelta(milliseconds=200)
        strategy.clock.set_time_alert("test_alert1", alert_time)

        # Act
        strategy.start()
        time.sleep(0.1)
        strategy.stop()

        # Assert
        self.assertEqual(2, strategy.object_storer.count)

    def test_stopping_a_strategy_cancels_a_running_timer(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.data_engine.register_strategy(strategy)
        self.exec_engine.register_strategy(strategy)

        start_time = datetime.now(pytz.utc) + timedelta(milliseconds=100)
        strategy.clock.set_timer("test_timer3", timedelta(milliseconds=100), start_time, stop_time=None)

        # Act
        strategy.start()
        time.sleep(0.1)
        strategy.stop()

        # Assert
        self.assertEqual(2, strategy.object_storer.count)

    def test_strategy_can_submit_order(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.exec_engine.register_strategy(strategy)

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

        # Act
        strategy.submit_order(order)

        # Assert
        self.assertTrue(order in strategy.execution.orders())
        self.assertEqual(OrderState.FILLED, strategy.execution.orders()[0].state())
        self.assertTrue(order.cl_ord_id not in strategy.execution.orders_working())
        self.assertFalse(strategy.execution.is_order_working(order.cl_ord_id))
        self.assertTrue(strategy.execution.is_order_completed(order.cl_ord_id))

    def test_cancel_order(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.exec_engine.register_strategy(strategy)

        order = strategy.order_factory.stop(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.005"),
        )

        strategy.submit_order(order)

        # Act
        strategy.cancel_order(order)

        # Assert
        self.assertTrue(order in strategy.execution.orders())
        self.assertEqual(OrderState.CANCELLED, strategy.execution.orders()[0].state())
        self.assertEqual(order.cl_ord_id, strategy.execution.orders_completed()[0].cl_ord_id)
        self.assertTrue(order.cl_ord_id not in strategy.execution.orders_working())
        self.assertTrue(strategy.execution.order_exists(order.cl_ord_id))
        self.assertFalse(strategy.execution.is_order_working(order.cl_ord_id))
        self.assertTrue(strategy.execution.is_order_completed(order.cl_ord_id))

    def test_modify_order(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger)
        self.exec_engine.register_strategy(strategy)

        order = strategy.order_factory.limit(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.001"),
        )

        strategy.submit_order(order)

        # Act
        strategy.modify_order(order, Quantity(110000), Price("90.002"))

        # Assert
        self.assertEqual(order, strategy.execution.orders()[0])
        self.assertEqual(OrderState.WORKING, strategy.execution.orders()[0].state())
        self.assertEqual(Quantity(110000), strategy.execution.orders()[0].quantity)
        self.assertEqual(Price("90.002"), strategy.execution.orders()[0].price)
        self.assertTrue(strategy.execution.is_flat())
        self.assertTrue(strategy.execution.order_exists(order.cl_ord_id))
        self.assertTrue(strategy.execution.is_order_working(order.cl_ord_id))
        self.assertFalse(strategy.execution.is_order_completed(order.cl_ord_id))

    def test_cancel_all_orders(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger)
        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.stop(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.003"),
        )

        order2 = strategy.order_factory.stop(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.005"),
        )

        strategy.submit_order(order1)
        strategy.submit_order(order2)

        # Act
        strategy.cancel_all_orders(USDJPY_FXCM)

        # Assert
        self.assertTrue(order1 in strategy.execution.orders())
        self.assertTrue(order2 in strategy.execution.orders())
        self.assertEqual(OrderState.CANCELLED, strategy.execution.orders()[0].state())
        self.assertEqual(OrderState.CANCELLED, strategy.execution.orders()[1].state())
        self.assertTrue(order1 in strategy.execution.orders_completed())
        self.assertTrue(order2 in strategy.execution.orders_completed())

    def test_flatten_position(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.exec_engine.register_strategy(strategy)

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

        strategy.submit_order(order)

        filled = TestStubs.event_order_filled(
            order,
            position_id=PositionId("B-USD/JPY-1"),
            strategy_id=strategy.id,
        )
        position = Position(filled)

        # Act
        strategy.flatten_position(position)

        # Assert
        self.assertTrue(order in strategy.execution.orders())
        self.assertEqual(OrderState.FILLED, strategy.execution.orders()[0].state())
        self.assertEqual(PositionSide.FLAT, strategy.execution.positions()[0].side)
        self.assertTrue(strategy.execution.positions()[0].is_closed())
        self.assertTrue(PositionId("B-USD/JPY-1") in strategy.execution.position_closed_ids())
        self.assertTrue(strategy.execution.is_completely_flat())

    def test_flatten_all_positions(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
        )

        strategy.submit_order(order1)
        strategy.submit_order(order2)

        filled1 = TestStubs.event_order_filled(
            order1,
            position_id=PositionId("B-USD/JPY-1"),
            strategy_id=strategy.id,
        )

        filled2 = TestStubs.event_order_filled(
            order2,
            position_id=PositionId("B-USD/JPY-2"),
            strategy_id=strategy.id,
        )

        position1 = Position(filled1)
        position2 = Position(filled2)

        # Act
        strategy.flatten_all_positions(USDJPY_FXCM)

        # Assert
        self.assertTrue(order1 in strategy.execution.orders())
        self.assertTrue(order2 in strategy.execution.orders())
        self.assertEqual(OrderState.FILLED, strategy.execution.orders()[0].state())
        self.assertEqual(OrderState.FILLED, strategy.execution.orders()[1].state())
        self.assertEqual(PositionSide.FLAT, strategy.execution.positions()[0].side)
        self.assertEqual(PositionSide.FLAT, strategy.execution.positions()[1].side)
        self.assertTrue(position1.id in strategy.execution.position_closed_ids())
        self.assertTrue(position2.id in strategy.execution.position_closed_ids())
        self.assertTrue(strategy.execution.is_completely_flat())

    def test_update_indicators(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )

        bar = Bar(
            Price("1.00001"),
            Price("1.00004"),
            Price("1.00002"),
            Price("1.00003"),
            Quantity(100000),
            datetime(1970, 1, 1, 00, 00, 0, 0, pytz.utc),
        )

        # Act
        strategy.handle_bar(bar_type, bar)

        # Assert
        self.assertEqual(1, strategy.ema1.count)
        self.assertEqual(1, strategy.ema2.count)

    def test_can_track_orders_for_an_opened_position(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.exec_engine.register_strategy(strategy)

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

        strategy.submit_order(order)

        # Act
        # Assert
        self.assertTrue(order in strategy.execution.orders())
        self.assertTrue(PositionId("B-USD/JPY-1") in strategy.execution.position_ids())
        self.assertEqual(0, len(strategy.execution.orders_working()))
        self.assertTrue(order in strategy.execution.orders_completed())
        self.assertEqual(0, len(strategy.execution.positions_closed()))
        self.assertTrue(order in strategy.execution.orders_completed())
        self.assertTrue(PositionId("B-USD/JPY-1") in strategy.execution.position_open_ids())
        self.assertFalse(strategy.execution.is_completely_flat())

    def test_can_track_orders_for_a_closing_position(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TestStrategy1(bar_type)
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )
        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            USDJPY_FXCM,
            OrderSide.BUY,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            USDJPY_FXCM,
            OrderSide.SELL,
            Quantity(100000),
        )

        strategy.submit_order(order1)
        strategy.submit_order(order2, PositionId("B-USD/JPY-1"))  # Position identifier generated by exchange

        # Act
        print(self.exec_engine.cache.orders())
        # Assert
        self.assertEqual(0, len(self.exec_engine.cache.orders_working()))
        self.assertTrue(order1 in self.exec_engine.cache.orders_completed())
        self.assertTrue(order2 in self.exec_engine.cache.orders_completed())
        self.assertEqual(1, len(self.exec_engine.cache.positions_closed()))
        self.assertEqual(0, len(self.exec_engine.cache.positions_open()))
        self.assertTrue(self.exec_engine.cache.is_completely_flat())
Esempio n. 6
0
class TestRiskEngine:
    def setup(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(self.clock)

        self.trader_id = TraderId("TESTER-000")
        self.account_id = TestStubs.account_id()
        self.venue = Venue("SIM")

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

        self.exec_engine = ExecutionEngine(
            portfolio=self.portfolio,
            cache=TestStubs.cache(),
            clock=self.clock,
            logger=self.logger,
        )

        self.risk_engine = RiskEngine(
            exec_engine=self.exec_engine,
            portfolio=self.portfolio,
            cache=TestStubs.cache(),
            clock=self.clock,
            logger=self.logger,
            config={},
        )

        self.exec_client = MockExecutionClient(
            client_id=ClientId(self.venue.value),
            venue_type=VenueType.ECN,
            account_id=self.account_id,
            account_type=AccountType.MARGIN,
            base_currency=USD,
            engine=self.exec_engine,
            clock=self.clock,
            logger=self.logger,
        )

        # Wire up components
        self.exec_engine.register_risk_engine(self.risk_engine)
        self.exec_engine.register_client(self.exec_client)

        # Prepare data
        self.exec_engine.cache.add_instrument(AUDUSD_SIM)

    def test_set_block_all_orders_changes_flag_value(self):
        # Arrange
        # Act
        self.risk_engine.set_block_all_orders()

        # Assert
        assert self.risk_engine.block_all_orders

    def test_given_random_command_logs_and_continues(self):
        # Arrange
        random = TradingCommand(
            self.trader_id,
            StrategyId("SCALPER-001"),
            AUDUSD_SIM.id,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(random)

    def test_given_random_event_logs_and_continues(self):
        # Arrange
        random = Event(
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.process(random)

    def test_submit_order_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_client.calls == ["connect", "submit_order"]

    def test_submit_bracket_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        bracket = strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price.from_str("1.00000"),
            take_profit=Price.from_str("1.00010"),
        )

        submit_bracket = SubmitBracketOrder(
            self.trader_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.exec_client.calls == ["connect", "submit_bracket_order"]

    def test_submit_order_when_block_all_orders_true_then_denies_order(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        self.risk_engine.set_block_all_orders()

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_client.calls == ["connect"]
        assert self.risk_engine.command_count == 1

    def test_update_order_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        update = UpdateOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            order.venue_order_id,
            order.quantity,
            Price.from_str("1.00010"),
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit)

        # Act
        self.risk_engine.execute(update)

        # Assert
        assert self.exec_client.calls == [
            "connect", "submit_order", "update_order"
        ]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 2

    def test_cancel_order_with_default_settings_sends_to_client(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

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

        cancel = CancelOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            order.venue_order_id,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit)

        # Act
        self.risk_engine.execute(cancel)

        # Assert
        assert self.exec_client.calls == [
            "connect", "submit_order", "cancel_order"
        ]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 2

    def test_submit_bracket_when_block_all_orders_true_then_denies_order(self):
        # Arrange
        self.exec_engine.start()

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

        self.exec_engine.register_strategy(strategy)

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

        bracket = strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price.from_str("1.00000"),
            take_profit=Price.from_str("1.00010"),
        )

        submit_bracket = SubmitBracketOrder(
            self.trader_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.set_block_all_orders()

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.exec_client.calls == ["connect"]
        assert self.risk_engine.command_count == 1
        assert self.exec_engine.event_count == 3
class TestTradingStrategy:
    def setup(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(
            clock=self.clock,
            level_stdout=LogLevel.DEBUG,
        )

        self.trader_id = TestStubs.trader_id()
        self.account_id = TestStubs.account_id()

        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 = DataEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_engine = ExecutionEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

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

        self.exchange = SimulatedExchange(
            venue=Venue("SIM"),
            venue_type=VenueType.ECN,
            oms_type=OMSType.HEDGING,
            account_type=AccountType.MARGIN,
            base_currency=USD,
            starting_balances=[Money(1_000_000, USD)],
            default_leverage=Decimal(50),
            leverages={},
            is_frozen_account=False,
            cache=self.cache,
            instruments=[USDJPY_SIM],
            modules=[],
            fill_model=FillModel(),
            clock=self.clock,
            logger=self.logger,
            latency_model=LatencyModel(0),
        )

        self.data_client = BacktestMarketDataClient(
            client_id=ClientId("SIM"),
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            account_id=self.account_id,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Wire up components
        self.exchange.register_client(self.exec_client)
        self.data_engine.register_client(self.data_client)
        self.exec_engine.register_client(self.exec_client)
        self.exchange.reset()

        # Add instruments
        self.data_engine.process(AUDUSD_SIM)
        self.data_engine.process(GBPUSD_SIM)
        self.data_engine.process(USDJPY_SIM)
        self.cache.add_instrument(AUDUSD_SIM)
        self.cache.add_instrument(GBPUSD_SIM)
        self.cache.add_instrument(USDJPY_SIM)

        self.exchange.process_tick(TestStubs.quote_tick_3decimal(
            USDJPY_SIM.id))  # Prepare market

        self.data_engine.start()
        self.exec_engine.start()

    def test_strategy_equality(self):
        # Arrange
        strategy1 = TradingStrategy(config=TradingStrategyConfig(
            order_id_tag="AUD/USD-001"))
        strategy2 = TradingStrategy(config=TradingStrategyConfig(
            order_id_tag="AUD/USD-001"))
        strategy3 = TradingStrategy(config=TradingStrategyConfig(
            order_id_tag="AUD/USD-002"))

        # Act, Assert
        assert strategy1 == strategy1
        assert strategy1 == strategy2
        assert strategy2 != strategy3

    def test_str_and_repr(self):
        # Arrange
        strategy = TradingStrategy(config=TradingStrategyConfig(
            order_id_tag="GBP/USD-MM"))

        # Act, Assert
        assert str(strategy) == "TradingStrategy-GBP/USD-MM"
        assert repr(strategy) == "TradingStrategy(TradingStrategy-GBP/USD-MM)"

    def test_id(self):
        # Arrange
        strategy = TradingStrategy()

        # Act, Assert
        assert strategy.id == StrategyId("TradingStrategy-000")

    def test_initialization(self):
        # Arrange
        strategy = TradingStrategy(config=TradingStrategyConfig(
            order_id_tag="001"))

        # Act, Assert
        assert strategy.state == ComponentState.PRE_INITIALIZED
        assert not strategy.indicators_initialized()

    def test_on_save_when_not_overridden_does_nothing(self):
        # Arrange
        strategy = TradingStrategy()

        # Act
        strategy.on_save()

        # Assert
        assert True  # Exception not raised

    def test_on_load_when_not_overridden_does_nothing(self):
        # Arrange
        strategy = TradingStrategy()

        # Act
        strategy.on_load({})

        # Assert
        assert True  # Exception not raised

    def test_save_when_not_registered_logs_error(self):
        # Arrange
        config = TradingStrategyConfig()

        strategy = TradingStrategy(config)
        strategy.save()

        # Assert
        assert True  # Exception not raised

    def test_save_when_user_code_raises_error_logs_and_reraises(self):
        # Arrange
        strategy = KaboomStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Act, Assert
        with pytest.raises(RuntimeError):
            strategy.save()

    def test_load_when_user_code_raises_error_logs_and_reraises(self):
        # Arrange
        strategy = KaboomStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Act, Assert
        with pytest.raises(RuntimeError):
            strategy.load({"something": b"123456"})

    def test_load(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        state = {}

        # Act
        strategy.load(state)

        # Assert
        # TODO: Write a users custom save method
        assert True

    def test_reset(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = MockStrategy(bar_type)
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        bar = Bar(
            bar_type,
            Price.from_str("1.00001"),
            Price.from_str("1.00004"),
            Price.from_str("1.00002"),
            Price.from_str("1.00003"),
            Quantity.from_int(100000),
            0,
            0,
        )

        strategy.handle_bar(bar)

        # Act
        strategy.reset()

        # Assert
        assert "on_reset" in strategy.calls
        assert strategy.is_initialized
        assert strategy.ema1.count == 0
        assert strategy.ema2.count == 0

    def test_dispose(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = MockStrategy(bar_type)
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        strategy.reset()

        # Act
        strategy.dispose()

        # Assert
        assert "on_dispose" in strategy.calls
        assert strategy.is_disposed

    def test_save_load(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = MockStrategy(bar_type)
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Act
        state = strategy.save()
        strategy.load(state)

        # Assert
        assert state == {"UserState": b"1"}
        assert "on_save" in strategy.calls
        assert strategy.is_initialized

    def test_register_indicator_for_quote_ticks_when_already_registered(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema1 = ExponentialMovingAverage(10, price_type=PriceType.MID)
        ema2 = ExponentialMovingAverage(10, price_type=PriceType.MID)

        # Act
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema1)
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema2)
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema2)

        assert len(strategy.registered_indicators) == 2
        assert ema1 in strategy.registered_indicators
        assert ema2 in strategy.registered_indicators

    def test_register_indicator_for_trade_ticks_when_already_registered(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema1 = ExponentialMovingAverage(10)
        ema2 = ExponentialMovingAverage(10)

        # Act
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema1)
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2)
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2)

        assert len(strategy.registered_indicators) == 2
        assert ema1 in strategy.registered_indicators
        assert ema2 in strategy.registered_indicators

    def test_register_indicator_for_bars_when_already_registered(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema1 = ExponentialMovingAverage(10)
        ema2 = ExponentialMovingAverage(10)
        bar_type = TestStubs.bartype_audusd_1min_bid()

        # Act
        strategy.register_indicator_for_bars(bar_type, ema1)
        strategy.register_indicator_for_bars(bar_type, ema2)
        strategy.register_indicator_for_bars(bar_type, ema2)

        assert len(strategy.registered_indicators) == 2
        assert ema1 in strategy.registered_indicators
        assert ema2 in strategy.registered_indicators

    def test_register_indicator_for_multiple_data_sources(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        bar_type = TestStubs.bartype_audusd_1min_bid()

        # Act
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema)
        strategy.register_indicator_for_quote_ticks(GBPUSD_SIM.id, ema)
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema)
        strategy.register_indicator_for_bars(bar_type, ema)

        assert len(strategy.registered_indicators) == 1
        assert ema in strategy.registered_indicators

    def test_handle_quote_tick_updates_indicator_registered_for_quote_ticks(
            self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10, price_type=PriceType.MID)
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema)

        tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id)

        # Act
        strategy.handle_quote_tick(tick)
        strategy.handle_quote_tick(tick, True)

        # Assert
        assert ema.count == 2

    def test_handle_quote_ticks_with_no_ticks_logs_and_continues(self):
        # Arrange
        strategy = KaboomStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10, price_type=PriceType.MID)
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema)

        # Act
        strategy.handle_quote_ticks([])

        # Assert
        assert ema.count == 0

    def test_handle_quote_ticks_updates_indicator_registered_for_quote_ticks(
            self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10, price_type=PriceType.MID)
        strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema)

        tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id)

        # Act
        strategy.handle_quote_ticks([tick])

        # Assert
        assert ema.count == 1

    def test_handle_trade_tick_updates_indicator_registered_for_trade_ticks(
            self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema)

        tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id)

        # Act
        strategy.handle_trade_tick(tick)
        strategy.handle_trade_tick(tick, True)

        # Assert
        assert ema.count == 2

    def test_handle_trade_ticks_updates_indicator_registered_for_trade_ticks(
            self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema)

        tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id)

        # Act
        strategy.handle_trade_ticks([tick])

        # Assert
        assert ema.count == 1

    def test_handle_trade_ticks_with_no_ticks_logs_and_continues(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema)

        # Act
        strategy.handle_trade_ticks([])

        # Assert
        assert ema.count == 0

    def test_handle_bar_updates_indicator_registered_for_bars(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        strategy.register_indicator_for_bars(bar_type, ema)
        bar = TestStubs.bar_5decimal()

        # Act
        strategy.handle_bar(bar)
        strategy.handle_bar(bar, True)

        # Assert
        assert ema.count == 2

    def test_handle_bars_updates_indicator_registered_for_bars(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        strategy.register_indicator_for_bars(bar_type, ema)
        bar = TestStubs.bar_5decimal()

        # Act
        strategy.handle_bars([bar])

        # Assert
        assert ema.count == 1

    def test_handle_bars_with_no_bars_logs_and_continues(self):
        # Arrange
        bar_type = TestStubs.bartype_gbpusd_1sec_mid()
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        ema = ExponentialMovingAverage(10)
        strategy.register_indicator_for_bars(bar_type, ema)

        # Act
        strategy.handle_bars([])

        # Assert
        assert ema.count == 0

    def test_stop_cancels_a_running_time_alert(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = MockStrategy(bar_type)
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        alert_time = datetime.now(pytz.utc) + timedelta(milliseconds=200)
        strategy.clock.set_time_alert("test_alert1", alert_time)

        # Act
        strategy.start()
        strategy.stop()

        # Assert
        assert len(strategy.clock.timer_names()) == 0

    def test_stop_cancels_a_running_timer(self):
        # Arrange
        bar_type = TestStubs.bartype_audusd_1min_bid()
        strategy = MockStrategy(bar_type)
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        start_time = datetime.now(pytz.utc) + timedelta(milliseconds=100)
        strategy.clock.set_timer("test_timer",
                                 timedelta(milliseconds=100),
                                 start_time,
                                 stop_time=None)

        # Act
        strategy.start()
        strategy.stop()

        # Assert
        assert len(strategy.clock.timer_names()) == 0

    def test_submit_order_with_valid_order_successfully_submits(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        # Act
        strategy.submit_order(order)
        self.exchange.process(0)

        # Assert
        assert order in strategy.cache.orders()
        assert strategy.cache.orders()[0].status == OrderStatus.FILLED
        assert order.client_order_id not in strategy.cache.orders_working()
        assert not strategy.cache.is_order_working(order.client_order_id)
        assert strategy.cache.is_order_completed(order.client_order_id)

    def test_submit_order_list_with_valid_order_successfully_submits(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        bracket = strategy.order_factory.bracket_market(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            stop_loss=Price.from_str("90.000"),
            take_profit=Price.from_str("90.500"),
        )

        # Act
        strategy.submit_order_list(bracket)

        # Assert
        assert bracket.orders[0] in strategy.cache.orders()
        assert bracket.orders[1] in strategy.cache.orders()
        assert bracket.orders[2] in strategy.cache.orders()
        # TODO: Implement
        # assert bracket.orders[0].status == OrderStatus.ACCEPTED
        # assert entry in strategy.cache.orders_working()
        # assert strategy.cache.is_order_working(entry.client_order_id)
        # assert not strategy.cache.is_order_completed(entry.client_order_id)

    def test_cancel_order(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.006"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)

        # Act
        strategy.cancel_order(order)
        self.exchange.process(0)

        # Assert
        assert order in strategy.cache.orders()
        assert strategy.cache.orders()[0].status == OrderStatus.CANCELED
        assert order.client_order_id == strategy.cache.orders_completed(
        )[0].client_order_id
        assert order not in strategy.cache.orders_working()
        assert strategy.cache.order_exists(order.client_order_id)
        assert not strategy.cache.is_order_working(order.client_order_id)
        assert strategy.cache.is_order_completed(order.client_order_id)

    def test_cancel_order_when_pending_cancel_does_not_submit_command(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.006"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)
        self.exec_engine.process(TestStubs.event_order_pending_cancel(order))

        # Act
        strategy.cancel_order(order)
        self.exchange.process(0)

        # Assert
        assert strategy.cache.orders()[0].status == OrderStatus.PENDING_CANCEL
        assert order in strategy.cache.orders_working()
        assert strategy.cache.order_exists(order.client_order_id)
        assert strategy.cache.is_order_working(order.client_order_id)
        assert not strategy.cache.is_order_completed(order.client_order_id)

    def test_cancel_order_when_completed_does_not_submit_command(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.006"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)
        self.exec_engine.process(TestStubs.event_order_expired(order))

        # Act
        strategy.cancel_order(order)
        self.exchange.process(0)

        # Assert
        assert strategy.cache.orders()[0].status == OrderStatus.EXPIRED
        assert order not in strategy.cache.orders_working()
        assert strategy.cache.order_exists(order.client_order_id)
        assert not strategy.cache.is_order_working(order.client_order_id)
        assert strategy.cache.is_order_completed(order.client_order_id)

    def test_modify_order_when_pending_update_does_not_submit_command(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.001"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)
        self.exec_engine.process(TestStubs.event_order_pending_update(order))

        # Act
        strategy.modify_order(
            order=order,
            quantity=Quantity.from_int(100000),
            price=Price.from_str("90.000"),
        )
        self.exchange.process(0)

        # Assert
        assert self.exec_engine.command_count == 1

    def test_modify_order_when_pending_cancel_does_not_submit_command(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.001"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)
        self.exec_engine.process(TestStubs.event_order_pending_cancel(order))

        # Act
        strategy.modify_order(
            order=order,
            quantity=Quantity.from_int(100000),
            price=Price.from_str("90.000"),
        )
        self.exchange.process(0)

        # Assert
        assert self.exec_engine.command_count == 1

    def test_modify_order_when_completed_does_not_submit_command(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.001"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)
        self.exec_engine.process(TestStubs.event_order_expired(order))

        # Act
        strategy.modify_order(
            order=order,
            quantity=Quantity.from_int(100000),
            price=Price.from_str("90.000"),
        )
        self.exchange.process(0)

        # Assert
        assert self.exec_engine.command_count == 1

    def test_modify_order_when_no_changes_does_not_submit_command(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.001"),
        )

        strategy.submit_order(order)

        # Act
        strategy.modify_order(
            order=order,
            quantity=Quantity.from_int(100000),
            price=Price.from_str("90.001"),
        )

        # Assert
        assert self.exec_engine.command_count == 1

    def test_modify_order(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.000"),
        )

        strategy.submit_order(order)
        self.exchange.process(0)

        # Act
        strategy.modify_order(
            order=order,
            quantity=Quantity.from_int(110000),
            price=Price.from_str("90.001"),
        )
        self.exchange.process(0)

        # Assert
        assert strategy.cache.orders()[0] == order
        assert strategy.cache.orders()[0].status == OrderStatus.ACCEPTED
        assert strategy.cache.orders()[0].quantity == Quantity.from_int(110000)
        assert strategy.cache.orders()[0].price == Price.from_str("90.001")
        assert strategy.cache.order_exists(order.client_order_id)
        assert strategy.cache.is_order_working(order.client_order_id)
        assert not strategy.cache.is_order_completed(order.client_order_id)
        assert strategy.portfolio.is_flat(order.instrument_id)

    def test_cancel_all_orders(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order1 = strategy.order_factory.stop_market(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.007"),
        )

        order2 = strategy.order_factory.stop_market(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("90.006"),
        )

        strategy.submit_order(order1)
        self.exchange.process(0)
        strategy.submit_order(order2)
        self.exchange.process(0)

        # Act
        strategy.cancel_all_orders(USDJPY_SIM.id)
        self.exchange.process(0)

        # Assert
        assert order1 in self.cache.orders()
        assert order2 in self.cache.orders()
        assert self.cache.orders()[0].status == OrderStatus.CANCELED
        assert self.cache.orders()[1].status == OrderStatus.CANCELED
        assert order1 in self.cache.orders_completed()
        assert order2 in strategy.cache.orders_completed()

    def test_flatten_position_when_position_already_flat_does_nothing(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        order1 = strategy.order_factory.market(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        order2 = strategy.order_factory.market(
            USDJPY_SIM.id,
            OrderSide.SELL,
            Quantity.from_int(100000),
        )

        strategy.submit_order(order1)
        self.exchange.process(0)
        strategy.submit_order(order2,
                              PositionId("1-001"))  # Generated by exchange
        self.exchange.process(0)

        position = strategy.cache.positions_closed()[0]

        # Act
        strategy.flatten_position(position)
        self.exchange.process(0)

        # Assert
        assert strategy.portfolio.is_completely_flat()

    def test_flatten_position(self):
        # Arrange
        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(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        strategy.submit_order(order)
        self.exchange.process(0)

        position = self.cache.positions_open()[0]

        # Act
        strategy.flatten_position(position)
        self.exchange.process(0)

        # Assert
        assert order.status == OrderStatus.FILLED
        assert strategy.portfolio.is_completely_flat()

    def test_flatten_all_positions(self):
        # Arrange
        strategy = TradingStrategy()
        strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Start strategy and submit orders to open positions
        strategy.start()

        order1 = strategy.order_factory.market(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        order2 = strategy.order_factory.market(
            USDJPY_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
        )

        strategy.submit_order(order1)
        self.exchange.process(0)
        strategy.submit_order(order2)
        self.exchange.process(0)

        # Act
        strategy.flatten_all_positions(USDJPY_SIM.id)
        self.exchange.process(0)

        # Assert
        assert order1.status == OrderStatus.FILLED
        assert order2.status == OrderStatus.FILLED
        assert strategy.portfolio.is_completely_flat()
class TestRiskEngine:
    def setup(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(
            clock=self.clock,
            level_stdout=LogLevel.DEBUG,
        )

        self.trader_id = TestIdStubs.trader_id()
        self.account_id = TestIdStubs.account_id()
        self.venue = Venue("SIM")

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

        self.cache = TestComponentStubs.cache()

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

        config = ExecEngineConfig()
        config.allow_cash_positions = True  # Retain original behaviour for now
        self.exec_engine = ExecutionEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            config=config,
        )

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

        self.exec_client = MockExecutionClient(
            client_id=ClientId(self.venue.value),
            venue=self.venue,
            account_type=AccountType.MARGIN,
            base_currency=USD,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )
        self.portfolio.update_account(TestEventStubs.margin_account_state())
        self.exec_engine.register_client(self.exec_client)

        # Prepare data
        self.cache.add_instrument(AUDUSD_SIM)

    def test_config_risk_engine(self):
        # Arrange
        self.msgbus.deregister("RiskEngine.execute", self.risk_engine.execute)

        config = RiskEngineConfig(
            bypass=True,  # <-- bypassing pre-trade risk checks for backtest
            max_order_rate="5/00:00:01",
            max_notional_per_order={"GBP/USD.SIM": 2_000_000},
        )

        # Act
        risk_engine = RiskEngine(
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            config=config,
        )

        # Assert
        assert risk_engine.max_order_rate() == (5, timedelta(seconds=1))
        assert risk_engine.max_notionals_per_order() == {GBPUSD_SIM.id: Decimal("2000000")}
        assert risk_engine.max_notional_per_order(GBPUSD_SIM.id) == 2_000_000

    def test_risk_engine_on_stop(self):
        # Arrange, Act
        self.risk_engine.start()
        self.risk_engine.stop()

        # Assert
        assert self.risk_engine.is_stopped

    def test_process_event_then_handles(self):
        # Arrange
        event = Event(
            event_id=self.uuid_factory.generate(),
            ts_event=self.clock.timestamp_ns(),
            ts_init=self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.process(event)

        # Assert
        assert self.risk_engine.event_count == 1

    def test_trading_state_after_instantiation_returns_active(self):
        # Arrange, Act
        result = self.risk_engine.trading_state

        # Assert
        assert result == TradingState.ACTIVE

    def test_set_trading_state_when_no_change_logs_warning(self):
        # Arrange, Act
        self.risk_engine.set_trading_state(TradingState.ACTIVE)

        # Assert
        assert self.risk_engine.trading_state == TradingState.ACTIVE

    def test_set_trading_state_changes_value_and_publishes_event(self):
        # Arrange
        handler = []
        self.msgbus.subscribe(topic="events.risk*", handler=handler.append)

        # Act
        self.risk_engine.set_trading_state(TradingState.HALTED)

        # Assert
        assert type(handler[0]) == TradingStateChanged
        assert self.risk_engine.trading_state == TradingState.HALTED

    def test_max_order_rate_when_no_risk_config_returns_100_per_second(self):
        # Arrange, Act
        result = self.risk_engine.max_order_rate()

        assert result == (100, timedelta(seconds=1))

    def test_max_notionals_per_order_when_no_risk_config_returns_empty_dict(self):
        # Arrange, Act
        result = self.risk_engine.max_notionals_per_order()

        assert result == {}

    def test_max_notional_per_order_when_no_risk_config_returns_none(self):
        # Arrange, Act
        result = self.risk_engine.max_notional_per_order(AUDUSD_SIM.id)

        assert result is None

    def test_set_max_notional_per_order_changes_setting(self):
        # Arrange, Act
        self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000)

        max_notionals = self.risk_engine.max_notionals_per_order()
        max_notional = self.risk_engine.max_notional_per_order(AUDUSD_SIM.id)

        # Assert
        assert max_notionals == {AUDUSD_SIM.id: Decimal("1000000")}
        assert max_notional == Decimal(1_000_000)

    def test_given_random_command_then_logs_and_continues(self):
        # Arrange
        random = TradingCommand(
            client_id=None,
            trader_id=self.trader_id,
            strategy_id=StrategyId("SCALPER-001"),
            instrument_id=AUDUSD_SIM.id,
            command_id=self.uuid_factory.generate(),
            ts_init=self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(random)

    def test_given_random_event_then_logs_and_continues(self):
        # Arrange
        random = Event(
            event_id=self.uuid_factory.generate(),
            ts_event=self.clock.timestamp_ns(),
            ts_init=self.clock.timestamp_ns(),
        )

        self.risk_engine.process(random)

    # -- SUBMIT ORDER TESTS ------------------------------------------------------------------------

    def test_submit_order_with_default_settings_then_sends_to_client(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.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 1
        assert self.exec_client.calls == ["_start", "submit_order"]

    def test_submit_order_when_duplicate_id_then_denies(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(),
        )

        self.risk_engine.execute(submit_order)

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 1
        assert self.exec_client.calls == ["_start", "submit_order"]

    def test_submit_order_when_risk_bypassed_sends_to_execution_engine(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(
            trader_id=self.trader_id,
            strategy_id=strategy.id,
            position_id=None,
            order=order,
            command_id=self.uuid_factory.generate(),
            ts_init=self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 1  # <-- initial account event
        assert self.exec_client.calls == ["_start", "submit_order"]

    def test_submit_order_when_position_already_closed_then_denies(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,
        )

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

        order2 = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.SELL,
            Quantity.from_int(100000),
        )

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

        submit_order1 = SubmitOrder(
            trader_id=self.trader_id,
            strategy_id=strategy.id,
            position_id=None,
            order=order1,
            command_id=self.uuid_factory.generate(),
            ts_init=self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order1)
        self.exec_engine.process(TestEventStubs.order_submitted(order1))
        self.exec_engine.process(TestEventStubs.order_accepted(order1))
        self.exec_engine.process(TestEventStubs.order_filled(order1, AUDUSD_SIM))

        submit_order2 = SubmitOrder(
            trader_id=self.trader_id,
            strategy_id=strategy.id,
            position_id=PositionId("P-19700101-000000-000-000-1"),
            order=order2,
            command_id=self.uuid_factory.generate(),
            ts_init=self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order2)
        self.exec_engine.process(TestEventStubs.order_submitted(order2))
        self.exec_engine.process(TestEventStubs.order_accepted(order2))
        self.exec_engine.process(TestEventStubs.order_filled(order2, AUDUSD_SIM))

        submit_order3 = SubmitOrder(
            trader_id=self.trader_id,
            strategy_id=strategy.id,
            position_id=PositionId("P-19700101-000000-000-000-1"),
            order=order3,
            command_id=self.uuid_factory.generate(),
            ts_init=self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(submit_order3)

        # Assert
        assert self.exec_engine.command_count == 2
        assert self.exec_client.calls == ["_start", "submit_order", "submit_order"]

    def test_submit_order_when_position_id_not_in_cache_then_denies(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,
            PositionId("009"),  # <-- not in the cache
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0

    def test_submit_order_when_instrument_not_in_cache_then_denies(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(
            GBPUSD_SIM.id,  # <-- not in the cache
            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.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_invalid_price_precision_then_denies(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("0.9999999999999999"),  # <- invalid price
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_invalid_negative_price_and_not_option_then_denies(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.0"),  # <- invalid price
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_invalid_trigger_price_then_denies(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.stop_limit(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
            Price.from_str("0.999999999999999"),  # <- invalid trigger
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_invalid_quantity_precision_then_denies(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_str("1.111111111111111111"),  # <- invalid quantity
            Price.from_str("1.00000"),
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_invalid_quantity_exceeds_maximum_then_denies(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(1_000_000_000),  # <- invalid quantity fat finger!
            Price.from_str("1.00000"),
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_invalid_quantity_less_than_minimum_then_denies(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(1),  # <- invalid quantity
            Price.from_str("1.00000"),
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_market_order_and_no_market_then_logs_warning(self):
        # Arrange
        self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000)

        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(10000000),
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 1  # <-- command reaches engine with warning

    def test_submit_order_when_market_order_and_over_max_notional_then_denies(self):
        # Arrange
        self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000)

        # Initialize market
        quote = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id)
        self.cache.add_quote_tick(quote)

        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(10000000),
        )

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

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    def test_submit_order_when_reducing_and_buy_order_adds_then_denies(self):
        # Arrange
        self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000)

        # Initialize market
        quote = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id)
        self.cache.add_quote_tick(quote)

        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,
        )

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

        submit_order1 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order1,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order1)
        self.risk_engine.set_trading_state(TradingState.REDUCING)  # <-- allow reducing orders only

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

        submit_order2 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order2,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.process(TestEventStubs.order_submitted(order1))
        self.exec_engine.process(TestEventStubs.order_accepted(order1))
        self.exec_engine.process(TestEventStubs.order_filled(order1, AUDUSD_SIM))

        # Act
        self.risk_engine.execute(submit_order2)

        # Assert
        assert self.portfolio.is_net_long(AUDUSD_SIM.id)
        assert self.exec_engine.command_count == 1  # <-- command never reaches engine

    def test_submit_order_when_reducing_and_sell_order_adds_then_denies(self):
        # Arrange
        self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000)

        # Initialize market
        quote = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id)
        self.cache.add_quote_tick(quote)

        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,
        )

        order1 = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.SELL,
            Quantity.from_int(100000),
        )

        submit_order1 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order1,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_order1)
        self.risk_engine.set_trading_state(TradingState.REDUCING)  # <-- allow reducing orders only

        order2 = strategy.order_factory.market(
            AUDUSD_SIM.id,
            OrderSide.SELL,
            Quantity.from_int(100000),
        )

        submit_order2 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order2,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.exec_engine.process(TestEventStubs.order_submitted(order1))
        self.exec_engine.process(TestEventStubs.order_accepted(order1))
        self.exec_engine.process(TestEventStubs.order_filled(order1, AUDUSD_SIM))

        # Act
        self.risk_engine.execute(submit_order2)

        # Assert
        assert self.portfolio.is_net_short(AUDUSD_SIM.id)
        assert self.exec_engine.command_count == 1  # <-- command never reaches engine

    def test_submit_order_when_trading_halted_then_denies_order(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(),
        )

        # Halt trading
        self.risk_engine.set_trading_state(TradingState.HALTED)

        # Act
        self.risk_engine.execute(submit_order)

        # Assert
        assert self.risk_engine.command_count == 1  # <-- command never reaches engine

    def test_submit_order_list_when_trading_halted_then_denies_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,
        )

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

        stop_loss = strategy.order_factory.stop_market(  # <-- duplicate
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

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

        bracket = OrderList(
            list_id=OrderListId("1"),
            orders=[entry, stop_loss, take_profit],
        )

        submit_bracket = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Halt trading
        self.risk_engine.set_trading_state(TradingState.HALTED)

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.risk_engine.command_count == 1  # <-- command never reaches engine

    # -- SUBMIT BRACKET ORDER TESTS ----------------------------------------------------------------

    def test_submit_bracket_with_default_settings_sends_to_client(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,
        )

        bracket = strategy.order_factory.bracket_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            stop_loss=Price.from_str("1.00000"),
            take_profit=Price.from_str("1.00010"),
        )

        submit_bracket = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.exec_engine.command_count == 1
        assert self.exec_client.calls == ["_start", "submit_order_list"]

    def test_submit_bracket_order_with_duplicate_entry_id_then_denies(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,
        )

        bracket = strategy.order_factory.bracket_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            stop_loss=Price.from_str("1.00000"),
            take_profit=Price.from_str("1.00010"),
        )

        submit_bracket = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_bracket)

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.exec_engine.command_count == 1  # <-- command never reaches engine

    def test_submit_bracket_order_with_duplicate_stop_loss_id_then_denies(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,
        )

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

        stop_loss = strategy.order_factory.stop_market(  # <-- duplicate
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00000"),
        )

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

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

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

        bracket1 = OrderList(
            list_id=OrderListId("1"),
            orders=[entry1, stop_loss, take_profit1],
        )

        bracket2 = OrderList(
            list_id=OrderListId("1"),
            orders=[entry2, stop_loss, take_profit2],
        )

        submit_bracket1 = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket1,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        submit_bracket2 = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket2,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_bracket1)

        # Act
        self.risk_engine.execute(submit_bracket2)

        # Assert
        assert self.exec_engine.command_count == 1  # <-- command never reaches engine

    def test_submit_bracket_order_with_duplicate_take_profit_id_then_denies(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,
        )

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

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

        take_profit = strategy.order_factory.limit(  # <-- duplicate
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.10000"),
        )

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

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

        bracket1 = OrderList(
            list_id=OrderListId("1"),
            orders=[entry1, stop_loss1, take_profit],
        )

        bracket2 = OrderList(
            list_id=OrderListId("1"),
            orders=[entry2, stop_loss2, take_profit],
        )

        submit_bracket1 = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket1,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        submit_bracket2 = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket2,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit_bracket1)

        # Act
        self.risk_engine.execute(submit_bracket2)

        # Assert
        assert self.exec_engine.command_count == 1  # <-- command never reaches engine

    def test_submit_bracket_order_when_instrument_not_in_cache_then_denies(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,
        )

        bracket = strategy.order_factory.bracket_market(
            GBPUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            stop_loss=Price.from_str("1.00000"),
            take_profit=Price.from_str("1.00010"),
        )

        submit_bracket = SubmitOrderList(
            self.trader_id,
            strategy.id,
            bracket,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(submit_bracket)

        # Assert
        assert self.exec_engine.command_count == 0  # <-- command never reaches engine

    # -- UPDATE ORDER TESTS ------------------------------------------------------------------------

    def test_update_order_when_no_order_found_denies(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,
        )

        modify = ModifyOrder(
            self.trader_id,
            strategy.id,
            AUDUSD_SIM.id,
            ClientOrderId("invalid"),
            VenueOrderId("1"),
            Quantity.from_int(100000),
            Price.from_str("1.00010"),
            None,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(modify)

        # Assert
        assert self.exec_client.calls == ["_start"]
        assert self.risk_engine.command_count == 1
        assert self.exec_engine.command_count == 0

    def test_update_order_when_already_closed_then_denies(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.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00010"),
        )

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

        self.risk_engine.execute(submit)

        self.exec_engine.process(TestEventStubs.order_submitted(order))
        self.exec_engine.process(TestEventStubs.order_accepted(order))
        self.exec_engine.process(TestEventStubs.order_filled(order, AUDUSD_SIM))

        modify = ModifyOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            VenueOrderId("1"),
            order.quantity,
            Price.from_str("1.00010"),
            None,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(modify)

        # Assert
        assert self.exec_client.calls == ["_start", "submit_order"]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 1

    def test_update_order_when_in_flight_then_denies(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.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00010"),
        )

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

        self.risk_engine.execute(submit)

        self.exec_engine.process(TestEventStubs.order_submitted(order))

        modify = ModifyOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            VenueOrderId("1"),
            order.quantity,
            Price.from_str("1.00010"),
            None,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(modify)

        # Assert
        assert self.exec_client.calls == ["_start", "submit_order"]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 1

    def test_modify_order_with_default_settings_then_sends_to_client(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.stop_market(
            AUDUSD_SIM.id,
            OrderSide.BUY,
            Quantity.from_int(100000),
            Price.from_str("1.00010"),
        )

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

        modify = ModifyOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            VenueOrderId("1"),
            order.quantity,
            Price.from_str("1.00010"),
            None,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit)

        # Act
        self.risk_engine.execute(modify)

        # Assert
        assert self.exec_client.calls == ["_start", "submit_order", "modify_order"]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 2

    # -- CANCEL ORDER TESTS ------------------------------------------------------------------------

    def test_cancel_order_when_order_does_not_exist_then_denies(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,
        )

        cancel = CancelOrder(
            self.trader_id,
            strategy.id,
            AUDUSD_SIM.id,
            ClientOrderId("1"),
            VenueOrderId("1"),
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(cancel)

        # Assert
        assert self.exec_client.calls == ["_start"]
        assert self.risk_engine.command_count == 1
        assert self.exec_engine.command_count == 0

    def test_cancel_order_when_already_closed_then_denies(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 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit)
        self.exec_engine.process(TestEventStubs.order_submitted(order))
        self.exec_engine.process(TestEventStubs.order_rejected(order))

        cancel = CancelOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            VenueOrderId("1"),
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        # Act
        self.risk_engine.execute(cancel)

        # Assert
        assert self.exec_client.calls == ["_start", "submit_order"]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 1

    def test_cancel_order_when_already_pending_cancel_then_denies(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 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        cancel = CancelOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            VenueOrderId("1"),
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit)
        self.exec_engine.process(TestEventStubs.order_submitted(order))
        self.exec_engine.process(TestEventStubs.order_accepted(order))

        self.risk_engine.execute(cancel)
        self.exec_engine.process(TestEventStubs.order_pending_cancel(order))

        # Act
        self.risk_engine.execute(cancel)

        # Assert
        assert self.exec_client.calls == ["_start", "submit_order", "cancel_order"]
        assert self.risk_engine.command_count == 3
        assert self.exec_engine.command_count == 2

    def test_cancel_order_with_default_settings_then_sends_to_client(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 = SubmitOrder(
            self.trader_id,
            strategy.id,
            None,
            order,
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        cancel = CancelOrder(
            self.trader_id,
            strategy.id,
            order.instrument_id,
            order.client_order_id,
            VenueOrderId("1"),
            self.uuid_factory.generate(),
            self.clock.timestamp_ns(),
        )

        self.risk_engine.execute(submit)

        # Act
        self.risk_engine.execute(cancel)

        # Assert
        assert self.exec_client.calls == ["_start", "submit_order", "cancel_order"]
        assert self.risk_engine.command_count == 2
        assert self.exec_engine.command_count == 2
class TestTrader:
    def setup(self):
        # Fixture Setup
        self.clock = TestClock()
        self.logger = Logger(self.clock)

        self.trader_id = TestStubs.trader_id()
        self.account_id = TestStubs.account_id()

        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 = DataEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.data_engine.process(USDJPY_SIM)

        self.exec_engine = ExecutionEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exchange = SimulatedExchange(
            venue=Venue("SIM"),
            venue_type=VenueType.ECN,
            oms_type=OMSType.HEDGING,
            account_type=AccountType.MARGIN,
            base_currency=USD,
            starting_balances=[Money(1_000_000, USD)],
            default_leverage=Decimal(50),
            leverages={},
            is_frozen_account=False,
            cache=self.cache,
            instruments=[USDJPY_SIM],
            modules=[],
            fill_model=FillModel(),
            clock=self.clock,
            logger=self.logger,
        )

        self.data_client = BacktestMarketDataClient(
            client_id=ClientId("SIM"),
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            account_id=self.account_id,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

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

        # Wire up components
        self.data_engine.register_client(self.data_client)
        self.exec_engine.register_client(self.exec_client)

        self.trader = Trader(
            trader_id=self.trader_id,
            msgbus=self.msgbus,
            cache=self.cache,
            portfolio=self.portfolio,
            data_engine=self.data_engine,
            risk_engine=self.risk_engine,
            exec_engine=self.exec_engine,
            clock=self.clock,
            logger=self.logger,
        )

    def test_initialize_trader(self):
        # Arrange, Act, Assert
        assert self.trader.id == TraderId("TESTER-000")
        assert self.trader.is_initialized
        assert len(self.trader.strategy_states()) == 0

    def test_add_strategy(self):
        # Arrange, Act
        self.trader.add_strategy(TradingStrategy())

        # Assert
        assert self.trader.strategy_states() == {
            StrategyId("TradingStrategy-000"): "INITIALIZED"
        }

    def test_add_strategies(self):
        # Arrange
        strategies = [
            TradingStrategy(TradingStrategyConfig(order_id_tag="001")),
            TradingStrategy(TradingStrategyConfig(order_id_tag="002")),
        ]

        # Act
        self.trader.add_strategies(strategies)

        # Assert
        assert self.trader.strategy_states() == {
            StrategyId("TradingStrategy-001"): "INITIALIZED",
            StrategyId("TradingStrategy-002"): "INITIALIZED",
        }

    def test_clear_strategies(self):
        # Arrange
        strategies = [
            TradingStrategy(TradingStrategyConfig(order_id_tag="001")),
            TradingStrategy(TradingStrategyConfig(order_id_tag="002")),
        ]
        self.trader.add_strategies(strategies)

        # Act
        self.trader.clear_strategies()

        # Assert
        assert self.trader.strategy_states() == {}

    def test_add_actor(self):
        # Arrange
        config = ActorConfig(component_id="MyPlugin-01")
        actor = Actor(config)

        # Act
        self.trader.add_actor(actor)

        # Assert
        assert self.trader.actor_ids() == [ComponentId("MyPlugin-01")]

    def test_add_actors(self):
        # Arrange
        actors = [
            Actor(ActorConfig(component_id="MyPlugin-01")),
            Actor(ActorConfig(component_id="MyPlugin-02")),
        ]

        # Act
        self.trader.add_actors(actors)

        # Assert
        assert self.trader.actor_ids() == [
            ComponentId("MyPlugin-01"),
            ComponentId("MyPlugin-02"),
        ]

    def test_clear_actors(self):
        # Arrange
        actors = [
            Actor(ActorConfig(component_id="MyPlugin-01")),
            Actor(ActorConfig(component_id="MyPlugin-02")),
        ]
        self.trader.add_actors(actors)

        # Act
        self.trader.clear_actors()

        # Assert
        assert self.trader.actor_ids() == []

    def test_get_strategy_states(self):
        # Arrange
        strategies = [
            TradingStrategy(TradingStrategyConfig(order_id_tag="001")),
            TradingStrategy(TradingStrategyConfig(order_id_tag="002")),
        ]
        self.trader.add_strategies(strategies)

        # Act
        status = self.trader.strategy_states()

        # Assert
        assert StrategyId("TradingStrategy-001") in status
        assert StrategyId("TradingStrategy-002") in status
        assert status[StrategyId("TradingStrategy-001")] == "INITIALIZED"
        assert status[StrategyId("TradingStrategy-002")] == "INITIALIZED"
        assert len(status) == 2

    def test_change_strategies(self):
        # Arrange
        strategies = [
            TradingStrategy(TradingStrategyConfig(order_id_tag="003")),
            TradingStrategy(TradingStrategyConfig(order_id_tag="004")),
        ]

        # Act
        self.trader.add_strategies(strategies)

        # Assert
        assert strategies[0].id in self.trader.strategy_states()
        assert strategies[1].id in self.trader.strategy_states()
        assert len(self.trader.strategy_states()) == 2

    def test_start_a_trader(self):
        # Arrange
        strategies = [
            TradingStrategy(TradingStrategyConfig(order_id_tag="001")),
            TradingStrategy(TradingStrategyConfig(order_id_tag="002")),
        ]
        self.trader.add_strategies(strategies)

        # Act
        self.trader.start()

        strategy_states = self.trader.strategy_states()

        # Assert
        assert self.trader.is_running
        assert strategy_states[StrategyId("TradingStrategy-001")] == "RUNNING"
        assert strategy_states[StrategyId("TradingStrategy-002")] == "RUNNING"

    def test_stop_a_running_trader(self):
        # Arrange
        strategies = [
            TradingStrategy(TradingStrategyConfig(order_id_tag="001")),
            TradingStrategy(TradingStrategyConfig(order_id_tag="002")),
        ]
        self.trader.add_strategies(strategies)
        self.trader.start()

        # Act
        self.trader.stop()

        strategy_states = self.trader.strategy_states()

        # Assert
        assert self.trader.is_stopped
        assert strategy_states[StrategyId("TradingStrategy-001")] == "STOPPED"
        assert strategy_states[StrategyId("TradingStrategy-002")] == "STOPPED"

    def test_subscribe_to_msgbus_topic_adds_subscription(self):
        # Arrange
        consumer = []

        # Act
        self.trader.subscribe("events*", consumer.append)

        # Assert
        assert len(self.msgbus.subscriptions("events*")) == 6
        assert "events*" in self.msgbus.topics()
        assert self.msgbus.subscriptions(
            "events*")[-1].handler == consumer.append

    def test_unsubscribe_from_msgbus_topic_removes_subscription(self):
        # Arrange
        consumer = []
        self.trader.subscribe("events*", consumer.append)

        # Act
        self.trader.unsubscribe("events*", consumer.append)

        # Assert
        assert len(self.msgbus.subscriptions("events*")) == 5
Esempio n. 10
0
class SimulatedExchangeTests(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,
            config={'use_previous_close': False},  # To correctly reproduce historical data bars
        )

        self.data_engine.cache.add_instrument(AUDUSD_SIM)
        self.data_engine.cache.add_instrument(USDJPY_SIM)
        self.portfolio.register_cache(self.data_engine.cache)

        self.analyzer = PerformanceAnalyzer()
        self.trader_id = TraderId("TESTER", "000")
        self.account_id = AccountId("SIM", "001")

        exec_db = BypassExecutionDatabase(
            trader_id=self.trader_id,
            logger=self.logger,
        )

        self.exec_engine = ExecutionEngine(
            database=exec_db,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.exchange = SimulatedExchange(
            venue=SIM,
            oms_type=OMSType.HEDGING,
            generate_position_ids=False,  # Will force execution engine to generate ids
            is_frozen_account=False,
            starting_balances=[Money(1_000_000, USD)],
            instruments=[AUDUSD_SIM, USDJPY_SIM],
            modules=[],
            fill_model=FillModel(),
            exec_cache=self.exec_engine.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            account_id=self.account_id,
            engine=self.exec_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_engine.register_client(self.exec_client)
        self.exchange.register_client(self.exec_client)

        self.strategy = MockStrategy(bar_type=TestStubs.bartype_usdjpy_1min_bid())
        self.strategy.register_trader(
            self.trader_id,
            self.clock,
            self.logger,
        )

        self.data_engine.register_strategy(self.strategy)
        self.exec_engine.register_strategy(self.strategy)
        self.data_engine.start()
        self.exec_engine.start()
        self.strategy.start()

    def test_repr(self):
        # Arrange
        # Act
        # Assert
        self.assertEqual("SimulatedExchange(SIM)", repr(self.exchange))

    def test_check_residuals(self):
        # Arrange
        # Act
        self.exchange.check_residuals()
        # Assert
        self.assertTrue(True)  # No exceptions raised

    def test_check_residuals_with_working_and_oco_orders(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry1 = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.000"),
        )

        entry2 = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("89.900"),
        )

        bracket1 = self.strategy.order_factory.bracket(
            entry_order=entry1,
            stop_loss=Price("89.900"),
            take_profit=Price("91.000"),
        )

        bracket2 = self.strategy.order_factory.bracket(
            entry_order=entry2,
            stop_loss=Price("89.800"),
        )

        self.strategy.submit_bracket_order(bracket1)
        self.strategy.submit_bracket_order(bracket2)

        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("89.998"),
            Price("89.999"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)

        # Act
        self.exchange.check_residuals()

        # Assert
        self.assertEqual(3, len(self.exchange.get_working_orders()))
        self.assertIn(bracket1.stop_loss, self.exchange.get_working_orders().values())
        self.assertIn(bracket1.take_profit, self.exchange.get_working_orders().values())
        self.assertIn(entry2, self.exchange.get_working_orders().values())

    def test_get_working_orders_when_no_orders_returns_empty_dict(self):
        # Arrange
        # Act
        orders = self.exchange.get_working_orders()

        self.assertEqual({}, orders)

    def test_submit_order_with_no_market_rejects_order(self):
        # Arrange
        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("80.000"),
        )

        # Act
        self.strategy.submit_order(order)

        # Assert
        self.assertEqual(2, self.strategy.object_storer.count)
        self.assertTrue(isinstance(self.strategy.object_storer.get_store()[1], OrderRejected))

    def test_submit_order_with_invalid_price_gets_rejected(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.exchange.process_tick(tick)
        self.portfolio.update_tick(tick)

        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("80.000"),
        )

        # Act
        self.strategy.submit_order(order)

        # Assert
        self.assertEqual(OrderState.REJECTED, order.state)

    def test_submit_market_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        # Create order
        order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        # Act
        self.strategy.submit_order(order)

        # Assert
        self.assertEqual(OrderState.FILLED, order.state)
        self.assertEqual(Decimal("90.003"), order.avg_price)

    def test_submit_limit_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("80.000"),
        )

        # Act
        self.strategy.submit_order(order)

        # Assert
        self.assertEqual(1, len(self.exchange.get_working_orders()))
        self.assertIn(order.cl_ord_id, self.exchange.get_working_orders())

    def test_submit_bracket_market_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry_order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        bracket_order = self.strategy.order_factory.bracket(
            entry_order,
            Price("80.000"),
        )

        # Act
        self.strategy.submit_bracket_order(bracket_order)

        # Assert
        self.assertEqual(OrderState.FILLED, entry_order.state)

    def test_submit_bracket_stop_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry_order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("96.710"),
        )

        bracket_order = self.strategy.order_factory.bracket(
            entry_order,
            Price("86.000"),
            Price("97.000"),
        )

        # Act
        self.strategy.submit_bracket_order(bracket_order)

        # Assert
        self.assertEqual(1, len(self.exchange.get_working_orders()))
        self.assertIn(entry_order.cl_ord_id, self.exchange.get_working_orders())

    def test_cancel_stop_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("96.711"),
        )

        self.strategy.submit_order(order)

        # Act
        self.strategy.cancel_order(order)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))

    def test_cancel_stop_order_when_order_does_not_exist_generates_cancel_reject(self):
        # Arrange
        command = CancelOrder(
            venue=SIM,
            trader_id=self.trader_id,
            account_id=self.account_id,
            cl_ord_id=ClientOrderId("O-123456"),
            order_id=OrderId("001"),
            command_id=self.uuid_factory.generate(),
            command_timestamp=UNIX_EPOCH,
        )

        # Act
        self.exchange.handle_cancel_order(command)

        # Assert
        self.assertEqual(2, self.exec_engine.event_count)

    def test_modify_stop_order_when_order_does_not_exist(self):
        # Arrange
        command = AmendOrder(
            venue=SIM,
            trader_id=self.trader_id,
            account_id=self.account_id,
            cl_ord_id=ClientOrderId("O-123456"),
            quantity=Quantity(100000),
            price=Price("1.00000"),
            command_id=self.uuid_factory.generate(),
            command_timestamp=UNIX_EPOCH,
        )

        # Act
        self.exchange.handle_amend_order(command)

        # Assert
        self.assertEqual(2, self.exec_engine.event_count)

    def test_modify_stop_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("96.711"),
        )

        self.strategy.submit_order(order)

        # Act
        self.strategy.amend_order(order, order.quantity, Price("96.714"))

        # Assert
        self.assertEqual(1, len(self.exchange.get_working_orders()))
        self.assertEqual(Price("96.714"), order.price)

    def test_expire_order(self):
        # Arrange
        # Prepare market
        tick1 = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick1)
        self.exchange.process_tick(tick1)

        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("96.711"),
            time_in_force=TimeInForce.GTD,
            expire_time=UNIX_EPOCH + timedelta(minutes=1),
        )

        self.strategy.submit_order(order)

        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("96.709"),
            Price("96.710"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH + timedelta(minutes=1),
        )

        # Act
        self.exchange.process_tick(tick2)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))

    def test_modify_bracket_order_working_stop_loss(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry_order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        bracket_order = self.strategy.order_factory.bracket(
            entry_order,
            stop_loss=Price("85.000"),
        )

        self.strategy.submit_bracket_order(bracket_order)

        # Act
        self.strategy.amend_order(bracket_order.stop_loss, bracket_order.entry.quantity, Price("85.100"))

        # Assert
        self.assertEqual(Price("85.100"), bracket_order.stop_loss.price)

    def test_submit_market_order_with_slippage_fill_model_slips_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        fill_model = FillModel(
            prob_fill_at_limit=0.0,
            prob_fill_at_stop=1.0,
            prob_slippage=1.0,
            random_seed=None,
        )

        self.exchange.set_fill_model(fill_model)

        order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        # Act
        self.strategy.submit_order(order)

        # Assert
        self.assertEqual(Decimal("90.004"), order.avg_price)

    def test_order_fills_gets_commissioned(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        top_up_order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        reduce_order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(50000),
        )

        # Act
        self.strategy.submit_order(order)

        position_id = PositionId("P-19700101-000000-000-001-1")  # Generated by platform

        self.strategy.submit_order(top_up_order, position_id)
        self.strategy.submit_order(reduce_order, position_id)

        account_event1 = self.strategy.object_storer.get_store()[2]
        account_event2 = self.strategy.object_storer.get_store()[6]
        account_event3 = self.strategy.object_storer.get_store()[10]

        account = self.exec_engine.cache.account_for_venue(Venue("SIM"))

        # Assert
        self.assertEqual(Money(180.01, JPY), account_event1.commission)
        self.assertEqual(Money(180.01, JPY), account_event2.commission)
        self.assertEqual(Money(90.00, JPY), account_event3.commission)
        self.assertTrue(Money(999995.00, USD), account.balance())

    def test_process_quote_tick_fills_buy_stop_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("96.711"),
        )

        self.strategy.submit_order(order)

        # Act
        tick2 = QuoteTick(
            AUDUSD_SIM.symbol,  # Different market
            Price("80.010"),
            Price("80.011"),
            Quantity(200000),
            Quantity(200000),
            UNIX_EPOCH,
        )

        tick3 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("96.710"),
            Price("96.712"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)
        self.exchange.process_tick(tick3)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))
        self.assertEqual(OrderState.FILLED, order.state)
        self.assertEqual(Price("96.711"), order.avg_price)

    def test_process_quote_tick_fills_buy_limit_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.001"),
        )

        self.strategy.submit_order(order)

        # Act
        tick2 = QuoteTick(
            AUDUSD_SIM.symbol,  # Different market
            Price("80.010"),
            Price("80.011"),
            Quantity(200000),
            Quantity(200000),
            UNIX_EPOCH,
        )

        tick3 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("89.998"),
            Price("89.999"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)
        self.exchange.process_tick(tick3)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))
        self.assertEqual(OrderState.FILLED, order.state)
        self.assertEqual(Price("90.001"), order.avg_price)

    def test_process_quote_tick_fills_sell_stop_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.stop_market(
            USDJPY_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
            Price("90.000"),
        )

        self.strategy.submit_order(order)

        # Act
        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("89.997"),
            Price("89.999"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))
        self.assertEqual(OrderState.FILLED, order.state)
        self.assertEqual(Price("90.000"), order.avg_price)

    def test_process_quote_tick_fills_sell_limit_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
            Price("90.100"),
        )

        self.strategy.submit_order(order)

        # Act
        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("90.101"),
            Price("90.102"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))
        self.assertEqual(OrderState.FILLED, order.state)
        self.assertEqual(Price("90.100"), order.avg_price)

    def test_process_quote_tick_fills_buy_limit_entry_with_bracket(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("90.000"),
        )

        bracket = self.strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price("89.900"),
        )

        self.strategy.submit_bracket_order(bracket)

        # Act
        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("89.998"),
            Price("89.999"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)

        # Assert
        self.assertEqual(1, len(self.exchange.get_working_orders()))
        self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values())

    def test_process_quote_tick_fills_sell_limit_entry_with_bracket(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
            Price("91.100"),
        )

        bracket = self.strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price("91.200"),
            take_profit=Price("90.000"),
        )

        self.strategy.submit_bracket_order(bracket)

        # Act
        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("91.101"),
            Price("91.102"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)

        # Assert
        self.assertEqual(2, len(self.exchange.get_working_orders()))  # SL and TP
        self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values())
        self.assertIn(bracket.take_profit, self.exchange.get_working_orders().values())

    def test_process_trade_tick_fills_buy_limit_entry_with_bracket(self):
        # Arrange
        # Prepare market
        tick1 = TradeTick(
            AUDUSD_SIM.symbol,
            Price("1.00000"),
            Quantity(100000),
            OrderSide.SELL,
            TradeMatchId("123456789"),
            UNIX_EPOCH,
        )

        tick2 = TradeTick(
            AUDUSD_SIM.symbol,
            Price("1.00001"),
            Quantity(100000),
            OrderSide.BUY,
            TradeMatchId("123456790"),
            UNIX_EPOCH,
        )

        self.data_engine.process(tick1)
        self.data_engine.process(tick2)
        self.exchange.process_tick(tick1)
        self.exchange.process_tick(tick2)

        entry = self.strategy.order_factory.limit(
            AUDUSD_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("0.99900"),
        )

        bracket = self.strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price("0.99800"),
            take_profit=Price("1.100"),
        )

        self.strategy.submit_bracket_order(bracket)

        # Act
        tick3 = TradeTick(
            AUDUSD_SIM.symbol,
            Price("0.99899"),
            Quantity(100000),
            OrderSide.BUY,  # Lowers ask price
            TradeMatchId("123456789"),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick3)

        # Assert
        self.assertEqual(2, len(self.exchange.get_working_orders()))  # SL and TP only
        self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values())
        self.assertIn(bracket.take_profit, self.exchange.get_working_orders().values())

    def test_filling_oco_sell_cancels_other_order(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        entry = self.strategy.order_factory.limit(
            USDJPY_SIM.symbol,
            OrderSide.SELL,
            Quantity(100000),
            Price("91.100"),
        )

        bracket = self.strategy.order_factory.bracket(
            entry_order=entry,
            stop_loss=Price("91.200"),
            take_profit=Price("90.000"),
        )

        self.strategy.submit_bracket_order(bracket)

        # Act
        tick2 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("91.101"),
            Price("91.102"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        tick3 = QuoteTick(
            USDJPY_SIM.symbol,
            Price("91.201"),
            Price("91.203"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(tick2)
        self.exchange.process_tick(tick3)

        # Assert
        self.assertEqual(0, len(self.exchange.get_working_orders()))

    def test_realized_pnl_contains_commission(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        # Act
        self.strategy.submit_order(order)
        position = self.exec_engine.cache.positions_open()[0]

        # Assert
        self.assertEqual(Money(-180.01, JPY), position.realized_pnl)
        self.assertEqual(Money(180.01, JPY), position.commission)
        self.assertEqual([Money(180.01, JPY)], position.commissions())

    def test_unrealized_pnl(self):
        # Arrange
        # Prepare market
        tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol)
        self.data_engine.process(tick)
        self.exchange.process_tick(tick)

        order_open = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        # Act 1
        self.strategy.submit_order(order_open)

        reduce_quote = QuoteTick(
            USDJPY_SIM.symbol,
            Price("100.003"),
            Price("100.003"),
            Quantity(100000),
            Quantity(100000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(reduce_quote)
        self.portfolio.update_tick(reduce_quote)

        order_reduce = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.SELL,
            Quantity(50000),
        )

        position_id = PositionId("P-19700101-000000-000-001-1")  # Generated by platform

        # Act 2
        self.strategy.submit_order(order_reduce, position_id)

        # Assert
        position = self.exec_engine.cache.positions_open()[0]
        self.assertEqual(Money(500000.00, JPY), position.unrealized_pnl(Price("100.003")))

    def test_position_flipped_when_reduce_order_exceeds_original_quantity(self):
        # Arrange
        # Prepare market
        open_quote = QuoteTick(
            USDJPY_SIM.symbol,
            Price("90.002"),
            Price("90.003"),
            Quantity(1),
            Quantity(1),
            UNIX_EPOCH,
        )

        self.data_engine.process(open_quote)
        self.exchange.process_tick(open_quote)

        order_open = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        # Act 1
        self.strategy.submit_order(order_open)

        reduce_quote = QuoteTick(
            USDJPY_SIM.symbol,
            Price("100.003"),
            Price("100.003"),
            Quantity(1),
            Quantity(1),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(reduce_quote)
        self.portfolio.update_tick(reduce_quote)

        order_reduce = self.strategy.order_factory.market(
            USDJPY_SIM.symbol,
            OrderSide.SELL,
            Quantity(150000),
        )

        # Act 2
        self.strategy.submit_order(order_reduce, PositionId("P-19700101-000000-000-001-1"))  # Generated by platform

        # Assert
        print(self.exec_engine.cache.positions())
        position_open = self.exec_engine.cache.positions_open()[0]
        position_closed = self.exec_engine.cache.positions_closed()[0]
        self.assertEqual(PositionSide.SHORT, position_open.side)
        self.assertEqual(Quantity(50000), position_open.quantity)
        self.assertEqual(Money(999619.98, JPY), position_closed.realized_pnl)
        self.assertEqual([Money(380.02, JPY)], position_closed.commissions())
Esempio n. 11
0
class BitmexExchangeTests(unittest.TestCase):

    def setUp(self):
        # Fixture Setup
        self.strategies = [MockStrategy(TestStubs.bartype_btcusdt_binance_1min_bid())]

        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,
            config={'use_previous_close': False},  # To correctly reproduce historical data bars
        )
        self.data_engine.cache.add_instrument(XBTUSD_BITMEX)
        self.portfolio.register_cache(self.data_engine.cache)

        self.analyzer = PerformanceAnalyzer()

        self.trader_id = TraderId("TESTER", "000")
        self.account_id = AccountId("BITMEX", "001")

        exec_db = BypassExecutionDatabase(
            trader_id=self.trader_id,
            logger=self.logger,
        )

        self.exec_engine = ExecutionEngine(
            database=exec_db,
            portfolio=self.portfolio,
            clock=self.clock,
            logger=self.logger,
        )

        self.exchange = SimulatedExchange(
            venue=Venue("BITMEX"),
            oms_type=OMSType.HEDGING,
            generate_position_ids=True,
            is_frozen_account=False,
            starting_balances=[Money(1_000_000, USD)],
            exec_cache=self.exec_engine.cache,
            instruments=[XBTUSD_BITMEX],
            modules=[],
            fill_model=FillModel(),
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            account_id=self.account_id,
            engine=self.exec_engine,
            clock=self.clock,
            logger=self.logger,
        )

        self.exec_engine.register_client(self.exec_client)
        self.exchange.register_client(self.exec_client)

        self.strategy = MockStrategy(bar_type=TestStubs.bartype_btcusdt_binance_1min_bid())
        self.strategy.register_trader(
            self.trader_id,
            self.clock,
            self.logger,
        )

        self.data_engine.register_strategy(self.strategy)
        self.exec_engine.register_strategy(self.strategy)
        self.data_engine.start()
        self.exec_engine.start()
        self.strategy.start()

    def test_commission_maker_taker_order(self):
        # Arrange
        # Prepare market
        quote1 = QuoteTick(
            XBTUSD_BITMEX.symbol,
            Price("11493.70"),
            Price("11493.75"),
            Quantity(1500000),
            Quantity(1500000),
            UNIX_EPOCH,
        )

        self.data_engine.process(quote1)
        self.exchange.process_tick(quote1)

        order_market = self.strategy.order_factory.market(
            XBTUSD_BITMEX.symbol,
            OrderSide.BUY,
            Quantity(100000),
        )

        order_limit = self.strategy.order_factory.limit(
            XBTUSD_BITMEX.symbol,
            OrderSide.BUY,
            Quantity(100000),
            Price("11493.65"),
        )

        # Act
        self.strategy.submit_order(order_market)
        self.strategy.submit_order(order_limit)

        quote2 = QuoteTick(
            XBTUSD_BITMEX.symbol,
            Price("11493.60"),
            Price("11493.64"),
            Quantity(1500000),
            Quantity(1500000),
            UNIX_EPOCH,
        )

        self.exchange.process_tick(quote2)  # Fill the limit order
        self.portfolio.update_tick(quote2)

        # Assert
        self.assertEqual(LiquiditySide.TAKER, self.strategy.object_storer.get_store()[2].liquidity_side)
        self.assertEqual(LiquiditySide.MAKER, self.strategy.object_storer.get_store()[6].liquidity_side)
        self.assertEqual(Money("0.00652529", BTC), self.strategy.object_storer.get_store()[2].commission)
        self.assertEqual(Money("-0.00217511", BTC), self.strategy.object_storer.get_store()[6].commission)
class TraderTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        usdjpy = InstrumentLoader.default_fx_ccy(
            TestStubs.symbol_usdjpy_fxcm())
        data = BacktestDataContainer()
        data.add_instrument(usdjpy)
        data.add_bars(usdjpy.symbol, BarAggregation.MINUTE, PriceType.BID,
                      TestDataProvider.usdjpy_1min_bid()[:2000])
        data.add_bars(usdjpy.symbol, BarAggregation.MINUTE, PriceType.ASK,
                      TestDataProvider.usdjpy_1min_ask()[:2000])

        clock = TestClock()
        uuid_factory = TestUUIDFactory()
        logger = TestLogger(clock)
        trader_id = TraderId("TESTER", "000")
        account_id = TestStubs.account_id()

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

        data_engine = BacktestDataEngine(
            data=data,
            tick_capacity=1000,
            bar_capacity=1000,
            portfolio=self.portfolio,
            clock=clock,
            logger=logger,
        )

        self.analyzer = PerformanceAnalyzer()

        self.exec_db = BypassExecutionDatabase(
            trader_id=trader_id,
            logger=logger,
        )

        self.exec_engine = ExecutionEngine(
            database=self.exec_db,
            portfolio=self.portfolio,
            clock=clock,
            uuid_factory=uuid_factory,
            logger=logger,
        )

        self.market = SimulatedMarket(
            venue=Venue("FXCM"),
            oms_type=OMSType.HEDGING,
            generate_position_ids=True,
            exec_cache=self.exec_engine.cache,
            instruments={usdjpy.symbol: usdjpy},
            config=BacktestConfig(),
            fill_model=FillModel(),
            commission_model=GenericCommissionModel(),
            clock=clock,
            uuid_factory=TestUUIDFactory(),
            logger=logger,
        )

        self.exec_client = BacktestExecClient(
            market=self.market,
            account_id=account_id,
            engine=self.exec_engine,
            logger=logger,
        )

        self.exec_engine.register_client(self.exec_client)

        strategies = [
            EmptyStrategy("001"),
            EmptyStrategy("002"),
        ]

        self.trader = Trader(
            trader_id=trader_id,
            strategies=strategies,
            data_engine=data_engine,
            exec_engine=self.exec_engine,
            clock=clock,
            uuid_factory=uuid_factory,
            logger=logger,
        )

    def test_initialize_trader(self):
        # Arrange
        # Act
        trader_id = self.trader.id

        # Assert
        self.assertEqual(TraderId("TESTER", "000"), trader_id)
        self.assertEqual(IdTag("000"), trader_id.tag)
        self.assertEqual(ComponentState.INITIALIZED, self.trader.state())
        self.assertEqual(2, len(self.trader.strategy_states()))

    def test_get_strategy_states(self):
        # Arrange
        # Act
        status = self.trader.strategy_states()

        # Assert
        self.assertTrue(StrategyId("EmptyStrategy", "001") in status)
        self.assertTrue(StrategyId("EmptyStrategy", "002") in status)
        self.assertEqual('INITIALIZED',
                         status[StrategyId("EmptyStrategy", "001")])
        self.assertEqual('INITIALIZED',
                         status[StrategyId("EmptyStrategy", "002")])
        self.assertEqual(2, len(status))

    def test_change_strategies(self):
        # Arrange
        strategies = [EmptyStrategy("003"), EmptyStrategy("004")]

        # Act
        self.trader.initialize_strategies(strategies)

        # Assert
        self.assertTrue(strategies[0].id in self.trader.strategy_states())
        self.assertTrue(strategies[1].id in self.trader.strategy_states())
        self.assertEqual(2, len(self.trader.strategy_states()))

    def test_trader_detects_none_unique_identifiers(self):
        # Arrange
        strategies = [EmptyStrategy("000"), EmptyStrategy("000")]

        # Act
        self.assertRaises(ValueError, self.trader.initialize_strategies,
                          strategies)

    def test_start_a_trader(self):
        # Arrange
        # Act
        self.trader.start()

        strategy_states = self.trader.strategy_states()

        # Assert
        self.assertEqual(ComponentState.RUNNING, self.trader.state())
        self.assertEqual('RUNNING',
                         strategy_states[StrategyId("EmptyStrategy", "001")])
        self.assertEqual('RUNNING',
                         strategy_states[StrategyId("EmptyStrategy", "002")])

    def test_stop_a_running_trader(self):
        # Arrange
        self.trader.start()

        # Act
        self.trader.stop()

        strategy_states = self.trader.strategy_states()

        # Assert
        self.assertEqual(ComponentState.STOPPED, self.trader.state())
        self.assertEqual('STOPPED',
                         strategy_states[StrategyId("EmptyStrategy", "001")])
        self.assertEqual('STOPPED',
                         strategy_states[StrategyId("EmptyStrategy", "002")])
Esempio n. 13
0
class TestL2OrderBookExchange:
    def setup(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = UUIDFactory()
        self.logger = Logger(
            clock=self.clock,
            level_stdout=LogLevel.DEBUG,
        )

        self.trader_id = TestIdStubs.trader_id()

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

        self.cache = TestComponentStubs.cache()

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

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

        self.exec_engine = ExecutionEngine(
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

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

        self.exchange = SimulatedExchange(
            venue=SIM,
            oms_type=OMSType.HEDGING,
            account_type=AccountType.MARGIN,
            base_currency=USD,
            starting_balances=[Money(1_000_000, USD)],
            default_leverage=Decimal(50),
            leverages={},
            is_frozen_account=False,
            instruments=[USDJPY_SIM],
            modules=[],
            fill_model=FillModel(),
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
            book_type=BookType.L2_MBP,
            latency_model=LatencyModel(0),
        )

        self.exec_client = BacktestExecClient(
            exchange=self.exchange,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        # Prepare components
        self.cache.add_instrument(USDJPY_SIM)
        self.cache.add_order_book(
            OrderBook.create(
                instrument=USDJPY_SIM,
                book_type=BookType.L2_MBP,
            ))

        self.exec_engine.register_client(self.exec_client)
        self.exchange.register_client(self.exec_client)

        self.strategy = MockStrategy(
            bar_type=TestDataStubs.bartype_usdjpy_1min_bid())
        self.strategy.register(
            trader_id=self.trader_id,
            portfolio=self.portfolio,
            msgbus=self.msgbus,
            cache=self.cache,
            clock=self.clock,
            logger=self.logger,
        )

        self.exchange.reset()
        self.data_engine.start()
        self.exec_engine.start()
        self.strategy.start()

    def test_submit_limit_order_aggressive_multiple_levels(self):
        # Arrange: Prepare market
        self.cache.add_instrument(USDJPY_SIM)

        quote = QuoteTick(
            instrument_id=USDJPY_SIM.id,
            bid=Price.from_str("110.000"),
            ask=Price.from_str("110.010"),
            bid_size=Quantity.from_int(1500000),
            ask_size=Quantity.from_int(1500000),
            ts_event=0,
            ts_init=0,
        )
        self.data_engine.process(quote)
        snapshot = TestDataStubs.order_book_snapshot(
            instrument_id=USDJPY_SIM.id,
            bid_volume=1000,
            ask_volume=1000,
        )
        self.data_engine.process(snapshot)
        self.exchange.process_order_book(snapshot)

        # Create order
        order = self.strategy.order_factory.limit(
            instrument_id=USDJPY_SIM.id,
            order_side=OrderSide.BUY,
            quantity=Quantity.from_int(2000),
            price=Price.from_int(20),
            post_only=False,
        )

        # Act
        self.strategy.submit_order(order)
        self.exchange.process(0)

        # Assert
        assert order.status == OrderStatus.FILLED
        assert order.filled_qty == Decimal("2000.0")  # No slippage
        assert order.avg_px == Decimal("15.33333333333333333333333333")
        assert self.exchange.get_account().balance_total(USD) == Money(
            999999.96, USD)

    def test_aggressive_partial_fill(self):
        # Arrange: Prepare market
        self.cache.add_instrument(USDJPY_SIM)

        quote = QuoteTick(
            instrument_id=USDJPY_SIM.id,
            bid=Price.from_str("110.000"),
            ask=Price.from_str("110.010"),
            bid_size=Quantity.from_int(1500000),
            ask_size=Quantity.from_int(1500000),
            ts_event=0,
            ts_init=0,
        )
        self.data_engine.process(quote)
        snapshot = TestDataStubs.order_book_snapshot(
            instrument_id=USDJPY_SIM.id,
            bid_volume=1000,
            ask_volume=1000,
        )
        self.data_engine.process(snapshot)
        self.exchange.process_order_book(snapshot)

        # Act
        order = self.strategy.order_factory.limit(
            instrument_id=USDJPY_SIM.id,
            order_side=OrderSide.BUY,
            quantity=Quantity.from_int(7000),
            price=Price.from_int(20),
            post_only=False,
        )
        self.strategy.submit_order(order)
        self.exchange.process(0)

        # Assert
        assert order.status == OrderStatus.PARTIALLY_FILLED
        assert order.filled_qty == Quantity.from_str("6000.0")  # No slippage
        assert order.avg_px == Decimal("15.93333333333333333333333333")
        assert self.exchange.get_account().balance_total(USD) == Money(
            999999.88, USD)

    def test_post_only_insert(self):
        # Arrange: Prepare market
        self.cache.add_instrument(USDJPY_SIM)
        # Market is 10 @ 15
        snapshot = TestDataStubs.order_book_snapshot(
            instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000)
        self.data_engine.process(snapshot)
        self.exchange.process_order_book(snapshot)

        # Act
        order = self.strategy.order_factory.limit(
            instrument_id=USDJPY_SIM.id,
            order_side=OrderSide.SELL,
            quantity=Quantity.from_int(2000),
            price=Price.from_str("14"),
            post_only=True,
        )
        self.strategy.submit_order(order)
        self.exchange.process(0)

        # Assert
        assert order.status == OrderStatus.ACCEPTED

    # TODO - Need to discuss how we are going to support passive quotes trading now
    @pytest.mark.skip
    def test_passive_partial_fill(self):
        # Arrange: Prepare market
        self.cache.add_instrument(USDJPY_SIM)
        # Market is 10 @ 15
        snapshot = TestDataStubs.order_book_snapshot(
            instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000)
        self.data_engine.process(snapshot)
        self.exchange.process_order_book(snapshot)

        order = self.strategy.order_factory.limit(
            instrument_id=USDJPY_SIM.id,
            order_side=OrderSide.SELL,
            quantity=Quantity.from_int(1000),
            price=Price.from_str("14"),
            post_only=False,
        )
        self.strategy.submit_order(order)

        # Act
        tick = TestDataStubs.quote_tick_3decimal(
            instrument_id=USDJPY_SIM.id,
            bid=Price.from_str("15"),
            bid_volume=Quantity.from_int(1000),
            ask=Price.from_str("16"),
            ask_volume=Quantity.from_int(1000),
        )
        # New tick will be in cross with our order
        self.exchange.process_tick(tick)

        # Assert
        assert order.status == OrderStatus.PARTIALLY_FILLED
        assert order.filled_qty == Quantity.from_str("1000.0")
        assert order.avg_px == Decimal("15.0")

    # TODO - Need to discuss how we are going to support passive quotes trading now
    @pytest.mark.skip
    def test_passive_fill_on_trade_tick(self):
        # Arrange: Prepare market
        # Market is 10 @ 15
        snapshot = TestDataStubs.order_book_snapshot(
            instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000)
        self.data_engine.process(snapshot)
        self.exchange.process_order_book(snapshot)

        order = self.strategy.order_factory.limit(
            instrument_id=USDJPY_SIM.id,
            order_side=OrderSide.SELL,
            quantity=Quantity.from_int(2000),
            price=Price.from_str("14"),
            post_only=False,
        )
        self.strategy.submit_order(order)

        # Act
        tick1 = TradeTick(
            instrument_id=USDJPY_SIM.id,
            price=Price.from_str("14.0"),
            size=Quantity.from_int(1000),
            aggressor_side=AggressorSide.SELL,
            trade_id=TradeId("123456789"),
            ts_event=0,
            ts_init=0,
        )
        self.exchange.process_tick(tick1)

        # Assert
        assert order.status == OrderStatus.PARTIALLY_FILLED
        assert order.filled_qty == Quantity.from_int(1000.0)  # No slippage
        assert order.avg_px == Decimal("14.0")
Esempio n. 14
0
class ExecutionEngineTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        self.clock = TestClock()
        self.uuid_factory = TestUUIDFactory()
        self.logger = TestLogger(self.clock)

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

        self.order_factory = OrderFactory(
            strategy_id=StrategyId("S", "001"),
            id_tag_trader=self.trader_id.tag,
            id_tag_strategy=IdTag("001"),
            clock=self.clock,
        )

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

        self.analyzer = PerformanceAnalyzer()

        database = BypassExecutionDatabase(trader_id=self.trader_id,
                                           logger=self.logger)
        self.exec_engine = ExecutionEngine(
            database=database,
            portfolio=self.portfolio,
            clock=self.clock,
            uuid_factory=self.uuid_factory,
            logger=self.logger,
        )

        self.cache = self.exec_engine.cache
        self.exec_engine.process(TestStubs.event_account_state())

        self.venue = Venue("FXCM")
        self.exec_client = MockExecutionClient(
            self.venue,
            self.account_id,
            self.exec_engine,
            self.logger,
        )

        self.exec_engine.register_client(self.exec_client)

    def test_register_strategy(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        # Act
        self.exec_engine.register_strategy(strategy)

        # Assert
        self.assertTrue(
            strategy.id in self.exec_engine.registered_strategies())

    def test_deregister_strategy(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

        # Act
        self.exec_engine.deregister_strategy(strategy)

        # Assert
        self.assertTrue(
            strategy.id not in self.exec_engine.registered_strategies())

    def test_is_flat_when_strategy_registered_returns_true(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        # Act
        self.exec_engine.register_strategy(strategy)

        # Assert
        self.assertTrue(
            self.exec_engine.cache.is_flat(strategy_id=strategy.id))
        self.assertTrue(self.exec_engine.cache.is_flat())

    def test_is_flat_when_no_registered_strategies_returns_true(self):
        # Arrange
        # Act
        # Assert
        self.assertTrue(self.exec_engine.cache.is_flat())

    def test_reset_execution_engine(self):
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(
            strategy)  # Also registers with portfolio

        # Act
        self.exec_engine.reset()

        # Assert
        self.assertTrue(
            strategy.id in self.exec_engine.registered_strategies())

    def test_submit_order(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

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

        submit_order = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.py_null(),
            order,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order)

        # Assert
        self.assertIn(submit_order, self.exec_client.received_commands)
        self.assertTrue(self.cache.order_exists(order.cl_ord_id))

    def test_handle_order_fill_event(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

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

        submit_order = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.py_null(),
            order,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.exec_engine.execute(submit_order)

        # Act
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        self.exec_engine.process(TestStubs.event_order_filled(order))

        expected_position_id = PositionId(
            "O-19700101-000000-000-001-1")  # Stubbed from order id?

        # Assert
        self.assertTrue(self.cache.position_exists(expected_position_id))
        self.assertTrue(self.cache.is_position_open(expected_position_id))
        self.assertFalse(self.cache.is_position_closed(expected_position_id))
        self.assertFalse(
            self.exec_engine.cache.is_flat(strategy_id=strategy.id))
        self.assertFalse(self.exec_engine.cache.is_flat())
        self.assertEqual(Position,
                         type(self.cache.position(expected_position_id)))
        self.assertTrue(expected_position_id in self.cache.position_ids())
        self.assertTrue(
            expected_position_id not in self.cache.position_closed_ids(
                strategy_id=strategy.id))
        self.assertTrue(
            expected_position_id not in self.cache.position_closed_ids())
        self.assertTrue(expected_position_id in self.cache.position_open_ids(
            strategy_id=strategy.id))
        self.assertTrue(expected_position_id in self.cache.position_open_ids())
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())
        self.assertTrue(self.cache.position_exists_for_order(order.cl_ord_id))

    def test_handle_position_opening_with_position_id_none(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

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

        submit_order = SubmitOrder(self.venue, self.trader_id, self.account_id,
                                   strategy.id, PositionId.py_null(), order,
                                   self.uuid_factory.generate(),
                                   self.clock.utc_now())

        self.exec_engine.execute(submit_order)

        # Act
        self.exec_engine.process(TestStubs.event_order_submitted(order))
        self.exec_engine.process(TestStubs.event_order_accepted(order))
        self.exec_engine.process(TestStubs.event_order_filled(order))

        expected_id = PositionId(
            "O-19700101-000000-000-001-1")  # Stubbed from order id

        # Assert
        self.assertTrue(self.cache.position_exists(expected_id))
        self.assertTrue(self.cache.is_position_open(expected_id))
        self.assertFalse(self.cache.is_position_closed(expected_id))
        self.assertFalse(
            self.exec_engine.cache.is_flat(strategy_id=strategy.id))
        self.assertFalse(self.exec_engine.cache.is_flat())
        self.assertEqual(Position, type(self.cache.position(expected_id)))
        self.assertTrue(expected_id in self.cache.position_ids())
        self.assertTrue(expected_id not in self.cache.position_closed_ids(
            strategy_id=strategy.id))
        self.assertTrue(expected_id not in self.cache.position_closed_ids())
        self.assertTrue(expected_id in self.cache.position_open_ids(
            strategy_id=strategy.id))
        self.assertTrue(expected_id in self.cache.position_open_ids())
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())
        self.assertTrue(self.cache.position_exists_for_order(order.cl_ord_id))

    def test_add_to_existing_position_on_order_fill(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.py_null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(TestStubs.event_order_filled(order1))

        expected_position_id = PositionId(
            "O-19700101-000000-000-001-1")  # Stubbed from order id?

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            expected_position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, expected_position_id))

        # Assert
        self.assertTrue(
            self.cache.position_exists(
                TestStubs.event_order_filled(order1).position_id))
        self.assertTrue(self.cache.is_position_open(expected_position_id))
        self.assertFalse(self.cache.is_position_closed(expected_position_id))
        self.assertFalse(self.cache.is_flat(strategy_id=strategy.id))
        self.assertFalse(self.cache.is_flat())
        self.assertEqual(Position,
                         type(self.cache.position(expected_position_id)))
        self.assertEqual(
            0, len(self.cache.positions_closed(strategy_id=strategy.id)))
        self.assertEqual(0, len(self.cache.positions_closed()))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy.id)))
        self.assertEqual(1, len(self.cache.positions_open()))
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())

    def test_close_position_on_order_fill(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        order2 = strategy.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.SELL,
            Quantity(100000),
            Price("1.00000"),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.py_null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id = PositionId("P-1")

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, position_id))

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, position_id))

        # # Assert
        self.assertTrue(self.cache.position_exists(position_id))
        self.assertFalse(self.cache.is_position_open(position_id))
        self.assertTrue(self.cache.is_position_closed(position_id))
        self.assertTrue(self.cache.is_flat(strategy_id=strategy.id))
        self.assertTrue(self.cache.is_flat())
        self.assertEqual(position_id, self.cache.position(position_id).id)
        self.assertEqual(position_id,
                         self.cache.positions(strategy_id=strategy.id)[0].id)
        self.assertEqual(position_id, self.cache.positions()[0].id)
        self.assertEqual(
            0, len(self.cache.positions_open(strategy_id=strategy.id)))
        self.assertEqual(0, len(self.cache.positions_open()))
        self.assertEqual(
            position_id,
            self.cache.positions_closed(strategy_id=strategy.id)[0].id)
        self.assertEqual(position_id, self.cache.positions_closed()[0].id)
        self.assertTrue(position_id not in self.cache.position_open_ids(
            strategy_id=strategy.id))
        self.assertTrue(position_id not in self.cache.position_open_ids())
        self.assertEqual(1, self.cache.positions_total_count())
        self.assertEqual(0, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())

    def test_multiple_strategy_positions_opened(self):
        # Arrange
        strategy1 = TradingStrategy(order_id_tag="001")
        strategy1.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        strategy2 = TradingStrategy(order_id_tag="002")
        strategy2.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy1)
        self.exec_engine.register_strategy(strategy2)

        order1 = strategy1.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        order2 = strategy2.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy1.id,
            PositionId.py_null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy2.id,
            PositionId.py_null(),
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position1_id = PositionId('P-1')
        position2_id = PositionId('P-2')

        # Act
        self.exec_engine.execute(submit_order1)
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, position1_id))
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, position2_id))

        # Assert
        self.assertTrue(self.cache.position_exists(position1_id))
        self.assertTrue(self.cache.position_exists(position2_id))
        self.assertTrue(self.cache.is_position_open(position1_id))
        self.assertTrue(self.cache.is_position_open(position2_id))
        self.assertFalse(self.cache.is_position_closed(position1_id))
        self.assertFalse(self.cache.is_position_closed(position2_id))
        self.assertFalse(self.cache.is_flat(strategy_id=strategy1.id))
        self.assertFalse(self.cache.is_flat(strategy_id=strategy2.id))
        self.assertFalse(self.cache.is_flat())
        self.assertEqual(Position, type(self.cache.position(position1_id)))
        self.assertEqual(Position, type(self.cache.position(position2_id)))
        self.assertTrue(position1_id in self.cache.position_ids(
            strategy_id=strategy1.id))
        self.assertTrue(position2_id in self.cache.position_ids(
            strategy_id=strategy2.id))
        self.assertTrue(position1_id in self.cache.position_ids())
        self.assertTrue(position2_id in self.cache.position_ids())
        self.assertEqual(2, len(self.cache.position_open_ids()))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy1.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertEqual(2, len(self.cache.positions_open()))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy1.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertTrue(position1_id in self.cache.position_open_ids(
            strategy_id=strategy1.id))
        self.assertTrue(position2_id in self.cache.position_open_ids(
            strategy_id=strategy2.id))
        self.assertTrue(position1_id in self.cache.position_open_ids())
        self.assertTrue(position2_id in self.cache.position_open_ids())
        self.assertTrue(position1_id not in self.cache.position_closed_ids(
            strategy_id=strategy1.id))
        self.assertTrue(position2_id not in self.cache.position_closed_ids(
            strategy_id=strategy2.id))
        self.assertTrue(position1_id not in self.cache.position_closed_ids())
        self.assertTrue(position2_id not in self.cache.position_closed_ids())
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(2, self.cache.positions_open_count())
        self.assertEqual(0, self.cache.positions_closed_count())

    def test_multiple_strategy_positions_one_active_one_closed(self):
        # Arrange
        strategy1 = TradingStrategy(order_id_tag="001")
        strategy1.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        strategy2 = TradingStrategy(order_id_tag="002")
        strategy2.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy1)
        self.exec_engine.register_strategy(strategy2)

        order1 = strategy1.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        order2 = strategy1.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.SELL,
            Quantity(100000),
            Price("1.00000"),
        )

        order3 = strategy2.order_factory.stop(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
            Price("1.00000"),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy1.id,
            PositionId.py_null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id1 = PositionId('P-1')

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy1.id,
            position_id1,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        submit_order3 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy2.id,
            PositionId.py_null(),
            order3,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id2 = PositionId('P-2')

        # Act
        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, position_id1))

        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, position_id1))

        self.exec_engine.execute(submit_order3)
        self.exec_engine.process(TestStubs.event_order_submitted(order3))
        self.exec_engine.process(TestStubs.event_order_accepted(order3))
        self.exec_engine.process(
            TestStubs.event_order_filled(order3, position_id2))

        # Assert
        # Already tested .is_position_active and .is_position_closed above
        self.assertTrue(self.cache.position_exists(position_id1))
        self.assertTrue(self.cache.position_exists(position_id2))
        self.assertTrue(self.cache.is_flat(strategy_id=strategy1.id))
        self.assertFalse(self.cache.is_flat(strategy_id=strategy2.id))
        self.assertFalse(self.cache.is_flat())
        self.assertTrue(position_id1 in self.cache.position_ids(
            strategy_id=strategy1.id))
        self.assertTrue(position_id2 in self.cache.position_ids(
            strategy_id=strategy2.id))
        self.assertTrue(position_id1 in self.cache.position_ids())
        self.assertTrue(position_id2 in self.cache.position_ids())
        self.assertEqual(
            0, len(self.cache.positions_open(strategy_id=strategy1.id)))
        self.assertEqual(
            1, len(self.cache.positions_open(strategy_id=strategy2.id)))
        self.assertEqual(1, len(self.cache.positions_open()))
        self.assertEqual(1, len(self.cache.positions_closed()))
        self.assertEqual(2, len(self.cache.positions()))
        self.assertTrue(position_id1 not in self.cache.position_open_ids(
            strategy_id=strategy1.id))
        self.assertTrue(position_id2 in self.cache.position_open_ids(
            strategy_id=strategy2.id))
        self.assertTrue(position_id1 not in self.cache.position_open_ids())
        self.assertTrue(position_id2 in self.cache.position_open_ids())
        self.assertTrue(position_id1 in self.cache.position_closed_ids(
            strategy_id=strategy1.id))
        self.assertTrue(position_id2 not in self.cache.position_closed_ids(
            strategy_id=strategy2.id))
        self.assertTrue(position_id1 in self.cache.position_closed_ids())
        self.assertTrue(position_id2 not in self.cache.position_closed_ids())
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())

    def test_flip_position_on_opposite_filled_same_position(self):
        # Arrange
        strategy = TradingStrategy(order_id_tag="001")
        strategy.register_trader(
            TraderId("TESTER", "000"),
            clock=self.clock,
            uuid_factory=TestUUIDFactory(),
            logger=self.logger,
        )

        self.exec_engine.register_strategy(strategy)

        order1 = strategy.order_factory.market(
            AUDUSD_FXCM,
            OrderSide.BUY,
            Quantity(100000),
        )

        order2 = strategy.order_factory.market(
            AUDUSD_FXCM,
            OrderSide.SELL,
            Quantity(150000),
        )

        submit_order1 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            PositionId.py_null(),
            order1,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        position_id = PositionId("P-000-AUD/USD.FXCM-1")

        self.exec_engine.execute(submit_order1)
        self.exec_engine.process(TestStubs.event_order_submitted(order1))
        self.exec_engine.process(TestStubs.event_order_accepted(order1))
        self.exec_engine.process(
            TestStubs.event_order_filled(order1, position_id))

        submit_order2 = SubmitOrder(
            self.venue,
            self.trader_id,
            self.account_id,
            strategy.id,
            position_id,
            order2,
            self.uuid_factory.generate(),
            self.clock.utc_now(),
        )

        # Act
        self.exec_engine.execute(submit_order2)
        self.exec_engine.process(TestStubs.event_order_submitted(order2))
        self.exec_engine.process(TestStubs.event_order_accepted(order2))
        self.exec_engine.process(
            TestStubs.event_order_filled(order2, position_id))

        position_id_flipped = PositionId("P-000-AUD/USD.FXCM-1F")
        order_id_flipped = ClientOrderId(order2.cl_ord_id.value + 'F')

        # Assert
        self.assertTrue(self.cache.position_exists(position_id))
        self.assertTrue(self.cache.position_exists(position_id_flipped))
        self.assertTrue(self.cache.is_position_closed(position_id))
        self.assertTrue(self.cache.is_position_open(position_id_flipped))
        self.assertFalse(self.cache.is_flat(strategy_id=strategy.id))
        self.assertTrue(position_id in self.cache.position_ids())
        self.assertTrue(position_id in self.cache.position_ids(
            strategy_id=strategy.id))
        self.assertTrue(position_id_flipped in self.cache.position_ids())
        self.assertTrue(position_id_flipped in self.cache.position_ids(
            strategy_id=strategy.id))
        self.assertTrue(order_id_flipped,
                        self.cache.position_exists_for_order(order_id_flipped))
        self.assertEqual(2, self.cache.positions_total_count())
        self.assertEqual(1, self.cache.positions_open_count())
        self.assertEqual(1, self.cache.positions_closed_count())