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 TestSimulatedExchangeContingencyAdvancedOrders: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.INFO, ) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, clock=self.clock, cache=self.cache, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=FTX, oms_type=OMSType.NETTING, account_type=AccountType.MARGIN, base_currency=None, # Multi-asset wallet starting_balances=[Money(200, ETH), Money(1_000_000, USD)], default_leverage=Decimal(100), leverages={}, is_frozen_account=False, instruments=[ETHUSD_FTX], modules=[], fill_model=FillModel(), cache=self.cache, clock=self.clock, logger=self.logger, latency_model=LatencyModel(0), ) self.exec_client = BacktestExecClient( exchange=self.exchange, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.cache.add_instrument(ETHUSD_FTX) # Create mock strategy self.strategy = MockStrategy( bar_type=TestDataStubs.bartype_usdjpy_1min_bid()) self.strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Start components self.exchange.reset() self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_submit_bracket_market_buy_accepts_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED def test_submit_bracket_market_sell_accepts_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3050.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED def test_submit_bracket_limit_buy_has_sl_tp_pending(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3090.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # # # Assert assert bracket.orders[0].status == OrderStatus.ACCEPTED assert bracket.orders[1].status == OrderStatus.SUBMITTED assert bracket.orders[2].status == OrderStatus.SUBMITTED def test_submit_bracket_limit_sell_has_sl_tp_pending(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3050.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # # # Assert assert bracket.orders[0].status == OrderStatus.ACCEPTED assert bracket.orders[1].status == OrderStatus.SUBMITTED assert bracket.orders[2].status == OrderStatus.SUBMITTED def test_submit_bracket_limit_buy_fills_then_triggers_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED assert len(self.exchange.get_open_orders()) == 2 assert bracket.orders[1] in self.exchange.get_open_orders() assert bracket.orders[2] in self.exchange.get_open_orders() def test_submit_bracket_limit_sell_fills_then_triggers_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3050.0), stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3000.0), ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.ACCEPTED assert bracket.orders[2].status == OrderStatus.ACCEPTED assert len(self.exchange.get_open_orders()) == 2 assert bracket.orders[1] in self.exchange.get_open_orders() assert bracket.orders[2] in self.exchange.get_open_orders() def test_reject_bracket_entry_then_rejects_sl_and_tp(self): # Arrange: Prepare market tick = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick) self.exchange.process_tick(tick) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3050.0), # <-- in the market stop_loss=ETHUSD_FTX.make_price(3150.0), take_profit=ETHUSD_FTX.make_price(3000.0), post_only=True, # <-- will reject placed into the market ) # Act self.strategy.submit_order_list(bracket) self.exchange.process(0) # Assert assert bracket.orders[0].status == OrderStatus.REJECTED assert bracket.orders[1].status == OrderStatus.REJECTED assert bracket.orders[2].status == OrderStatus.REJECTED assert len(self.exchange.get_open_orders()) == 0 assert bracket.orders[1] not in self.exchange.get_open_orders() assert bracket.orders[2] not in self.exchange.get_open_orders() def test_filling_bracket_sl_cancels_tp_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) self.strategy.submit_order_list(bracket) self.exchange.process(0) tick2 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3150.0), ask=ETHUSD_FTX.make_price(3151.0), bid_size=ETHUSD_FTX.make_qty(10.000), ask_size=ETHUSD_FTX.make_qty(10.000), ts_event=0, ts_init=0, ) # Act self.exchange.process_tick(tick2) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.CANCELED assert bracket.orders[2].status == OrderStatus.FILLED assert len(self.exchange.get_open_orders()) == 0 assert len(self.exchange.cache.positions_open()) == 0 def test_filling_bracket_tp_cancels_sl_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act tick2 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3150.0), ask=ETHUSD_FTX.make_price(3151.0), bid_size=ETHUSD_FTX.make_qty(10.000), ask_size=ETHUSD_FTX.make_qty(10.000), ts_event=0, ts_init=0, ) self.exchange.process_tick(tick2) # Assert assert bracket.orders[0].status == OrderStatus.FILLED assert bracket.orders[1].status == OrderStatus.CANCELED assert bracket.orders[2].status == OrderStatus.FILLED assert len(self.exchange.get_open_orders()) == 0 assert len(self.exchange.cache.positions_open()) == 0 def test_partial_fill_bracket_tp_updates_sl_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act tick2 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3150.0), ask=ETHUSD_FTX.make_price(3151.0), bid_size=ETHUSD_FTX.make_qty(5.000), ask_size=ETHUSD_FTX.make_qty(5.1000), ts_event=0, ts_init=0, ) self.exchange.process_tick(tick2) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.ACCEPTED assert tp.status == OrderStatus.PARTIALLY_FILLED assert sl.quantity == Quantity.from_int(5) assert tp.leaves_qty == Quantity.from_int(5) assert tp.quantity == Quantity.from_int(10) assert len(self.exchange.get_open_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1 def test_modifying_bracket_tp_updates_sl_order(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_limit( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), entry=ETHUSD_FTX.make_price(3100.0), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act self.strategy.modify_order( order=sl, quantity=Quantity.from_int(5), trigger_price=sl.trigger_price, ) self.exchange.process(0) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.ACCEPTED assert tp.status == OrderStatus.ACCEPTED assert sl.quantity == Quantity.from_int(5) assert tp.quantity == Quantity.from_int(5) assert len(self.exchange.get_open_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1 def test_closing_position_cancels_bracket_ocos(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act self.strategy.flatten_position( self.strategy.cache.position(en.position_id)) self.exchange.process(0) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.CANCELED assert tp.status == OrderStatus.CANCELED assert len(self.exchange.get_open_orders()) == 0 assert len(self.exchange.cache.positions_open()) == 0 def test_partially_filling_position_updates_bracket_ocos(self): # Arrange: Prepare market tick1 = QuoteTick( instrument_id=ETHUSD_FTX.id, bid=ETHUSD_FTX.make_price(3090.2), ask=ETHUSD_FTX.make_price(3090.5), bid_size=ETHUSD_FTX.make_qty(15.100), ask_size=ETHUSD_FTX.make_qty(15.100), ts_event=0, ts_init=0, ) self.data_engine.process(tick1) self.exchange.process_tick(tick1) bracket = self.strategy.order_factory.bracket_market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.BUY, quantity=ETHUSD_FTX.make_qty(10.000), stop_loss=ETHUSD_FTX.make_price(3050.0), take_profit=ETHUSD_FTX.make_price(3150.0), ) en = bracket.orders[0] sl = bracket.orders[1] tp = bracket.orders[2] self.strategy.submit_order_list(bracket) self.exchange.process(0) # Act reduce_order = self.strategy.order_factory.market( instrument_id=ETHUSD_FTX.id, order_side=OrderSide.SELL, quantity=ETHUSD_FTX.make_qty(5.000), ) self.strategy.submit_order( reduce_order, position_id=self.cache.position_for_order(en.client_order_id).id, ) self.exchange.process(0) # Assert assert en.status == OrderStatus.FILLED assert sl.status == OrderStatus.ACCEPTED assert tp.status == OrderStatus.ACCEPTED assert sl.quantity == ETHUSD_FTX.make_qty(5.000) assert tp.quantity == ETHUSD_FTX.make_qty(5.000) assert len(self.exchange.get_open_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1
class 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 = Logger(self.clock) self.trader_id = TraderId("TESTER-000") self.account_id = TestStubs.account_id() self.venue = Venue("SIM") self.portfolio = Portfolio( cache=TestStubs.cache(), clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( portfolio=self.portfolio, cache=TestStubs.cache(), clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( exec_engine=self.exec_engine, portfolio=self.portfolio, cache=TestStubs.cache(), clock=self.clock, logger=self.logger, config={}, ) self.exec_client = MockExecutionClient( client_id=ClientId(self.venue.value), venue_type=VenueType.ECN, account_id=self.account_id, account_type=AccountType.MARGIN, base_currency=USD, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) # Wire up components self.exec_engine.register_risk_engine(self.risk_engine) self.exec_engine.register_client(self.exec_client) # Prepare data self.exec_engine.cache.add_instrument(AUDUSD_SIM) def test_set_block_all_orders_changes_flag_value(self): # Arrange # Act self.risk_engine.set_block_all_orders() # Assert assert self.risk_engine.block_all_orders def test_given_random_command_logs_and_continues(self): # Arrange random = TradingCommand( self.trader_id, StrategyId("SCALPER-001"), AUDUSD_SIM.id, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(random) def test_given_random_event_logs_and_continues(self): # Arrange random = Event( self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.exec_engine.process(random) def test_submit_order_with_default_settings_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_client.calls == ["connect", "submit_order"] def test_submit_bracket_with_default_settings_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) entry = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) bracket = strategy.order_factory.bracket( entry_order=entry, stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitBracketOrder( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_client.calls == ["connect", "submit_bracket_order"] def test_submit_order_when_block_all_orders_true_then_denies_order(self): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.set_block_all_orders() # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_client.calls == ["connect"] assert self.risk_engine.command_count == 1 def test_update_order_with_default_settings_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit = SubmitOrder( self.trader_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) update = UpdateOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, order.venue_order_id, order.quantity, Price.from_str("1.00010"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) # Act self.risk_engine.execute(update) # Assert assert self.exec_client.calls == [ "connect", "submit_order", "update_order" ] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 2 def test_cancel_order_with_default_settings_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit = SubmitOrder( self.trader_id, strategy.id, PositionId.null(), order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) cancel = CancelOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, order.venue_order_id, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == [ "connect", "submit_order", "cancel_order" ] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 2 def test_submit_bracket_when_block_all_orders_true_then_denies_order(self): # Arrange self.exec_engine.start() strategy = TradingStrategy(order_id_tag="001") strategy.register_trader( TraderId("TESTER-000"), self.clock, self.logger, ) self.exec_engine.register_strategy(strategy) entry = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) bracket = strategy.order_factory.bracket( entry_order=entry, stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitBracketOrder( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.set_block_all_orders() # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_client.calls == ["connect"] assert self.risk_engine.command_count == 1 assert self.exec_engine.event_count == 3
class TestTradingStrategy: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("SIM"), venue_type=VenueType.ECN, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], default_leverage=Decimal(50), leverages={}, is_frozen_account=False, cache=self.cache, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, latency_model=LatencyModel(0), ) self.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Wire up components self.exchange.register_client(self.exec_client) self.data_engine.register_client(self.data_client) self.exec_engine.register_client(self.exec_client) self.exchange.reset() # Add instruments self.data_engine.process(AUDUSD_SIM) self.data_engine.process(GBPUSD_SIM) self.data_engine.process(USDJPY_SIM) self.cache.add_instrument(AUDUSD_SIM) self.cache.add_instrument(GBPUSD_SIM) self.cache.add_instrument(USDJPY_SIM) self.exchange.process_tick(TestStubs.quote_tick_3decimal( USDJPY_SIM.id)) # Prepare market self.data_engine.start() self.exec_engine.start() def test_strategy_equality(self): # Arrange strategy1 = TradingStrategy(config=TradingStrategyConfig( order_id_tag="AUD/USD-001")) strategy2 = TradingStrategy(config=TradingStrategyConfig( order_id_tag="AUD/USD-001")) strategy3 = TradingStrategy(config=TradingStrategyConfig( order_id_tag="AUD/USD-002")) # Act, Assert assert strategy1 == strategy1 assert strategy1 == strategy2 assert strategy2 != strategy3 def test_str_and_repr(self): # Arrange strategy = TradingStrategy(config=TradingStrategyConfig( order_id_tag="GBP/USD-MM")) # Act, Assert assert str(strategy) == "TradingStrategy-GBP/USD-MM" assert repr(strategy) == "TradingStrategy(TradingStrategy-GBP/USD-MM)" def test_id(self): # Arrange strategy = TradingStrategy() # Act, Assert assert strategy.id == StrategyId("TradingStrategy-000") def test_initialization(self): # Arrange strategy = TradingStrategy(config=TradingStrategyConfig( order_id_tag="001")) # Act, Assert assert strategy.state == ComponentState.PRE_INITIALIZED assert not strategy.indicators_initialized() def test_on_save_when_not_overridden_does_nothing(self): # Arrange strategy = TradingStrategy() # Act strategy.on_save() # Assert assert True # Exception not raised def test_on_load_when_not_overridden_does_nothing(self): # Arrange strategy = TradingStrategy() # Act strategy.on_load({}) # Assert assert True # Exception not raised def test_save_when_not_registered_logs_error(self): # Arrange config = TradingStrategyConfig() strategy = TradingStrategy(config) strategy.save() # Assert assert True # Exception not raised def test_save_when_user_code_raises_error_logs_and_reraises(self): # Arrange strategy = KaboomStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): strategy.save() def test_load_when_user_code_raises_error_logs_and_reraises(self): # Arrange strategy = KaboomStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): strategy.load({"something": b"123456"}) def test_load(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) state = {} # Act strategy.load(state) # Assert # TODO: Write a users custom save method assert True def test_reset(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar = Bar( bar_type, Price.from_str("1.00001"), Price.from_str("1.00004"), Price.from_str("1.00002"), Price.from_str("1.00003"), Quantity.from_int(100000), 0, 0, ) strategy.handle_bar(bar) # Act strategy.reset() # Assert assert "on_reset" in strategy.calls assert strategy.is_initialized assert strategy.ema1.count == 0 assert strategy.ema2.count == 0 def test_dispose(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) strategy.reset() # Act strategy.dispose() # Assert assert "on_dispose" in strategy.calls assert strategy.is_disposed def test_save_load(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act state = strategy.save() strategy.load(state) # Assert assert state == {"UserState": b"1"} assert "on_save" in strategy.calls assert strategy.is_initialized def test_register_indicator_for_quote_ticks_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10, price_type=PriceType.MID) ema2 = ExponentialMovingAverage(10, price_type=PriceType.MID) # Act strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema1) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema2) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators def test_register_indicator_for_trade_ticks_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10) ema2 = ExponentialMovingAverage(10) # Act strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema1) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators def test_register_indicator_for_bars_when_already_registered(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema1 = ExponentialMovingAverage(10) ema2 = ExponentialMovingAverage(10) bar_type = TestStubs.bartype_audusd_1min_bid() # Act strategy.register_indicator_for_bars(bar_type, ema1) strategy.register_indicator_for_bars(bar_type, ema2) strategy.register_indicator_for_bars(bar_type, ema2) assert len(strategy.registered_indicators) == 2 assert ema1 in strategy.registered_indicators assert ema2 in strategy.registered_indicators def test_register_indicator_for_multiple_data_sources(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) bar_type = TestStubs.bartype_audusd_1min_bid() # Act strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) strategy.register_indicator_for_quote_ticks(GBPUSD_SIM.id, ema) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) strategy.register_indicator_for_bars(bar_type, ema) assert len(strategy.registered_indicators) == 1 assert ema in strategy.registered_indicators def test_handle_quote_tick_updates_indicator_registered_for_quote_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10, price_type=PriceType.MID) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_quote_tick(tick) strategy.handle_quote_tick(tick, True) # Assert assert ema.count == 2 def test_handle_quote_ticks_with_no_ticks_logs_and_continues(self): # Arrange strategy = KaboomStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10, price_type=PriceType.MID) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) # Act strategy.handle_quote_ticks([]) # Assert assert ema.count == 0 def test_handle_quote_ticks_updates_indicator_registered_for_quote_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10, price_type=PriceType.MID) strategy.register_indicator_for_quote_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_quote_ticks([tick]) # Assert assert ema.count == 1 def test_handle_trade_tick_updates_indicator_registered_for_trade_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_trade_tick(tick) strategy.handle_trade_tick(tick, True) # Assert assert ema.count == 2 def test_handle_trade_ticks_updates_indicator_registered_for_trade_ticks( self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) tick = TestStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act strategy.handle_trade_ticks([tick]) # Assert assert ema.count == 1 def test_handle_trade_ticks_with_no_ticks_logs_and_continues(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_trade_ticks(AUDUSD_SIM.id, ema) # Act strategy.handle_trade_ticks([]) # Assert assert ema.count == 0 def test_handle_bar_updates_indicator_registered_for_bars(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) bar = TestStubs.bar_5decimal() # Act strategy.handle_bar(bar) strategy.handle_bar(bar, True) # Assert assert ema.count == 2 def test_handle_bars_updates_indicator_registered_for_bars(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) bar = TestStubs.bar_5decimal() # Act strategy.handle_bars([bar]) # Assert assert ema.count == 1 def test_handle_bars_with_no_bars_logs_and_continues(self): # Arrange bar_type = TestStubs.bartype_gbpusd_1sec_mid() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ema = ExponentialMovingAverage(10) strategy.register_indicator_for_bars(bar_type, ema) # Act strategy.handle_bars([]) # Assert assert ema.count == 0 def test_stop_cancels_a_running_time_alert(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) alert_time = datetime.now(pytz.utc) + timedelta(milliseconds=200) strategy.clock.set_time_alert("test_alert1", alert_time) # Act strategy.start() strategy.stop() # Assert assert len(strategy.clock.timer_names()) == 0 def test_stop_cancels_a_running_timer(self): # Arrange bar_type = TestStubs.bartype_audusd_1min_bid() strategy = MockStrategy(bar_type) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) start_time = datetime.now(pytz.utc) + timedelta(milliseconds=100) strategy.clock.set_timer("test_timer", timedelta(milliseconds=100), start_time, stop_time=None) # Act strategy.start() strategy.stop() # Assert assert len(strategy.clock.timer_names()) == 0 def test_submit_order_with_valid_order_successfully_submits(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) # Act strategy.submit_order(order) self.exchange.process(0) # Assert assert order in strategy.cache.orders() assert strategy.cache.orders()[0].status == OrderStatus.FILLED assert order.client_order_id not in strategy.cache.orders_working() assert not strategy.cache.is_order_working(order.client_order_id) assert strategy.cache.is_order_completed(order.client_order_id) def test_submit_order_list_with_valid_order_successfully_submits(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("90.000"), take_profit=Price.from_str("90.500"), ) # Act strategy.submit_order_list(bracket) # Assert assert bracket.orders[0] in strategy.cache.orders() assert bracket.orders[1] in strategy.cache.orders() assert bracket.orders[2] in strategy.cache.orders() # TODO: Implement # assert bracket.orders[0].status == OrderStatus.ACCEPTED # assert entry in strategy.cache.orders_working() # assert strategy.cache.is_order_working(entry.client_order_id) # assert not strategy.cache.is_order_completed(entry.client_order_id) def test_cancel_order(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order) self.exchange.process(0) # Act strategy.cancel_order(order) self.exchange.process(0) # Assert assert order in strategy.cache.orders() assert strategy.cache.orders()[0].status == OrderStatus.CANCELED assert order.client_order_id == strategy.cache.orders_completed( )[0].client_order_id assert order not in strategy.cache.orders_working() assert strategy.cache.order_exists(order.client_order_id) assert not strategy.cache.is_order_working(order.client_order_id) assert strategy.cache.is_order_completed(order.client_order_id) def test_cancel_order_when_pending_cancel_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_pending_cancel(order)) # Act strategy.cancel_order(order) self.exchange.process(0) # Assert assert strategy.cache.orders()[0].status == OrderStatus.PENDING_CANCEL assert order in strategy.cache.orders_working() assert strategy.cache.order_exists(order.client_order_id) assert strategy.cache.is_order_working(order.client_order_id) assert not strategy.cache.is_order_completed(order.client_order_id) def test_cancel_order_when_completed_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_expired(order)) # Act strategy.cancel_order(order) self.exchange.process(0) # Assert assert strategy.cache.orders()[0].status == OrderStatus.EXPIRED assert order not in strategy.cache.orders_working() assert strategy.cache.order_exists(order.client_order_id) assert not strategy.cache.is_order_working(order.client_order_id) assert strategy.cache.is_order_completed(order.client_order_id) def test_modify_order_when_pending_update_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_pending_update(order)) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.000"), ) self.exchange.process(0) # Assert assert self.exec_engine.command_count == 1 def test_modify_order_when_pending_cancel_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_pending_cancel(order)) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.000"), ) self.exchange.process(0) # Assert assert self.exec_engine.command_count == 1 def test_modify_order_when_completed_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) self.exchange.process(0) self.exec_engine.process(TestStubs.event_order_expired(order)) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.000"), ) self.exchange.process(0) # Assert assert self.exec_engine.command_count == 1 def test_modify_order_when_no_changes_does_not_submit_command(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.001"), ) strategy.submit_order(order) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(100000), price=Price.from_str("90.001"), ) # Assert assert self.exec_engine.command_count == 1 def test_modify_order(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.000"), ) strategy.submit_order(order) self.exchange.process(0) # Act strategy.modify_order( order=order, quantity=Quantity.from_int(110000), price=Price.from_str("90.001"), ) self.exchange.process(0) # Assert assert strategy.cache.orders()[0] == order assert strategy.cache.orders()[0].status == OrderStatus.ACCEPTED assert strategy.cache.orders()[0].quantity == Quantity.from_int(110000) assert strategy.cache.orders()[0].price == Price.from_str("90.001") assert strategy.cache.order_exists(order.client_order_id) assert strategy.cache.is_order_working(order.client_order_id) assert not strategy.cache.is_order_completed(order.client_order_id) assert strategy.portfolio.is_flat(order.instrument_id) def test_cancel_all_orders(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.007"), ) order2 = strategy.order_factory.stop_market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("90.006"), ) strategy.submit_order(order1) self.exchange.process(0) strategy.submit_order(order2) self.exchange.process(0) # Act strategy.cancel_all_orders(USDJPY_SIM.id) self.exchange.process(0) # Assert assert order1 in self.cache.orders() assert order2 in self.cache.orders() assert self.cache.orders()[0].status == OrderStatus.CANCELED assert self.cache.orders()[1].status == OrderStatus.CANCELED assert order1 in self.cache.orders_completed() assert order2 in strategy.cache.orders_completed() def test_flatten_position_when_position_already_flat_does_nothing(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) strategy.submit_order(order1) self.exchange.process(0) strategy.submit_order(order2, PositionId("1-001")) # Generated by exchange self.exchange.process(0) position = strategy.cache.positions_closed()[0] # Act strategy.flatten_position(position) self.exchange.process(0) # Assert assert strategy.portfolio.is_completely_flat() def test_flatten_position(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) strategy.submit_order(order) self.exchange.process(0) position = self.cache.positions_open()[0] # Act strategy.flatten_position(position) self.exchange.process(0) # Assert assert order.status == OrderStatus.FILLED assert strategy.portfolio.is_completely_flat() def test_flatten_all_positions(self): # Arrange strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Start strategy and submit orders to open positions strategy.start() order1 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = strategy.order_factory.market( USDJPY_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) strategy.submit_order(order1) self.exchange.process(0) strategy.submit_order(order2) self.exchange.process(0) # Act strategy.flatten_all_positions(USDJPY_SIM.id) self.exchange.process(0) # Assert assert order1.status == OrderStatus.FILLED assert order2.status == OrderStatus.FILLED assert strategy.portfolio.is_completely_flat()
class TestRiskEngine: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestIdStubs.trader_id() self.account_id = TestIdStubs.account_id() self.venue = Venue("SIM") self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) config = ExecEngineConfig() config.allow_cash_positions = True # Retain original behaviour for now self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=config, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_client = MockExecutionClient( client_id=ClientId(self.venue.value), venue=self.venue, account_type=AccountType.MARGIN, base_currency=USD, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.portfolio.update_account(TestEventStubs.margin_account_state()) self.exec_engine.register_client(self.exec_client) # Prepare data self.cache.add_instrument(AUDUSD_SIM) def test_config_risk_engine(self): # Arrange self.msgbus.deregister("RiskEngine.execute", self.risk_engine.execute) config = RiskEngineConfig( bypass=True, # <-- bypassing pre-trade risk checks for backtest max_order_rate="5/00:00:01", max_notional_per_order={"GBP/USD.SIM": 2_000_000}, ) # Act risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=config, ) # Assert assert risk_engine.max_order_rate() == (5, timedelta(seconds=1)) assert risk_engine.max_notionals_per_order() == {GBPUSD_SIM.id: Decimal("2000000")} assert risk_engine.max_notional_per_order(GBPUSD_SIM.id) == 2_000_000 def test_risk_engine_on_stop(self): # Arrange, Act self.risk_engine.start() self.risk_engine.stop() # Assert assert self.risk_engine.is_stopped def test_process_event_then_handles(self): # Arrange event = Event( event_id=self.uuid_factory.generate(), ts_event=self.clock.timestamp_ns(), ts_init=self.clock.timestamp_ns(), ) # Act self.risk_engine.process(event) # Assert assert self.risk_engine.event_count == 1 def test_trading_state_after_instantiation_returns_active(self): # Arrange, Act result = self.risk_engine.trading_state # Assert assert result == TradingState.ACTIVE def test_set_trading_state_when_no_change_logs_warning(self): # Arrange, Act self.risk_engine.set_trading_state(TradingState.ACTIVE) # Assert assert self.risk_engine.trading_state == TradingState.ACTIVE def test_set_trading_state_changes_value_and_publishes_event(self): # Arrange handler = [] self.msgbus.subscribe(topic="events.risk*", handler=handler.append) # Act self.risk_engine.set_trading_state(TradingState.HALTED) # Assert assert type(handler[0]) == TradingStateChanged assert self.risk_engine.trading_state == TradingState.HALTED def test_max_order_rate_when_no_risk_config_returns_100_per_second(self): # Arrange, Act result = self.risk_engine.max_order_rate() assert result == (100, timedelta(seconds=1)) def test_max_notionals_per_order_when_no_risk_config_returns_empty_dict(self): # Arrange, Act result = self.risk_engine.max_notionals_per_order() assert result == {} def test_max_notional_per_order_when_no_risk_config_returns_none(self): # Arrange, Act result = self.risk_engine.max_notional_per_order(AUDUSD_SIM.id) assert result is None def test_set_max_notional_per_order_changes_setting(self): # Arrange, Act self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) max_notionals = self.risk_engine.max_notionals_per_order() max_notional = self.risk_engine.max_notional_per_order(AUDUSD_SIM.id) # Assert assert max_notionals == {AUDUSD_SIM.id: Decimal("1000000")} assert max_notional == Decimal(1_000_000) def test_given_random_command_then_logs_and_continues(self): # Arrange random = TradingCommand( client_id=None, trader_id=self.trader_id, strategy_id=StrategyId("SCALPER-001"), instrument_id=AUDUSD_SIM.id, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) self.risk_engine.execute(random) def test_given_random_event_then_logs_and_continues(self): # Arrange random = Event( event_id=self.uuid_factory.generate(), ts_event=self.clock.timestamp_ns(), ts_init=self.clock.timestamp_ns(), ) self.risk_engine.process(random) # -- SUBMIT ORDER TESTS ------------------------------------------------------------------------ def test_submit_order_with_default_settings_then_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 1 assert self.exec_client.calls == ["_start", "submit_order"] def test_submit_order_when_duplicate_id_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 1 assert self.exec_client.calls == ["_start", "submit_order"] def test_submit_order_when_risk_bypassed_sends_to_execution_engine(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=None, order=order, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 1 # <-- initial account event assert self.exec_client.calls == ["_start", "submit_order"] def test_submit_order_when_position_already_closed_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) order3 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order1 = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=None, order=order1, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order1) self.exec_engine.process(TestEventStubs.order_submitted(order1)) self.exec_engine.process(TestEventStubs.order_accepted(order1)) self.exec_engine.process(TestEventStubs.order_filled(order1, AUDUSD_SIM)) submit_order2 = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=PositionId("P-19700101-000000-000-000-1"), order=order2, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order2) self.exec_engine.process(TestEventStubs.order_submitted(order2)) self.exec_engine.process(TestEventStubs.order_accepted(order2)) self.exec_engine.process(TestEventStubs.order_filled(order2, AUDUSD_SIM)) submit_order3 = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=PositionId("P-19700101-000000-000-000-1"), order=order3, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order3) # Assert assert self.exec_engine.command_count == 2 assert self.exec_client.calls == ["_start", "submit_order", "submit_order"] def test_submit_order_when_position_id_not_in_cache_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, PositionId("009"), # <-- not in the cache order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 def test_submit_order_when_instrument_not_in_cache_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( GBPUSD_SIM.id, # <-- not in the cache OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_invalid_price_precision_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("0.9999999999999999"), # <- invalid price ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_invalid_negative_price_and_not_option_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("-1.0"), # <- invalid price ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_invalid_trigger_price_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), Price.from_str("0.999999999999999"), # <- invalid trigger ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_invalid_quantity_precision_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_str("1.111111111111111111"), # <- invalid quantity Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_invalid_quantity_exceeds_maximum_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(1_000_000_000), # <- invalid quantity fat finger! Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_invalid_quantity_less_than_minimum_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(1), # <- invalid quantity Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_market_order_and_no_market_then_logs_warning(self): # Arrange self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(10000000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 1 # <-- command reaches engine with warning def test_submit_order_when_market_order_and_over_max_notional_then_denies(self): # Arrange self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) # Initialize market quote = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) self.cache.add_quote_tick(quote) self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(10000000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine def test_submit_order_when_reducing_and_buy_order_adds_then_denies(self): # Arrange self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) # Initialize market quote = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) self.cache.add_quote_tick(quote) self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order1 = SubmitOrder( self.trader_id, strategy.id, None, order1, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order1) self.risk_engine.set_trading_state(TradingState.REDUCING) # <-- allow reducing orders only order2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order2 = SubmitOrder( self.trader_id, strategy.id, None, order2, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.exec_engine.process(TestEventStubs.order_submitted(order1)) self.exec_engine.process(TestEventStubs.order_accepted(order1)) self.exec_engine.process(TestEventStubs.order_filled(order1, AUDUSD_SIM)) # Act self.risk_engine.execute(submit_order2) # Assert assert self.portfolio.is_net_long(AUDUSD_SIM.id) assert self.exec_engine.command_count == 1 # <-- command never reaches engine def test_submit_order_when_reducing_and_sell_order_adds_then_denies(self): # Arrange self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) # Initialize market quote = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) self.cache.add_quote_tick(quote) self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) submit_order1 = SubmitOrder( self.trader_id, strategy.id, None, order1, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order1) self.risk_engine.set_trading_state(TradingState.REDUCING) # <-- allow reducing orders only order2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) submit_order2 = SubmitOrder( self.trader_id, strategy.id, None, order2, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.exec_engine.process(TestEventStubs.order_submitted(order1)) self.exec_engine.process(TestEventStubs.order_accepted(order1)) self.exec_engine.process(TestEventStubs.order_filled(order1, AUDUSD_SIM)) # Act self.risk_engine.execute(submit_order2) # Assert assert self.portfolio.is_net_short(AUDUSD_SIM.id) assert self.exec_engine.command_count == 1 # <-- command never reaches engine def test_submit_order_when_trading_halted_then_denies_order(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Halt trading self.risk_engine.set_trading_state(TradingState.HALTED) # Act self.risk_engine.execute(submit_order) # Assert assert self.risk_engine.command_count == 1 # <-- command never reaches engine def test_submit_order_list_when_trading_halted_then_denies_orders(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) entry = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) stop_loss = strategy.order_factory.stop_market( # <-- duplicate AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) take_profit = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.10000"), ) bracket = OrderList( list_id=OrderListId("1"), orders=[entry, stop_loss, take_profit], ) submit_bracket = SubmitOrderList( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Halt trading self.risk_engine.set_trading_state(TradingState.HALTED) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.risk_engine.command_count == 1 # <-- command never reaches engine # -- SUBMIT BRACKET ORDER TESTS ---------------------------------------------------------------- def test_submit_bracket_with_default_settings_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitOrderList( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_engine.command_count == 1 assert self.exec_client.calls == ["_start", "submit_order_list"] def test_submit_bracket_order_with_duplicate_entry_id_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitOrderList( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_bracket) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_engine.command_count == 1 # <-- command never reaches engine def test_submit_bracket_order_with_duplicate_stop_loss_id_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) entry1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) stop_loss = strategy.order_factory.stop_market( # <-- duplicate AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) take_profit1 = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.10000"), ) entry2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) take_profit2 = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.10000"), ) bracket1 = OrderList( list_id=OrderListId("1"), orders=[entry1, stop_loss, take_profit1], ) bracket2 = OrderList( list_id=OrderListId("1"), orders=[entry2, stop_loss, take_profit2], ) submit_bracket1 = SubmitOrderList( self.trader_id, strategy.id, bracket1, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) submit_bracket2 = SubmitOrderList( self.trader_id, strategy.id, bracket2, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_bracket1) # Act self.risk_engine.execute(submit_bracket2) # Assert assert self.exec_engine.command_count == 1 # <-- command never reaches engine def test_submit_bracket_order_with_duplicate_take_profit_id_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) entry1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) stop_loss1 = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) take_profit = strategy.order_factory.limit( # <-- duplicate AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.10000"), ) entry2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) stop_loss2 = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) bracket1 = OrderList( list_id=OrderListId("1"), orders=[entry1, stop_loss1, take_profit], ) bracket2 = OrderList( list_id=OrderListId("1"), orders=[entry2, stop_loss2, take_profit], ) submit_bracket1 = SubmitOrderList( self.trader_id, strategy.id, bracket1, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) submit_bracket2 = SubmitOrderList( self.trader_id, strategy.id, bracket2, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_bracket1) # Act self.risk_engine.execute(submit_bracket2) # Assert assert self.exec_engine.command_count == 1 # <-- command never reaches engine def test_submit_bracket_order_when_instrument_not_in_cache_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bracket = strategy.order_factory.bracket_market( GBPUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), stop_loss=Price.from_str("1.00000"), take_profit=Price.from_str("1.00010"), ) submit_bracket = SubmitOrderList( self.trader_id, strategy.id, bracket, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_bracket) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine # -- UPDATE ORDER TESTS ------------------------------------------------------------------------ def test_update_order_when_no_order_found_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) modify = ModifyOrder( self.trader_id, strategy.id, AUDUSD_SIM.id, ClientOrderId("invalid"), VenueOrderId("1"), Quantity.from_int(100000), Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == ["_start"] assert self.risk_engine.command_count == 1 assert self.exec_engine.command_count == 0 def test_update_order_when_already_closed_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00010"), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) self.exec_engine.process(TestEventStubs.order_submitted(order)) self.exec_engine.process(TestEventStubs.order_accepted(order)) self.exec_engine.process(TestEventStubs.order_filled(order, AUDUSD_SIM)) modify = ModifyOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), order.quantity, Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == ["_start", "submit_order"] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 1 def test_update_order_when_in_flight_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00010"), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) self.exec_engine.process(TestEventStubs.order_submitted(order)) modify = ModifyOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), order.quantity, Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == ["_start", "submit_order"] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 1 def test_modify_order_with_default_settings_then_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00010"), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) modify = ModifyOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), order.quantity, Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == ["_start", "submit_order", "modify_order"] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 2 # -- CANCEL ORDER TESTS ------------------------------------------------------------------------ def test_cancel_order_when_order_does_not_exist_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) cancel = CancelOrder( self.trader_id, strategy.id, AUDUSD_SIM.id, ClientOrderId("1"), VenueOrderId("1"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == ["_start"] assert self.risk_engine.command_count == 1 assert self.exec_engine.command_count == 0 def test_cancel_order_when_already_closed_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) self.exec_engine.process(TestEventStubs.order_submitted(order)) self.exec_engine.process(TestEventStubs.order_rejected(order)) cancel = CancelOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == ["_start", "submit_order"] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 1 def test_cancel_order_when_already_pending_cancel_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) cancel = CancelOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) self.exec_engine.process(TestEventStubs.order_submitted(order)) self.exec_engine.process(TestEventStubs.order_accepted(order)) self.risk_engine.execute(cancel) self.exec_engine.process(TestEventStubs.order_pending_cancel(order)) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == ["_start", "submit_order", "cancel_order"] assert self.risk_engine.command_count == 3 assert self.exec_engine.command_count == 2 def test_cancel_order_with_default_settings_then_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) cancel = CancelOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == ["_start", "submit_order", "cancel_order"] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 2
class SimulatedExchangeTests(unittest.TestCase): def setUp(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={'use_previous_close': False}, # To correctly reproduce historical data bars ) self.data_engine.cache.add_instrument(AUDUSD_SIM) self.data_engine.cache.add_instrument(USDJPY_SIM) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("SIM", "001") exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=exec_db, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=SIM, oms_type=OMSType.HEDGING, generate_position_ids=False, # Will force execution engine to generate ids is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], instruments=[AUDUSD_SIM, USDJPY_SIM], modules=[], fill_model=FillModel(), exec_cache=self.exec_engine.cache, clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy(bar_type=TestStubs.bartype_usdjpy_1min_bid()) self.strategy.register_trader( self.trader_id, self.clock, self.logger, ) self.data_engine.register_strategy(self.strategy) self.exec_engine.register_strategy(self.strategy) self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_repr(self): # Arrange # Act # Assert self.assertEqual("SimulatedExchange(SIM)", repr(self.exchange)) def test_check_residuals(self): # Arrange # Act self.exchange.check_residuals() # Assert self.assertTrue(True) # No exceptions raised def test_check_residuals_with_working_and_oco_orders(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry1 = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("90.000"), ) entry2 = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("89.900"), ) bracket1 = self.strategy.order_factory.bracket( entry_order=entry1, stop_loss=Price("89.900"), take_profit=Price("91.000"), ) bracket2 = self.strategy.order_factory.bracket( entry_order=entry2, stop_loss=Price("89.800"), ) self.strategy.submit_bracket_order(bracket1) self.strategy.submit_bracket_order(bracket2) tick2 = QuoteTick( USDJPY_SIM.symbol, Price("89.998"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Act self.exchange.check_residuals() # Assert self.assertEqual(3, len(self.exchange.get_working_orders())) self.assertIn(bracket1.stop_loss, self.exchange.get_working_orders().values()) self.assertIn(bracket1.take_profit, self.exchange.get_working_orders().values()) self.assertIn(entry2, self.exchange.get_working_orders().values()) def test_get_working_orders_when_no_orders_returns_empty_dict(self): # Arrange # Act orders = self.exchange.get_working_orders() self.assertEqual({}, orders) def test_submit_order_with_no_market_rejects_order(self): # Arrange order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("80.000"), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(2, self.strategy.object_storer.count) self.assertTrue(isinstance(self.strategy.object_storer.get_store()[1], OrderRejected)) def test_submit_order_with_invalid_price_gets_rejected(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.exchange.process_tick(tick) self.portfolio.update_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("80.000"), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(OrderState.REJECTED, order.state) def test_submit_market_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) # Create order order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Decimal("90.003"), order.avg_price) def test_submit_limit_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("80.000"), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertIn(order.cl_ord_id, self.exchange.get_working_orders()) def test_submit_bracket_market_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) bracket_order = self.strategy.order_factory.bracket( entry_order, Price("80.000"), ) # Act self.strategy.submit_bracket_order(bracket_order) # Assert self.assertEqual(OrderState.FILLED, entry_order.state) def test_submit_bracket_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry_order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.710"), ) bracket_order = self.strategy.order_factory.bracket( entry_order, Price("86.000"), Price("97.000"), ) # Act self.strategy.submit_bracket_order(bracket_order) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertIn(entry_order.cl_ord_id, self.exchange.get_working_orders()) def test_cancel_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), ) self.strategy.submit_order(order) # Act self.strategy.cancel_order(order) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) def test_cancel_stop_order_when_order_does_not_exist_generates_cancel_reject(self): # Arrange command = CancelOrder( venue=SIM, trader_id=self.trader_id, account_id=self.account_id, cl_ord_id=ClientOrderId("O-123456"), order_id=OrderId("001"), command_id=self.uuid_factory.generate(), command_timestamp=UNIX_EPOCH, ) # Act self.exchange.handle_cancel_order(command) # Assert self.assertEqual(2, self.exec_engine.event_count) def test_modify_stop_order_when_order_does_not_exist(self): # Arrange command = AmendOrder( venue=SIM, trader_id=self.trader_id, account_id=self.account_id, cl_ord_id=ClientOrderId("O-123456"), quantity=Quantity(100000), price=Price("1.00000"), command_id=self.uuid_factory.generate(), command_timestamp=UNIX_EPOCH, ) # Act self.exchange.handle_amend_order(command) # Assert self.assertEqual(2, self.exec_engine.event_count) def test_modify_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), ) self.strategy.submit_order(order) # Act self.strategy.amend_order(order, order.quantity, Price("96.714")) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertEqual(Price("96.714"), order.price) def test_expire_order(self): # Arrange # Prepare market tick1 = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick1) self.exchange.process_tick(tick1) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), time_in_force=TimeInForce.GTD, expire_time=UNIX_EPOCH + timedelta(minutes=1), ) self.strategy.submit_order(order) tick2 = QuoteTick( USDJPY_SIM.symbol, Price("96.709"), Price("96.710"), Quantity(100000), Quantity(100000), UNIX_EPOCH + timedelta(minutes=1), ) # Act self.exchange.process_tick(tick2) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) def test_modify_bracket_order_working_stop_loss(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) bracket_order = self.strategy.order_factory.bracket( entry_order, stop_loss=Price("85.000"), ) self.strategy.submit_bracket_order(bracket_order) # Act self.strategy.amend_order(bracket_order.stop_loss, bracket_order.entry.quantity, Price("85.100")) # Assert self.assertEqual(Price("85.100"), bracket_order.stop_loss.price) def test_submit_market_order_with_slippage_fill_model_slips_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) fill_model = FillModel( prob_fill_at_limit=0.0, prob_fill_at_stop=1.0, prob_slippage=1.0, random_seed=None, ) self.exchange.set_fill_model(fill_model) order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act self.strategy.submit_order(order) # Assert self.assertEqual(Decimal("90.004"), order.avg_price) def test_order_fills_gets_commissioned(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) top_up_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) reduce_order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(50000), ) # Act self.strategy.submit_order(order) position_id = PositionId("P-19700101-000000-000-001-1") # Generated by platform self.strategy.submit_order(top_up_order, position_id) self.strategy.submit_order(reduce_order, position_id) account_event1 = self.strategy.object_storer.get_store()[2] account_event2 = self.strategy.object_storer.get_store()[6] account_event3 = self.strategy.object_storer.get_store()[10] account = self.exec_engine.cache.account_for_venue(Venue("SIM")) # Assert self.assertEqual(Money(180.01, JPY), account_event1.commission) self.assertEqual(Money(180.01, JPY), account_event2.commission) self.assertEqual(Money(90.00, JPY), account_event3.commission) self.assertTrue(Money(999995.00, USD), account.balance()) def test_process_quote_tick_fills_buy_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("96.711"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( AUDUSD_SIM.symbol, # Different market Price("80.010"), Price("80.011"), Quantity(200000), Quantity(200000), UNIX_EPOCH, ) tick3 = QuoteTick( USDJPY_SIM.symbol, Price("96.710"), Price("96.712"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) self.exchange.process_tick(tick3) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("96.711"), order.avg_price) def test_process_quote_tick_fills_buy_limit_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("90.001"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( AUDUSD_SIM.symbol, # Different market Price("80.010"), Price("80.011"), Quantity(200000), Quantity(200000), UNIX_EPOCH, ) tick3 = QuoteTick( USDJPY_SIM.symbol, Price("89.998"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) self.exchange.process_tick(tick3) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("90.001"), order.avg_price) def test_process_quote_tick_fills_sell_stop_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.stop_market( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("90.000"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("89.997"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("90.000"), order.avg_price) def test_process_quote_tick_fills_sell_limit_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("90.100"), ) self.strategy.submit_order(order) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("90.101"), Price("90.102"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) self.assertEqual(OrderState.FILLED, order.state) self.assertEqual(Price("90.100"), order.avg_price) def test_process_quote_tick_fills_buy_limit_entry_with_bracket(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("90.000"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("89.900"), ) self.strategy.submit_bracket_order(bracket) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("89.998"), Price("89.999"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(1, len(self.exchange.get_working_orders())) self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values()) def test_process_quote_tick_fills_sell_limit_entry_with_bracket(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("91.100"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("91.200"), take_profit=Price("90.000"), ) self.strategy.submit_bracket_order(bracket) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("91.101"), Price("91.102"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) # Assert self.assertEqual(2, len(self.exchange.get_working_orders())) # SL and TP self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values()) self.assertIn(bracket.take_profit, self.exchange.get_working_orders().values()) def test_process_trade_tick_fills_buy_limit_entry_with_bracket(self): # Arrange # Prepare market tick1 = TradeTick( AUDUSD_SIM.symbol, Price("1.00000"), Quantity(100000), OrderSide.SELL, TradeMatchId("123456789"), UNIX_EPOCH, ) tick2 = TradeTick( AUDUSD_SIM.symbol, Price("1.00001"), Quantity(100000), OrderSide.BUY, TradeMatchId("123456790"), UNIX_EPOCH, ) self.data_engine.process(tick1) self.data_engine.process(tick2) self.exchange.process_tick(tick1) self.exchange.process_tick(tick2) entry = self.strategy.order_factory.limit( AUDUSD_SIM.symbol, OrderSide.BUY, Quantity(100000), Price("0.99900"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("0.99800"), take_profit=Price("1.100"), ) self.strategy.submit_bracket_order(bracket) # Act tick3 = TradeTick( AUDUSD_SIM.symbol, Price("0.99899"), Quantity(100000), OrderSide.BUY, # Lowers ask price TradeMatchId("123456789"), UNIX_EPOCH, ) self.exchange.process_tick(tick3) # Assert self.assertEqual(2, len(self.exchange.get_working_orders())) # SL and TP only self.assertIn(bracket.stop_loss, self.exchange.get_working_orders().values()) self.assertIn(bracket.take_profit, self.exchange.get_working_orders().values()) def test_filling_oco_sell_cancels_other_order(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) entry = self.strategy.order_factory.limit( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(100000), Price("91.100"), ) bracket = self.strategy.order_factory.bracket( entry_order=entry, stop_loss=Price("91.200"), take_profit=Price("90.000"), ) self.strategy.submit_bracket_order(bracket) # Act tick2 = QuoteTick( USDJPY_SIM.symbol, Price("91.101"), Price("91.102"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) tick3 = QuoteTick( USDJPY_SIM.symbol, Price("91.201"), Price("91.203"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(tick2) self.exchange.process_tick(tick3) # Assert self.assertEqual(0, len(self.exchange.get_working_orders())) def test_realized_pnl_contains_commission(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act self.strategy.submit_order(order) position = self.exec_engine.cache.positions_open()[0] # Assert self.assertEqual(Money(-180.01, JPY), position.realized_pnl) self.assertEqual(Money(180.01, JPY), position.commission) self.assertEqual([Money(180.01, JPY)], position.commissions()) def test_unrealized_pnl(self): # Arrange # Prepare market tick = TestStubs.quote_tick_3decimal(USDJPY_SIM.symbol) self.data_engine.process(tick) self.exchange.process_tick(tick) order_open = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act 1 self.strategy.submit_order(order_open) reduce_quote = QuoteTick( USDJPY_SIM.symbol, Price("100.003"), Price("100.003"), Quantity(100000), Quantity(100000), UNIX_EPOCH, ) self.exchange.process_tick(reduce_quote) self.portfolio.update_tick(reduce_quote) order_reduce = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(50000), ) position_id = PositionId("P-19700101-000000-000-001-1") # Generated by platform # Act 2 self.strategy.submit_order(order_reduce, position_id) # Assert position = self.exec_engine.cache.positions_open()[0] self.assertEqual(Money(500000.00, JPY), position.unrealized_pnl(Price("100.003"))) def test_position_flipped_when_reduce_order_exceeds_original_quantity(self): # Arrange # Prepare market open_quote = QuoteTick( USDJPY_SIM.symbol, Price("90.002"), Price("90.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.data_engine.process(open_quote) self.exchange.process_tick(open_quote) order_open = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.BUY, Quantity(100000), ) # Act 1 self.strategy.submit_order(order_open) reduce_quote = QuoteTick( USDJPY_SIM.symbol, Price("100.003"), Price("100.003"), Quantity(1), Quantity(1), UNIX_EPOCH, ) self.exchange.process_tick(reduce_quote) self.portfolio.update_tick(reduce_quote) order_reduce = self.strategy.order_factory.market( USDJPY_SIM.symbol, OrderSide.SELL, Quantity(150000), ) # Act 2 self.strategy.submit_order(order_reduce, PositionId("P-19700101-000000-000-001-1")) # Generated by platform # Assert print(self.exec_engine.cache.positions()) position_open = self.exec_engine.cache.positions_open()[0] position_closed = self.exec_engine.cache.positions_closed()[0] self.assertEqual(PositionSide.SHORT, position_open.side) self.assertEqual(Quantity(50000), position_open.quantity) self.assertEqual(Money(999619.98, JPY), position_closed.realized_pnl) self.assertEqual([Money(380.02, JPY)], position_closed.commissions())
class BitmexExchangeTests(unittest.TestCase): def setUp(self): # Fixture Setup self.strategies = [MockStrategy(TestStubs.bartype_btcusdt_binance_1min_bid())] self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = TestLogger(self.clock) self.portfolio = Portfolio( clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( portfolio=self.portfolio, clock=self.clock, logger=self.logger, config={'use_previous_close': False}, # To correctly reproduce historical data bars ) self.data_engine.cache.add_instrument(XBTUSD_BITMEX) self.portfolio.register_cache(self.data_engine.cache) self.analyzer = PerformanceAnalyzer() self.trader_id = TraderId("TESTER", "000") self.account_id = AccountId("BITMEX", "001") exec_db = BypassExecutionDatabase( trader_id=self.trader_id, logger=self.logger, ) self.exec_engine = ExecutionEngine( database=exec_db, portfolio=self.portfolio, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=Venue("BITMEX"), oms_type=OMSType.HEDGING, generate_position_ids=True, is_frozen_account=False, starting_balances=[Money(1_000_000, USD)], exec_cache=self.exec_engine.cache, instruments=[XBTUSD_BITMEX], modules=[], fill_model=FillModel(), clock=self.clock, logger=self.logger, ) self.exec_client = BacktestExecClient( exchange=self.exchange, account_id=self.account_id, engine=self.exec_engine, clock=self.clock, logger=self.logger, ) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy(bar_type=TestStubs.bartype_btcusdt_binance_1min_bid()) self.strategy.register_trader( self.trader_id, self.clock, self.logger, ) self.data_engine.register_strategy(self.strategy) self.exec_engine.register_strategy(self.strategy) self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_commission_maker_taker_order(self): # Arrange # Prepare market quote1 = QuoteTick( XBTUSD_BITMEX.symbol, Price("11493.70"), Price("11493.75"), Quantity(1500000), Quantity(1500000), UNIX_EPOCH, ) self.data_engine.process(quote1) self.exchange.process_tick(quote1) order_market = self.strategy.order_factory.market( XBTUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100000), ) order_limit = self.strategy.order_factory.limit( XBTUSD_BITMEX.symbol, OrderSide.BUY, Quantity(100000), Price("11493.65"), ) # Act self.strategy.submit_order(order_market) self.strategy.submit_order(order_limit) quote2 = QuoteTick( XBTUSD_BITMEX.symbol, Price("11493.60"), Price("11493.64"), Quantity(1500000), Quantity(1500000), UNIX_EPOCH, ) self.exchange.process_tick(quote2) # Fill the limit order self.portfolio.update_tick(quote2) # Assert self.assertEqual(LiquiditySide.TAKER, self.strategy.object_storer.get_store()[2].liquidity_side) self.assertEqual(LiquiditySide.MAKER, self.strategy.object_storer.get_store()[6].liquidity_side) self.assertEqual(Money("0.00652529", BTC), self.strategy.object_storer.get_store()[2].commission) self.assertEqual(Money("-0.00217511", BTC), self.strategy.object_storer.get_store()[6].commission)
class TestActor: 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.component_id = "MyComponent-001" self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() 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.data_client = BacktestMarketDataClient( client_id=ClientId("SIM"), msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine.register_client(self.data_client) # 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.data_engine.start() self.exec_engine.start() def test_actor_fully_qualified_name(self): # Arrange, Act, Assert assert Actor.fully_qualified_name( ) == "nautilus_trader.common.actor.Actor" def test_id(self): # Arrange, Act actor = Actor(config=ActorConfig(component_id=self.component_id)) # Assert assert actor.id == ComponentId(self.component_id) def test_pre_initialization(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert assert actor.state == ComponentState.PRE_INITIALIZED assert not actor.is_initialized def test_initialization(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert assert actor.state == ComponentState.INITIALIZED assert actor.is_initialized def test_register_warning_event(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.register_warning_event(OrderDenied) # Assert assert True # Exception not raised def test_deregister_warning_event(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.register_warning_event(OrderDenied) # Act actor.deregister_warning_event(OrderDenied) # Assert assert True # Exception not raised def test_handle_event(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) event = TestEventStubs.cash_account_state() # Act actor.handle_event(event) # Assert assert True # Exception not raised def test_on_start_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_start() # Assert assert True # Exception not raised def test_on_stop_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_stop() # Assert assert True # Exception not raised def test_on_resume_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_resume() # Assert assert True # Exception not raised def test_on_reset_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_reset() # Assert assert True # Exception not raised def test_on_dispose_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_dispose() # Assert assert True # Exception not raised def test_on_degrade_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_degrade() # Assert assert True # Exception not raised def test_on_fault_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_fault() # Assert assert True # Exception not raised def test_on_instrument_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_instrument(TestInstrumentProvider.btcusdt_binance()) # Assert assert True # Exception not raised def test_on_order_book_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_order_book(TestDataStubs.order_book()) # Assert assert True # Exception not raised def test_on_order_book_delta_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_order_book_delta(TestDataStubs.order_book_snapshot()) # Assert assert True # Exception not raised def test_on_ticker_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_ticker(TestDataStubs.ticker()) # Assert assert True # Exception not raised def test_on_venue_status_update_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_venue_status_update(TestDataStubs.venue_status_update()) # Assert assert True # Exception not raised def test_on_instrument_status_update_when_not_overridden_does_nothing( self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_instrument_status_update( TestDataStubs.instrument_status_update()) # Assert assert True # Exception not raised def test_on_event_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act actor.on_event(TestEventStubs.cash_account_state()) # Assert assert True # Exception not raised def test_on_quote_tick_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) tick = TestDataStubs.quote_tick_5decimal() # Act actor.on_quote_tick(tick) # Assert assert True # Exception not raised def test_on_trade_tick_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) tick = TestDataStubs.trade_tick_5decimal() # Act actor.on_trade_tick(tick) # Assert assert True # Exception not raised def test_on_bar_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) bar = TestDataStubs.bar_5decimal() # Act actor.on_bar(bar) # Assert assert True # Exception not raised def test_on_data_when_not_overridden_does_nothing(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) news_event = NewsEvent( impact=NewsImpact.HIGH, name="Unemployment Rate", currency=EUR, ts_event=0, ts_init=0, ) # Act actor.on_data(news_event) # Assert assert True # Exception not raised def test_start_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert with pytest.raises(InvalidStateTrigger): actor.start() def test_stop_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) try: actor.start() except InvalidStateTrigger: # Normally a bad practice but allows strategy to be put into # the needed state to run the test. pass # Act, Assert with pytest.raises(InvalidStateTrigger): actor.stop() def test_resume_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert with pytest.raises(InvalidStateTrigger): actor.resume() def test_reset_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert with pytest.raises(InvalidStateTrigger): actor.reset() def test_dispose_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert with pytest.raises(InvalidStateTrigger): actor.dispose() def test_degrade_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert with pytest.raises(InvalidStateTrigger): actor.degrade() def test_fault_when_not_initialized_raises_invalid_state_trigger(self): # Arrange actor = Actor(config=ActorConfig(component_id=self.component_id)) # Act, Assert with pytest.raises(InvalidStateTrigger): actor.fault() def test_start_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): actor.start() assert actor.state == ComponentState.RUNNING assert actor.is_running def test_stop_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() # Act, Assert with pytest.raises(RuntimeError): actor.stop() assert actor.state == ComponentState.STOPPED assert actor.is_stopped def test_resume_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.set_explode_on_stop(False) actor.start() actor.stop() # Act, Assert with pytest.raises(RuntimeError): actor.resume() assert actor.state == ComponentState.RUNNING assert actor.is_running def test_reset_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): actor.reset() assert actor.state == ComponentState.INITIALIZED assert actor.is_initialized def test_dispose_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act, Assert with pytest.raises(RuntimeError): actor.dispose() assert actor.state == ComponentState.DISPOSED assert actor.is_disposed def test_degrade_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() # Act, Assert with pytest.raises(RuntimeError): actor.degrade() assert actor.state == ComponentState.DEGRADED assert actor.is_degraded def test_fault_when_user_code_raises_error_logs_and_reraises(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() # Act, Assert with pytest.raises(RuntimeError): actor.fault() assert actor.state == ComponentState.FAULTED assert actor.is_faulted def test_handle_quote_tick_when_user_code_raises_exception_logs_and_reraises( self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() tick = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act, Assert with pytest.raises(RuntimeError): actor.handle_quote_tick(tick) def test_handle_trade_tick_when_user_code_raises_exception_logs_and_reraises( self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() tick = TestDataStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act, Assert with pytest.raises(RuntimeError): actor.handle_trade_tick(tick) def test_handle_bar_when_user_code_raises_exception_logs_and_reraises( self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() bar = TestDataStubs.bar_5decimal() # Act, Assert with pytest.raises(RuntimeError): actor.handle_bar(bar) def test_handle_data_when_user_code_raises_exception_logs_and_reraises( self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() # Act, Assert with pytest.raises(RuntimeError): actor.handle_data( NewsEvent( impact=NewsImpact.HIGH, name="Unemployment Rate", currency=USD, ts_event=0, ts_init=0, ), ) def test_handle_event_when_user_code_raises_exception_logs_and_reraises( self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() event = TestEventStubs.cash_account_state( account_id=AccountId("TEST", "000")) # Act, Assert with pytest.raises(RuntimeError): actor.on_event(event) def test_start(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.start() # Assert assert "on_start" in actor.calls assert actor.state == ComponentState.RUNNING def test_stop(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.start() actor.stop() # Assert assert "on_stop" in actor.calls assert actor.state == ComponentState.STOPPED def test_resume(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() actor.stop() # Act actor.resume() # Assert assert "on_resume" in actor.calls assert actor.state == ComponentState.RUNNING def test_reset(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.reset() # Assert assert "on_reset" in actor.calls assert actor.state == ComponentState.INITIALIZED def test_dispose(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.reset() # Act actor.dispose() # Assert assert "on_dispose" in actor.calls assert actor.state == ComponentState.DISPOSED def test_degrade(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() # Act actor.degrade() # Assert assert "on_degrade" in actor.calls assert actor.state == ComponentState.DEGRADED def test_fault(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() # Act actor.fault() # Assert assert "on_fault" in actor.calls assert actor.state == ComponentState.FAULTED def test_handle_instrument_with_blow_up_logs_exception(self): # Arrange actor = KaboomActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.set_explode_on_start(False) actor.start() # Act, Assert with pytest.raises(RuntimeError): actor.handle_instrument(AUDUSD_SIM) def test_handle_instrument_when_not_running_does_not_send_to_on_instrument( self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.handle_instrument(AUDUSD_SIM) # Assert assert actor.calls == [] assert actor.object_storer.get_store() == [] def test_handle_instrument_when_running_sends_to_on_instrument(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() # Act actor.handle_instrument(AUDUSD_SIM) # Assert assert actor.calls == ["on_start", "on_instrument"] assert actor.object_storer.get_store()[0] == AUDUSD_SIM def test_handle_ticker_when_not_running_does_not_send_to_on_quote_tick( self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) tick = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act actor.handle_quote_tick(tick) # Assert assert actor.calls == [] assert actor.object_storer.get_store() == [] def test_handle_ticker_when_running_sends_to_on_quote_tick(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() ticker = TestDataStubs.ticker() # Act actor.handle_ticker(ticker) # Assert assert actor.calls == ["on_start", "on_ticker"] assert actor.object_storer.get_store()[0] == ticker def test_handle_quote_tick_when_not_running_does_not_send_to_on_quote_tick( self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) ticker = TestDataStubs.ticker() # Act actor.handle_ticker(ticker) # Assert assert actor.calls == [] assert actor.object_storer.get_store() == [] def test_handle_quote_tick_when_running_sends_to_on_quote_tick(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() tick = TestDataStubs.quote_tick_5decimal(AUDUSD_SIM.id) # Act actor.handle_quote_tick(tick) # Assert assert actor.calls == ["on_start", "on_quote_tick"] assert actor.object_storer.get_store()[0] == tick def test_handle_trade_tick_when_not_running_does_not_send_to_on_trade_tick( self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) tick = TestDataStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act actor.handle_trade_tick(tick) # Assert assert actor.calls == [] assert actor.object_storer.get_store() == [] def test_handle_trade_tick_when_running_sends_to_on_trade_tick(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() tick = TestDataStubs.trade_tick_5decimal(AUDUSD_SIM.id) # Act actor.handle_trade_tick(tick) # Assert assert actor.calls == ["on_start", "on_trade_tick"] assert actor.object_storer.get_store()[0] == tick def test_handle_bar_when_not_running_does_not_send_to_on_bar(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar = TestDataStubs.bar_5decimal() # Act actor.handle_bar(bar) # Assert assert actor.calls == [] assert actor.object_storer.get_store() == [] def test_handle_bar_when_running_sends_to_on_bar(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() bar = TestDataStubs.bar_5decimal() # Act actor.handle_bar(bar) # Assert assert actor.calls == ["on_start", "on_bar"] assert actor.object_storer.get_store()[0] == bar def test_handle_data_when_not_running_does_not_send_to_on_data(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) data = NewsEvent( impact=NewsImpact.HIGH, name="Unemployment Rate", currency=USD, ts_event=0, ts_init=0, ) # Act actor.handle_data(data) # Assert assert actor.calls == [] assert actor.object_storer.get_store() == [] def test_handle_data_when_running_sends_to_on_data(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.start() data = NewsEvent( impact=NewsImpact.HIGH, name="Unemployment Rate", currency=USD, ts_event=0, ts_init=0, ) # Act actor.handle_data(data) # Assert assert actor.calls == ["on_start", "on_data"] assert actor.object_storer.get_store()[0] == data def test_subscribe_custom_data(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) data_type = DataType(str, {"type": "NEWS_WIRE", "topic": "Earthquake"}) # Act actor.subscribe_data(data_type) # Assert assert self.data_engine.command_count == 0 assert actor.msgbus.subscriptions( )[0].topic == "data.str.type=NEWS_WIRE.topic=Earthquake" def test_subscribe_custom_data_with_client_id(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) data_type = DataType(str, {"type": "NEWS_WIRE", "topic": "Earthquake"}) # Act actor.subscribe_data(data_type, ClientId("QUANDL")) # Assert assert self.data_engine.command_count == 1 assert actor.msgbus.subscriptions( )[0].topic == "data.str.type=NEWS_WIRE.topic=Earthquake" def test_unsubscribe_custom_data(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) data_type = DataType(str, {"type": "NEWS_WIRE", "topic": "Earthquake"}) actor.subscribe_data(data_type) # Act actor.unsubscribe_data(data_type) # Assert assert self.data_engine.command_count == 0 assert actor.msgbus.subscriptions() == [] def test_unsubscribe_custom_data_with_client_id(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) data_type = DataType(str, {"type": "NEWS_WIRE", "topic": "Earthquake"}) actor.subscribe_data(data_type, ClientId("QUANDL")) # Act actor.unsubscribe_data(data_type, ClientId("QUANDL")) # Assert assert self.data_engine.command_count == 2 assert actor.msgbus.subscriptions() == [] def test_subscribe_order_book(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_order_book_snapshots(AUDUSD_SIM.id, book_type=BookType.L2_MBP) # Assert assert self.data_engine.command_count == 1 def test_unsubscribe_order_book(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.subscribe_order_book_snapshots(AUDUSD_SIM.id, book_type=BookType.L2_MBP) # Act actor.unsubscribe_order_book_snapshots(AUDUSD_SIM.id) # Assert assert self.data_engine.command_count == 2 def test_subscribe_order_book_data(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_order_book_deltas(AUDUSD_SIM.id, book_type=BookType.L2_MBP) # Assert assert self.data_engine.command_count == 1 def test_unsubscribe_order_book_deltas(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.unsubscribe_order_book_deltas(AUDUSD_SIM.id) # Act actor.unsubscribe_order_book_deltas(AUDUSD_SIM.id) # Assert assert self.data_engine.command_count == 2 def test_subscribe_instruments(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_instruments(Venue("SIM")) # Assert assert self.data_engine.command_count == 1 assert self.data_engine.subscribed_instruments() == [ InstrumentId.from_str("AUD/USD.SIM"), InstrumentId.from_str("GBP/USD.SIM"), InstrumentId.from_str("USD/JPY.SIM"), ] def test_unsubscribe_instruments(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.unsubscribe_instruments(Venue("SIM")) # Assert assert self.data_engine.command_count == 1 assert self.data_engine.subscribed_instruments() == [] def test_subscribe_instrument(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_instrument(AUDUSD_SIM.id) # Assert expected_instrument = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) assert self.data_engine.command_count == 1 assert self.data_engine.subscribed_instruments() == [ expected_instrument ] def test_unsubscribe_instrument(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.subscribe_instrument(AUDUSD_SIM.id) # Act actor.unsubscribe_instrument(AUDUSD_SIM.id) # Assert assert self.data_engine.subscribed_instruments() == [] assert self.data_engine.command_count == 2 def test_subscribe_ticker(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_ticker(AUDUSD_SIM.id) # Assert expected_instrument = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) assert self.data_engine.subscribed_tickers() == [expected_instrument] assert self.data_engine.command_count == 1 def test_unsubscribe_ticker(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.subscribe_ticker(AUDUSD_SIM.id) # Act actor.unsubscribe_ticker(AUDUSD_SIM.id) # Assert assert self.data_engine.subscribed_tickers() == [] assert self.data_engine.command_count == 2 def test_subscribe_quote_ticks(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_quote_ticks(AUDUSD_SIM.id) # Assert expected_instrument = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) assert self.data_engine.subscribed_quote_ticks() == [ expected_instrument ] assert self.data_engine.command_count == 1 def test_unsubscribe_quote_ticks(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.subscribe_quote_ticks(AUDUSD_SIM.id) # Act actor.unsubscribe_quote_ticks(AUDUSD_SIM.id) # Assert assert self.data_engine.subscribed_quote_ticks() == [] assert self.data_engine.command_count == 2 def test_subscribe_trade_ticks(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.subscribe_trade_ticks(AUDUSD_SIM.id) # Assert expected_instrument = InstrumentId(Symbol("AUD/USD"), Venue("SIM")) assert self.data_engine.subscribed_trade_ticks() == [ expected_instrument ] assert self.data_engine.command_count == 1 def test_unsubscribe_trade_ticks(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.subscribe_trade_ticks(AUDUSD_SIM.id) # Act actor.unsubscribe_trade_ticks(AUDUSD_SIM.id) # Assert assert self.data_engine.subscribed_trade_ticks() == [] assert self.data_engine.command_count == 2 def test_publish_data_sends_to_subscriber(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) handler = [] self.msgbus.subscribe( topic="data*", handler=handler.append, ) # Act data = Data( ts_event=self.clock.timestamp_ns(), ts_init=self.clock.timestamp_ns(), ) actor.publish_data(data_type=DataType(Data), data=data) # Assert assert data in handler def test_subscribe_bars(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar_type = TestDataStubs.bartype_audusd_1min_bid() # Act actor.subscribe_bars(bar_type) # Assert assert self.data_engine.subscribed_bars() == [bar_type] assert self.data_engine.command_count == 1 def test_unsubscribe_bars(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar_type = TestDataStubs.bartype_audusd_1min_bid() actor.subscribe_bars(bar_type) # Act actor.unsubscribe_bars(bar_type) # Assert assert self.data_engine.subscribed_bars() == [] assert self.data_engine.command_count == 2 def test_subscribe_venue_status_updates(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) actor.subscribe_venue_status_updates(Venue("NYMEX")) # Assert # TODO(cs): DataEngine.subscribed_venue_status_updates() def test_request_data_sends_request_to_data_engine(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) data_type = DataType(str, { "type": "NEWS_WIRE", "topic": "Earthquakes" }) # Act actor.request_data(ClientId("BLOOMBERG-01"), data_type) # Assert assert self.data_engine.request_count == 1 def test_request_quote_ticks_sends_request_to_data_engine(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.request_quote_ticks(AUDUSD_SIM.id) # Assert assert self.data_engine.request_count == 1 def test_request_trade_ticks_sends_request_to_data_engine(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act actor.request_trade_ticks(AUDUSD_SIM.id) # Assert assert self.data_engine.request_count == 1 def test_request_bars_sends_request_to_data_engine(self): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar_type = TestDataStubs.bartype_audusd_1min_bid() # Act actor.request_bars(bar_type) # Assert assert self.data_engine.request_count == 1 @pytest.mark.parametrize( "start, stop", [ (UNIX_EPOCH, UNIX_EPOCH), (UNIX_EPOCH + timedelta(milliseconds=1), UNIX_EPOCH), ], ) def test_request_bars_with_invalid_params_raises_value_error( self, start, stop): # Arrange actor = MockActor() actor.register_base( trader_id=self.trader_id, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) bar_type = TestDataStubs.bartype_audusd_1min_bid() # Act, Assert with pytest.raises(ValueError): actor.request_bars(bar_type, start, stop)
class TestL2OrderBookExchange: def setup(self): # Fixture Setup self.clock = TestClock() self.uuid_factory = UUIDFactory() self.logger = Logger( clock=self.clock, level_stdout=LogLevel.DEBUG, ) self.trader_id = TestIdStubs.trader_id() self.msgbus = MessageBus( trader_id=self.trader_id, clock=self.clock, logger=self.logger, ) self.cache = TestComponentStubs.cache() self.portfolio = Portfolio( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.data_engine = DataEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exec_engine = ExecutionEngine( msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.risk_engine = RiskEngine( portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange = SimulatedExchange( venue=SIM, oms_type=OMSType.HEDGING, account_type=AccountType.MARGIN, base_currency=USD, starting_balances=[Money(1_000_000, USD)], default_leverage=Decimal(50), leverages={}, is_frozen_account=False, instruments=[USDJPY_SIM], modules=[], fill_model=FillModel(), cache=self.cache, clock=self.clock, logger=self.logger, book_type=BookType.L2_MBP, latency_model=LatencyModel(0), ) self.exec_client = BacktestExecClient( exchange=self.exchange, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Prepare components self.cache.add_instrument(USDJPY_SIM) self.cache.add_order_book( OrderBook.create( instrument=USDJPY_SIM, book_type=BookType.L2_MBP, )) self.exec_engine.register_client(self.exec_client) self.exchange.register_client(self.exec_client) self.strategy = MockStrategy( bar_type=TestDataStubs.bartype_usdjpy_1min_bid()) self.strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) self.exchange.reset() self.data_engine.start() self.exec_engine.start() self.strategy.start() def test_submit_limit_order_aggressive_multiple_levels(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) quote = QuoteTick( instrument_id=USDJPY_SIM.id, bid=Price.from_str("110.000"), ask=Price.from_str("110.010"), bid_size=Quantity.from_int(1500000), ask_size=Quantity.from_int(1500000), ts_event=0, ts_init=0, ) self.data_engine.process(quote) snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000, ) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) # Create order order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.BUY, quantity=Quantity.from_int(2000), price=Price.from_int(20), post_only=False, ) # Act self.strategy.submit_order(order) self.exchange.process(0) # Assert assert order.status == OrderStatus.FILLED assert order.filled_qty == Decimal("2000.0") # No slippage assert order.avg_px == Decimal("15.33333333333333333333333333") assert self.exchange.get_account().balance_total(USD) == Money( 999999.96, USD) def test_aggressive_partial_fill(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) quote = QuoteTick( instrument_id=USDJPY_SIM.id, bid=Price.from_str("110.000"), ask=Price.from_str("110.010"), bid_size=Quantity.from_int(1500000), ask_size=Quantity.from_int(1500000), ts_event=0, ts_init=0, ) self.data_engine.process(quote) snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000, ) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) # Act order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.BUY, quantity=Quantity.from_int(7000), price=Price.from_int(20), post_only=False, ) self.strategy.submit_order(order) self.exchange.process(0) # Assert assert order.status == OrderStatus.PARTIALLY_FILLED assert order.filled_qty == Quantity.from_str("6000.0") # No slippage assert order.avg_px == Decimal("15.93333333333333333333333333") assert self.exchange.get_account().balance_total(USD) == Money( 999999.88, USD) def test_post_only_insert(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) # Market is 10 @ 15 snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) # Act order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.SELL, quantity=Quantity.from_int(2000), price=Price.from_str("14"), post_only=True, ) self.strategy.submit_order(order) self.exchange.process(0) # Assert assert order.status == OrderStatus.ACCEPTED # TODO - Need to discuss how we are going to support passive quotes trading now @pytest.mark.skip def test_passive_partial_fill(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) # Market is 10 @ 15 snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.SELL, quantity=Quantity.from_int(1000), price=Price.from_str("14"), post_only=False, ) self.strategy.submit_order(order) # Act tick = TestDataStubs.quote_tick_3decimal( instrument_id=USDJPY_SIM.id, bid=Price.from_str("15"), bid_volume=Quantity.from_int(1000), ask=Price.from_str("16"), ask_volume=Quantity.from_int(1000), ) # New tick will be in cross with our order self.exchange.process_tick(tick) # Assert assert order.status == OrderStatus.PARTIALLY_FILLED assert order.filled_qty == Quantity.from_str("1000.0") assert order.avg_px == Decimal("15.0") # TODO - Need to discuss how we are going to support passive quotes trading now @pytest.mark.skip def test_passive_fill_on_trade_tick(self): # Arrange: Prepare market # Market is 10 @ 15 snapshot = TestDataStubs.order_book_snapshot( instrument_id=USDJPY_SIM.id, bid_volume=1000, ask_volume=1000) self.data_engine.process(snapshot) self.exchange.process_order_book(snapshot) order = self.strategy.order_factory.limit( instrument_id=USDJPY_SIM.id, order_side=OrderSide.SELL, quantity=Quantity.from_int(2000), price=Price.from_str("14"), post_only=False, ) self.strategy.submit_order(order) # Act tick1 = TradeTick( instrument_id=USDJPY_SIM.id, price=Price.from_str("14.0"), size=Quantity.from_int(1000), aggressor_side=AggressorSide.SELL, trade_id=TradeId("123456789"), ts_event=0, ts_init=0, ) self.exchange.process_tick(tick1) # Assert assert order.status == OrderStatus.PARTIALLY_FILLED assert order.filled_qty == Quantity.from_int(1000.0) # No slippage assert order.avg_px == Decimal("14.0")