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())
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
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())
class PortfolioTests(unittest.TestCase): def setUp(self): # Fixture Setup clock = TestClock() logger = Logger(clock, level_stdout=LogLevel.DEBUG) trader_id = TraderId("TESTER-000") self.order_factory = OrderFactory( trader_id=trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) cache_db = BypassCacheDatabase( trader_id=trader_id, logger=logger, ) self.cache = Cache( database=cache_db, logger=logger, ) self.portfolio = Portfolio( cache=self.cache, clock=clock, logger=logger, ) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=self.cache, clock=clock, logger=logger, ) self.risk_engine = RiskEngine( exec_engine=self.exec_engine, portfolio=self.portfolio, cache=self.cache, clock=clock, logger=logger, ) # Wire up components self.exec_engine.register_risk_engine(self.risk_engine) # Prepare components self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(BTCUSDT_BINANCE) self.cache.add_instrument(BTCUSD_BITMEX) self.cache.add_instrument(ETHUSD_BITMEX) def test_account_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.account(SIM)) def test_account_when_account_returns_the_account_facade(self): # Arrange account_state = AccountState( account_id=AccountId("BINANCE", "1513111"), account_type=AccountType.CASH, base_currency=None, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ) ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(account_state) # Act result = self.portfolio.account(BINANCE) # Assert self.assertEqual("BINANCE", result.id.issuer) def test_net_position_when_no_positions_returns_zero(self): # Arrange # Act # Assert self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.id)) def test_is_net_long_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_long(AUDUSD_SIM.id)) def test_is_net_short_when_no_positions_returns_false(self): # Arrange # Act # Assert self.assertEqual(False, self.portfolio.is_net_short(AUDUSD_SIM.id)) def test_is_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.id)) def test_is_completely_flat_when_no_positions_returns_true(self): # Arrange # Act # Assert self.assertEqual(True, self.portfolio.is_flat(AUDUSD_SIM.id)) def test_unrealized_pnl_for_instrument_when_no_instrument_returns_none( self): # Arrange # Act # Assert self.assertIsNone(self.portfolio.unrealized_pnl(USDJPY_SIM.id)) def test_unrealized_pnl_for_venue_when_no_account_returns_empty_dict(self): # Arrange # Act # Assert self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) def test_initial_margins_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.initial_margins(SIM)) def test_maint_margins_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.maint_margins(SIM)) def test_open_value_when_no_account_returns_none(self): # Arrange # Act # Assert self.assertEqual(None, self.portfolio.net_exposures(SIM)) def test_update_tick(self): # Arrange tick = TestStubs.quote_tick_5decimal(GBPUSD_SIM.id) # Act self.portfolio.update_tick(tick) # Assert self.assertIsNone(self.portfolio.unrealized_pnl(GBPUSD_SIM.id)) def test_update_orders_working(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) # Create two working orders order1 = self.order_factory.stop_market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.5"), Price.from_str("25000.00"), ) order2 = self.order_factory.stop_market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.5"), Price.from_str("25000.00"), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.exec_engine.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.exec_engine.cache.update_order(order1) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) self.exec_engine.process(filled1) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("25001.00"), Price.from_str("25002.00"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) # Act self.portfolio.update_tick(last) self.portfolio.initialize_orders() # Assert self.assertEqual({}, self.portfolio.initial_margins(BINANCE)) def test_update_positions(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) # Create a closed position order1 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.50000000"), ) order2 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("10.50000000"), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.exec_engine.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.exec_engine.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) fill2 = TestStubs.event_order_filled( order2, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) position1 = Position(instrument=BTCUSDT_BINANCE, fill=fill1) position1.apply(fill2) order3 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.00000000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("25000.00"), ) position2 = Position(instrument=BTCUSDT_BINANCE, fill=fill3) # Update the last quote last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("25001.00"), Price.from_str("25002.00"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) # Act self.cache.add_position(position1) self.cache.add_position(position2) self.portfolio.initialize_positions() self.portfolio.update_tick(last) # Assert self.assertTrue(self.portfolio.is_net_long(BTCUSDT_BINANCE.id)) def test_opening_one_long_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.000000"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("10500.00"), ) last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("10510.00"), Price.from_str("10511.00"), Quantity.from_str("1.000000"), Quantity.from_str("1.000000"), 0, 0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {USDT: Money(105100.00000000, USDT)}, self.portfolio.net_exposures(BINANCE), ) self.assertEqual( {USDT: Money(100.00000000, USDT)}, self.portfolio.unrealized_pnls(BINANCE), ) self.assertEqual( {USDT: Money(105.10000000, USDT)}, self.portfolio.maint_margins(BINANCE), ) self.assertEqual( Money(105100.00000000, USDT), self.portfolio.net_exposure(BTCUSDT_BINANCE.id), ) self.assertEqual( Money(100.00000000, USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id), ) self.assertEqual( Decimal("10.00000000"), self.portfolio.net_position(order.instrument_id), ) self.assertTrue(self.portfolio.is_net_long(order.instrument_id)) self.assertFalse(self.portfolio.is_net_short(order.instrument_id)) self.assertFalse(self.portfolio.is_flat(order.instrument_id)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_one_short_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("0.515"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("15000.00"), ) last = QuoteTick( BTCUSDT_BINANCE.id, Price.from_str("15510.15"), Price.from_str("15510.25"), Quantity.from_str("12.62"), Quantity.from_str("3.1"), 0, 0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {USDT: Money(7987.77875000, USDT)}, self.portfolio.net_exposures(BINANCE), ) self.assertEqual( {USDT: Money(-262.77875000, USDT)}, self.portfolio.unrealized_pnls(BINANCE), ) self.assertEqual( {USDT: Money(7.98777875, USDT)}, self.portfolio.maint_margins(BINANCE), ) self.assertEqual( Money(7987.77875000, USDT), self.portfolio.net_exposure(BTCUSDT_BINANCE.id), ) self.assertEqual( Money(-262.77875000, USDT), self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id), ) self.assertEqual( Decimal("-0.515"), self.portfolio.net_position(order.instrument_id), ) self.assertFalse(self.portfolio.is_net_long(order.instrument_id)) self.assertTrue(self.portfolio.is_net_short(order.instrument_id)) self.assertFalse(self.portfolio.is_flat(order.instrument_id)) self.assertFalse(self.portfolio.is_completely_flat()) def test_opening_positions_with_multi_asset_account(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_ethusd = QuoteTick( ETHUSD_BITMEX.id, Price.from_str("376.05"), Price.from_str("377.10"), Quantity.from_str("16"), Quantity.from_str("25"), 0, 0, ) last_btcusd = QuoteTick( BTCUSD_BITMEX.id, Price.from_str("10500.05"), Price.from_str("10501.51"), Quantity.from_str("2.54"), Quantity.from_str("0.91"), 0, 0, ) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_btcusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_btcusd) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) # Act self.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert self.assertEqual( {ETH: Money(26.59220848, ETH)}, self.portfolio.net_exposures(BITMEX), ) self.assertEqual( {ETH: Money(0.20608962, ETH)}, self.portfolio.maint_margins(BITMEX), ) self.assertEqual( Money(26.59220848, ETH), self.portfolio.net_exposure(ETHUSD_BITMEX.id), ) self.assertEqual( Money(0.00000000, ETH), self.portfolio.unrealized_pnl(ETHUSD_BITMEX.id), ) def test_unrealized_pnl_when_insufficient_data_for_xrate_returns_none( self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) self.exec_engine.cache.add_order(order, PositionId.null()) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) self.exec_engine.process(fill) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Act result = self.portfolio.unrealized_pnls(BITMEX) # # Assert self.assertEqual({}, result) def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("376.05"), ) last_ethusd = QuoteTick( ETHUSD_BITMEX.id, Price.from_str("376.05"), Price.from_str("377.10"), Quantity.from_str("16"), Quantity.from_str("25"), 0, 0, ) last_xbtusd = QuoteTick( BTCUSD_BITMEX.id, Price.from_str("50000.00"), Price.from_str("50000.00"), Quantity.from_str("1"), Quantity.from_str("1"), 0, 0, ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) self.cache.add_position(position) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_xbtusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_xbtusd) # Act result = self.portfolio.net_exposures(BITMEX) # Assert self.assertEqual({BTC: Money(0.00200000, BTC)}, result) def test_opening_several_positions_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_audusd = QuoteTick( AUDUSD_SIM.id, Price.from_str("0.80501"), Price.from_str("0.80505"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) last_gbpusd = QuoteTick( GBPUSD_SIM.id, Price.from_str("1.30315"), Price.from_str("1.30317"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) self.cache.add_quote_tick(last_audusd) self.cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) self.exec_engine.cache.add_order(order1, PositionId.null()) self.exec_engine.cache.add_order(order2, PositionId.null()) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill2 = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) self.exec_engine.cache.update_order(order1) self.exec_engine.cache.update_order(order2) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) position2 = Position(instrument=GBPUSD_SIM, fill=fill2) position_opened1 = TestStubs.event_position_opened(position1) position_opened2 = TestStubs.event_position_opened(position2) # Act self.cache.add_position(position1) self.cache.add_position(position2) self.portfolio.update_position(position_opened1) self.portfolio.update_position(position_opened2) # Assert self.assertEqual( {USD: Money(210816.00, USD)}, self.portfolio.net_exposures(SIM), ) self.assertEqual( {USD: Money(10816.00, USD)}, self.portfolio.unrealized_pnls(SIM), ) self.assertEqual({USD: Money(3912.06, USD)}, self.portfolio.maint_margins(SIM)), self.assertEqual( Money(80501.00, USD), self.portfolio.net_exposure(AUDUSD_SIM.id), ) self.assertEqual( Money(130315.00, USD), self.portfolio.net_exposure(GBPUSD_SIM.id), ) self.assertEqual( Money(-19499.00, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id), ) self.assertEqual( Money(30315.00, USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.id), ) self.assertEqual(Decimal(100000), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertEqual(Decimal(100000), self.portfolio.net_position(GBPUSD_SIM.id)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_completely_flat()) def test_modifying_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) last_audusd = QuoteTick( AUDUSD_SIM.id, Price.from_str("0.80501"), Price.from_str("0.80505"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) self.cache.add_quote_tick(last_audusd) self.portfolio.update_tick(last_audusd) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.exec_engine.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(50000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) position.apply(order2_filled) # Act self.portfolio.update_position( TestStubs.event_position_changed(position)) # Assert self.assertEqual( {USD: Money(40250.50, USD)}, self.portfolio.net_exposures(SIM), ) self.assertEqual( {USD: Money(-9749.50, USD)}, self.portfolio.unrealized_pnls(SIM), ) self.assertEqual( {USD: Money(1208.32, USD)}, self.portfolio.maint_margins(SIM), ) self.assertEqual( Money(40250.50, USD), self.portfolio.net_exposure(AUDUSD_SIM.id), ) self.assertEqual( Money(-9749.50, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id), ) self.assertEqual(Decimal(50000), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_flat(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_completely_flat()) self.assertEqual({}, self.portfolio.unrealized_pnls(BINANCE)) self.assertIsNone(self.portfolio.net_exposures(BINANCE)) def test_closing_position_updates_portfolio(self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.exec_engine.cache.add_position(position) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00010"), ) position.apply(order2_filled) self.exec_engine.cache.update_position(position) # Act self.portfolio.update_position( TestStubs.event_position_closed(position)) # Assert self.assertEqual({}, self.portfolio.net_exposures(SIM)) self.assertEqual({}, self.portfolio.unrealized_pnls(SIM)) self.assertEqual({}, self.portfolio.maint_margins(SIM)) self.assertEqual(Money(0, USD), self.portfolio.net_exposure(AUDUSD_SIM.id)) self.assertEqual(Money(0, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id)) self.assertEqual(Decimal(0), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertFalse(self.portfolio.is_net_short(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_flat(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_completely_flat()) def test_several_positions_with_different_instruments_updates_portfolio( self): # Arrange state = AccountState( account_id=AccountId("SIM", "01234"), account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=uuid4(), updated_ns=0, timestamp_ns=0, ) self.exec_engine.process(state) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order3 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order4 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, position_id=PositionId("P-1"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill2 = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, position_id=PositionId("P-2"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00000"), ) fill4 = TestStubs.event_order_filled( order4, instrument=GBPUSD_SIM, position_id=PositionId("P-3"), strategy_id=StrategyId("S-1"), last_px=Price.from_str("1.00100"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) position2 = Position(instrument=AUDUSD_SIM, fill=fill2) position3 = Position(instrument=GBPUSD_SIM, fill=fill3) last_audusd = QuoteTick( AUDUSD_SIM.id, Price.from_str("0.80501"), Price.from_str("0.80505"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) last_gbpusd = QuoteTick( GBPUSD_SIM.id, Price.from_str("1.30315"), Price.from_str("1.30317"), Quantity.from_int(1), Quantity.from_int(1), 0, 0, ) self.cache.add_quote_tick(last_audusd) self.cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) self.cache.add_position(position1) self.cache.add_position(position2) self.cache.add_position(position3) # Act self.portfolio.update_position( TestStubs.event_position_opened(position1)) self.portfolio.update_position( TestStubs.event_position_opened(position2)) self.portfolio.update_position( TestStubs.event_position_opened(position3)) position3.apply(fill4) self.cache.update_position(position3) self.portfolio.update_position( TestStubs.event_position_closed(position3)) # Assert self.assertEqual( {USD: Money(-38998.00, USD)}, self.portfolio.unrealized_pnls(SIM), ) self.assertEqual( {USD: Money(161002.00, USD)}, self.portfolio.net_exposures(SIM), ) self.assertEqual({USD: Money(3912.06, USD)}, self.portfolio.maint_margins(SIM)), self.assertEqual( Money(161002.00, USD), self.portfolio.net_exposure(AUDUSD_SIM.id), ) self.assertEqual( Money(-38998.00, USD), self.portfolio.unrealized_pnl(AUDUSD_SIM.id), ) self.assertEqual(Money(0, USD), self.portfolio.unrealized_pnl(GBPUSD_SIM.id)) self.assertEqual(Decimal(200000), self.portfolio.net_position(AUDUSD_SIM.id)) self.assertEqual(Decimal(0), self.portfolio.net_position(GBPUSD_SIM.id)) self.assertTrue(self.portfolio.is_net_long(AUDUSD_SIM.id)) self.assertTrue(self.portfolio.is_flat(GBPUSD_SIM.id)) self.assertFalse(self.portfolio.is_completely_flat())
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(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 TestPortfolio: def setup(self): # Fixture Setup self.clock = TestClock() self.logger = Logger(self.clock) self.trader_id = TestStubs.trader_id() self.order_factory = OrderFactory( trader_id=self.trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) 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.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Prepare components self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(BTCUSDT_BINANCE) self.cache.add_instrument(BTCUSD_BITMEX) self.cache.add_instrument(ETHUSD_BITMEX) self.cache.add_instrument(BETTING_INSTRUMENT) def test_account_when_no_account_returns_none(self): # Arrange, Act, Assert assert self.portfolio.account(SIM) is None def test_account_when_account_returns_the_account_facade(self): # Arrange state = AccountState( account_id=AccountId("BINANCE", "1513111"), account_type=AccountType.CASH, base_currency=None, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ) ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) # Act result = self.portfolio.account(BINANCE) # Assert assert result.id.issuer == "BINANCE" def test_balances_locked_when_no_account_for_venue_returns_none(self): # Arrange, Act, Assert assert self.portfolio.balances_locked(SIM) is None def test_margins_init_when_no_account_for_venue_returns_none(self): # Arrange, Act, Assert assert self.portfolio.margins_init(SIM) is None def test_margins_maint_when_no_account_for_venue_returns_none(self): # Arrange, Act, Assert assert self.portfolio.margins_maint(SIM) is None def test_unrealized_pnl_for_instrument_when_no_instrument_returns_none( self): # Arrange, Act, Assert assert self.portfolio.unrealized_pnl(USDJPY_SIM.id) is None def test_unrealized_pnl_for_venue_when_no_account_returns_empty_dict(self): # Arrange, Act, Assert assert self.portfolio.unrealized_pnls(SIM) == {} def test_net_position_when_no_positions_returns_zero(self): # Arrange, Act, Assert assert self.portfolio.net_position(AUDUSD_SIM.id) == Decimal(0) def test_net_exposures_when_no_positions_returns_none(self): # Arrange, Act, Assert assert self.portfolio.net_exposures(SIM) is None def test_is_net_long_when_no_positions_returns_false(self): # Arrange, Act, Assert assert self.portfolio.is_net_long(AUDUSD_SIM.id) is False def test_is_net_short_when_no_positions_returns_false(self): # Arrange, Act, Assert assert self.portfolio.is_net_short(AUDUSD_SIM.id) is False def test_is_flat_when_no_positions_returns_true(self): # Arrange, Act, Assert assert self.portfolio.is_flat(AUDUSD_SIM.id) is True def test_is_completely_flat_when_no_positions_returns_true(self): # Arrange, Act, Assert assert self.portfolio.is_flat(AUDUSD_SIM.id) is True def test_open_value_when_no_account_returns_none(self): # Arrange, Act, Assert assert self.portfolio.net_exposures(SIM) is None def test_update_tick(self): # Arrange tick = TestStubs.quote_tick_5decimal(GBPUSD_SIM.id) # Act self.portfolio.update_tick(tick) # Assert assert self.portfolio.unrealized_pnl(GBPUSD_SIM.id) is None def test_update_orders_working_cash_account(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "000") state = AccountState( account_id=account_id, account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( USDT, Money(100000.00000000, USDT), Money(0.00000000, USDT), Money(100000.00000000, USDT), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) # Create two working orders order = self.order_factory.limit( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("1.0"), Price.from_str("50000.00"), ) self.cache.add_order(order, position_id=None) # Act: push order state to ACCEPTED self.exec_engine.process( TestStubs.event_order_submitted(order, account_id=account_id)) self.exec_engine.process( TestStubs.event_order_accepted(order, account_id=account_id)) # Assert assert self.portfolio.balances_locked( BINANCE)[USDT].as_decimal() == 50100 def test_update_orders_working_margin_account(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), AccountBalance( USDT, Money(100000.00000000, USDT), Money(0.00000000, USDT), Money(100000.00000000, USDT), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) # Create two working orders order1 = self.order_factory.stop_market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.5"), Price.from_str("25000.00"), ) order2 = self.order_factory.stop_market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.5"), Price.from_str("25000.00"), ) self.cache.add_order(order1, position_id=None) self.cache.add_order(order2, position_id=None) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) filled1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-1"), last_px=Price.from_str("25000.00"), ) self.exec_engine.process(filled1) # Update the last quote last = QuoteTick( instrument_id=BTCUSDT_BINANCE.id, bid=Price.from_str("25001.00"), ask=Price.from_str("25002.00"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) # Act self.portfolio.update_tick(last) self.portfolio.initialize_orders() # Assert assert self.portfolio.margins_init(BINANCE) == {} def test_order_accept_updates_margin_init(self): # Arrange AccountFactory.register_calculated_account("BINANCE") state = AccountState( account_id=AccountId("BETFAIR", "01234"), account_type=AccountType.MARGIN, base_currency=GBP, reported=True, balances=[ AccountBalance( currency=GBP, total=Money(1000, GBP), free=Money(1000, GBP), locked=Money(0, GBP), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) AccountFactory.register_calculated_account("BETFAIR") self.portfolio.update_account(state) # Create a passive order order1 = self.order_factory.limit( BETTING_INSTRUMENT.id, OrderSide.BUY, Quantity.from_str("100"), Price.from_str("0.5"), ) self.cache.add_order(order1, position_id=None) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply( TestStubs.event_order_accepted(order1, venue_order_id=VenueOrderId("1"))) self.cache.update_order(order1) # Act self.portfolio.initialize_orders() # Assert assert self.portfolio.margins_init(BETFAIR)[ BETTING_INSTRUMENT.id] == Money(200, GBP) def test_update_positions(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "01234") state = AccountState( account_id=account_id, account_type=AccountType.CASH, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) # Create a closed position order1 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.50000000"), ) order2 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("10.50000000"), ) self.cache.add_order(order1, position_id=None) self.cache.add_order(order2, position_id=None) # Push states to ACCEPTED order1.apply(TestStubs.event_order_submitted(order1)) self.cache.update_order(order1) order1.apply(TestStubs.event_order_accepted(order1)) self.cache.update_order(order1) fill1 = TestStubs.event_order_filled( order1, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-1"), last_px=Price.from_str("25000.00"), ) fill2 = TestStubs.event_order_filled( order2, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-1"), last_px=Price.from_str("25000.00"), ) position1 = Position(instrument=BTCUSDT_BINANCE, fill=fill1) position1.apply(fill2) order3 = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.00000000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-2"), last_px=Price.from_str("25000.00"), ) position2 = Position(instrument=BTCUSDT_BINANCE, fill=fill3) # Update the last quote last = QuoteTick( instrument_id=BTCUSDT_BINANCE.id, bid=Price.from_str("25001.00"), ask=Price.from_str("25002.00"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) # Act self.cache.add_position(position1, OMSType.HEDGING) self.cache.add_position(position2, OMSType.HEDGING) self.portfolio.initialize_positions() self.portfolio.update_tick(last) # Assert assert self.portfolio.is_net_long(BTCUSDT_BINANCE.id) def test_opening_one_long_position_updates_portfolio(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), AccountBalance( USDT, Money(100000.00000000, USDT), Money(0.00000000, USDT), Money(100000.00000000, USDT), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("10.000000"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-001"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("10500.00"), ) last = QuoteTick( instrument_id=BTCUSDT_BINANCE.id, bid=Price.from_str("10510.00"), ask=Price.from_str("10511.00"), bid_size=Quantity.from_str("1.000000"), ask_size=Quantity.from_str("1.000000"), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert assert self.portfolio.net_exposures(BINANCE) == { USDT: Money(105100.00000000, USDT) } assert self.portfolio.unrealized_pnls(BINANCE) == { USDT: Money(100.00000000, USDT) } assert self.portfolio.margins_maint(BINANCE) == { BTCUSDT_BINANCE.id: Money(105.00000000, USDT) } assert self.portfolio.net_exposure(BTCUSDT_BINANCE.id) == Money( 105100.00000000, USDT) assert self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id) == Money( 100.00000000, USDT) assert self.portfolio.net_position( order.instrument_id) == Decimal("10.00000000") assert self.portfolio.is_net_long(order.instrument_id) assert not self.portfolio.is_net_short(order.instrument_id) assert not self.portfolio.is_flat(order.instrument_id) assert not self.portfolio.is_completely_flat() def test_opening_one_short_position_updates_portfolio(self): # Arrange AccountFactory.register_calculated_account("BINANCE") account_id = AccountId("BINANCE", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), AccountBalance( USDT, Money(100000.00000000, USDT), Money(0.00000000, USDT), Money(100000.00000000, USDT), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order = self.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.SELL, Quantity.from_str("0.515"), ) fill = TestStubs.event_order_filled( order=order, instrument=BTCUSDT_BINANCE, strategy_id=StrategyId("S-001"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("15000.00"), ) last = QuoteTick( instrument_id=BTCUSDT_BINANCE.id, bid=Price.from_str("15510.15"), ask=Price.from_str("15510.25"), bid_size=Quantity.from_str("12.62"), ask_size=Quantity.from_str("3.1"), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last) self.portfolio.update_tick(last) position = Position(instrument=BTCUSDT_BINANCE, fill=fill) # Act self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert assert self.portfolio.net_exposures(BINANCE) == { USDT: Money(7987.77875000, USDT) } assert self.portfolio.unrealized_pnls(BINANCE) == { USDT: Money(-262.77875000, USDT) } assert self.portfolio.margins_maint(BINANCE) == { BTCUSDT_BINANCE.id: Money(7.72500000, USDT) } assert self.portfolio.net_exposure(BTCUSDT_BINANCE.id) == Money( 7987.77875000, USDT) assert self.portfolio.unrealized_pnl(BTCUSDT_BINANCE.id) == Money( -262.77875000, USDT) assert self.portfolio.net_position( order.instrument_id) == Decimal("-0.515") assert not self.portfolio.is_net_long(order.instrument_id) assert self.portfolio.is_net_short(order.instrument_id) assert not self.portfolio.is_flat(order.instrument_id) assert not self.portfolio.is_completely_flat() def test_opening_positions_with_multi_asset_account(self): # Arrange AccountFactory.register_calculated_account("BITMEX") account_id = AccountId("BITMEX", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=None, # Multi-currency account reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) last_ethusd = QuoteTick( instrument_id=ETHUSD_BITMEX.id, bid=Price.from_str("376.05"), ask=Price.from_str("377.10"), bid_size=Quantity.from_str("16"), ask_size=Quantity.from_str("25"), ts_event=0, ts_init=0, ) last_btcusd = QuoteTick( instrument_id=BTCUSD_BITMEX.id, bid=Price.from_str("10500.05"), ask=Price.from_str("10501.51"), bid_size=Quantity.from_str("2.54"), ask_size=Quantity.from_str("0.91"), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_btcusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_btcusd) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(10000), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, strategy_id=StrategyId("S-001"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("376.05"), ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) # Act self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Assert assert self.portfolio.net_exposures(BITMEX) == { ETH: Money(26.59220848, ETH) } assert self.portfolio.margins_maint(BITMEX) == { ETHUSD_BITMEX.id: Money(0.20608962, ETH) } assert self.portfolio.net_exposure(ETHUSD_BITMEX.id) == Money( 26.59220848, ETH) assert self.portfolio.unrealized_pnl(ETHUSD_BITMEX.id) == Money( 0.00000000, ETH) def test_unrealized_pnl_when_insufficient_data_for_xrate_returns_none( self): # Arrange AccountFactory.register_calculated_account("BITMEX") state = AccountState( account_id=AccountId("BITMEX", "01234"), account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), AccountBalance( ETH, Money(20.00000000, ETH), Money(0.00000000, ETH), Money(20.00000000, ETH), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) self.cache.add_order(order, position_id=None) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, strategy_id=StrategyId("S-1"), position_id=PositionId("P-123456"), last_px=Price.from_str("376.05"), ) self.exec_engine.process(fill) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) # Act result = self.portfolio.unrealized_pnls(BITMEX) # # Assert assert result == {} def test_market_value_when_insufficient_data_for_xrate_returns_none(self): # Arrange AccountFactory.register_calculated_account("BITMEX") account_id = AccountId("BITMEX", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=BTC, reported=True, balances=[ AccountBalance( BTC, Money(10.00000000, BTC), Money(0.00000000, BTC), Money(10.00000000, BTC), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order = self.order_factory.market( ETHUSD_BITMEX.id, OrderSide.BUY, Quantity.from_int(100), ) fill = TestStubs.event_order_filled( order=order, instrument=ETHUSD_BITMEX, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("376.05"), ) last_ethusd = QuoteTick( instrument_id=ETHUSD_BITMEX.id, bid=Price.from_str("376.05"), ask=Price.from_str("377.10"), bid_size=Quantity.from_str("16"), ask_size=Quantity.from_str("25"), ts_event=0, ts_init=0, ) last_xbtusd = QuoteTick( instrument_id=BTCUSD_BITMEX.id, bid=Price.from_str("50000.00"), ask=Price.from_str("50000.00"), bid_size=Quantity.from_str("1"), ask_size=Quantity.from_str("1"), ts_event=0, ts_init=0, ) position = Position(instrument=ETHUSD_BITMEX, fill=fill) self.portfolio.update_position( TestStubs.event_position_opened(position)) self.cache.add_position(position, OMSType.HEDGING) self.cache.add_quote_tick(last_ethusd) self.cache.add_quote_tick(last_xbtusd) self.portfolio.update_tick(last_ethusd) self.portfolio.update_tick(last_xbtusd) # Act result = self.portfolio.net_exposures(BITMEX) # Assert assert result == {BTC: Money(0.00200000, BTC)} def test_opening_several_positions_updates_portfolio(self): # Arrange AccountFactory.register_calculated_account("SIM") account_id = AccountId("SIM", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) last_audusd = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("0.80501"), ask=Price.from_str("0.80505"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) last_gbpusd = QuoteTick( instrument_id=GBPUSD_SIM.id, bid=Price.from_str("1.30315"), ask=Price.from_str("1.30317"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last_audusd) self.cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) self.cache.add_order(order1, position_id=None) self.cache.add_order(order2, position_id=None) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-1"), last_px=Price.from_str("1.00000"), ) fill2 = TestStubs.event_order_filled( order2, instrument=GBPUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-2"), last_px=Price.from_str("1.00000"), ) self.cache.update_order(order1) self.cache.update_order(order2) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) position2 = Position(instrument=GBPUSD_SIM, fill=fill2) position_opened1 = TestStubs.event_position_opened(position1) position_opened2 = TestStubs.event_position_opened(position2) # Act self.cache.add_position(position1, OMSType.HEDGING) self.cache.add_position(position2, OMSType.HEDGING) self.portfolio.update_position(position_opened1) self.portfolio.update_position(position_opened2) # Assert assert self.portfolio.net_exposures(SIM) == { USD: Money(210816.00, USD) } assert self.portfolio.unrealized_pnls(SIM) == { USD: Money(10816.00, USD) } assert self.portfolio.margins_maint(SIM) == { AUDUSD_SIM.id: Money(3002.00, USD), GBPUSD_SIM.id: Money(3002.00, USD), } assert self.portfolio.net_exposure(AUDUSD_SIM.id) == Money( 80501.00, USD) assert self.portfolio.net_exposure(GBPUSD_SIM.id) == Money( 130315.00, USD) assert self.portfolio.unrealized_pnl(AUDUSD_SIM.id) == Money( -19499.00, USD) assert self.portfolio.unrealized_pnl(GBPUSD_SIM.id) == Money( 30315.00, USD) assert self.portfolio.net_position(AUDUSD_SIM.id) == Decimal(100000) assert self.portfolio.net_position(GBPUSD_SIM.id) == Decimal(100000) assert self.portfolio.is_net_long(AUDUSD_SIM.id) assert not self.portfolio.is_net_short(AUDUSD_SIM.id) assert not self.portfolio.is_flat(AUDUSD_SIM.id) assert not self.portfolio.is_completely_flat() def test_modifying_position_updates_portfolio(self): # Arrange AccountFactory.register_calculated_account("SIM") account_id = AccountId("SIM", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) last_audusd = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("0.80501"), ask=Price.from_str("0.80505"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last_audusd) self.portfolio.update_tick(last_audusd) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(50000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("1.00000"), ) position.apply(order2_filled) # Act self.portfolio.update_position( TestStubs.event_position_changed(position)) # Assert assert self.portfolio.net_exposures(SIM) == {USD: Money(40250.50, USD)} assert self.portfolio.unrealized_pnls(SIM) == { USD: Money(-9749.50, USD) } assert self.portfolio.margins_maint(SIM) == { AUDUSD_SIM.id: Money(1501.00, USD) } assert self.portfolio.net_exposure(AUDUSD_SIM.id) == Money( 40250.50, USD) assert self.portfolio.unrealized_pnl(AUDUSD_SIM.id) == Money( -9749.50, USD) assert self.portfolio.net_position(AUDUSD_SIM.id) == Decimal(50000) assert self.portfolio.is_net_long(AUDUSD_SIM.id) assert not self.portfolio.is_net_short(AUDUSD_SIM.id) assert not self.portfolio.is_flat(AUDUSD_SIM.id) assert not self.portfolio.is_completely_flat() assert self.portfolio.unrealized_pnls(BINANCE) == {} assert self.portfolio.net_exposures(BINANCE) is None def test_closing_position_updates_portfolio(self): # Arrange AccountFactory.register_calculated_account("SIM") account_id = AccountId("SIM", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("1.00000"), ) position = Position(instrument=AUDUSD_SIM, fill=fill1) self.cache.add_position(position, OMSType.HEDGING) self.portfolio.update_position( TestStubs.event_position_opened(position)) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) order2_filled = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-123456"), last_px=Price.from_str("1.00010"), ) position.apply(order2_filled) self.cache.update_position(position) # Act self.portfolio.update_position( TestStubs.event_position_closed(position)) # Assert assert self.portfolio.net_exposures(SIM) == {} assert self.portfolio.unrealized_pnls(SIM) == {} assert self.portfolio.margins_maint(SIM) == {} assert self.portfolio.net_exposure(AUDUSD_SIM.id) == Money(0, USD) assert self.portfolio.unrealized_pnl(AUDUSD_SIM.id) == Money(0, USD) assert self.portfolio.net_position(AUDUSD_SIM.id) == Decimal(0) assert not self.portfolio.is_net_long(AUDUSD_SIM.id) assert not self.portfolio.is_net_short(AUDUSD_SIM.id) assert self.portfolio.is_flat(AUDUSD_SIM.id) assert self.portfolio.is_completely_flat() def test_several_positions_with_different_instruments_updates_portfolio( self): # Arrange account_id = AccountId("SIM", "01234") state = AccountState( account_id=account_id, account_type=AccountType.MARGIN, base_currency=USD, reported=True, balances=[ AccountBalance( USD, Money(1_000_000, USD), Money(0, USD), Money(1_000_000, USD), ), ], info={}, event_id=UUID4(), ts_event=0, ts_init=0, ) self.portfolio.update_account(state) order1 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order3 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order4 = self.order_factory.market( GBPUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) fill1 = TestStubs.event_order_filled( order1, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-1"), last_px=Price.from_str("1.00000"), ) fill2 = TestStubs.event_order_filled( order2, instrument=AUDUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-2"), last_px=Price.from_str("1.00000"), ) fill3 = TestStubs.event_order_filled( order3, instrument=GBPUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-3"), last_px=Price.from_str("1.00000"), ) fill4 = TestStubs.event_order_filled( order4, instrument=GBPUSD_SIM, strategy_id=StrategyId("S-1"), account_id=account_id, position_id=PositionId("P-3"), last_px=Price.from_str("1.00100"), ) position1 = Position(instrument=AUDUSD_SIM, fill=fill1) position2 = Position(instrument=AUDUSD_SIM, fill=fill2) position3 = Position(instrument=GBPUSD_SIM, fill=fill3) last_audusd = QuoteTick( instrument_id=AUDUSD_SIM.id, bid=Price.from_str("0.80501"), ask=Price.from_str("0.80505"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) last_gbpusd = QuoteTick( instrument_id=GBPUSD_SIM.id, bid=Price.from_str("1.30315"), ask=Price.from_str("1.30317"), bid_size=Quantity.from_int(1), ask_size=Quantity.from_int(1), ts_event=0, ts_init=0, ) self.cache.add_quote_tick(last_audusd) self.cache.add_quote_tick(last_gbpusd) self.portfolio.update_tick(last_audusd) self.portfolio.update_tick(last_gbpusd) self.cache.add_position(position1, OMSType.HEDGING) self.cache.add_position(position2, OMSType.HEDGING) self.cache.add_position(position3, OMSType.HEDGING) # Act self.portfolio.update_position( TestStubs.event_position_opened(position1)) self.portfolio.update_position( TestStubs.event_position_opened(position2)) self.portfolio.update_position( TestStubs.event_position_opened(position3)) position3.apply(fill4) self.cache.update_position(position3) self.portfolio.update_position( TestStubs.event_position_closed(position3)) # Assert assert { USD: Money(-38998.00, USD) } == self.portfolio.unrealized_pnls(SIM) assert { USD: Money(161002.00, USD) } == self.portfolio.net_exposures(SIM) assert Money(161002.00, USD) == self.portfolio.net_exposure(AUDUSD_SIM.id) assert Money(-38998.00, USD) == self.portfolio.unrealized_pnl(AUDUSD_SIM.id) assert self.portfolio.unrealized_pnl(GBPUSD_SIM.id) == Money(0, USD) assert self.portfolio.net_position(AUDUSD_SIM.id) == Decimal(200000) assert self.portfolio.net_position(GBPUSD_SIM.id) == Decimal(0) assert self.portfolio.is_net_long(AUDUSD_SIM.id) assert self.portfolio.is_flat(GBPUSD_SIM.id) assert not self.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 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())