diff --git a/MIGRATION.md b/MIGRATION.md index 7f7cf66..c41e879 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,11 @@ # Migration Guide +## 2.6.0 -> 2.7.0 + +- The `rfq_start_price` argument of `RestApiClient.place_order`, `BlockingTradingClient.create_and_place_order` + and `create_order_object` has been replaced with `rfq: CreateOrderRfqModel` (`x10.models.order`), which carries + `start_price` and the new `max_reprice_steps`. `NewOrderModel.rfq_start_price` has been replaced with `NewOrderModel.rfq`. + ## 2.0.0 -> 2.3.0 - `Config` dataclass has been renamed to `ClientConfig` and moved to `x10.core.client_config`. diff --git a/tests/signing/order_object/test_conditional_order_object.py b/tests/signing/order_object/test_conditional_order_object.py index 28fdc1b..a028a92 100644 --- a/tests/signing/order_object/test_conditional_order_object.py +++ b/tests/signing/order_object/test_conditional_order_object.py @@ -55,7 +55,6 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c "side": "BUY", "qty": "0.00100000", "price": "43445.11680000", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "GTT", @@ -84,6 +83,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c "debuggingAmounts": {"collateralAmount": "-43445117", "feeAmount": "21723", "syntheticAmount": "1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) diff --git a/tests/signing/order_object/test_limit_order_object.py b/tests/signing/order_object/test_limit_order_object.py index f643167..38c554b 100644 --- a/tests/signing/order_object/test_limit_order_object.py +++ b/tests/signing/order_object/test_limit_order_object.py @@ -51,7 +51,6 @@ async def test_create_sell_order_with_default_expiration( "side": "SELL", "qty": "0.00100000", "price": "43445.11680000", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "GTT", @@ -75,6 +74,7 @@ async def test_create_sell_order_with_default_expiration( "debuggingAmounts": {"collateralAmount": "43445116", "feeAmount": "21723", "syntheticAmount": "-1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) @@ -110,7 +110,6 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, "side": "SELL", "qty": "0.00100000", "price": "43445.11680000", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "GTT", @@ -134,6 +133,7 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, "debuggingAmounts": {"collateralAmount": "43445116", "feeAmount": "21723", "syntheticAmount": "-1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) @@ -169,7 +169,6 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c "side": "BUY", "qty": "0.00100000", "price": "43445.11680000", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "GTT", @@ -193,6 +192,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c "debuggingAmounts": {"collateralAmount": "-43445117", "feeAmount": "21723", "syntheticAmount": "1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) @@ -241,7 +241,6 @@ async def test_create_buy_order_with_order_tpsl(mocker: MockerFixture, create_tr "side": "BUY", "qty": "0.00100000", "price": "43445.11680000", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "GTT", @@ -301,6 +300,7 @@ async def test_create_buy_order_with_order_tpsl(mocker: MockerFixture, create_tr "debuggingAmounts": {"collateralAmount": "-43445117", "feeAmount": "21723", "syntheticAmount": "1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) @@ -351,7 +351,6 @@ async def test_create_buy_order_with_position_tpsl( "side": "BUY", "qty": "0.00100000", "price": "43445.11680000", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "GTT", @@ -411,6 +410,7 @@ async def test_create_buy_order_with_position_tpsl( "debuggingAmounts": {"collateralAmount": "-43445117", "feeAmount": "21723", "syntheticAmount": "1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) diff --git a/tests/signing/order_object/test_market_order_object.py b/tests/signing/order_object/test_market_order_object.py index 26c6046..a056a46 100644 --- a/tests/signing/order_object/test_market_order_object.py +++ b/tests/signing/order_object/test_market_order_object.py @@ -54,7 +54,6 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, "side": "SELL", "qty": "0.00100000", "price": "49625.0", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "IOC", @@ -78,6 +77,7 @@ async def test_create_sell_order(mocker: MockerFixture, create_trading_account, "debuggingAmounts": {"collateralAmount": "49625000", "feeAmount": "24813", "syntheticAmount": "-1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) @@ -122,7 +122,6 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c "side": "BUY", "qty": "0.00100000", "price": "50375.0", - "rfqStartPrice": None, "reduceOnly": False, "postOnly": False, "timeInForce": "IOC", @@ -146,6 +145,7 @@ async def test_create_buy_order(mocker: MockerFixture, create_trading_account, c "debuggingAmounts": {"collateralAmount": "-50375000", "feeAmount": "25188", "syntheticAmount": "1000"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) diff --git a/tests/signing/order_object/test_order_object_attrs.py b/tests/signing/order_object/test_order_object_attrs.py index 8fb8e5c..c4d2b89 100644 --- a/tests/signing/order_object/test_order_object_attrs.py +++ b/tests/signing/order_object/test_order_object_attrs.py @@ -7,7 +7,8 @@ from pytest_mock import MockerFixture from x10.config import TESTNET_CONFIG -from x10.models.order import OrderSide +from x10.errors import ValidationError +from x10.models.order import CreateOrderRfqModel, OrderSide, OrderType, TimeInForce from x10.utils.date import utc_now FROZEN_NONCE = 1473459052 @@ -71,3 +72,21 @@ async def test_external_order_id(mocker: MockerFixture, create_trading_account, } ), ) + + +@pytest.mark.asyncio +async def test_rfq_not_allowed_for_non_rfq_market(create_trading_account, create_btc_usd_market): + from x10.signing.order_object import create_order_object + + with pytest.raises(ValidationError, match="only supported for RFQ markets"): + create_order_object( + account=create_trading_account(), + market=create_btc_usd_market(), + order_type=OrderType.MARKET, + time_in_force=TimeInForce.IOC, + amount_of_synthetic=Decimal("0.00100000"), + price=Decimal("43445.11680000"), + side=OrderSide.BUY, + starknet_domain=TESTNET_CONFIG.signing.starknet_domain, + rfq=CreateOrderRfqModel(start_price=Decimal("43000"), max_reprice_steps=3), + ) diff --git a/tests/signing/order_object/test_tpsl_order_object.py b/tests/signing/order_object/test_tpsl_order_object.py index 99662eb..0b6ba33 100644 --- a/tests/signing/order_object/test_tpsl_order_object.py +++ b/tests/signing/order_object/test_tpsl_order_object.py @@ -65,7 +65,6 @@ async def test_create_buy_partial_tpsl_order(mocker: MockerFixture, create_tradi "side": "SELL", "qty": "0.0001", "price": "0", - "rfqStartPrice": None, "reduceOnly": True, "postOnly": False, "timeInForce": "GTT", @@ -110,6 +109,7 @@ async def test_create_buy_partial_tpsl_order(mocker: MockerFixture, create_tradi "debuggingAmounts": {"collateralAmount": "0", "feeAmount": "0", "syntheticAmount": "-100"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) @@ -160,7 +160,6 @@ async def test_create_buy_position_tpsl_order(mocker: MockerFixture, create_trad "side": "SELL", "qty": "0", "price": "0", - "rfqStartPrice": None, "reduceOnly": True, "postOnly": False, "timeInForce": "GTT", @@ -213,6 +212,7 @@ async def test_create_buy_position_tpsl_order(mocker: MockerFixture, create_trad "debuggingAmounts": {"collateralAmount": "0", "feeAmount": "0", "syntheticAmount": "0"}, "builderFee": None, "builderId": None, + "rfq": None, } ), ) diff --git a/x10/clients/blocking/blocking_trading_client.py b/x10/clients/blocking/blocking_trading_client.py index 69830f5..2aeabc2 100644 --- a/x10/clients/blocking/blocking_trading_client.py +++ b/x10/clients/blocking/blocking_trading_client.py @@ -14,6 +14,7 @@ from x10.models.http import WrappedStreamResponseModel from x10.models.market import MarketModel from x10.models.order import ( + CreateOrderRfqModel, NewOrderModel, OpenOrderModel, OrderSide, @@ -207,7 +208,7 @@ async def create_and_place_order( time_in_force: TimeInForce = TimeInForce.GTT, reduce_only: bool = False, order_type: OrderType = OrderType.LIMIT, - rfq_start_price: Optional[Decimal] = None, + rfq: Optional[CreateOrderRfqModel] = None, ) -> TimedOpenOrderModel: market = (await self.get_markets()).get(market_name) @@ -220,7 +221,6 @@ async def create_and_place_order( order_type=order_type, amount_of_synthetic=amount_of_synthetic, price=price, - rfq_start_price=rfq_start_price, side=side, post_only=post_only, reduce_only=reduce_only, @@ -231,6 +231,7 @@ async def create_and_place_order( builder_id=builder_id, time_in_force=time_in_force, taker_fee=taker_fee, + rfq=rfq, ) if order.id in self.__order_waiters: diff --git a/x10/clients/rest/rest_api_client.py b/x10/clients/rest/rest_api_client.py index 2a1b76a..9ebd6bc 100644 --- a/x10/clients/rest/rest_api_client.py +++ b/x10/clients/rest/rest_api_client.py @@ -13,8 +13,10 @@ from x10.errors import ValidationError from x10.models.market import MarketModel from x10.models.order import ( + CreateOrderRfqModel, OrderSide, OrderTpslType, + OrderType, PlacedOrderModel, SelfTradeProtectionLevel, TimeInForce, @@ -51,6 +53,7 @@ async def place_order( price: Decimal, side: OrderSide, taker_fee: Decimal, + order_type: OrderType = OrderType.LIMIT, post_only: bool = False, previous_order_id=None, expire_time: Optional[datetime] = None, @@ -63,7 +66,7 @@ async def place_order( tp_sl_type: Optional[OrderTpslType] = None, take_profit: Optional[OrderTpslTriggerParam] = None, stop_loss: Optional[OrderTpslTriggerParam] = None, - rfq_start_price: Optional[Decimal] = None, + rfq: Optional[CreateOrderRfqModel] = None, ) -> WrappedApiResponseModel[PlacedOrderModel]: # FIXME: Remove all the checks, should proxy the request? if not self.__stark_account: @@ -85,8 +88,8 @@ async def place_order( market=market, amount_of_synthetic=amount_of_synthetic, price=price, - rfq_start_price=rfq_start_price, side=side, + order_type=order_type, post_only=post_only, previous_order_external_id=previous_order_id, expire_time=expire_time, @@ -101,6 +104,7 @@ async def place_order( tp_sl_type=tp_sl_type, take_profit=take_profit, stop_loss=stop_loss, + rfq=rfq, ) if market.is_rfq: diff --git a/x10/models/order.py b/x10/models/order.py index 9b26019..7044f6d 100644 --- a/x10/models/order.py +++ b/x10/models/order.py @@ -152,19 +152,26 @@ class CreateOrderTpslTriggerModel(X10BaseModel): debugging_amounts: Optional[StarkDebuggingOrderAmountsModel] = None -class NewOrderModel(X10BaseModel): +class CreateOrderRfqModel(X10BaseModel): """ + RFQ quote walk parameters. Supported for RFQ markets only. + Attributes: - rfq_start_price: The start price of the quote walk. Supported for RFQ markets MARKET orders only. + start_price: The start price of the quote walk. + max_reprice_steps: The maximum number of repricing steps of the quote walk. """ + start_price: Optional[Decimal] = None + max_reprice_steps: Optional[int] = None + + +class NewOrderModel(X10BaseModel): id: str market: str type: OrderType side: OrderSide qty: Decimal price: Decimal - rfq_start_price: Optional[Decimal] = None reduce_only: bool = False post_only: bool = False time_in_force: TimeInForce @@ -181,6 +188,7 @@ class NewOrderModel(X10BaseModel): debugging_amounts: Optional[StarkDebuggingOrderAmountsModel] = None builder_fee: Optional[Decimal] = None builder_id: Optional[int] = None + rfq: Optional[CreateOrderRfqModel] = None class PlacedOrderModel(X10BaseModel): diff --git a/x10/signing/order_object.py b/x10/signing/order_object.py index 181e4c8..3cd5fdc 100644 --- a/x10/signing/order_object.py +++ b/x10/signing/order_object.py @@ -9,6 +9,7 @@ from x10.models.market import MarketModel from x10.models.order import ( CreateOrderConditionalTriggerModel, + CreateOrderRfqModel, CreateOrderTpslTriggerModel, NewOrderModel, OrderPriceType, @@ -53,7 +54,6 @@ def create_order_object( market: MarketModel, amount_of_synthetic: Decimal, price: Decimal, - rfq_start_price: Optional[Decimal] = None, side: OrderSide, starknet_domain: StarknetDomain, order_type: OrderType = OrderType.LIMIT, @@ -72,6 +72,7 @@ def create_order_object( tp_sl_type: Optional[OrderTpslType] = None, take_profit: Optional[OrderTpslTriggerParam] = None, stop_loss: Optional[OrderTpslTriggerParam] = None, + rfq: Optional[CreateOrderRfqModel] = None, ) -> NewOrderModel: """ Creates an order object to be placed on the exchange using the `place_order` method. @@ -85,7 +86,6 @@ def create_order_object( order_type=order_type, synthetic_amount=amount_of_synthetic, price=price, - rfq_start_price=rfq_start_price, side=side, collateral_position_id=account.vault, signer=account.sign, @@ -107,6 +107,7 @@ def create_order_object( tp_sl_type=tp_sl_type, take_profit=take_profit, stop_loss=stop_loss, + rfq=rfq, ) @@ -156,7 +157,6 @@ def __create_order_object( order_type: OrderType, synthetic_amount: Decimal, price: Decimal, - rfq_start_price: Optional[Decimal] = None, side: OrderSide, collateral_position_id: int, signer: Callable[[int], Tuple[int, int]], @@ -178,6 +178,7 @@ def __create_order_object( tp_sl_type: Optional[OrderTpslType] = None, take_profit: Optional[OrderTpslTriggerParam] = None, stop_loss: Optional[OrderTpslTriggerParam] = None, + rfq: Optional[CreateOrderRfqModel] = None, ) -> NewOrderModel: def validate_market_order(): if post_only: @@ -190,12 +191,9 @@ def validate_conditional_order(): if not trigger: raise ValidationError("CONDITIONAL orders must have `trigger` specified") - def validate_rfq_start_price(): - if rfq_start_price and not market.is_rfq: - raise ValidationError("`rfq_start_price` must not be provided for non-RFQ markets") - - if rfq_start_price and order_type != OrderType.MARKET: - raise ValidationError("`rfq_start_price` must not be provided for non-MARKET orders") + def validate_rfq(): + if rfq is not None and not market.is_rfq: + raise ValidationError(f"`rfq` is only supported for RFQ markets, {market.name} is not one") def validate_tpsl_order(): if not reduce_only: @@ -232,7 +230,7 @@ def validate_tpsl_order(): elif order_type == OrderType.TPSL: validate_tpsl_order() - validate_rfq_start_price() + validate_rfq() if nonce is None: nonce = generate_nonce() @@ -280,7 +278,6 @@ def create_tpsl_trigger_model(trigger_param: OrderTpslTriggerParam | None): side=side, qty=settlement_data.synthetic_amount_human.value, price=price, - rfq_start_price=rfq_start_price, post_only=post_only, time_in_force=time_in_force, expiry_epoch_millis=to_epoch_millis(expire_time), @@ -304,6 +301,7 @@ def create_tpsl_trigger_model(trigger_param: OrderTpslTriggerParam | None): builder_fee=builder_fee, builder_id=builder_id, reduce_only=reduce_only, + rfq=rfq, ) return order