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