Example #1
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_check_integrity_calls_check_on_cache(self):
        # Arrange
        # Act
        self.exec_engine.check_integrity()

        # Assert
        # TODO: WIP
        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, PositionId.null()))

        expected_id = PositionId("P-000-AUD/USD.SIM-1")  # Generated inside engine

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

        # Assert
        position_id_flipped = PositionId("P-000-AUD/USD.SIM-1F")
        position_flipped = self.cache.position(position_id_flipped)

        self.assertEqual(-50000, position_flipped.relative_quantity)
        self.assertEqual(50000, position_flipped.last_event.fill_qty)
        self.assertEqual(150000, position_flipped.last_event.cum_qty)
        self.assertEqual(0, position_flipped.last_event.leaves_qty)
        self.assertEqual(Quantity(100000), self.cache.order(order1.cl_ord_id).last_event.cum_qty)
        self.assertEqual(0, self.cache.order(order1.cl_ord_id).last_event.leaves_qty)
        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))

        # Assert
        position_id_flipped = PositionId("P-000-AUD/USD.SIM-1F")
        position_flipped = self.cache.position(position_id_flipped)

        self.assertEqual(50000, position_flipped.relative_quantity)
        self.assertEqual(50000, position_flipped.last_event.fill_qty)
        self.assertEqual(150000, position_flipped.last_event.cum_qty)
        self.assertEqual(0, position_flipped.last_event.leaves_qty)
        self.assertEqual(Quantity(100000), self.cache.order(order1.cl_ord_id).last_event.cum_qty)
        self.assertEqual(0, self.cache.order(order1.cl_ord_id).last_event.leaves_qty)
        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())
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())
Example #3
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())
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)