def test_submit_order_command_to_from_dict_and_str_repr(self): # Arrange uuid = self.uuid_factory.generate() order = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) command = SubmitOrder( trader_id=TraderId("TRADER-001"), strategy_id=StrategyId("S-001"), position_id=PositionId("P-001"), order=order, command_id=uuid, ts_init=self.clock.timestamp_ns(), ) # Act, Assert assert SubmitOrder.from_dict(SubmitOrder.to_dict(command)) == command assert ( str(command) == "SubmitOrder(instrument_id=AUD/USD.SIM, client_order_id=O-19700101-000000-000-001-1, position_id=P-001, order=BUY 100_000 AUD/USD.SIM MARKET GTC)" # noqa ) assert ( repr(command) == f"SubmitOrder(trader_id=TRADER-001, strategy_id=S-001, instrument_id=AUD/USD.SIM, client_order_id=O-19700101-000000-000-001-1, position_id=P-001, order=BUY 100_000 AUD/USD.SIM MARKET GTC, command_id={uuid}, ts_init=0)" # noqa )
def setup(self): # Fixture Setup self.venue = Venue("SIM") self.trader_id = TestStubs.trader_id() self.account_id = TestStubs.account_id() self.order_factory = OrderFactory( trader_id=self.trader_id, strategy_id=StrategyId("S-001"), clock=TestClock(), ) self.order = self.order_factory.market( AUDUSD, OrderSide.BUY, Quantity.from_int(100000), ) self.command = SubmitOrder( self.trader_id, StrategyId("SCALPER-001"), PositionId("P-123456"), self.order, UUID4(), 0, ) self.serializer = MsgPackSerializer()
def test_submit_order_when_invalid_quantity_precision_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_str("1.111111111111111111"), # <- invalid quantity Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine
def test_submit_order_when_instrument_not_in_cache_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( GBPUSD_SIM.id, # <-- not in the cache OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine
def test_submit_order_when_risk_bypassed_sends_to_execution_engine(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=None, order=order, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 1 # <-- initial account event assert self.exec_client.calls == ["_start", "submit_order"]
def test_submit_order_with_default_settings_then_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 1 assert self.exec_client.calls == ["_start", "submit_order"]
def test_serialize_and_deserialize_submit_order_commands(self): # Arrange order = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity(100000, precision=0), ) command = SubmitOrder( self.trader_id, StrategyId("SCALPER-001"), PositionId("P-123456"), order, UUID4(), 0, ) # Act serialized = self.serializer.serialize(command) deserialized = self.serializer.deserialize(serialized) # Assert assert deserialized == command assert deserialized.order == order print(command) print(len(serialized)) print(serialized) print(b64encode(serialized))
def test_update_order_when_already_completed_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00010"), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) self.exec_engine.process( TestStubs.event_order_filled(order, AUDUSD_SIM)) modify = ModifyOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), order.quantity, Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == ["_start", "submit_order"] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 1
def submit_order_command(time_in_force=TimeInForce.GTC, order=None): return SubmitOrder( trader_id=TestStubs.trader_id(), strategy_id=TestStubs.strategy_id(), position_id=BetfairTestStubs.position_id(), order=order or BetfairTestStubs.limit_order(time_in_force=time_in_force), command_id=BetfairTestStubs.uuid(), ts_init=BetfairTestStubs.clock().timestamp_ns(), )
def test_cancel_order_when_already_pending_cancel_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) cancel = CancelOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) self.risk_engine.execute(cancel) self.exec_engine.process(TestStubs.event_order_pending_cancel(order)) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == [ "_start", "submit_order", "cancel_order" ] assert self.risk_engine.command_count == 3 assert self.exec_engine.command_count == 2
async def test_reconcile_state_when_order_completed_returns_true_with_warning2( self): # Arrange self.exec_engine.start() self.risk_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order) await asyncio.sleep(0) # Process queue self.exec_engine.process(TestStubs.event_order_submitted(order)) await asyncio.sleep(0) # Process queue self.exec_engine.process(TestStubs.event_order_accepted(order)) await asyncio.sleep(0) # Process queue self.exec_engine.process( TestStubs.event_order_filled(order, AUDUSD_SIM)) await asyncio.sleep(0) # Process queue report = OrderStatusReport( client_order_id=order.client_order_id, venue_order_id=VenueOrderId("1"), # <-- from stub event order_status=OrderStatus.FILLED, filled_qty=Quantity.from_int(100000), ts_init=0, ) # Act result = await self.client.reconcile_state(report, order) # Assert assert result
def test_modify_order_with_default_settings_then_sends_to_client(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.stop_market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00010"), ) submit = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) modify = ModifyOrder( self.trader_id, strategy.id, order.instrument_id, order.client_order_id, VenueOrderId("1"), order.quantity, Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == [ "_start", "submit_order", "modify_order" ] assert self.risk_engine.command_count == 2 assert self.exec_engine.command_count == 2
async def test_message_qsize_at_max_blocks_on_put_event(self): # Arrange # Deregister test fixture ExecutionEngine from msgbus) self.msgbus.deregister(endpoint="ExecEngine.execute", handler=self.exec_engine.execute) self.msgbus.deregister(endpoint="ExecEngine.process", handler=self.exec_engine.process) self.exec_engine = LiveExecutionEngine( loop=self.loop, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=LiveExecEngineConfig(qsize=1), ) strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) event = TestStubs.event_order_submitted(order) # Act self.exec_engine.execute(submit_order) self.exec_engine.process(event) # Add over max size await asyncio.sleep(0.1) # Assert assert self.exec_engine.qsize() == 1 assert self.exec_engine.command_count == 0
async def test_reconcile_state_when_expired_reconciles(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.exec_engine.execute(submit_order) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) report = OrderStatusReport( client_order_id=order.client_order_id, venue_order_id=VenueOrderId("1"), # <-- from stub event order_status=OrderStatus.EXPIRED, filled_qty=Quantity.zero(), ts_init=0, ) self.client.add_order_status_report(report) await asyncio.sleep(0.1) # Allow processing time # Act result = await self.exec_engine.reconcile_state(timeout_secs=10) self.exec_engine.stop() # Assert assert result
async def test_message_qsize_at_max_blocks_on_put_command(self): # Arrange self.msgbus.deregister("RiskEngine.execute", self.risk_engine.execute) self.risk_engine = LiveRiskEngine( loop=self.loop, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, config=LiveRiskEngineConfig(qsize=1), ) strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) self.risk_engine.execute(submit_order) await asyncio.sleep(0.1) # Assert assert self.risk_engine.qsize() == 1 assert self.risk_engine.command_count == 0
def test_submit_order_when_market_order_and_over_max_notional_then_denies( self): # Arrange self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) # Initialize market quote = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) self.cache.add_quote_tick(quote) self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(10000000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) # Assert assert self.exec_engine.command_count == 0 # <-- command never reaches engine
async def test_execute_command_places_command_on_queue(self): # Arrange self.risk_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order) await asyncio.sleep(0.1) # Assert assert self.risk_engine.qsize() == 0 assert self.risk_engine.command_count == 1 # Tear Down self.risk_engine.stop() await self.risk_engine.get_run_queue_task()
def test_execute_command(self): order = self.strategy.order_factory.market( BTCUSDT_BINANCE.id, OrderSide.BUY, Quantity.from_str("1.00000000"), ) command = SubmitOrder( self.trader_id, self.strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) def execute_command(): self.exec_engine.execute(command) self.benchmark.pedantic(execute_command, iterations=100, rounds=100, warmup_rounds=5)
def submit_order_command(): return SubmitOrder( trader_id=TestStubs.trader_id(), strategy_id=TestStubs.strategy_id(), position_id=BetfairTestStubs.position_id(), order=LimitOrder( trader_id=TestStubs.trader_id(), strategy_id=TestStubs.strategy_id(), instrument_id=BetfairTestStubs.instrument_id(), client_order_id=ClientOrderId( f"O-20210410-022422-001-001-{TestStubs.strategy_id().value}" ), order_side=OrderSide.BUY, quantity=Quantity.from_int(10), price=Price(0.33, precision=5), time_in_force=TimeInForce.GTC, expire_time=None, init_id=BetfairTestStubs.uuid(), ts_init=BetfairTestStubs.clock().timestamp_ns(), ), command_id=BetfairTestStubs.uuid(), ts_init=BetfairTestStubs.clock().timestamp_ns(), )
def test_submit_order_when_trading_halted_then_denies_order(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Halt trading self.risk_engine.set_trading_state(TradingState.HALTED) # Act self.risk_engine.execute(submit_order) # Assert assert self.risk_engine.command_count == 1 # <-- command never reaches engine
async def test_place_orders_market_on_close(self): instrument = BetfairTestStubs.betting_instrument() market_on_close_order = BetfairTestStubs.market_order( side=OrderSide.BUY, time_in_force=TimeInForce.OC, ) submit_order_command = SubmitOrder( trader_id=TestStubs.trader_id(), strategy_id=TestStubs.strategy_id(), position_id=PositionId("1"), order=market_on_close_order, command_id=UUID4("be7dffa0-46f2-fce5-d820-c7634d022ca1"), ts_init=0, ) place_orders = order_submit_to_betfair(command=submit_order_command, instrument=instrument) with mock_client_request(response=BetfairResponses. betting_place_order_success()) as req: resp = await self.client.place_orders(**place_orders) assert resp expected = BetfairRequests.betting_place_order_bsp() result = req.call_args.kwargs["json"] assert result == expected
async def test_reconcile_state_when_filled_reconciles(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order = strategy.order_factory.limit( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), Price.from_str("1.00000"), ) submit_order = SubmitOrder( self.trader_id, strategy.id, None, order, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.exec_engine.execute(submit_order) self.exec_engine.process(TestStubs.event_order_submitted(order)) self.exec_engine.process(TestStubs.event_order_accepted(order)) report = OrderStatusReport( client_order_id=order.client_order_id, venue_order_id=VenueOrderId("1"), # <-- from stub event order_status=OrderStatus.FILLED, filled_qty=Quantity.from_int(100000), ts_init=0, ) trade1 = ExecutionReport( execution_id=ExecutionId("1"), client_order_id=order.client_order_id, venue_order_id=VenueOrderId("1"), venue_position_id=None, last_qty=Quantity.from_int(50000), last_px=Price.from_str("1.00000"), commission=Money(5.00, USD), liquidity_side=LiquiditySide.MAKER, ts_event=0, ts_init=0, ) trade2 = ExecutionReport( execution_id=ExecutionId("2"), client_order_id=order.client_order_id, venue_order_id=VenueOrderId("1"), venue_position_id=None, last_qty=Quantity.from_int(50000), last_px=Price.from_str("1.00000"), commission=Money(2.00, USD), liquidity_side=LiquiditySide.MAKER, ts_event=0, ts_init=0, ) self.client.add_order_status_report(report) self.client.add_trades_list(VenueOrderId("1"), [trade1, trade2]) await asyncio.sleep(0.1) # Allow processing time # Act result = await self.exec_engine.reconcile_state(timeout_secs=10) self.exec_engine.stop() # Assert assert result
def test_submit_order_when_position_already_closed_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) order2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) order3 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) submit_order1 = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=None, order=order1, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order1) self.exec_engine.process(TestStubs.event_order_submitted(order1)) self.exec_engine.process(TestStubs.event_order_accepted(order1)) self.exec_engine.process( TestStubs.event_order_filled(order1, AUDUSD_SIM)) submit_order2 = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=PositionId("P-19700101-000000-000-000-1"), order=order2, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order2) self.exec_engine.process(TestStubs.event_order_submitted(order2)) self.exec_engine.process(TestStubs.event_order_accepted(order2)) self.exec_engine.process( TestStubs.event_order_filled(order2, AUDUSD_SIM)) submit_order3 = SubmitOrder( trader_id=self.trader_id, strategy_id=strategy.id, position_id=PositionId("P-19700101-000000-000-000-1"), order=order3, command_id=self.uuid_factory.generate(), ts_init=self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(submit_order3) # Assert assert self.exec_engine.command_count == 2 assert self.exec_client.calls == [ "_start", "submit_order", "submit_order" ]
def test_submit_order_when_reducing_and_sell_order_adds_then_denies(self): # Arrange self.risk_engine.set_max_notional_per_order(AUDUSD_SIM.id, 1_000_000) # Initialize market quote = TestStubs.quote_tick_5decimal(AUDUSD_SIM.id) self.cache.add_quote_tick(quote) self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) order1 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) submit_order1 = SubmitOrder( self.trader_id, strategy.id, None, order1, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.risk_engine.execute(submit_order1) self.risk_engine.set_trading_state( TradingState.REDUCING) # <-- allow reducing orders only order2 = strategy.order_factory.market( AUDUSD_SIM.id, OrderSide.SELL, Quantity.from_int(100000), ) submit_order2 = SubmitOrder( self.trader_id, strategy.id, None, order2, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) self.exec_engine.process(TestStubs.event_order_submitted(order1)) self.exec_engine.process(TestStubs.event_order_accepted(order1)) self.exec_engine.process( TestStubs.event_order_filled(order1, AUDUSD_SIM)) # Act self.risk_engine.execute(submit_order2) # Assert assert self.portfolio.is_net_short(AUDUSD_SIM.id) assert self.exec_engine.command_count == 1 # <-- command never reaches engine