def _generate_external_order_status(self, instrument: Instrument, data: Dict[str, Any]) -> None: client_id_str = data.get("clientId") price = data.get("price") created_at = int( pd.to_datetime(data["createdAt"], utc=True).to_datetime64()) report = OrderStatusReport( account_id=self.account_id, instrument_id=InstrumentId(Symbol(data["market"]), FTX_VENUE), client_order_id=ClientOrderId(client_id_str) if client_id_str is not None else None, venue_order_id=VenueOrderId(str(data["id"])), order_side=OrderSide.BUY if data["side"] == "buy" else OrderSide.SELL, order_type=parse_order_type(data=data, price_str="price"), time_in_force=TimeInForce.IOC if data["ioc"] else TimeInForce.GTC, order_status=OrderStatus.ACCEPTED, price=instrument.make_price(price) if price is not None else None, quantity=instrument.make_qty(data["size"]), filled_qty=instrument.make_qty(0), avg_px=None, post_only=data["postOnly"], reduce_only=data["reduceOnly"], report_id=self._uuid_factory.generate(), ts_accepted=created_at, ts_last=created_at, ts_init=self._clock.timestamp_ns(), ) self._send_order_status_report(report)
async def _submit_stop_limit_order( self, order: StopLimitOrder, position: Optional[Position], ) -> None: order_type = "stop" if position is not None: if order.is_buy and order.trigger_price < position.avg_px_open: order_type = "take_profit" elif order.is_sell and order.trigger_price > position.avg_px_open: order_type = "take_profit" response = await self._http_client.place_trigger_order( market=order.instrument_id.symbol.value, side=OrderSideParser.to_str_py(order.side).lower(), size=str(order.quantity), order_type=order_type, client_id=order.client_order_id.value, price=str(order.price), trigger_price=str(order.trigger_price), reduce_only=order.is_reduce_only, ) self.generate_order_accepted( strategy_id=order.strategy_id, instrument_id=order.instrument_id, client_order_id=order.client_order_id, venue_order_id=VenueOrderId(str(response["id"])), ts_event=self._clock.timestamp_ns(), )
def parse_trade_report_http( account_id: AccountId, instrument_id: InstrumentId, data: BinanceFuturesAccountTrade, report_id: UUID4, ts_init: int, ) -> TradeReport: return TradeReport( account_id=account_id, instrument_id=instrument_id, venue_order_id=VenueOrderId(str(data.orderId)), venue_position_id=PositionId( f"{instrument_id}-{data.positionSide.value}"), trade_id=TradeId(str(data.id)), order_side=OrderSide[data.side.value], last_qty=Quantity.from_str(data.qty), last_px=Price.from_str(data.price), commission=Money(data.commission, Currency.from_str(data.commissionAsset)), liquidity_side=LiquiditySide.MAKER if data.maker else LiquiditySide.TAKER, report_id=report_id, ts_event=millis_to_nanos(data.time), ts_init=ts_init, )
def test_serialize_and_deserialize_order_filled_events(self): # Arrange event = OrderFilled( self.account_id, ClientOrderId("O-123456"), VenueOrderId("1"), ExecutionId("E123456"), PositionId("T123456"), StrategyId("S-001"), AUDUSD_SIM.id, OrderSide.SELL, Quantity(100000, precision=0), Price(1.00000, precision=5), AUDUSD_SIM.quote_currency, Money(0, USD), LiquiditySide.TAKER, 0, uuid4(), 0, ) # Act serialized = self.serializer.serialize(event) deserialized = self.serializer.deserialize(serialized) # Assert assert deserialized == event
def event_order_filled( order, instrument, strategy_id=None, account_id=None, venue_order_id=None, execution_id=None, position_id=None, last_qty=None, last_px=None, liquidity_side=LiquiditySide.TAKER, ts_filled_ns=0, account=None, ) -> OrderFilled: if strategy_id is None: strategy_id = order.strategy_id if account_id is None: account_id = order.account_id if account_id is None: account_id = TestStubs.account_id() if venue_order_id is None: venue_order_id = VenueOrderId("1") if execution_id is None: execution_id = ExecutionId(order.client_order_id.value.replace("O", "E")) if position_id is None: position_id = order.position_id if last_px is None: last_px = Price.from_str(f"{1:.{instrument.price_precision}f}") if last_qty is None: last_qty = order.quantity if account is None: account = TestStubs.cash_account() commission = account.calculate_commission( instrument=instrument, last_qty=order.quantity, last_px=last_px, liquidity_side=liquidity_side, ) return OrderFilled( trader_id=TestStubs.trader_id(), strategy_id=strategy_id, account_id=account_id, instrument_id=instrument.id, client_order_id=order.client_order_id, venue_order_id=venue_order_id, execution_id=execution_id, position_id=position_id, order_side=order.side, order_type=order.type, last_qty=last_qty, last_px=last_px or order.price, currency=instrument.quote_currency, commission=commission, liquidity_side=liquidity_side, ts_event=ts_filled_ns, event_id=UUID4(), ts_init=0, )
def test_add_order_state_report(self): # Arrange report = ExecutionMassStatus( client_id=ClientId("IB"), account_id=TestStubs.account_id(), timestamp_ns=0, ) venue_order_id = VenueOrderId("1") order_report = OrderStatusReport( client_order_id=ClientOrderId("O-123456"), venue_order_id=venue_order_id, order_state=OrderState.REJECTED, filled_qty=Quantity.zero(), timestamp_ns=0, ) # Act report.add_order_report(order_report) # Assert assert report.order_reports()[venue_order_id] == order_report assert ( repr(report) == "ExecutionMassStatus(client_id=IB, account_id=SIM-000, ts_recv_ns=0, order_reports={VenueOrderId('1'): OrderStatusReport(client_order_id=O-123456, venue_order_id=1, order_state=REJECTED, filled_qty=0, ts_recv_ns=0)}, exec_reports={}, position_reports={})" # noqa ) assert ( repr(order_report) == "OrderStatusReport(client_order_id=O-123456, venue_order_id=1, order_state=REJECTED, filled_qty=0, ts_recv_ns=0)" # noqa )
def test_serialize_and_deserialize_order_partially_filled_events(self): # Arrange event = OrderFilled( self.account_id, ClientOrderId("O-123456"), VenueOrderId("1"), ExecutionId("E123456"), PositionId("T123456"), StrategyId("S", "001"), AUDUSD_SIM.id, OrderSide.SELL, Quantity(50000), Price("1.00000"), Quantity(50000), Quantity(50000), AUDUSD_SIM.quote_currency, AUDUSD_SIM.is_inverse, Money(0, USD), LiquiditySide.MAKER, 0, uuid4(), 0, ) # Act serialized = self.serializer.serialize(event) deserialized = self.serializer.deserialize(serialized) # Assert self.assertEqual(deserialized, event)
def test_order_canceled_event_to_from_dict_and_str_repr(self): # Arrange uuid = UUID4() event = OrderCanceled( trader_id=TraderId("TRADER-001"), strategy_id=StrategyId("SCALPER-001"), account_id=AccountId("SIM", "000"), instrument_id=InstrumentId(Symbol("BTC/USDT"), Venue("BINANCE")), client_order_id=ClientOrderId("O-2020872378423"), venue_order_id=VenueOrderId("123456"), ts_event=0, event_id=uuid, ts_init=0, ) # Act, Assert assert OrderCanceled.from_dict(OrderCanceled.to_dict(event)) == event assert ( str(event) == "OrderCanceled(account_id=SIM-000, instrument_id=BTC/USDT.BINANCE, client_order_id=O-2020872378423, venue_order_id=123456, ts_event=0)" # noqa ) assert ( repr(event) == f"OrderCanceled(trader_id=TRADER-001, strategy_id=SCALPER-001, account_id=SIM-000, instrument_id=BTC/USDT.BINANCE, client_order_id=O-2020872378423, venue_order_id=123456, event_id={uuid}, ts_event=0, ts_init=0)" # noqa )
async def generate_trades_list( self, venue_order_id: VenueOrderId, symbol: Symbol, since: datetime = None) -> List[ExecutionReport]: filled = self.client().betting.list_cleared_orders( bet_ids=[venue_order_id], ) if not filled["clearedOrders"]: self._log.warn(f"Found no existing order for {venue_order_id}") return [] fill = filled["clearedOrders"][0] timestamp_ns = millis_to_nanos( pd.Timestamp(fill["lastMatchedDate"]).timestamp()) return [ ExecutionReport( client_order_id=self. venue_order_id_to_client_order_id[venue_order_id], venue_order_id=VenueOrderId(fill["betId"]), execution_id=ExecutionId(fill["lastMatchedDate"]), last_qty=Quantity.from_str(str( fill["sizeSettled"])), # TODO: Possibly incorrect precision last_px=Price.from_str(str( fill["priceMatched"])), # TODO: Possibly incorrect precision commission=None, # Can be None liquidity_side=LiquiditySide.NONE, ts_filled_ns=timestamp_ns, timestamp_ns=timestamp_ns, ) ]
def test_modify_order_command_to_from_dict_and_str_repr(self): # Arrange uuid = self.uuid_factory.generate() command = ModifyOrder( trader_id=TraderId("TRADER-001"), strategy_id=StrategyId("S-001"), instrument_id=AUDUSD_SIM.id, client_order_id=ClientOrderId("O-123456"), venue_order_id=VenueOrderId("001"), price=Price.from_str("1.00000"), trigger=Price.from_str("1.00010"), quantity=Quantity.from_int(100000), command_id=uuid, ts_init=self.clock.timestamp_ns(), ) # Act, Assert assert ModifyOrder.from_dict(ModifyOrder.to_dict(command)) == command assert ( str(command) == "ModifyOrder(instrument_id=AUD/USD.SIM, client_order_id=O-123456, venue_order_id=001, quantity=100_000, price=1.00000, trigger=1.00010)" # noqa ) assert ( repr(command) == f"ModifyOrder(trader_id=TRADER-001, strategy_id=S-001, instrument_id=AUD/USD.SIM, client_order_id=O-123456, venue_order_id=001, quantity=100_000, price=1.00000, trigger=1.00010, command_id={uuid}, ts_init=0)" # noqa )
def test_cancel_order_when_order_does_not_exist_then_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) cancel = CancelOrder( self.trader_id, strategy.id, AUDUSD_SIM.id, ClientOrderId("1"), VenueOrderId("1"), self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(cancel) # Assert assert self.exec_client.calls == ["_start"] assert self.risk_engine.command_count == 1 assert self.exec_engine.command_count == 0
async def test_on_order_modify(self): # Arrange self.instrument_setup() nautilus_order = TestExecStubs.limit_order() self.exec_client._client_order_id_to_strategy_id[ nautilus_order.client_order_id] = TestIdStubs.strategy_id() self.exec_client._venue_order_id_to_client_order_id[ 1] = nautilus_order.client_order_id order = IBExecTestStubs.ib_order(permId=1) order.permId = 1 self.cache.add_order(nautilus_order, None) trade = IBExecTestStubs.trade_submitted(order=order) # Act with patch.object(self.exec_client, "generate_order_updated") as mock: self.exec_client._on_order_modify(trade) # Assert name, args, kwargs = mock.mock_calls[0] expected = { "client_order_id": nautilus_order.client_order_id, "instrument_id": self.instrument.id, "price": Price.from_str("0.01"), "quantity": Quantity.from_str("1"), "strategy_id": TestIdStubs.strategy_id(), "trigger_price": None, "ts_event": 1646449588378175000, "venue_order_id": VenueOrderId("189868420"), "venue_order_id_modified": False, } assert kwargs == expected
def test_update_order_when_no_order_found_denies(self): # Arrange self.exec_engine.start() strategy = TradingStrategy() strategy.register( trader_id=self.trader_id, portfolio=self.portfolio, msgbus=self.msgbus, cache=self.cache, clock=self.clock, logger=self.logger, ) modify = ModifyOrder( self.trader_id, strategy.id, AUDUSD_SIM.id, ClientOrderId("invalid"), VenueOrderId("1"), Quantity.from_int(100000), Price.from_str("1.00010"), None, self.uuid_factory.generate(), self.clock.timestamp_ns(), ) # Act self.risk_engine.execute(modify) # Assert assert self.exec_client.calls == ["_start"] assert self.risk_engine.command_count == 1 assert self.exec_engine.command_count == 0
def parse_order_status_http( account_id: AccountId, instrument: Instrument, data: Dict[str, Any], report_id: UUID4, ts_init: int, ) -> OrderStatusReport: client_id_str = data.get("clientId") price = data.get("price") avg_px = data["avgFillPrice"] created_at = int( pd.to_datetime(data["createdAt"], utc=True).to_datetime64()) return OrderStatusReport( account_id=account_id, instrument_id=InstrumentId(Symbol(data["market"]), FTX_VENUE), client_order_id=ClientOrderId(client_id_str) if client_id_str is not None else None, venue_order_id=VenueOrderId(str(data["id"])), order_side=OrderSide.BUY if data["side"] == "buy" else OrderSide.SELL, order_type=parse_order_type(data=data, price_str="price"), time_in_force=TimeInForce.IOC if data["ioc"] else TimeInForce.GTC, order_status=parse_order_status(data), price=instrument.make_price(price) if price is not None else None, quantity=instrument.make_qty(data["size"]), filled_qty=instrument.make_qty(data["filledSize"]), avg_px=Decimal(str(avg_px)) if avg_px is not None else None, post_only=data["postOnly"], reduce_only=data["reduceOnly"], report_id=report_id, ts_accepted=created_at, ts_last=created_at, ts_init=ts_init, )
async def test_update_order(mocker, execution_client, exec_engine, risk_engine): exec_engine.register_risk_engine(risk_engine) # Add sample order to the cache order = BetfairTestStubs.make_order(exec_engine) order.apply(BetfairTestStubs.event_order_submitted(order=order)) order.apply( BetfairTestStubs.event_order_accepted( order=order, venue_order_id=VenueOrderId("229435133092") ) ) exec_engine.cache.add_order(order, PositionId("1")) mock_replace_orders = mocker.patch( "betfairlightweight.endpoints.betting.Betting.replace_orders", return_value=BetfairTestStubs.place_orders_success(), ) # Actual test update = BetfairTestStubs.update_order_command( instrument_id=order.instrument_id, client_order_id=order.client_order_id ) execution_client.update_order(update) await asyncio.sleep(0.1) expected = { "customer_ref": update.id.value.replace("-", ""), "instructions": [{"betId": "229435133092", "newPrice": 1.35}], "market_id": "1.179082386", } mock_replace_orders.assert_called_with(**expected)
async def _submit_order(self, command: SubmitOrder) -> None: self._log.debug(f"Received submit_order {command}") self.generate_order_submitted( instrument_id=command.instrument_id, strategy_id=command.strategy_id, client_order_id=command.order.client_order_id, ts_event=self._clock.timestamp_ns(), ) self._log.debug("Generated _generate_order_submitted") instrument = self._cache.instrument(command.instrument_id) PyCondition.not_none(instrument, "instrument") client_order_id = command.order.client_order_id place_order = order_submit_to_betfair(command=command, instrument=instrument) try: result = await self._client.place_orders(**place_order) except Exception as exc: if isinstance(exc, BetfairAPIError): await self.on_api_exception(exc=exc) self._log.warning(f"Submit failed: {exc}") self.generate_order_rejected( strategy_id=command.strategy_id, instrument_id=command.instrument_id, client_order_id=client_order_id, reason="client error", # type: ignore ts_event=self._clock.timestamp_ns(), ) return self._log.debug(f"result={result}") for report in result["instructionReports"]: if result["status"] == "FAILURE": reason = f"{result['errorCode']}: {report['errorCode']}" self._log.warning(f"Submit failed - {reason}") self.generate_order_rejected( strategy_id=command.strategy_id, instrument_id=command.instrument_id, client_order_id=client_order_id, reason=reason, # type: ignore ts_event=self._clock.timestamp_ns(), ) self._log.debug("Generated _generate_order_rejected") return else: venue_order_id = VenueOrderId(report["betId"]) self._log.debug( f"Matching venue_order_id: {venue_order_id} to client_order_id: {client_order_id}" ) self.venue_order_id_to_client_order_id[venue_order_id] = client_order_id # type: ignore self.generate_order_accepted( strategy_id=command.strategy_id, instrument_id=command.instrument_id, client_order_id=client_order_id, venue_order_id=venue_order_id, # type: ignore ts_event=self._clock.timestamp_ns(), ) self._log.debug("Generated _generate_order_accepted")
def _prefill_venue_order_id_to_client_order_id(self, update): order_ids = [ update["id"] for market in update.get("oc", []) for order in market.get("orc", []) for update in order.get("uo", []) ] return {VenueOrderId(oid): ClientOrderId(str(i + 1)) for i, oid in enumerate(order_ids)}
def test_add_order_status_reports(self): # Arrange report_id1 = UUID4() mass_status = ExecutionMassStatus( client_id=ClientId("IB"), account_id=AccountId("IB", "U123456789"), venue=Venue("IDEALPRO"), report_id=report_id1, ts_init=0, ) venue_order_id = VenueOrderId("2") report_id2 = UUID4() report = OrderStatusReport( account_id=AccountId("IB", "U123456789"), instrument_id=AUDUSD_IDEALPRO, client_order_id=ClientOrderId("O-123456"), order_list_id=OrderListId("1"), venue_order_id=venue_order_id, order_side=OrderSide.SELL, order_type=OrderType.STOP_LIMIT, contingency_type=ContingencyType.OCO, time_in_force=TimeInForce.DAY, expire_time=None, order_status=OrderStatus.REJECTED, price=Price.from_str("0.90090"), trigger_price=Price.from_str("0.90100"), trigger_type=TriggerType.DEFAULT, limit_offset=None, trailing_offset=Decimal("0.00010"), offset_type=TrailingOffsetType.PRICE, quantity=Quantity.from_int(1_000_000), filled_qty=Quantity.from_int(0), display_qty=None, avg_px=None, post_only=True, reduce_only=False, cancel_reason="SOME_REASON", report_id=report_id2, ts_accepted=1_000_000, ts_triggered=0, ts_last=2_000_000, ts_init=3_000_000, ) # Act mass_status.add_order_reports([report]) # Assert assert mass_status.order_reports()[venue_order_id] == report assert ( repr(mass_status) == f"ExecutionMassStatus(client_id=IB, account_id=IB-U123456789, venue=IDEALPRO, order_reports={{VenueOrderId('2'): OrderStatusReport(account_id=IB-U123456789, instrument_id=AUD/USD.IDEALPRO, client_order_id=O-123456, order_list_id=1, venue_order_id=2, order_side=SELL, order_type=STOP_LIMIT, contingency_type=OCO, time_in_force=DAY, expire_time=None, order_status=REJECTED, price=0.90090, trigger_price=0.90100, trigger_type=DEFAULT, limit_offset=None, trailing_offset=0.00010, offset_type=PRICE, quantity=1_000_000, filled_qty=0, leaves_qty=1_000_000, display_qty=None, avg_px=None, post_only=True, reduce_only=False, cancel_reason=SOME_REASON, report_id={report_id2}, ts_accepted=1000000, ts_triggered=0, ts_last=2000000, ts_init=3000000)}}, trade_reports={{}}, position_reports={{}}, report_id={report_id1}, ts_init=0)" # noqa ) assert ( repr(report) == f"OrderStatusReport(account_id=IB-U123456789, instrument_id=AUD/USD.IDEALPRO, client_order_id=O-123456, order_list_id=1, venue_order_id=2, order_side=SELL, order_type=STOP_LIMIT, contingency_type=OCO, time_in_force=DAY, expire_time=None, order_status=REJECTED, price=0.90090, trigger_price=0.90100, trigger_type=DEFAULT, limit_offset=None, trailing_offset=0.00010, offset_type=PRICE, quantity=1_000_000, filled_qty=0, leaves_qty=1_000_000, display_qty=None, avg_px=None, post_only=True, reduce_only=False, cancel_reason=SOME_REASON, report_id={report_id2}, ts_accepted=1000000, ts_triggered=0, ts_last=2000000, ts_init=3000000)" # noqa )
def test_position_filled_with_buy_order_returns_expected_attributes(self): # Arrange order = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill = TestStubs.event_order_filled( order, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("1.00001"), ) last = Price.from_str("1.00050") # Act position = Position(instrument=AUDUSD_SIM, fill=fill) # Assert assert position.symbol == AUDUSD_SIM.id.symbol assert position.venue == AUDUSD_SIM.id.venue assert not position.is_opposite_side(fill.order_side) assert not position != position # Equality operator test assert position.from_order == ClientOrderId( "O-19700101-000000-000-001-1") assert position.quantity == Quantity.from_int(100000) assert position.peak_qty == Quantity.from_int(100000) assert position.entry == OrderSide.BUY assert position.side == PositionSide.LONG assert position.ts_opened == 0 assert position.duration_ns == 0 assert position.avg_px_open == Decimal("1.00001") assert position.event_count == 1 assert position.client_order_ids == [order.client_order_id] assert position.venue_order_ids == [VenueOrderId("1")] assert position.execution_ids == [ ExecutionId("E-19700101-000000-000-001-1") ] assert position.last_execution_id == ExecutionId( "E-19700101-000000-000-001-1") assert position.id == PositionId("P-123456") assert len(position.events) == 1 assert position.is_long assert not position.is_short assert position.is_open assert not position.is_closed assert position.realized_points == 0 assert position.realized_return == 0 assert position.realized_pnl == Money(-2.00, USD) assert position.unrealized_pnl(last) == Money(49.00, USD) assert position.total_pnl(last) == Money(47.00, USD) assert position.commissions() == [Money(2.00, USD)] assert repr( position) == "Position(LONG 100_000 AUD/USD.SIM, id=P-123456)"
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 test_position_filled_with_buy_order_returns_expected_attributes(self): # Arrange order = self.order_factory.market( AUDUSD_SIM.id, OrderSide.BUY, Quantity.from_int(100000), ) fill = TestStubs.event_order_filled( order, instrument=AUDUSD_SIM, position_id=PositionId("P-123456"), strategy_id=StrategyId("S-001"), last_px=Price.from_str("1.00001"), ) last = Price.from_str("1.00050") # Act position = Position(instrument=AUDUSD_SIM, fill=fill) # Assert assert position.symbol == AUDUSD_SIM.id.symbol assert position.venue == AUDUSD_SIM.id.venue assert not position.is_opposite_side(fill.order_side) self.assertFalse(position != position) # Equality operator test self.assertEqual(ClientOrderId("O-19700101-000000-000-001-1"), position.from_order) self.assertEqual(Quantity.from_int(100000), position.quantity) self.assertEqual(Quantity.from_int(100000), position.peak_qty) self.assertEqual(OrderSide.BUY, position.entry) self.assertEqual(PositionSide.LONG, position.side) self.assertEqual(0, position.opened_timestamp_ns) self.assertEqual(0, position.open_duration_ns) self.assertEqual(Decimal("1.00001"), position.avg_px_open) self.assertEqual(1, position.event_count) self.assertEqual([order.client_order_id], position.client_order_ids) self.assertEqual([VenueOrderId("1")], position.venue_order_ids) self.assertEqual([ExecutionId("E-19700101-000000-000-001-1")], position.execution_ids) self.assertEqual(ExecutionId("E-19700101-000000-000-001-1"), position.last_execution_id) self.assertEqual(PositionId("P-123456"), position.id) self.assertEqual(1, len(position.events)) self.assertTrue(position.is_long) self.assertFalse(position.is_short) self.assertTrue(position.is_open) self.assertFalse(position.is_closed) self.assertEqual(0, position.realized_points) self.assertEqual(0, position.realized_return) self.assertEqual(Money(-2.00, USD), position.realized_pnl) self.assertEqual(Money(49.00, USD), position.unrealized_pnl(last)) self.assertEqual(Money(47.00, USD), position.total_pnl(last)) self.assertEqual([Money(2.00, USD)], position.commissions()) self.assertEqual("Position(LONG 100_000 AUD/USD.SIM, id=P-123456)", repr(position))
def cancel_order_command(): return CancelOrder( trader_id=BetfairTestStubs.trader_id(), strategy_id=BetfairTestStubs.strategy_id(), instrument_id=BetfairTestStubs.instrument_id(), client_order_id=ClientOrderId("O-20210410-022422-001-001-1"), venue_order_id=VenueOrderId("229597791245"), command_id=BetfairTestStubs.uuid(), timestamp_ns=BetfairTestStubs.clock().timestamp_ns(), )
def cancel_order_command(): return CancelOrder( instrument_id=BetfairTestStubs.instrument_id(), trader_id=BetfairTestStubs.trader_id(), account_id=BetfairTestStubs.account_id(), client_order_id=ClientOrderId("1"), venue_order_id=VenueOrderId("1"), command_id=BetfairTestStubs.uuid(), timestamp_ns=BetfairTestStubs.clock().timestamp_ns(), )
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_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 generate_order_status_report(self, order) -> Optional[OrderStatusReport]: return [ OrderStatusReport( client_order_id=ClientOrderId(), venue_order_id=VenueOrderId(), order_state=OrderState(), filled_qty=Quantity.zero(), timestamp_ns=millis_to_nanos(), ) for order in self.client().betting.list_current_orders()["currentOrders"] ]
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
def event_order_filled( order, instrument, venue_order_id=None, execution_id=None, position_id=None, strategy_id=None, last_qty=None, last_px=None, liquidity_side=LiquiditySide.TAKER, execution_ns=0, ) -> OrderFilled: if venue_order_id is None: venue_order_id = VenueOrderId("1") if execution_id is None: execution_id = ExecutionId( order.client_order_id.value.replace("O", "E")) if position_id is None: position_id = order.position_id if strategy_id is None: strategy_id = order.strategy_id if last_px is None: last_px = Price("1.00000") if last_qty is None: last_qty = order.quantity commission = instrument.calculate_commission( last_qty=order.quantity, last_px=last_px, liquidity_side=liquidity_side, ) return OrderFilled( account_id=TestStubs.account_id(), client_order_id=order.client_order_id, venue_order_id=venue_order_id, execution_id=execution_id, position_id=position_id, strategy_id=strategy_id, instrument_id=order.instrument_id, order_side=order.side, last_qty=last_qty, last_px=order.price if last_px is None else last_px, cum_qty=Quantity(order.filled_qty + last_qty), leaves_qty=Quantity( max(0, order.quantity - order.filled_qty - last_qty)), currency=instrument.quote_currency, is_inverse=instrument.is_inverse, commission=commission, liquidity_side=liquidity_side, execution_ns=execution_ns, event_id=uuid4(), timestamp_ns=0, )
def event_order_accepted(order, venue_order_id=None) -> OrderAccepted: if venue_order_id is None: venue_order_id = VenueOrderId("1") return OrderAccepted( TestStubs.account_id(), order.client_order_id, venue_order_id, 0, uuid4(), 0, )
async def generate_order_status_report(self, order) -> Optional[OrderStatusReport]: return [ OrderStatusReport( client_order_id=ClientOrderId(), venue_order_id=VenueOrderId(), order_status=OrderStatus(), filled_qty=Quantity.zero(), ts_init=int(pd.Timestamp(order["timestamp"]).to_datetime64()), ) for order in self.client().betting.list_current_orders()["currentOrders"] ]