def test_update_strategy(self): # Arrange strategy = MockStrategy(TestStubs.bartype_btcusdt_binance_100tick_last()) strategy.register_trader(self.trader_id, self.clock, self.logger) # Act self.database.update_strategy(strategy) result = self.database.load_strategy(strategy.id) # Assert self.assertEqual({"UserState": b"1"}, result)
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_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_update_strategy(self): # Arrange strategy = MockStrategy(TestStubs.bartype_btcusdt_binance_100tick_last()) strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) # Act self.database.update_strategy(strategy) result = self.database.load_strategy(strategy.id) # Assert assert result == {"UserState": b"1"}
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 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()
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)
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 = 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, 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, venue_type=VenueType.EXCHANGE, 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, account_id=AccountId("FTX", "001"), 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=TestStubs.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()
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 = 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, 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, venue_type=VenueType.EXCHANGE, 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, account_id=AccountId("FTX", "001"), 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=TestStubs.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_working_orders()) == 2 assert bracket.orders[1] in self.exchange.get_working_orders() assert bracket.orders[2] in self.exchange.get_working_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_working_orders()) == 2 assert bracket.orders[1] in self.exchange.get_working_orders() assert bracket.orders[2] in self.exchange.get_working_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_working_orders()) == 0 assert bracket.orders[1] not in self.exchange.get_working_orders() assert bracket.orders[2] not in self.exchange.get_working_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_working_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_working_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_working_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), price=sl.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_working_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_working_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_working_orders()) == 2 assert len(self.exchange.cache.positions_open()) == 1
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=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, 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, account_id=self.account_id, 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=TestStubs.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()
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 = 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=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, 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, account_id=self.account_id, 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=TestStubs.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 = TestStubs.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 = TestStubs.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_passive_post_only_insert(self): # Arrange: Prepare market self.cache.add_instrument(USDJPY_SIM) # Market is 10 @ 15 snapshot = TestStubs.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 = TestStubs.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 = TestStubs.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 = TestStubs.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="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")