async-hyperliquid 0.4.5__tar.gz → 0.4.6__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.
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/PKG-INFO +1 -1
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/pyproject.toml +1 -1
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/core.py +180 -35
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/info.py +53 -6
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/orders.py +62 -12
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/signing.py +8 -5
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/LICENSE +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/README.md +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/__init__.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/__init__.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/actions.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/async_api.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/async_hyperliquid.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/exchange.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/info.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/__init__.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/constants.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/decorators.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/miscs.py +0 -0
- {async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/types.py +0 -0
{async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/core.py
RENAMED
|
@@ -34,6 +34,9 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
34
34
|
coin_symbols: dict[str, str]
|
|
35
35
|
asset_sz_decimals: dict[int, int]
|
|
36
36
|
spot_tokens: dict[str, SpotTokenMeta]
|
|
37
|
+
_meta_init_lock: asyncio.Lock
|
|
38
|
+
_meta_init_task: asyncio.Task[None] | None
|
|
39
|
+
_metas_initialized: bool
|
|
37
40
|
|
|
38
41
|
perp_dexs: list[str]
|
|
39
42
|
|
|
@@ -79,6 +82,9 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
79
82
|
self.coin_symbols = {}
|
|
80
83
|
self.asset_sz_decimals = {}
|
|
81
84
|
self.spot_tokens = {}
|
|
85
|
+
self._meta_init_lock = asyncio.Lock()
|
|
86
|
+
self._meta_init_task = None
|
|
87
|
+
self._metas_initialized = False
|
|
82
88
|
|
|
83
89
|
self.vault = vault
|
|
84
90
|
self.expires: int | None = None
|
|
@@ -124,23 +130,50 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
124
130
|
|
|
125
131
|
self.evm_exchange = EVMExchange(rpc_url, private_key)
|
|
126
132
|
|
|
127
|
-
def _init_perp_meta(
|
|
133
|
+
def _init_perp_meta(
|
|
134
|
+
self,
|
|
135
|
+
meta: PerpMeta,
|
|
136
|
+
offset: int,
|
|
137
|
+
*,
|
|
138
|
+
coin_assets: dict[str, int] | None = None,
|
|
139
|
+
coin_names: dict[str, str] | None = None,
|
|
140
|
+
asset_sz_decimals: dict[int, int] | None = None,
|
|
141
|
+
) -> None:
|
|
142
|
+
coin_assets = self.coin_assets if coin_assets is None else coin_assets
|
|
143
|
+
coin_names = self.coin_names if coin_names is None else coin_names
|
|
144
|
+
asset_sz_decimals = (
|
|
145
|
+
self.asset_sz_decimals if asset_sz_decimals is None else asset_sz_decimals
|
|
146
|
+
)
|
|
128
147
|
for asset, info in enumerate(meta["universe"]):
|
|
129
148
|
asset += offset
|
|
130
149
|
asset_name = info["name"]
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
coin_assets[asset_name] = asset
|
|
151
|
+
coin_names[asset_name] = asset_name
|
|
152
|
+
asset_sz_decimals[asset] = info["szDecimals"]
|
|
134
153
|
|
|
135
|
-
def _init_spot_meta(
|
|
154
|
+
def _init_spot_meta(
|
|
155
|
+
self,
|
|
156
|
+
meta: SpotMeta,
|
|
157
|
+
*,
|
|
158
|
+
coin_assets: dict[str, int] | None = None,
|
|
159
|
+
coin_names: dict[str, str] | None = None,
|
|
160
|
+
asset_sz_decimals: dict[int, int] | None = None,
|
|
161
|
+
spot_tokens: dict[str, SpotTokenMeta] | None = None,
|
|
162
|
+
) -> None:
|
|
163
|
+
coin_assets = self.coin_assets if coin_assets is None else coin_assets
|
|
164
|
+
coin_names = self.coin_names if coin_names is None else coin_names
|
|
165
|
+
asset_sz_decimals = (
|
|
166
|
+
self.asset_sz_decimals if asset_sz_decimals is None else asset_sz_decimals
|
|
167
|
+
)
|
|
168
|
+
spot_tokens = self.spot_tokens if spot_tokens is None else spot_tokens
|
|
136
169
|
tokens = meta["tokens"]
|
|
137
170
|
total_tokens = len(tokens)
|
|
138
171
|
for info in meta["universe"]:
|
|
139
172
|
asset = info["index"] + SPOT_OFFSET
|
|
140
173
|
asset_name = info["name"]
|
|
141
174
|
|
|
142
|
-
|
|
143
|
-
|
|
175
|
+
coin_assets[asset_name] = asset
|
|
176
|
+
coin_names[asset_name] = asset_name
|
|
144
177
|
|
|
145
178
|
base, quote = info["tokens"]
|
|
146
179
|
if not 0 <= base < total_tokens or not 0 <= quote < total_tokens:
|
|
@@ -151,19 +184,58 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
151
184
|
quote_info = tokens[quote]
|
|
152
185
|
quote_name = quote_info["name"]
|
|
153
186
|
name = f"{base_name}/{quote_name}"
|
|
154
|
-
|
|
155
|
-
|
|
187
|
+
coin_names.setdefault(name, asset_name)
|
|
188
|
+
coin_names.setdefault(quote_name, quote_name)
|
|
156
189
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
190
|
+
asset_sz_decimals[asset] = base_info["szDecimals"]
|
|
191
|
+
spot_tokens[asset_name] = base_info
|
|
192
|
+
spot_tokens.setdefault(quote_name, quote_info)
|
|
160
193
|
|
|
161
|
-
def
|
|
162
|
-
|
|
163
|
-
v: k for k, v in self.coin_names.items() if not k.startswith("@")
|
|
164
|
-
}
|
|
194
|
+
def _build_coin_symbols(self, coin_names: dict[str, str]) -> dict[str, str]:
|
|
195
|
+
return {v: k for k, v in coin_names.items() if not k.startswith("@")}
|
|
165
196
|
|
|
166
|
-
|
|
197
|
+
def _get_meta_init_lock(self) -> asyncio.Lock:
|
|
198
|
+
lock = getattr(self, "_meta_init_lock", None)
|
|
199
|
+
if lock is None:
|
|
200
|
+
lock = asyncio.Lock()
|
|
201
|
+
self._meta_init_lock = lock
|
|
202
|
+
return lock
|
|
203
|
+
|
|
204
|
+
def _lookup_cached_coin_name(self, coin: str) -> str | None:
|
|
205
|
+
coin_names = getattr(self, "coin_names", {})
|
|
206
|
+
if coin in coin_names:
|
|
207
|
+
return coin_names[coin]
|
|
208
|
+
|
|
209
|
+
coin_assets = getattr(self, "coin_assets", {})
|
|
210
|
+
if coin in coin_assets:
|
|
211
|
+
return coin
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def _lookup_cached_asset_id(self, coin: str) -> int | None:
|
|
216
|
+
coin_assets = getattr(self, "coin_assets", {})
|
|
217
|
+
asset = coin_assets.get(coin)
|
|
218
|
+
if asset is not None:
|
|
219
|
+
return asset
|
|
220
|
+
|
|
221
|
+
coin_name = getattr(self, "coin_names", {}).get(coin)
|
|
222
|
+
if coin_name is None:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
return coin_assets.get(coin_name)
|
|
226
|
+
|
|
227
|
+
def _lookup_cached_asset(self, coin: str) -> tuple[str, int] | None:
|
|
228
|
+
asset = self._lookup_cached_asset_id(coin)
|
|
229
|
+
if asset is None:
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
coin_name = self._lookup_cached_coin_name(coin)
|
|
233
|
+
if coin_name is None:
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
return coin_name, asset
|
|
237
|
+
|
|
238
|
+
async def _refresh_metas(self) -> None:
|
|
167
239
|
# TODO: Add HIP-4 outcome meta initialization from the spot info
|
|
168
240
|
# `outcomeMeta` endpoint once outcomes move beyond testnet-only rollout.
|
|
169
241
|
# Outcome asset IDs do not follow the current perp/spot offset scheme:
|
|
@@ -179,17 +251,36 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
179
251
|
meta_task, spot_meta_task, all_dex_names_task
|
|
180
252
|
)
|
|
181
253
|
|
|
182
|
-
|
|
183
|
-
|
|
254
|
+
coin_assets: dict[str, int] = {}
|
|
255
|
+
coin_names: dict[str, str] = {}
|
|
256
|
+
asset_sz_decimals: dict[int, int] = {}
|
|
257
|
+
spot_tokens: dict[str, SpotTokenMeta] = {}
|
|
258
|
+
|
|
259
|
+
self._init_perp_meta(
|
|
260
|
+
meta,
|
|
261
|
+
0,
|
|
262
|
+
coin_assets=coin_assets,
|
|
263
|
+
coin_names=coin_names,
|
|
264
|
+
asset_sz_decimals=asset_sz_decimals,
|
|
265
|
+
)
|
|
266
|
+
self._init_spot_meta(
|
|
267
|
+
spot_meta,
|
|
268
|
+
coin_assets=coin_assets,
|
|
269
|
+
coin_names=coin_names,
|
|
270
|
+
asset_sz_decimals=asset_sz_decimals,
|
|
271
|
+
spot_tokens=spot_tokens,
|
|
272
|
+
)
|
|
184
273
|
|
|
274
|
+
dex_index_by_name = {
|
|
275
|
+
name: idx for idx, name in enumerate(all_dex_names) if name is not None
|
|
276
|
+
}
|
|
185
277
|
dex_meta_tasks = []
|
|
186
278
|
dex_indices = []
|
|
187
279
|
for dex in self.perp_dexs:
|
|
188
280
|
if dex == "":
|
|
189
281
|
continue
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
except ValueError:
|
|
282
|
+
idx = dex_index_by_name.get(dex)
|
|
283
|
+
if idx is None:
|
|
193
284
|
continue
|
|
194
285
|
|
|
195
286
|
if idx > 0:
|
|
@@ -200,9 +291,49 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
200
291
|
dex_metas = await asyncio.gather(*dex_meta_tasks)
|
|
201
292
|
for idx, dex_meta in zip(dex_indices, dex_metas):
|
|
202
293
|
dex_asset_offset = PERP_DEX_OFFSET + (idx - 1) * 10000
|
|
203
|
-
self._init_perp_meta(
|
|
294
|
+
self._init_perp_meta(
|
|
295
|
+
dex_meta,
|
|
296
|
+
dex_asset_offset,
|
|
297
|
+
coin_assets=coin_assets,
|
|
298
|
+
coin_names=coin_names,
|
|
299
|
+
asset_sz_decimals=asset_sz_decimals,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
self.coin_assets = coin_assets
|
|
303
|
+
self.coin_names = coin_names
|
|
304
|
+
self.asset_sz_decimals = asset_sz_decimals
|
|
305
|
+
self.spot_tokens = spot_tokens
|
|
306
|
+
self.coin_symbols = self._build_coin_symbols(coin_names)
|
|
307
|
+
self._metas_initialized = True
|
|
308
|
+
|
|
309
|
+
async def _run_meta_refresh(self, *, only_if_missing: bool) -> None:
|
|
310
|
+
if only_if_missing and getattr(self, "_metas_initialized", False):
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
task = getattr(self, "_meta_init_task", None)
|
|
314
|
+
if task is not None and not task.done():
|
|
315
|
+
await task
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
async with self._get_meta_init_lock():
|
|
319
|
+
task = getattr(self, "_meta_init_task", None)
|
|
320
|
+
if task is None or task.done():
|
|
321
|
+
if only_if_missing and getattr(self, "_metas_initialized", False):
|
|
322
|
+
return
|
|
323
|
+
task = asyncio.create_task(self._refresh_metas())
|
|
324
|
+
self._meta_init_task = task
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
await task
|
|
328
|
+
finally:
|
|
329
|
+
if getattr(self, "_meta_init_task", None) is task and task.done():
|
|
330
|
+
self._meta_init_task = None
|
|
331
|
+
|
|
332
|
+
async def _ensure_metas_initialized(self) -> None:
|
|
333
|
+
await self._run_meta_refresh(only_if_missing=True)
|
|
204
334
|
|
|
205
|
-
|
|
335
|
+
async def init_metas(self) -> None:
|
|
336
|
+
await self._run_meta_refresh(only_if_missing=False)
|
|
206
337
|
|
|
207
338
|
async def get_metas(self, perp_only: bool = False) -> Metas:
|
|
208
339
|
metas: Metas = {"perp": {}, "spot": [], "dexs": {}} # type: ignore
|
|
@@ -244,31 +375,45 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
244
375
|
return names
|
|
245
376
|
|
|
246
377
|
async def get_coin_name(self, coin: str) -> str:
|
|
247
|
-
|
|
248
|
-
|
|
378
|
+
coin_name = self._lookup_cached_coin_name(coin)
|
|
379
|
+
if coin_name is not None:
|
|
380
|
+
return coin_name
|
|
249
381
|
|
|
250
|
-
if
|
|
382
|
+
if getattr(self, "_metas_initialized", False):
|
|
383
|
+
await self.init_metas()
|
|
384
|
+
else:
|
|
385
|
+
await self._ensure_metas_initialized()
|
|
386
|
+
coin_name = self._lookup_cached_coin_name(coin)
|
|
387
|
+
if coin_name is None:
|
|
251
388
|
raise ValueError(f"Coin {coin} not found")
|
|
252
389
|
|
|
253
|
-
return
|
|
390
|
+
return coin_name
|
|
254
391
|
|
|
255
392
|
async def get_coin_asset(self, coin: str) -> int:
|
|
393
|
+
cached = self._lookup_cached_asset(coin)
|
|
394
|
+
if cached is not None:
|
|
395
|
+
return cached[1]
|
|
396
|
+
|
|
256
397
|
coin_name = await self.get_coin_name(coin)
|
|
257
|
-
|
|
398
|
+
asset = self.coin_assets.get(coin_name)
|
|
399
|
+
if asset is None:
|
|
258
400
|
raise ValueError(f"Coin {coin}({coin_name}) not found")
|
|
259
401
|
|
|
260
|
-
return
|
|
402
|
+
return asset
|
|
261
403
|
|
|
262
404
|
async def get_coin_symbol(self, coin: str) -> str:
|
|
263
405
|
coin_name = await self.get_coin_name(coin)
|
|
264
406
|
return self.coin_symbols[coin_name]
|
|
265
407
|
|
|
266
408
|
async def get_coin_sz_decimals(self, coin: str) -> int:
|
|
267
|
-
|
|
268
|
-
if
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
409
|
+
cached = self._lookup_cached_asset(coin)
|
|
410
|
+
if cached is not None:
|
|
411
|
+
_, asset = cached
|
|
412
|
+
else:
|
|
413
|
+
coin_name = await self.get_coin_name(coin)
|
|
414
|
+
asset = self.coin_assets.get(coin_name)
|
|
415
|
+
if asset is None:
|
|
416
|
+
raise ValueError(f"Coin {coin}({coin_name}) not found")
|
|
272
417
|
return self.asset_sz_decimals[asset]
|
|
273
418
|
|
|
274
419
|
async def get_token_info(self, coin: str) -> SpotTokenMeta:
|
{async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/info.py
RENAMED
|
@@ -27,22 +27,55 @@ from .core import AsyncHyperliquidCore
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class AsyncHyperliquidInfoClient(AsyncHyperliquidCore):
|
|
30
|
-
|
|
30
|
+
def _get_cached_perp_ctx_index(self, coin_name: str) -> tuple[str, int] | None:
|
|
31
|
+
asset = self._lookup_cached_asset_id(coin_name)
|
|
32
|
+
if asset is None:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
if asset < SPOT_OFFSET:
|
|
36
|
+
return get_coin_dex(coin_name), asset
|
|
37
|
+
|
|
38
|
+
if asset >= PERP_DEX_OFFSET:
|
|
39
|
+
return get_coin_dex(coin_name), (asset - PERP_DEX_OFFSET) % 10000
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def _get_cached_spot_ctx_index(self, coin_name: str) -> int | None:
|
|
44
|
+
asset = self._lookup_cached_asset_id(coin_name)
|
|
45
|
+
if asset is None:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
if SPOT_OFFSET <= asset < PERP_DEX_OFFSET:
|
|
49
|
+
return asset - SPOT_OFFSET
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
async def _get_perp_mark_price(
|
|
54
|
+
self, coin_name: str, dex: str = "", asset_index: int | None = None
|
|
55
|
+
) -> float:
|
|
31
56
|
meta_ctx = await self.info.get_perp_meta_ctx(dex)
|
|
32
57
|
meta = cast(PerpMeta, meta_ctx[0])
|
|
33
58
|
asset_ctxs = cast(list[PerpMetaCtxItem], meta_ctx[1])
|
|
34
59
|
|
|
60
|
+
if asset_index is not None and 0 <= asset_index < len(asset_ctxs):
|
|
61
|
+
return float(asset_ctxs[asset_index]["markPx"])
|
|
62
|
+
|
|
35
63
|
for idx, info in enumerate(meta["universe"]):
|
|
36
64
|
if info["name"] == coin_name:
|
|
37
65
|
return float(asset_ctxs[idx]["markPx"])
|
|
38
66
|
|
|
39
67
|
raise ValueError(f"Coin {coin_name} not found in perp dex '{dex}'")
|
|
40
68
|
|
|
41
|
-
async def _get_spot_mark_price(
|
|
69
|
+
async def _get_spot_mark_price(
|
|
70
|
+
self, coin_name: str, asset_index: int | None = None
|
|
71
|
+
) -> float:
|
|
42
72
|
meta_ctx = await self.info.get_spot_meta_ctx()
|
|
43
73
|
meta = cast(SpotMeta, meta_ctx[0])
|
|
44
74
|
asset_ctxs = cast(list[SpotMetaCtxItem], meta_ctx[1])
|
|
45
75
|
|
|
76
|
+
if asset_index is not None and 0 <= asset_index < len(asset_ctxs):
|
|
77
|
+
return float(asset_ctxs[asset_index]["markPx"])
|
|
78
|
+
|
|
46
79
|
for info in meta["universe"]:
|
|
47
80
|
if info["name"] == coin_name:
|
|
48
81
|
asset_index = info["index"]
|
|
@@ -52,18 +85,32 @@ class AsyncHyperliquidInfoClient(AsyncHyperliquidCore):
|
|
|
52
85
|
|
|
53
86
|
async def get_mark_price(self, coin: str) -> float:
|
|
54
87
|
if ":" in coin:
|
|
88
|
+
cached = self._get_cached_perp_ctx_index(coin)
|
|
89
|
+
if cached is not None:
|
|
90
|
+
dex, asset_index = cached
|
|
91
|
+
return await self._get_perp_mark_price(
|
|
92
|
+
coin, dex, asset_index=asset_index
|
|
93
|
+
)
|
|
55
94
|
return await self._get_perp_mark_price(coin, get_coin_dex(coin))
|
|
56
95
|
|
|
57
96
|
coin_name = await self.get_coin_name(coin)
|
|
58
|
-
|
|
97
|
+
asset = self._lookup_cached_asset_id(coin_name)
|
|
98
|
+
if asset is None:
|
|
59
99
|
raise ValueError(f"Coin {coin}({coin_name}) not found")
|
|
60
100
|
|
|
61
|
-
asset = self.coin_assets[coin_name]
|
|
62
101
|
is_spot_asset = SPOT_OFFSET <= asset < PERP_DEX_OFFSET
|
|
63
102
|
if is_spot_asset:
|
|
64
|
-
return await self._get_spot_mark_price(
|
|
103
|
+
return await self._get_spot_mark_price(
|
|
104
|
+
coin_name, asset_index=asset - SPOT_OFFSET
|
|
105
|
+
)
|
|
65
106
|
|
|
66
|
-
return await self._get_perp_mark_price(
|
|
107
|
+
return await self._get_perp_mark_price(
|
|
108
|
+
coin_name,
|
|
109
|
+
get_coin_dex(coin_name),
|
|
110
|
+
asset_index=(
|
|
111
|
+
asset if asset < SPOT_OFFSET else (asset - PERP_DEX_OFFSET) % 10000
|
|
112
|
+
),
|
|
113
|
+
)
|
|
67
114
|
|
|
68
115
|
async def get_market_price(self, coin: str) -> float:
|
|
69
116
|
warnings.warn(
|
|
@@ -20,14 +20,33 @@ from .info import AsyncHyperliquidInfoClient
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class AsyncHyperliquidOrdersClient(AsyncHyperliquidInfoClient):
|
|
23
|
+
def _round_sz_px_cached(
|
|
24
|
+
self, coin: str, sz: float, px: float
|
|
25
|
+
) -> tuple[int, float, float | int] | None:
|
|
26
|
+
asset = self._lookup_cached_asset_id(coin)
|
|
27
|
+
if asset is None:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
sz_decimals = self.asset_sz_decimals.get(asset)
|
|
31
|
+
if sz_decimals is None:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
is_spot = SPOT_OFFSET <= asset < PERP_DEX_OFFSET
|
|
35
|
+
px_decimals = (6 if not is_spot else 8) - sz_decimals
|
|
36
|
+
return asset, round_float(sz, sz_decimals), round_px(px, px_decimals)
|
|
37
|
+
|
|
23
38
|
async def _round_sz_px(self, coin: str, sz: float, px: float):
|
|
39
|
+
cached = self._round_sz_px_cached(coin, sz, px)
|
|
40
|
+
if cached is not None:
|
|
41
|
+
return cached
|
|
42
|
+
|
|
24
43
|
coin_name = await self.get_coin_name(coin)
|
|
25
|
-
|
|
44
|
+
asset = self.coin_assets.get(coin_name)
|
|
45
|
+
if asset is None:
|
|
26
46
|
raise ValueError(f"Coin {coin}({coin_name}) not found")
|
|
27
47
|
|
|
28
|
-
asset = self.coin_assets[coin_name]
|
|
29
48
|
sz_decimals = self.asset_sz_decimals[asset]
|
|
30
|
-
is_spot =
|
|
49
|
+
is_spot = SPOT_OFFSET <= asset < PERP_DEX_OFFSET
|
|
31
50
|
px_decimals = (6 if not is_spot else 8) - sz_decimals
|
|
32
51
|
return asset, round_float(sz, sz_decimals), round_px(px, px_decimals)
|
|
33
52
|
|
|
@@ -155,12 +174,21 @@ class AsyncHyperliquidOrdersClient(AsyncHyperliquidInfoClient):
|
|
|
155
174
|
return await self.place_orders(reqs, grouping=grouping, builder=builder)
|
|
156
175
|
|
|
157
176
|
async def _get_batch_limit_orders(self, orders: BatchPlaceOrderRequest):
|
|
177
|
+
rounded_orders = [
|
|
178
|
+
self._round_sz_px_cached(o["coin"], o["sz"], o["px"]) for o in orders
|
|
179
|
+
]
|
|
180
|
+
if all(rounded_order is not None for rounded_order in rounded_orders):
|
|
181
|
+
return [
|
|
182
|
+
{**order, "asset": asset, "sz": sz, "px": px}
|
|
183
|
+
for order, (asset, sz, px) in zip(orders, rounded_orders, strict=True)
|
|
184
|
+
]
|
|
185
|
+
|
|
158
186
|
rounded_orders = await asyncio.gather(
|
|
159
187
|
*(self._round_sz_px(o["coin"], o["sz"], o["px"]) for o in orders)
|
|
160
188
|
)
|
|
161
189
|
return [
|
|
162
190
|
{**order, "asset": asset, "sz": sz, "px": px}
|
|
163
|
-
for order, (asset, sz, px) in zip(orders, rounded_orders)
|
|
191
|
+
for order, (asset, sz, px) in zip(orders, rounded_orders, strict=True)
|
|
164
192
|
]
|
|
165
193
|
|
|
166
194
|
async def _get_batch_market_orders(
|
|
@@ -175,15 +203,25 @@ class AsyncHyperliquidOrdersClient(AsyncHyperliquidInfoClient):
|
|
|
175
203
|
slippage_factor = (1 + slippage) if order["is_buy"] else (1 - slippage)
|
|
176
204
|
quoted_prices.append(market_price * slippage_factor)
|
|
177
205
|
|
|
206
|
+
rounded_orders = [
|
|
207
|
+
self._round_sz_px_cached(order["coin"], order["sz"], quoted_price)
|
|
208
|
+
for order, quoted_price in zip(orders, quoted_prices, strict=True)
|
|
209
|
+
]
|
|
210
|
+
if all(rounded_order is not None for rounded_order in rounded_orders):
|
|
211
|
+
return [
|
|
212
|
+
{**order, "asset": asset, "sz": sz, "px": px, "order_type": order_type}
|
|
213
|
+
for order, (asset, sz, px) in zip(orders, rounded_orders, strict=True)
|
|
214
|
+
]
|
|
215
|
+
|
|
178
216
|
rounded_orders = await asyncio.gather(
|
|
179
217
|
*(
|
|
180
218
|
self._round_sz_px(order["coin"], order["sz"], quoted_price)
|
|
181
|
-
for order, quoted_price in zip(orders, quoted_prices)
|
|
219
|
+
for order, quoted_price in zip(orders, quoted_prices, strict=True)
|
|
182
220
|
)
|
|
183
221
|
)
|
|
184
222
|
return [
|
|
185
223
|
{**order, "asset": asset, "sz": sz, "px": px, "order_type": order_type}
|
|
186
|
-
for order, (asset, sz, px) in zip(orders, rounded_orders)
|
|
224
|
+
for order, (asset, sz, px) in zip(orders, rounded_orders, strict=True)
|
|
187
225
|
]
|
|
188
226
|
|
|
189
227
|
async def cancel_order(self, coin: str, oid: int):
|
|
@@ -193,9 +231,15 @@ class AsyncHyperliquidOrdersClient(AsyncHyperliquidInfoClient):
|
|
|
193
231
|
return await self.cancel_orders(cancels)
|
|
194
232
|
|
|
195
233
|
async def cancel_orders(self, cancels: BatchCancelRequest):
|
|
196
|
-
assets =
|
|
197
|
-
|
|
198
|
-
|
|
234
|
+
assets = [
|
|
235
|
+
asset
|
|
236
|
+
for coin, _ in cancels
|
|
237
|
+
if (asset := self._lookup_cached_asset_id(coin)) is not None
|
|
238
|
+
]
|
|
239
|
+
if len(assets) != len(cancels):
|
|
240
|
+
assets = await asyncio.gather(
|
|
241
|
+
*(self.get_coin_asset(coin) for coin, _ in cancels)
|
|
242
|
+
)
|
|
199
243
|
action = {
|
|
200
244
|
"type": "cancel",
|
|
201
245
|
"cancels": [
|
|
@@ -212,9 +256,15 @@ class AsyncHyperliquidOrdersClient(AsyncHyperliquidInfoClient):
|
|
|
212
256
|
return await self.batch_cancel_by_cloid([(coin, cloid)])
|
|
213
257
|
|
|
214
258
|
async def batch_cancel_by_cloid(self, cancels: list[tuple[str, Cloid]]):
|
|
215
|
-
assets =
|
|
216
|
-
|
|
217
|
-
|
|
259
|
+
assets = [
|
|
260
|
+
asset
|
|
261
|
+
for coin, _ in cancels
|
|
262
|
+
if (asset := self._lookup_cached_asset_id(coin)) is not None
|
|
263
|
+
]
|
|
264
|
+
if len(assets) != len(cancels):
|
|
265
|
+
assets = await asyncio.gather(
|
|
266
|
+
*(self.get_coin_asset(coin) for coin, _ in cancels)
|
|
267
|
+
)
|
|
218
268
|
action = {
|
|
219
269
|
"type": "cancelByCloid",
|
|
220
270
|
"cancels": [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import math
|
|
1
2
|
from typing import Any, List
|
|
2
|
-
from decimal import Decimal
|
|
3
3
|
|
|
4
4
|
import msgpack
|
|
5
5
|
from eth_utils.crypto import keccak
|
|
@@ -174,14 +174,17 @@ def sign_action(
|
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def round_float(x: float) -> str:
|
|
177
|
+
if not math.isfinite(x):
|
|
178
|
+
raise ValueError("round_float requires finite number", x)
|
|
179
|
+
|
|
177
180
|
rounded = f"{x:.8f}"
|
|
178
181
|
if abs(float(rounded) - x) >= 1e-12:
|
|
179
182
|
raise ValueError("round_float causes rounding", x)
|
|
180
183
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return
|
|
184
|
+
trimmed = rounded.rstrip("0").rstrip(".")
|
|
185
|
+
if trimmed in {"", "-0"}:
|
|
186
|
+
return "0"
|
|
187
|
+
return trimmed
|
|
185
188
|
|
|
186
189
|
|
|
187
190
|
def ensure_order_type(order_type: OrderType) -> OrderType:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/async_hyperliquid.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/constants.py
RENAMED
|
File without changes
|
{async_hyperliquid-0.4.5 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|