Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 78 additions & 6 deletions x10/perpetual/order_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from x10.perpetual.orders import (
CreateOrderTpslTriggerModel,
CreateOrderConditionalTriggerModel,
NewOrderModel,
OrderPriceType,
OrderSide,
Expand All @@ -21,12 +22,17 @@
OrderType,
SelfTradeProtectionLevel,
TimeInForce,
OrderTriggerDirection,
)
from x10.utils.date import to_epoch_millis, utc_now
from x10.utils.nonce import generate_nonce
from x10.utils.tpsl import calc_entire_position_size


# --------------------------------------------------
# Trigger parameter models
# --------------------------------------------------

@dataclass(kw_only=True)
class OrderTpslTriggerParam:
trigger_price: Decimal
Expand All @@ -35,6 +41,18 @@ class OrderTpslTriggerParam:
price_type: OrderPriceType


@dataclass(kw_only=True)
class OrderConditionalTriggerParam:
trigger_price: Decimal
trigger_price_type: OrderTriggerPriceType
direction: OrderTriggerDirection
execution_price_type: OrderPriceType


# --------------------------------------------------
# Public factory
# --------------------------------------------------

def create_order_object(
*,
account: StarkPerpetualAccount,
Expand All @@ -43,7 +61,6 @@ def create_order_object(
price: Decimal,
side: OrderSide,
starknet_domain: StarknetDomain,
order_type: OrderType = OrderType.LIMIT,
post_only: bool = False,
previous_order_external_id: Optional[str] = None,
expire_time: Optional[datetime] = None,
Expand All @@ -54,6 +71,12 @@ def create_order_object(
builder_fee: Optional[Decimal] = None,
builder_id: Optional[int] = None,
reduce_only: bool = False,

# conditional-specific
order_type: OrderType = OrderType.LIMIT,
trigger: Optional[OrderConditionalTriggerParam] = None,

# TPSL-specific
tp_sl_type: Optional[OrderTpslType] = None,
take_profit: Optional[OrderTpslTriggerParam] = None,
stop_loss: Optional[OrderTpslTriggerParam] = None,
Expand Down Expand Up @@ -89,12 +112,17 @@ def create_order_object(
builder_fee=builder_fee,
builder_id=builder_id,
reduce_only=reduce_only,
trigger=trigger,
tp_sl_type=tp_sl_type,
take_profit=take_profit,
stop_loss=stop_loss,
)


# --------------------------------------------------
# Internal helpers
# --------------------------------------------------

def __create_order_tpsl_trigger_model(
*,
trigger_param: OrderTpslTriggerParam,
Expand Down Expand Up @@ -135,6 +163,10 @@ def __get_opposite_side(side: OrderSide) -> OrderSide:
return OrderSide.BUY if side == OrderSide.SELL else OrderSide.SELL


# --------------------------------------------------
# Core builder
# --------------------------------------------------

def __create_order_object(
*,
market: MarketModel,
Expand All @@ -158,11 +190,16 @@ def __create_order_object(
builder_fee: Optional[Decimal] = None,
builder_id: Optional[int] = None,
reduce_only: bool = False,
trigger: Optional[OrderConditionalTriggerParam] = None,
tp_sl_type: Optional[OrderTpslType] = None,
take_profit: Optional[OrderTpslTriggerParam] = None,
stop_loss: Optional[OrderTpslTriggerParam] = None,
) -> NewOrderModel:
if order_type not in [OrderType.LIMIT, OrderType.TPSL]:

if side not in OrderSide:
raise ValueError(f"Unexpected order side value: {side}")

if order_type not in [OrderType.LIMIT, OrderType.TPSL, OrderType.CONDITIONAL]:
raise NotImplementedError(f"{order_type} order type is not supported yet")

if exact_only:
Expand All @@ -178,14 +215,34 @@ def __create_order_object(
if not reduce_only:
raise ValueError("TPSL orders must be reduce-only")

# --------------------------------------------------
# Conditional order validation
# --------------------------------------------------
if order_type == OrderType.CONDITIONAL:
if trigger is None:
raise ValueError("Conditional order requires trigger")

if tp_sl_type or take_profit or stop_loss:
raise ValueError("Conditional orders cannot include TPSL fields")

if post_only:
raise ValueError("post_only is not supported for conditional orders")

if price <= 0:
raise ValueError("Conditional order requires valid execution price")

# --------------------------------------------------
# TPSL validation
# --------------------------------------------------
if order_type == OrderType.TPSL:
if post_only:
raise ValueError("TPSL orders must not be post-only")

if tp_sl_type == OrderTpslType.POSITION and synthetic_amount != Decimal(0):
raise ValueError("`amount_of_synthetic` must be 0 for entire position TPSL orders")
if tp_sl_type == OrderTpslType.POSITION:
raise NotImplementedError("`POSITION` TPSL type is not supported yet")

if price != Decimal(0):
raise ValueError("`price` must be 0 for TPSL orders")
raise ValueError("`price` must be 0 for TPSL orders")

if nonce is None:
nonce = generate_nonce()
Expand All @@ -203,8 +260,12 @@ def __create_order_object(
public_key=public_key,
starknet_domain=starknet_domain,
)

settlement_data = create_order_settlement_data(
side=side, synthetic_amount=synthetic_amount, price=price, ctx=settlement_data_ctx
side=side,
synthetic_amount=synthetic_amount,
price=price,
ctx=settlement_data_ctx,
)

def create_tpsl_trigger_model(trigger_param: OrderTpslTriggerParam | None):
Expand All @@ -228,6 +289,7 @@ def create_tpsl_trigger_model(trigger_param: OrderTpslTriggerParam | None):
)

order_id = str(settlement_data.order_hash) if order_external_id is None else order_external_id

order = NewOrderModel(
id=order_id,
market=market.name,
Expand All @@ -242,6 +304,16 @@ def create_tpsl_trigger_model(trigger_param: OrderTpslTriggerParam | None):
self_trade_protection_level=self_trade_protection_level,
nonce=Decimal(nonce),
cancel_id=previous_order_external_id,
trigger=(
CreateOrderConditionalTriggerModel(
trigger_price=trigger.trigger_price,
trigger_price_type=trigger.trigger_price_type,
direction=trigger.direction,
execution_price_type=trigger.execution_price_type,
)
if order_type == OrderType.CONDITIONAL and trigger is not None
else None
),
settlement=settlement_data.settlement if order_type != OrderType.TPSL else None,
tp_sl_type=tp_sl_type,
take_profit=create_tpsl_trigger_model(take_profit),
Expand Down