x10-python-trading-starknet 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. x10/__init__.py +0 -0
  2. x10/config.py +13 -0
  3. x10/errors.py +2 -0
  4. x10/perpetual/__init__.py +0 -0
  5. x10/perpetual/accounts.py +86 -0
  6. x10/perpetual/amounts.py +55 -0
  7. x10/perpetual/assets.py +71 -0
  8. x10/perpetual/balances.py +15 -0
  9. x10/perpetual/candles.py +18 -0
  10. x10/perpetual/configuration.py +86 -0
  11. x10/perpetual/fees.py +16 -0
  12. x10/perpetual/funding_rates.py +11 -0
  13. x10/perpetual/markets.py +125 -0
  14. x10/perpetual/order_object.py +201 -0
  15. x10/perpetual/orderbook.py +211 -0
  16. x10/perpetual/orderbooks.py +17 -0
  17. x10/perpetual/orders.py +179 -0
  18. x10/perpetual/positions.py +51 -0
  19. x10/perpetual/simple_client/simple_trading_client.py +216 -0
  20. x10/perpetual/stream_client/__init__.py +3 -0
  21. x10/perpetual/stream_client/perpetual_stream_connection.py +108 -0
  22. x10/perpetual/stream_client/stream_client.py +83 -0
  23. x10/perpetual/trades.py +38 -0
  24. x10/perpetual/trading_client/__init__.py +3 -0
  25. x10/perpetual/trading_client/account_module.py +209 -0
  26. x10/perpetual/trading_client/base_module.py +58 -0
  27. x10/perpetual/trading_client/info_module.py +13 -0
  28. x10/perpetual/trading_client/markets_information_module.py +76 -0
  29. x10/perpetual/trading_client/order_management_module.py +81 -0
  30. x10/perpetual/trading_client/testnet_module.py +68 -0
  31. x10/perpetual/trading_client/trading_client.py +123 -0
  32. x10/perpetual/transfer_object.py +80 -0
  33. x10/perpetual/transfers.py +39 -0
  34. x10/perpetual/user_client/__init__.py +0 -0
  35. x10/perpetual/user_client/l1_signing.py +0 -0
  36. x10/perpetual/user_client/onboarding.py +231 -0
  37. x10/perpetual/user_client/user_client.py +199 -0
  38. x10/perpetual/withdrawal_object.py +29 -0
  39. x10/perpetual/withdrawals.py +29 -0
  40. x10/utils/__init__.py +2 -0
  41. x10/utils/date.py +11 -0
  42. x10/utils/http.py +239 -0
  43. x10/utils/log.py +5 -0
  44. x10/utils/model.py +66 -0
  45. x10/utils/nonce.py +11 -0
  46. x10/utils/string.py +10 -0
  47. x10_python_trading_starknet-0.0.1.dist-info/LICENSE +21 -0
  48. x10_python_trading_starknet-0.0.1.dist-info/METADATA +413 -0
  49. x10_python_trading_starknet-0.0.1.dist-info/RECORD +50 -0
  50. x10_python_trading_starknet-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,201 @@
1
+ import math
2
+ from datetime import datetime, timedelta
3
+ from decimal import Decimal
4
+ from typing import Callable, Optional, Tuple
5
+
6
+ from fast_stark_crypto import get_order_msg_hash
7
+
8
+ from x10.perpetual.accounts import StarkPerpetualAccount
9
+ from x10.perpetual.amounts import (
10
+ ROUNDING_BUY_CONTEXT,
11
+ ROUNDING_FEE_CONTEXT,
12
+ ROUNDING_SELL_CONTEXT,
13
+ HumanReadableAmount,
14
+ StarkAmount,
15
+ )
16
+ from x10.perpetual.configuration import StarknetDomain
17
+ from x10.perpetual.fees import DEFAULT_FEES, TradingFeeModel
18
+ from x10.perpetual.markets import MarketModel
19
+ from x10.perpetual.orders import (
20
+ OrderSide,
21
+ OrderType,
22
+ PerpetualOrderModel,
23
+ SelfTradeProtectionLevel,
24
+ SettlementSignatureModel,
25
+ StarkDebuggingOrderAmountsModel,
26
+ StarkSettlementModel,
27
+ TimeInForce,
28
+ )
29
+ from x10.utils import generate_nonce
30
+ from x10.utils.date import to_epoch_millis, utc_now
31
+
32
+
33
+ def create_order_object(
34
+ account: StarkPerpetualAccount,
35
+ market: MarketModel,
36
+ amount_of_synthetic: Decimal,
37
+ price: Decimal,
38
+ side: OrderSide,
39
+ starknet_domain: StarknetDomain,
40
+ post_only: bool = False,
41
+ previous_order_id: Optional[str] = None,
42
+ expire_time: Optional[datetime] = None,
43
+ order_external_id: Optional[str] = None,
44
+ time_in_force: TimeInForce = TimeInForce.GTT,
45
+ self_trade_protection_level: SelfTradeProtectionLevel = SelfTradeProtectionLevel.ACCOUNT,
46
+ nonce: Optional[int] = None,
47
+ ) -> PerpetualOrderModel:
48
+ """
49
+ Creates an order object to be placed on the exchange using the `place_order` method.
50
+ """
51
+
52
+ if expire_time is None:
53
+ expire_time = utc_now() + timedelta(hours=1)
54
+
55
+ fees = account.trading_fee.get(market.name, DEFAULT_FEES)
56
+
57
+ return __create_order_object(
58
+ market=market,
59
+ synthetic_amount=amount_of_synthetic,
60
+ price=price,
61
+ side=side,
62
+ collateral_position_id=account.vault,
63
+ fees=fees,
64
+ signer=account.sign,
65
+ public_key=account.public_key,
66
+ exact_only=False,
67
+ expire_time=expire_time,
68
+ post_only=post_only,
69
+ previous_order_external_id=previous_order_id,
70
+ order_external_id=order_external_id,
71
+ time_in_force=time_in_force,
72
+ self_trade_protection_level=self_trade_protection_level,
73
+ starknet_domain=starknet_domain,
74
+ nonce=nonce,
75
+ )
76
+
77
+
78
+ def __create_order_object(
79
+ market: MarketModel,
80
+ synthetic_amount: Decimal,
81
+ price: Decimal,
82
+ side: OrderSide,
83
+ collateral_position_id: int,
84
+ fees: TradingFeeModel,
85
+ signer: Callable[[int], Tuple[int, int]],
86
+ public_key: int,
87
+ starknet_domain: StarknetDomain,
88
+ exact_only: bool = False,
89
+ expire_time: Optional[datetime] = None,
90
+ post_only: bool = False,
91
+ previous_order_external_id: Optional[str] = None,
92
+ order_external_id: Optional[str] = None,
93
+ time_in_force: TimeInForce = TimeInForce.GTT,
94
+ self_trade_protection_level: SelfTradeProtectionLevel = SelfTradeProtectionLevel.ACCOUNT,
95
+ nonce: Optional[int] = None,
96
+ ) -> PerpetualOrderModel:
97
+ if exact_only:
98
+ raise NotImplementedError("`exact_only` option is not supported yet")
99
+
100
+ if expire_time is None:
101
+ raise ValueError("`expire_time` must be provided")
102
+ if nonce is None:
103
+ nonce = generate_nonce()
104
+ is_buying_synthetic = side == OrderSide.BUY
105
+ rounding_context = ROUNDING_BUY_CONTEXT if is_buying_synthetic else ROUNDING_SELL_CONTEXT
106
+
107
+ collateral_amount_human = HumanReadableAmount(synthetic_amount * price, market.collateral_asset)
108
+ synthetic_amount_human = HumanReadableAmount(synthetic_amount, market.synthetic_asset)
109
+ fee_amount_human = HumanReadableAmount(
110
+ fees.taker_fee_rate * collateral_amount_human.value,
111
+ market.collateral_asset,
112
+ )
113
+ fee_rate = fees.taker_fee_rate
114
+
115
+ stark_collateral_amount: StarkAmount = collateral_amount_human.to_stark_amount(rounding_context=rounding_context)
116
+ stark_synthetic_amount: StarkAmount = synthetic_amount_human.to_stark_amount(rounding_context=rounding_context)
117
+ stark_fee_amount: StarkAmount = fee_amount_human.to_stark_amount(rounding_context=ROUNDING_FEE_CONTEXT)
118
+
119
+ if is_buying_synthetic:
120
+ stark_collateral_amount = stark_collateral_amount.negate()
121
+ else:
122
+ stark_synthetic_amount = stark_synthetic_amount.negate()
123
+
124
+ debugging_amounts = StarkDebuggingOrderAmountsModel(
125
+ collateral_amount=Decimal(stark_collateral_amount.value),
126
+ fee_amount=Decimal(stark_fee_amount.value),
127
+ synthetic_amount=Decimal(stark_synthetic_amount.value),
128
+ )
129
+
130
+ order_hash = hash_order(
131
+ amount_synthetic=stark_synthetic_amount,
132
+ amount_collateral=stark_collateral_amount,
133
+ max_fee=stark_fee_amount,
134
+ nonce=nonce,
135
+ position_id=collateral_position_id,
136
+ expiration_timestamp=expire_time,
137
+ public_key=public_key,
138
+ starknet_domain=starknet_domain,
139
+ )
140
+
141
+ (order_signature_r, order_signature_s) = signer(order_hash)
142
+ settlement = StarkSettlementModel(
143
+ signature=SettlementSignatureModel(r=order_signature_r, s=order_signature_s),
144
+ stark_key=public_key,
145
+ collateral_position=Decimal(collateral_position_id),
146
+ )
147
+
148
+ order_id = str(order_hash) if order_external_id is None else order_external_id
149
+ order = PerpetualOrderModel(
150
+ id=order_id,
151
+ market=market.name,
152
+ type=OrderType.LIMIT,
153
+ side=side,
154
+ qty=synthetic_amount_human.value,
155
+ price=price,
156
+ post_only=post_only,
157
+ time_in_force=time_in_force,
158
+ expiry_epoch_millis=to_epoch_millis(expire_time),
159
+ fee=fee_rate,
160
+ self_trade_protection_level=self_trade_protection_level,
161
+ nonce=Decimal(nonce),
162
+ cancel_id=previous_order_external_id,
163
+ settlement=settlement,
164
+ debugging_amounts=debugging_amounts,
165
+ )
166
+
167
+ return order
168
+
169
+
170
+ def hash_order(
171
+ amount_synthetic: StarkAmount,
172
+ amount_collateral: StarkAmount,
173
+ max_fee: StarkAmount,
174
+ nonce: int,
175
+ position_id: int,
176
+ expiration_timestamp: datetime,
177
+ public_key: int,
178
+ starknet_domain: StarknetDomain,
179
+ ) -> int:
180
+ synthetic_asset = amount_synthetic.asset
181
+ collateral_asset = amount_collateral.asset
182
+
183
+ expire_time_with_buffer = expiration_timestamp + timedelta(days=14)
184
+ expire_time_as_seconds = math.ceil(expire_time_with_buffer.timestamp())
185
+
186
+ return get_order_msg_hash(
187
+ position_id=position_id,
188
+ base_asset_id=int(synthetic_asset.settlement_external_id, 16),
189
+ base_amount=amount_synthetic.value,
190
+ quote_asset_id=int(collateral_asset.settlement_external_id, 16),
191
+ quote_amount=amount_collateral.value,
192
+ fee_amount=max_fee.value,
193
+ fee_asset_id=int(collateral_asset.settlement_external_id, 16),
194
+ expiration=expire_time_as_seconds,
195
+ salt=nonce,
196
+ user_public_key=public_key,
197
+ domain_name=starknet_domain.name,
198
+ domain_version=starknet_domain.version,
199
+ domain_chain_id=starknet_domain.chain_id,
200
+ domain_revision=starknet_domain.revision,
201
+ )
@@ -0,0 +1,211 @@
1
+ import asyncio
2
+ import dataclasses
3
+ import decimal
4
+ from typing import Callable, Iterable, Tuple
5
+
6
+ from sortedcontainers import SortedDict # type: ignore[import-untyped]
7
+
8
+ from x10.perpetual.configuration import EndpointConfig
9
+ from x10.perpetual.orderbooks import OrderbookUpdateModel
10
+ from x10.perpetual.stream_client.stream_client import PerpetualStreamClient
11
+ from x10.utils.http import StreamDataType
12
+
13
+
14
+ @dataclasses.dataclass
15
+ class OrderBookEntry:
16
+ price: decimal.Decimal
17
+ amount: decimal.Decimal
18
+
19
+ def __repr__(self) -> str:
20
+ return f"OrderBookEntry(price={self.price}, amount={self.amount})"
21
+
22
+
23
+ @dataclasses.dataclass
24
+ class ImpactDetails:
25
+ price: decimal.Decimal
26
+ amount: decimal.Decimal
27
+
28
+
29
+ class OrderBook:
30
+ @staticmethod
31
+ async def create(
32
+ endpoint_config: EndpointConfig,
33
+ market_name: str,
34
+ best_ask_change_callback: Callable[[OrderBookEntry], None] | None = None,
35
+ best_bid_change_callback: Callable[[OrderBookEntry], None] | None = None,
36
+ start=False,
37
+ ) -> "OrderBook":
38
+ ob = OrderBook(
39
+ endpoint_config,
40
+ market_name,
41
+ best_ask_change_callback,
42
+ best_bid_change_callback,
43
+ )
44
+ if start:
45
+ await ob.start_orderbook()
46
+ return ob
47
+
48
+ def __init__(
49
+ self,
50
+ endpoint_config: EndpointConfig,
51
+ market_name: str,
52
+ best_ask_change_callback: Callable[[OrderBookEntry], None] | None = None,
53
+ best_bid_change_callback: Callable[[OrderBookEntry], None] | None = None,
54
+ ) -> None:
55
+ self.__stream_client = PerpetualStreamClient(api_url=endpoint_config.stream_url)
56
+ self.__market_name = market_name
57
+ self.__task: asyncio.Task | None = None
58
+ self._bid_prices: SortedDict[decimal.Decimal, OrderBookEntry] = SortedDict()
59
+ self._ask_prices: SortedDict[decimal.Decimal, OrderBookEntry] = SortedDict()
60
+ self.best_ask_change_callback = best_ask_change_callback
61
+ self.best_bid_change_callback = best_bid_change_callback
62
+
63
+ def update_orderbook(self, data: OrderbookUpdateModel):
64
+ best_bid_before_update = self.best_bid()
65
+ for bid in data.bid:
66
+ if bid.price in self._bid_prices:
67
+ existing_bid_entry: OrderBookEntry = self._bid_prices.get(bid.price)
68
+ existing_bid_entry.amount = existing_bid_entry.amount + bid.qty
69
+ if existing_bid_entry.amount == 0:
70
+ del self._bid_prices[bid.price]
71
+ else:
72
+ self._bid_prices[bid.price] = OrderBookEntry(
73
+ price=bid.price,
74
+ amount=bid.qty,
75
+ )
76
+ now_best_bid = self.best_bid()
77
+ if now_best_bid and best_bid_before_update != now_best_bid:
78
+ if self.best_bid_change_callback:
79
+ self.best_bid_change_callback(now_best_bid)
80
+
81
+ best_ask_before_update = self.best_ask()
82
+ for ask in data.ask:
83
+ if ask.price in self._ask_prices:
84
+ existing_ask_entry: OrderBookEntry = self._ask_prices.get(ask.price)
85
+ existing_ask_entry.amount = existing_ask_entry.amount + ask.qty
86
+ if existing_ask_entry.amount == 0:
87
+ del self._ask_prices[ask.price]
88
+ else:
89
+ self._ask_prices[ask.price] = OrderBookEntry(
90
+ price=ask.price,
91
+ amount=ask.qty,
92
+ )
93
+ now_best_ask = self.best_ask()
94
+ if now_best_ask and best_ask_before_update != now_best_ask:
95
+ if self.best_ask_change_callback:
96
+ self.best_ask_change_callback(now_best_ask)
97
+
98
+ def init_orderbook(self, data: OrderbookUpdateModel):
99
+ for bid in data.bid:
100
+ self._bid_prices[bid.price] = OrderBookEntry(
101
+ price=bid.price,
102
+ amount=bid.qty,
103
+ )
104
+ for ask in data.ask:
105
+ self._ask_prices[ask.price] = OrderBookEntry(
106
+ price=ask.price,
107
+ amount=ask.qty,
108
+ )
109
+
110
+ async def start_orderbook(self) -> asyncio.Task:
111
+ loop = asyncio.get_running_loop()
112
+
113
+ async def inner():
114
+ async with self.__stream_client.subscribe_to_orderbooks(self.__market_name) as stream:
115
+ async for event in stream:
116
+ if event.type == StreamDataType.SNAPSHOT.value:
117
+ self.init_orderbook(event.data)
118
+ elif event.type == StreamDataType.DELTA.value:
119
+ self.update_orderbook(event.data)
120
+
121
+ self.__task = loop.create_task(inner())
122
+ return self.__task
123
+
124
+ def stop_orderbook(self):
125
+ if self.__task:
126
+ self.__task.cancel()
127
+ self.__task = None
128
+
129
+ def best_bid(self) -> OrderBookEntry | None:
130
+ try:
131
+ entry = self._bid_prices.peekitem(-1)
132
+ return entry[1]
133
+ except IndexError:
134
+ return None
135
+
136
+ def best_ask(self) -> OrderBookEntry | None:
137
+ try:
138
+ entry = self._ask_prices.peekitem(0)
139
+ return entry[1]
140
+ except IndexError:
141
+ return None
142
+
143
+ def __price_impact_notional(
144
+ self, notional: decimal.Decimal, levels: Iterable[Tuple[decimal.Decimal, OrderBookEntry]]
145
+ ):
146
+ remaining_to_spend = notional
147
+ total_amount = decimal.Decimal(0)
148
+ weighted_sum = decimal.Decimal(0)
149
+ for price, entry in levels:
150
+ available_at_price = entry.amount
151
+ amount_to_purchase = min(remaining_to_spend / price, available_at_price)
152
+ if remaining_to_spend <= 0:
153
+ break
154
+ if available_at_price <= 0:
155
+ continue
156
+ take = amount_to_purchase
157
+ spent = take * price
158
+ weighted_sum += take * price
159
+ total_amount += take
160
+ remaining_to_spend -= spent
161
+
162
+ if remaining_to_spend > 0:
163
+ return None
164
+ average_price = weighted_sum / total_amount
165
+ return ImpactDetails(price=average_price, amount=total_amount)
166
+
167
+ def __price_impact_qty(self, qty: decimal.Decimal, levels: Iterable[Tuple[decimal.Decimal, OrderBookEntry]]):
168
+ remaining_qty = qty
169
+ total_amount = decimal.Decimal(0)
170
+ total_spent = decimal.Decimal(0)
171
+ for price, entry in levels:
172
+ available_at_price = entry.amount
173
+ take = min(remaining_qty, available_at_price)
174
+ if remaining_qty <= 0:
175
+ break
176
+ if available_at_price <= 0:
177
+ continue
178
+ total_spent += take * price
179
+ total_amount += take
180
+ remaining_qty -= take
181
+
182
+ if remaining_qty > 0:
183
+ return None
184
+ average_price = total_spent / total_amount
185
+ return ImpactDetails(price=average_price, amount=total_amount)
186
+
187
+ def calculate_price_impact_notional(self, notional: decimal.Decimal, side: str) -> ImpactDetails | None:
188
+ if notional <= 0:
189
+ return None
190
+ if side == "SELL":
191
+ if not self._bid_prices:
192
+ return None
193
+ return self.__price_impact_notional(notional, reversed(self._bid_prices.items()))
194
+ elif side == "BUY":
195
+ if not self._ask_prices:
196
+ return None
197
+ return self.__price_impact_notional(notional, self._ask_prices.items())
198
+ return None
199
+
200
+ def calculate_price_impact_qty(self, qty: decimal.Decimal, side: str) -> ImpactDetails | None:
201
+ if qty <= 0:
202
+ return None
203
+ if side == "SELL":
204
+ if not self._bid_prices:
205
+ return None
206
+ return self.__price_impact_qty(qty, reversed(self._bid_prices.items()))
207
+ elif side == "BUY":
208
+ if not self._ask_prices:
209
+ return None
210
+ return self.__price_impact_qty(qty, self._ask_prices.items())
211
+ return None
@@ -0,0 +1,17 @@
1
+ from decimal import Decimal
2
+ from typing import List
3
+
4
+ from pydantic import AliasChoices, Field
5
+
6
+ from x10.utils.model import X10BaseModel
7
+
8
+
9
+ class OrderbookQuantityModel(X10BaseModel):
10
+ qty: Decimal = Field(validation_alias=AliasChoices("qty", "q"), serialization_alias="q")
11
+ price: Decimal = Field(validation_alias=AliasChoices("price", "p"), serialization_alias="p")
12
+
13
+
14
+ class OrderbookUpdateModel(X10BaseModel):
15
+ market: str = Field(validation_alias=AliasChoices("market", "m"), serialization_alias="m")
16
+ bid: List[OrderbookQuantityModel] = Field(validation_alias=AliasChoices("bid", "b"), serialization_alias="b")
17
+ ask: List[OrderbookQuantityModel] = Field(validation_alias=AliasChoices("ask", "a"), serialization_alias="a")
@@ -0,0 +1,179 @@
1
+ from decimal import Decimal
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ from x10.utils.model import HexValue, SettlementSignatureModel, X10BaseModel
6
+
7
+
8
+ class TimeInForce(Enum):
9
+ GTT = "GTT"
10
+ IOC = "IOC"
11
+ FOK = "FOK"
12
+
13
+
14
+ class OrderSide(Enum):
15
+ BUY = "BUY"
16
+ SELL = "SELL"
17
+
18
+
19
+ class OrderType(Enum):
20
+ LIMIT = "LIMIT"
21
+ CONDITIONAL = "CONDITIONAL"
22
+ MARKET = "MARKET"
23
+ TPSL = "TPSL"
24
+
25
+
26
+ class OrderTpslType(Enum):
27
+ ORDER = "ORDER"
28
+ POSITION = "POSITION"
29
+
30
+
31
+ class OrderStatus(Enum):
32
+ # Technical status
33
+ UNKNOWN = "UNKNOWN"
34
+
35
+ NEW = "NEW"
36
+ UNTRIGGERED = "UNTRIGGERED"
37
+ PARTIALLY_FILLED = "PARTIALLY_FILLED"
38
+ FILLED = "FILLED"
39
+ CANCELLED = "CANCELLED"
40
+ EXPIRED = "EXPIRED"
41
+ REJECTED = "REJECTED"
42
+
43
+
44
+ class OrderStatusReason(Enum):
45
+ # Technical status
46
+ UNKNOWN = "UNKNOWN"
47
+
48
+ NONE = "NONE"
49
+ UNKNOWN_MARKET = "UNKNOWN_MARKET"
50
+ DISABLED_MARKET = "DISABLED_MARKET"
51
+ NOT_ENOUGH_FUNDS = "NOT_ENOUGH_FUNDS"
52
+ NO_LIQUIDITY = "NO_LIQUIDITY"
53
+ INVALID_FEE = "INVALID_FEE"
54
+ INVALID_QTY = "INVALID_QTY"
55
+ INVALID_PRICE = "INVALID_PRICE"
56
+ INVALID_VALUE = "INVALID_VALUE"
57
+ UNKNOWN_ACCOUNT = "UNKNOWN_ACCOUNT"
58
+ SELF_TRADE_PROTECTION = "SELF_TRADE_PROTECTION"
59
+ POST_ONLY_FAILED = "POST_ONLY_FAILED"
60
+ REDUCE_ONLY_FAILED = "REDUCE_ONLY_FAILED"
61
+ INVALID_EXPIRE_TIME = "INVALID_EXPIRE_TIME"
62
+ POSITION_TPSL_CONFLICT = "POSITION_TPSL_CONFLICT"
63
+ INVALID_LEVERAGE = "INVALID_LEVERAGE"
64
+ PREV_ORDER_NOT_FOUND = "PREV_ORDER_NOT_FOUND"
65
+ PREV_ORDER_TRIGGERED = "PREV_ORDER_TRIGGERED"
66
+ TPSL_OTHER_SIDE_FILLED = "TPSL_OTHER_SIDE_FILLED"
67
+ PREV_ORDER_CONFLICT = "PREV_ORDER_CONFLICT"
68
+ ORDER_REPLACED = "ORDER_REPLACED"
69
+ POST_ONLY_MODE = "POST_ONLY_MODE"
70
+ REDUCE_ONLY_MODE = "REDUCE_ONLY_MODE"
71
+ TRADING_OFF_MODE = "TRADING_OFF_MODE"
72
+
73
+
74
+ class OrderTriggerPriceType(Enum):
75
+ # Technical status
76
+ UNKNOWN = "UNKNOWN"
77
+
78
+ MARK = "MARK"
79
+ INDEX = "INDEX"
80
+ LAST = "LAST"
81
+
82
+
83
+ class OrderTriggerDirection(Enum):
84
+ # Technical status
85
+ UNKNOWN = "UNKNOWN"
86
+
87
+ UP = "UP"
88
+ DOWN = "DOWN"
89
+
90
+
91
+ class OrderPriceType(Enum):
92
+ # Technical status
93
+ UNKNOWN = "UNKNOWN"
94
+
95
+ MARKET = "MARKET"
96
+ LIMIT = "LIMIT"
97
+
98
+
99
+ class SelfTradeProtectionLevel(Enum):
100
+ DISABLED = "DISABLED"
101
+ ACCOUNT = "ACCOUNT"
102
+ CLIENT = "CLIENT"
103
+
104
+
105
+ class StarkSettlementModel(X10BaseModel):
106
+ signature: SettlementSignatureModel
107
+ stark_key: HexValue
108
+ collateral_position: Decimal
109
+
110
+
111
+ class StarkDebuggingOrderAmountsModel(X10BaseModel):
112
+ collateral_amount: Decimal
113
+ fee_amount: Decimal
114
+ synthetic_amount: Decimal
115
+
116
+
117
+ class CreateOrderConditionalTriggerModel(X10BaseModel):
118
+ trigger_price: Decimal
119
+ trigger_price_type: OrderTriggerPriceType
120
+ direction: OrderTriggerDirection
121
+ execution_price_type: OrderPriceType
122
+
123
+
124
+ class CreateOrderTpslTriggerModel(X10BaseModel):
125
+ trigger_price: Decimal
126
+ trigger_price_type: OrderTriggerPriceType
127
+ price: Decimal
128
+ price_type: OrderPriceType
129
+ settlement: StarkSettlementModel
130
+ debugging_amounts: Optional[StarkDebuggingOrderAmountsModel] = None
131
+
132
+
133
+ class PerpetualOrderModel(X10BaseModel):
134
+ id: str
135
+ market: str
136
+ type: OrderType
137
+ side: OrderSide
138
+ qty: Decimal
139
+ price: Decimal
140
+ reduce_only: bool = False
141
+ post_only: bool = False
142
+ time_in_force: TimeInForce
143
+ expiry_epoch_millis: int
144
+ fee: Decimal
145
+ nonce: Decimal
146
+ self_trade_protection_level: SelfTradeProtectionLevel
147
+ cancel_id: Optional[str] = None
148
+ settlement: Optional[StarkSettlementModel] = None
149
+ trigger: Optional[CreateOrderConditionalTriggerModel] = None
150
+ tp_sl_type: Optional[OrderTpslType] = None
151
+ take_profit: Optional[CreateOrderTpslTriggerModel] = None
152
+ stop_loss: Optional[CreateOrderTpslTriggerModel] = None
153
+ debugging_amounts: Optional[StarkDebuggingOrderAmountsModel] = None
154
+
155
+
156
+ class PlacedOrderModel(X10BaseModel):
157
+ id: int
158
+ external_id: str
159
+
160
+
161
+ class OpenOrderModel(X10BaseModel):
162
+ id: int
163
+ account_id: int
164
+ external_id: str
165
+ market: str
166
+ type: OrderType
167
+ side: OrderSide
168
+ status: OrderStatus
169
+ status_reason: Optional[OrderStatusReason] = None
170
+ price: Decimal
171
+ average_price: Optional[Decimal] = None
172
+ qty: Decimal
173
+ filled_qty: Optional[Decimal] = None
174
+ reduce_only: bool
175
+ post_only: bool
176
+ payed_fee: Optional[Decimal] = None
177
+ created_time: int
178
+ updated_time: int
179
+ expiry_time: Optional[int] = None
@@ -0,0 +1,51 @@
1
+ from decimal import Decimal
2
+ from enum import Enum
3
+ from typing import Optional
4
+
5
+ from x10.utils.model import X10BaseModel
6
+
7
+
8
+ class ExitType(Enum):
9
+ TRADE = "TRADE"
10
+ LIQUIDATION = "LIQUIDATION"
11
+ ADL = "ADL"
12
+
13
+
14
+ class PositionSide(Enum):
15
+ LONG = "LONG"
16
+ SHORT = "SHORT"
17
+
18
+
19
+ class PositionModel(X10BaseModel):
20
+ id: int
21
+ account_id: int
22
+ market: str
23
+ side: PositionSide
24
+ leverage: Decimal
25
+ size: Decimal
26
+ value: Decimal
27
+ open_price: Decimal
28
+ mark_price: Decimal
29
+ liquidation_price: Optional[Decimal] = None
30
+ unrealised_pnl: Decimal
31
+ realised_pnl: Decimal
32
+ tp_price: Optional[Decimal] = None
33
+ sl_price: Optional[Decimal] = None
34
+ adl: Optional[int] = None
35
+ created_at: int
36
+ updated_at: int
37
+
38
+
39
+ class PositionHistoryModel(X10BaseModel):
40
+ id: int
41
+ account_id: int
42
+ market: str
43
+ side: PositionSide
44
+ leverage: Decimal
45
+ size: Decimal
46
+ open_price: Decimal
47
+ exit_type: Optional[ExitType]
48
+ exit_price: Optional[Decimal]
49
+ realised_pnl: Decimal
50
+ created_time: int
51
+ closed_time: Optional[int]