x10-python-trading-starknet 0.0.2__tar.gz → 0.0.5__tar.gz
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.
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/PKG-INFO +1 -1
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/pyproject.toml +1 -1
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/candles.py +4 -2
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/configuration.py +1 -1
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/markets.py +3 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/order_object.py +2 -2
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/orderbook.py +23 -14
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/positions.py +3 -3
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/simple_client/simple_trading_client.py +71 -46
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/stream_client/perpetual_stream_connection.py +6 -6
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/stream_client/stream_client.py +2 -2
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/order_management_module.py +4 -4
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/trading_client.py +3 -1
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/transfer_object.py +2 -2
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/user_client/onboarding.py +18 -10
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/user_client/user_client.py +2 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/LICENSE +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/README.md +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/__init__.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/config.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/errors.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/__init__.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/accounts.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/amounts.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/assets.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/balances.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/fees.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/funding_rates.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/orderbooks.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/orders.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/stream_client/__init__.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trades.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/__init__.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/account_module.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/base_module.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/info_module.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/markets_information_module.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trading_client/testnet_module.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/transfers.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/user_client/__init__.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/user_client/l1_signing.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/withdrawal_object.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/withdrawals.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/__init__.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/date.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/http.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/log.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/model.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/nonce.py +0 -0
- {x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/string.py +0 -0
|
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
5
5
|
|
|
6
6
|
[tool.poetry]
|
|
7
7
|
name = "x10-python-trading-starknet"
|
|
8
|
-
version = "0.0.
|
|
8
|
+
version = "0.0.5"
|
|
9
9
|
description = "Python client for X10 API"
|
|
10
10
|
authors = ["X10 <tech@ex10.org>"]
|
|
11
11
|
repository = "https://github.com/x10xchange/python_sdk"
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/candles.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
|
-
from typing import Literal
|
|
2
|
+
from typing import Literal, Optional
|
|
3
3
|
|
|
4
4
|
from pydantic import AliasChoices, Field
|
|
5
5
|
|
|
@@ -14,5 +14,7 @@ class CandleModel(X10BaseModel):
|
|
|
14
14
|
low: Decimal = Field(validation_alias=AliasChoices("low", "l"), serialization_alias="l")
|
|
15
15
|
high: Decimal = Field(validation_alias=AliasChoices("high", "h"), serialization_alias="h")
|
|
16
16
|
close: Decimal = Field(validation_alias=AliasChoices("close", "c"), serialization_alias="c")
|
|
17
|
-
volume: Decimal = Field(
|
|
17
|
+
volume: Optional[Decimal] = Field(
|
|
18
|
+
validation_alias=AliasChoices("volume", "v"), serialization_alias="v", default=None
|
|
19
|
+
)
|
|
18
20
|
timestamp: int = Field(validation_alias=AliasChoices("timestamp", "T"), serialization_alias="T")
|
|
@@ -74,7 +74,7 @@ MAINNET_CONFIG_LEGACY_SIGNING_DOMAIN = EndpointConfig(
|
|
|
74
74
|
STARKNET_TESTNET_CONFIG = EndpointConfig(
|
|
75
75
|
chain_rpc_url="https://rpc.sepolia.org",
|
|
76
76
|
api_base_url="https://api.starknet.sepolia.extended.exchange/api/v1",
|
|
77
|
-
stream_url="wss://
|
|
77
|
+
stream_url="wss://starknet.sepolia.extended.exchange/stream.extended.exchange/v1",
|
|
78
78
|
onboarding_url="https://api.starknet.sepolia.extended.exchange",
|
|
79
79
|
signing_domain="starknet.sepolia.extended.exchange",
|
|
80
80
|
collateral_asset_contract="0x0C9165046063B7bCD05C6924Bbe05ed535c140a1",
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/markets.py
RENAMED
|
@@ -76,6 +76,9 @@ class TradingConfigModel(X10BaseModel):
|
|
|
76
76
|
else:
|
|
77
77
|
return Decimal(0)
|
|
78
78
|
|
|
79
|
+
def round_price(self, price: Decimal, rounding_direction: str = ROUND_CEILING) -> Decimal:
|
|
80
|
+
return self.price_precision * (price / self.price_precision).to_integral_exact(rounding_direction)
|
|
81
|
+
|
|
79
82
|
|
|
80
83
|
class L2ConfigModel(X10BaseModel):
|
|
81
84
|
type: str
|
|
@@ -38,7 +38,7 @@ def create_order_object(
|
|
|
38
38
|
side: OrderSide,
|
|
39
39
|
starknet_domain: StarknetDomain,
|
|
40
40
|
post_only: bool = False,
|
|
41
|
-
|
|
41
|
+
previous_order_external_id: Optional[str] = None,
|
|
42
42
|
expire_time: Optional[datetime] = None,
|
|
43
43
|
order_external_id: Optional[str] = None,
|
|
44
44
|
time_in_force: TimeInForce = TimeInForce.GTT,
|
|
@@ -66,7 +66,7 @@ def create_order_object(
|
|
|
66
66
|
exact_only=False,
|
|
67
67
|
expire_time=expire_time,
|
|
68
68
|
post_only=post_only,
|
|
69
|
-
previous_order_external_id=
|
|
69
|
+
previous_order_external_id=previous_order_external_id,
|
|
70
70
|
order_external_id=order_external_id,
|
|
71
71
|
time_in_force=time_in_force,
|
|
72
72
|
self_trade_protection_level=self_trade_protection_level,
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/orderbook.py
RENAMED
|
@@ -31,9 +31,10 @@ class OrderBook:
|
|
|
31
31
|
async def create(
|
|
32
32
|
endpoint_config: EndpointConfig,
|
|
33
33
|
market_name: str,
|
|
34
|
-
best_ask_change_callback: Callable[[OrderBookEntry], None] | None = None,
|
|
35
|
-
best_bid_change_callback: Callable[[OrderBookEntry], None] | None = None,
|
|
34
|
+
best_ask_change_callback: Callable[[OrderBookEntry | None], None] | None = None,
|
|
35
|
+
best_bid_change_callback: Callable[[OrderBookEntry | None], None] | None = None,
|
|
36
36
|
start=False,
|
|
37
|
+
depth_1: bool = False,
|
|
37
38
|
) -> "OrderBook":
|
|
38
39
|
ob = OrderBook(
|
|
39
40
|
endpoint_config,
|
|
@@ -49,8 +50,8 @@ class OrderBook:
|
|
|
49
50
|
self,
|
|
50
51
|
endpoint_config: EndpointConfig,
|
|
51
52
|
market_name: str,
|
|
52
|
-
best_ask_change_callback: Callable[[OrderBookEntry], None] | None = None,
|
|
53
|
-
best_bid_change_callback: Callable[[OrderBookEntry], None] | None = None,
|
|
53
|
+
best_ask_change_callback: Callable[[OrderBookEntry | None], None] | None = None,
|
|
54
|
+
best_bid_change_callback: Callable[[OrderBookEntry | None], None] | None = None,
|
|
54
55
|
) -> None:
|
|
55
56
|
self.__stream_client = PerpetualStreamClient(api_url=endpoint_config.stream_url)
|
|
56
57
|
self.__market_name = market_name
|
|
@@ -64,7 +65,7 @@ class OrderBook:
|
|
|
64
65
|
best_bid_before_update = self.best_bid()
|
|
65
66
|
for bid in data.bid:
|
|
66
67
|
if bid.price in self._bid_prices:
|
|
67
|
-
existing_bid_entry: OrderBookEntry = self._bid_prices
|
|
68
|
+
existing_bid_entry: OrderBookEntry = self._bid_prices[bid.price]
|
|
68
69
|
existing_bid_entry.amount = existing_bid_entry.amount + bid.qty
|
|
69
70
|
if existing_bid_entry.amount == 0:
|
|
70
71
|
del self._bid_prices[bid.price]
|
|
@@ -74,14 +75,14 @@ class OrderBook:
|
|
|
74
75
|
amount=bid.qty,
|
|
75
76
|
)
|
|
76
77
|
now_best_bid = self.best_bid()
|
|
77
|
-
if
|
|
78
|
+
if best_bid_before_update != now_best_bid:
|
|
78
79
|
if self.best_bid_change_callback:
|
|
79
80
|
self.best_bid_change_callback(now_best_bid)
|
|
80
81
|
|
|
81
82
|
best_ask_before_update = self.best_ask()
|
|
82
83
|
for ask in data.ask:
|
|
83
84
|
if ask.price in self._ask_prices:
|
|
84
|
-
existing_ask_entry: OrderBookEntry = self._ask_prices
|
|
85
|
+
existing_ask_entry: OrderBookEntry = self._ask_prices[ask.price]
|
|
85
86
|
existing_ask_entry.amount = existing_ask_entry.amount + ask.qty
|
|
86
87
|
if existing_ask_entry.amount == 0:
|
|
87
88
|
del self._ask_prices[ask.price]
|
|
@@ -91,7 +92,7 @@ class OrderBook:
|
|
|
91
92
|
amount=ask.qty,
|
|
92
93
|
)
|
|
93
94
|
now_best_ask = self.best_ask()
|
|
94
|
-
if
|
|
95
|
+
if best_ask_before_update != now_best_ask:
|
|
95
96
|
if self.best_ask_change_callback:
|
|
96
97
|
self.best_ask_change_callback(now_best_ask)
|
|
97
98
|
|
|
@@ -111,12 +112,20 @@ class OrderBook:
|
|
|
111
112
|
loop = asyncio.get_running_loop()
|
|
112
113
|
|
|
113
114
|
async def inner():
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
while True:
|
|
116
|
+
print(f"Connecting to orderbook stream for market: {self.__market_name}")
|
|
117
|
+
async with self.__stream_client.subscribe_to_orderbooks(self.__market_name) as stream:
|
|
118
|
+
async for event in stream:
|
|
119
|
+
if event.type == StreamDataType.SNAPSHOT.value:
|
|
120
|
+
if not event.data:
|
|
121
|
+
continue
|
|
122
|
+
self.init_orderbook(event.data)
|
|
123
|
+
elif event.type == StreamDataType.DELTA.value:
|
|
124
|
+
if not event.data:
|
|
125
|
+
continue
|
|
126
|
+
self.update_orderbook(event.data)
|
|
127
|
+
print("Orderbook stream disconnected, reconnecting...")
|
|
128
|
+
await asyncio.sleep(1)
|
|
120
129
|
|
|
121
130
|
self.__task = loop.create_task(inner())
|
|
122
131
|
return self.__task
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/positions.py
RENAMED
|
@@ -44,8 +44,8 @@ class PositionHistoryModel(X10BaseModel):
|
|
|
44
44
|
leverage: Decimal
|
|
45
45
|
size: Decimal
|
|
46
46
|
open_price: Decimal
|
|
47
|
-
exit_type: Optional[ExitType]
|
|
48
|
-
exit_price: Optional[Decimal]
|
|
47
|
+
exit_type: Optional[ExitType] = None
|
|
48
|
+
exit_price: Optional[Decimal] = None
|
|
49
49
|
realised_pnl: Decimal
|
|
50
50
|
created_time: int
|
|
51
|
-
closed_time: Optional[int]
|
|
51
|
+
closed_time: Optional[int] = None
|
|
@@ -25,12 +25,12 @@ from x10.perpetual.trading_client.order_management_module import OrderManagement
|
|
|
25
25
|
from x10.utils.http import WrappedStreamResponse
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
def condition_to_awaitable(condition: asyncio.Condition) -> Awaitable:
|
|
29
29
|
async def __inner():
|
|
30
30
|
async with condition:
|
|
31
31
|
await condition.wait()
|
|
32
32
|
|
|
33
|
-
return
|
|
33
|
+
return __inner()
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class TimedOpenOrderModel(OpenOrderModel):
|
|
@@ -74,6 +74,10 @@ class CancelWaiter:
|
|
|
74
74
|
|
|
75
75
|
class BlockingTradingClient:
|
|
76
76
|
def __init__(self, endpoint_config: EndpointConfig, account: StarkPerpetualAccount):
|
|
77
|
+
if not asyncio.get_event_loop().is_running():
|
|
78
|
+
raise RuntimeError(
|
|
79
|
+
"BlockingTradingClient must be initialized from an async function, use BlockingTradingClient.create()"
|
|
80
|
+
)
|
|
77
81
|
self.__endpoint_config = endpoint_config
|
|
78
82
|
self.__account = account
|
|
79
83
|
self.__market_module = MarketsInformationModule(endpoint_config, api_key=account.api_key)
|
|
@@ -85,14 +89,19 @@ class BlockingTradingClient:
|
|
|
85
89
|
PerpetualStreamConnection[WrappedStreamResponse[AccountStreamDataModel]],
|
|
86
90
|
] = None
|
|
87
91
|
self.__order_waiters: Dict[str, OrderWaiter] = {}
|
|
88
|
-
self.__cancel_waiters: Dict[
|
|
89
|
-
self.
|
|
90
|
-
|
|
92
|
+
self.__cancel_waiters: Dict[str, CancelWaiter] = {}
|
|
93
|
+
self.__stream_task = asyncio.create_task(self.___order_stream())
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
async def create(endpoint_config: EndpointConfig, account: StarkPerpetualAccount) -> "BlockingTradingClient":
|
|
97
|
+
client = BlockingTradingClient(endpoint_config, account)
|
|
98
|
+
await client.__stream_client.subscribe_to_account_updates(account.api_key)
|
|
99
|
+
return client
|
|
91
100
|
|
|
92
|
-
async def
|
|
93
|
-
if
|
|
101
|
+
async def __handle_cancel(self, order_external_id: str):
|
|
102
|
+
if order_external_id not in self.__cancel_waiters:
|
|
94
103
|
return
|
|
95
|
-
cancel_waiter = self.__cancel_waiters.get(
|
|
104
|
+
cancel_waiter = self.__cancel_waiters.get(order_external_id)
|
|
96
105
|
if not cancel_waiter:
|
|
97
106
|
return
|
|
98
107
|
if cancel_waiter.condition:
|
|
@@ -100,57 +109,61 @@ class BlockingTradingClient:
|
|
|
100
109
|
cancel_waiter.end_nanos = time.time_ns()
|
|
101
110
|
cancel_waiter.condition.notify_all()
|
|
102
111
|
|
|
103
|
-
async def
|
|
104
|
-
if order.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
order_waiter.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
async def __handle_update(self, order: OpenOrderModel):
|
|
113
|
+
if order.status == OrderStatus.NEW.value:
|
|
114
|
+
if order.external_id not in self.__order_waiters:
|
|
115
|
+
return
|
|
116
|
+
order_waiter = self.__order_waiters.get(order.external_id)
|
|
117
|
+
if not order_waiter:
|
|
118
|
+
return
|
|
119
|
+
if order_waiter.condition:
|
|
120
|
+
async with order_waiter.condition:
|
|
121
|
+
order_waiter.open_order = TimedOpenOrderModel(
|
|
122
|
+
start_nanos=order_waiter.start_nanos,
|
|
123
|
+
end_nanos=time.time_ns(),
|
|
124
|
+
open_order=order,
|
|
125
|
+
)
|
|
126
|
+
order_waiter.condition.notify_all()
|
|
127
|
+
|
|
128
|
+
async def __handle_order(self, order: OpenOrderModel):
|
|
119
129
|
if order.status == OrderStatus.CANCELLED.value:
|
|
120
|
-
await self.
|
|
130
|
+
await self.__handle_cancel(order.external_id)
|
|
121
131
|
else:
|
|
122
|
-
await self.
|
|
132
|
+
await self.__handle_update(order)
|
|
123
133
|
|
|
124
134
|
async def ___order_stream(self):
|
|
135
|
+
self.__account_stream = await self.__stream_client.subscribe_to_account_updates(self.__account.api_key)
|
|
125
136
|
async for event in self.__account_stream:
|
|
126
137
|
if not (event.data and event.data.orders):
|
|
127
138
|
continue
|
|
128
139
|
for order in event.data.orders:
|
|
129
|
-
await self.
|
|
140
|
+
await self.__handle_order(order)
|
|
141
|
+
print("Order stream closed, reconnecting...")
|
|
142
|
+
await self.___order_stream()
|
|
130
143
|
|
|
131
|
-
async def cancel_order(self,
|
|
144
|
+
async def cancel_order(self, order_external_id: str) -> TimedCancel:
|
|
132
145
|
awaitable: Awaitable
|
|
133
|
-
if
|
|
134
|
-
awaitable = condition_to_awaitable(self.__cancel_waiters[
|
|
146
|
+
if order_external_id in self.__cancel_waiters:
|
|
147
|
+
awaitable = condition_to_awaitable(self.__cancel_waiters[order_external_id].condition)
|
|
135
148
|
else:
|
|
136
|
-
self.__cancel_waiters[
|
|
149
|
+
self.__cancel_waiters[order_external_id] = CancelWaiter(
|
|
137
150
|
asyncio.Condition(), start_nanos=time.time_ns(), end_nanos=None
|
|
138
151
|
)
|
|
139
|
-
cancel_task = asyncio.create_task(self.__orders_module.
|
|
152
|
+
cancel_task = asyncio.create_task(self.__orders_module.cancel_order_by_external_id(order_external_id))
|
|
140
153
|
awaitable = asyncio.gather(
|
|
141
154
|
cancel_task,
|
|
142
|
-
asyncio.wait_for(condition_to_awaitable(self.__cancel_waiters[
|
|
155
|
+
asyncio.wait_for(condition_to_awaitable(self.__cancel_waiters[order_external_id].condition), 5),
|
|
143
156
|
return_exceptions=False,
|
|
144
157
|
)
|
|
145
158
|
|
|
146
|
-
cancel_waiter = self.__cancel_waiters[
|
|
159
|
+
cancel_waiter = self.__cancel_waiters[order_external_id]
|
|
147
160
|
end_nanos = None
|
|
148
161
|
if cancel_waiter.end_nanos:
|
|
149
162
|
end_nanos = cancel_waiter.end_nanos
|
|
150
163
|
else:
|
|
151
164
|
await awaitable
|
|
152
|
-
end_nanos = self.__cancel_waiters[
|
|
153
|
-
del self.__cancel_waiters[
|
|
165
|
+
end_nanos = self.__cancel_waiters[order_external_id].end_nanos
|
|
166
|
+
del self.__cancel_waiters[order_external_id]
|
|
154
167
|
end_nanos = cast(int, end_nanos)
|
|
155
168
|
return TimedCancel(
|
|
156
169
|
start_nanos=cancel_waiter.start_nanos,
|
|
@@ -161,9 +174,26 @@ class BlockingTradingClient:
|
|
|
161
174
|
async def get_markets(self) -> Dict[str, MarketModel]:
|
|
162
175
|
if not self.__markets:
|
|
163
176
|
markets = await self.__market_module.get_markets()
|
|
164
|
-
|
|
177
|
+
market_data = markets.data
|
|
178
|
+
if not market_data:
|
|
179
|
+
raise ValueError("Core market data is empty, check your connection or API key.")
|
|
180
|
+
self.__markets = {m.name: m for m in market_data}
|
|
165
181
|
return self.__markets
|
|
166
182
|
|
|
183
|
+
async def mass_cancel(
|
|
184
|
+
self,
|
|
185
|
+
order_ids: list[int] | None = None,
|
|
186
|
+
external_order_ids: list[str] | None = None,
|
|
187
|
+
markets: list[str] | None = None,
|
|
188
|
+
cancel_all: bool = False,
|
|
189
|
+
) -> None:
|
|
190
|
+
await self.__orders_module.mass_cancel(
|
|
191
|
+
order_ids=order_ids,
|
|
192
|
+
external_order_ids=external_order_ids,
|
|
193
|
+
markets=markets,
|
|
194
|
+
cancel_all=cancel_all,
|
|
195
|
+
)
|
|
196
|
+
|
|
167
197
|
async def create_and_place_order(
|
|
168
198
|
self,
|
|
169
199
|
market_name: str,
|
|
@@ -171,19 +201,13 @@ class BlockingTradingClient:
|
|
|
171
201
|
price: Decimal,
|
|
172
202
|
side: OrderSide,
|
|
173
203
|
post_only: bool = False,
|
|
174
|
-
|
|
204
|
+
previous_order_external_id: str | None = None,
|
|
205
|
+
external_id: str | None = None,
|
|
175
206
|
) -> TimedOpenOrderModel:
|
|
176
207
|
market = (await self.get_markets()).get(market_name)
|
|
177
208
|
if not market:
|
|
178
209
|
raise ValueError(f"Market '{market_name}' not found.")
|
|
179
210
|
|
|
180
|
-
if not self.__account_stream:
|
|
181
|
-
await self.__stream_lock.acquire()
|
|
182
|
-
if not self.__account_stream:
|
|
183
|
-
self.__account_stream = await self.__stream_client.subscribe_to_account_updates(self.__account.api_key)
|
|
184
|
-
self.__orders_task = asyncio.create_task(self.___order_stream())
|
|
185
|
-
self.__stream_lock.release()
|
|
186
|
-
|
|
187
211
|
order: PerpetualOrderModel = create_order_object(
|
|
188
212
|
account=self.__account,
|
|
189
213
|
market=market,
|
|
@@ -191,8 +215,9 @@ class BlockingTradingClient:
|
|
|
191
215
|
price=price,
|
|
192
216
|
side=side,
|
|
193
217
|
post_only=post_only,
|
|
194
|
-
|
|
218
|
+
previous_order_external_id=previous_order_external_id,
|
|
195
219
|
starknet_domain=self.__endpoint_config.starknet_domain,
|
|
220
|
+
order_external_id=external_id,
|
|
196
221
|
)
|
|
197
222
|
|
|
198
223
|
if order.id in self.__order_waiters:
|
|
@@ -43,10 +43,8 @@ class PerpetualStreamConnection(Generic[StreamMsgResponseType]):
|
|
|
43
43
|
|
|
44
44
|
async def close(self):
|
|
45
45
|
assert self.__websocket is not None
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
await self.__websocket.close()
|
|
49
|
-
|
|
46
|
+
if not self.__websocket.closed:
|
|
47
|
+
await self.__websocket.close()
|
|
50
48
|
LOGGER.debug("Stream closed: %s", self.__stream_url)
|
|
51
49
|
|
|
52
50
|
@property
|
|
@@ -67,8 +65,10 @@ class PerpetualStreamConnection(Generic[StreamMsgResponseType]):
|
|
|
67
65
|
|
|
68
66
|
if self.__websocket.closed:
|
|
69
67
|
raise StopAsyncIteration
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
try:
|
|
69
|
+
return await self.__receive()
|
|
70
|
+
except websockets.ConnectionClosed:
|
|
71
|
+
raise StopAsyncIteration from None
|
|
72
72
|
|
|
73
73
|
async def __receive(self) -> StreamMsgResponseType:
|
|
74
74
|
assert self.__websocket is not None
|
|
@@ -24,12 +24,12 @@ class PerpetualStreamClient:
|
|
|
24
24
|
|
|
25
25
|
self.__api_url = api_url
|
|
26
26
|
|
|
27
|
-
def subscribe_to_orderbooks(self, market_name: Optional[str] = None):
|
|
27
|
+
def subscribe_to_orderbooks(self, market_name: Optional[str] = None, depth: int | None = None):
|
|
28
28
|
"""
|
|
29
29
|
https://api.docs.extended.exchange/#orderbooks-stream
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
url = self.__get_url("/orderbooks/<market?>", market=market_name)
|
|
32
|
+
url = self.__get_url("/orderbooks/<market?>" + (f"?depth={depth}" if depth else ""), market=market_name)
|
|
33
33
|
return self.__connect(url, WrappedStreamResponse[OrderbookUpdateModel])
|
|
34
34
|
|
|
35
35
|
def subscribe_to_public_trades(self, market_name: Optional[str] = None):
|
|
@@ -10,10 +10,10 @@ LOGGER = get_logger(__name__)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class _MassCancelRequestModel(X10BaseModel):
|
|
13
|
-
order_ids: Optional[List[int]]
|
|
14
|
-
external_order_ids: Optional[List[str]]
|
|
15
|
-
markets: Optional[List[str]]
|
|
16
|
-
cancel_all: Optional[bool]
|
|
13
|
+
order_ids: Optional[List[int]] = None
|
|
14
|
+
external_order_ids: Optional[List[str]] = None
|
|
15
|
+
markets: Optional[List[str]] = None
|
|
16
|
+
cancel_all: Optional[bool] = None
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class OrderManagementModule(BaseModule):
|
|
@@ -52,6 +52,7 @@ class PerpetualTradingClient:
|
|
|
52
52
|
expire_time: Optional[datetime] = None,
|
|
53
53
|
time_in_force: TimeInForce = TimeInForce.GTT,
|
|
54
54
|
self_trade_protection_level: SelfTradeProtectionLevel = SelfTradeProtectionLevel.ACCOUNT,
|
|
55
|
+
external_id: Optional[str] = None,
|
|
55
56
|
) -> WrappedApiResponse[PlacedOrderModel]:
|
|
56
57
|
if not self.__stark_account:
|
|
57
58
|
raise ValueError("Stark account is not set")
|
|
@@ -74,11 +75,12 @@ class PerpetualTradingClient:
|
|
|
74
75
|
price=price,
|
|
75
76
|
side=side,
|
|
76
77
|
post_only=post_only,
|
|
77
|
-
|
|
78
|
+
previous_order_external_id=previous_order_id,
|
|
78
79
|
expire_time=expire_time,
|
|
79
80
|
time_in_force=time_in_force,
|
|
80
81
|
self_trade_protection_level=self_trade_protection_level,
|
|
81
82
|
starknet_domain=self.__config.starknet_domain,
|
|
83
|
+
order_external_id=external_id,
|
|
82
84
|
)
|
|
83
85
|
return await self.__order_management_module.place_order(order)
|
|
84
86
|
|
|
@@ -55,13 +55,13 @@ def create_transfer_object(
|
|
|
55
55
|
domain_version=starknet_domain.version,
|
|
56
56
|
domain_chain_id=starknet_domain.chain_id,
|
|
57
57
|
domain_revision=starknet_domain.revision,
|
|
58
|
-
collateral_id=
|
|
58
|
+
collateral_id=int(config.collateral_asset_on_chain_id, base=16),
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
(transfer_signature_r, transfer_signature_s) = stark_account.sign(transfer_hash)
|
|
62
62
|
settlement = StarkTransferSettlement(
|
|
63
63
|
amount=int(stark_amount),
|
|
64
|
-
asset_id=int(config.
|
|
64
|
+
asset_id=int(config.collateral_asset_on_chain_id, base=16),
|
|
65
65
|
expiration_timestamp=expiration_timestamp,
|
|
66
66
|
nonce=nonce,
|
|
67
67
|
receiver_position_id=to_vault,
|
|
@@ -39,6 +39,7 @@ class AccountRegistration:
|
|
|
39
39
|
tos_accepted: bool
|
|
40
40
|
time: datetime
|
|
41
41
|
action: str
|
|
42
|
+
host: str
|
|
42
43
|
|
|
43
44
|
def __post_init__(self):
|
|
44
45
|
self.time_string = self.time.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -52,6 +53,7 @@ class AccountRegistration:
|
|
|
52
53
|
"tosAccepted": self.tos_accepted,
|
|
53
54
|
"time": self.time_string,
|
|
54
55
|
"action": self.action,
|
|
56
|
+
"host": self.host,
|
|
55
57
|
}
|
|
56
58
|
types = {
|
|
57
59
|
"EIP712Domain": [
|
|
@@ -63,6 +65,7 @@ class AccountRegistration:
|
|
|
63
65
|
{"name": "tosAccepted", "type": "bool"},
|
|
64
66
|
{"name": "time", "type": "string"},
|
|
65
67
|
{"name": "action", "type": "string"},
|
|
68
|
+
{"name": "host", "type": "string"},
|
|
66
69
|
],
|
|
67
70
|
}
|
|
68
71
|
primary_type = "AccountRegistration"
|
|
@@ -81,6 +84,7 @@ class AccountRegistration:
|
|
|
81
84
|
"tosAccepted": self.tos_accepted,
|
|
82
85
|
"time": self.time_string,
|
|
83
86
|
"action": self.action,
|
|
87
|
+
"host": self.host,
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
|
|
@@ -127,7 +131,7 @@ class OnboardingPayLoad:
|
|
|
127
131
|
|
|
128
132
|
|
|
129
133
|
def get_registration_struct_to_sign(
|
|
130
|
-
account_index: int, address: str, timestamp: datetime, action: str
|
|
134
|
+
account_index: int, address: str, timestamp: datetime, action: str, host: str
|
|
131
135
|
) -> AccountRegistration:
|
|
132
136
|
return AccountRegistration(
|
|
133
137
|
account_index=account_index,
|
|
@@ -135,6 +139,7 @@ def get_registration_struct_to_sign(
|
|
|
135
139
|
tos_accepted=True,
|
|
136
140
|
time=timestamp,
|
|
137
141
|
action=action,
|
|
142
|
+
host=host,
|
|
138
143
|
)
|
|
139
144
|
|
|
140
145
|
|
|
@@ -180,6 +185,7 @@ def get_onboarding_payload(
|
|
|
180
185
|
account: LocalAccount,
|
|
181
186
|
signing_domain: str,
|
|
182
187
|
key_pair: StarkKeyPair,
|
|
188
|
+
host: str,
|
|
183
189
|
time: datetime | None = None,
|
|
184
190
|
referral_code: str | None = None,
|
|
185
191
|
) -> OnboardingPayLoad:
|
|
@@ -187,11 +193,11 @@ def get_onboarding_payload(
|
|
|
187
193
|
time = datetime.now(timezone.utc)
|
|
188
194
|
|
|
189
195
|
registration_payload = get_registration_struct_to_sign(
|
|
190
|
-
account_index=0, address=account.address, timestamp=time, action=register_action
|
|
196
|
+
account_index=0, address=account.address, timestamp=time, action=register_action, host=host
|
|
191
197
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
payload = registration_payload.to_signable_message(signing_domain=signing_domain)
|
|
199
|
+
l1_signature = account.sign_message(payload).signature.hex()
|
|
200
|
+
|
|
195
201
|
l2_message = pedersen_hash(int(account.address, 16), key_pair.public)
|
|
196
202
|
l2_r, l2_s = stark_sign(msg_hash=l2_message, private_key=key_pair.private)
|
|
197
203
|
|
|
@@ -207,16 +213,18 @@ def get_onboarding_payload(
|
|
|
207
213
|
|
|
208
214
|
|
|
209
215
|
def get_sub_account_creation_payload(
|
|
210
|
-
account_index: int,
|
|
216
|
+
account_index: int,
|
|
217
|
+
l1_address: str,
|
|
218
|
+
key_pair: StarkKeyPair,
|
|
219
|
+
description: str,
|
|
220
|
+
host: str,
|
|
221
|
+
time: datetime | None = None,
|
|
211
222
|
):
|
|
212
223
|
if time is None:
|
|
213
224
|
time = datetime.now(timezone.utc)
|
|
214
225
|
|
|
215
226
|
registration_payload = get_registration_struct_to_sign(
|
|
216
|
-
account_index=account_index,
|
|
217
|
-
address=l1_address,
|
|
218
|
-
timestamp=time,
|
|
219
|
-
action=sub_account_action,
|
|
227
|
+
account_index=account_index, address=l1_address, timestamp=time, action=sub_account_action, host=host
|
|
220
228
|
)
|
|
221
229
|
|
|
222
230
|
l2_message = pedersen_hash(int(l1_address, 16), key_pair.public)
|
|
@@ -78,6 +78,7 @@ class UserClient:
|
|
|
78
78
|
signing_domain=self.__endpoint_config.signing_domain,
|
|
79
79
|
key_pair=key_pair,
|
|
80
80
|
referral_code=referral_code,
|
|
81
|
+
host=self.__endpoint_config.onboarding_url,
|
|
81
82
|
)
|
|
82
83
|
url = self._get_url(self.__endpoint_config.onboarding_url, path="/auth/onboard")
|
|
83
84
|
onboarding_response = await send_post_request(
|
|
@@ -111,6 +112,7 @@ class UserClient:
|
|
|
111
112
|
l1_address=signing_account.address,
|
|
112
113
|
key_pair=key_pair,
|
|
113
114
|
description=description,
|
|
115
|
+
host=self.__endpoint_config.onboarding_url,
|
|
114
116
|
)
|
|
115
117
|
headers = {
|
|
116
118
|
L1_AUTH_SIGNATURE_HEADER: l1_signature.signature.hex(),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/__init__.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/accounts.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/amounts.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/assets.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/balances.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/fees.py
RENAMED
|
File without changes
|
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/orderbooks.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/orders.py
RENAMED
|
File without changes
|
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/trades.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/transfers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/perpetual/withdrawals.py
RENAMED
|
File without changes
|
{x10_python_trading_starknet-0.0.2 → x10_python_trading_starknet-0.0.5}/x10/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|