async-hyperliquid 0.4.4__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.4 → async_hyperliquid-0.4.6}/PKG-INFO +1 -1
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/pyproject.toml +1 -1
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/core.py +190 -38
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/info.py +91 -4
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/orders.py +62 -12
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/info.py +2 -2
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/signing.py +8 -5
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/LICENSE +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/README.md +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/__init__.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/__init__.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/actions.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/async_api.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/async_hyperliquid.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/exchange.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/__init__.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/constants.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/decorators.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/miscs.py +0 -0
- {async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/types.py +0 -0
{async_hyperliquid-0.4.4 → 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,65 @@ 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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
coin_names.setdefault(name, asset_name)
|
|
188
|
+
coin_names.setdefault(quote_name, quote_name)
|
|
189
|
+
|
|
190
|
+
asset_sz_decimals[asset] = base_info["szDecimals"]
|
|
191
|
+
spot_tokens[asset_name] = base_info
|
|
192
|
+
spot_tokens.setdefault(quote_name, quote_info)
|
|
193
|
+
|
|
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("@")}
|
|
196
|
+
|
|
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:
|
|
239
|
+
# TODO: Add HIP-4 outcome meta initialization from the spot info
|
|
240
|
+
# `outcomeMeta` endpoint once outcomes move beyond testnet-only rollout.
|
|
241
|
+
# Outcome asset IDs do not follow the current perp/spot offset scheme:
|
|
242
|
+
# `encoding = 10 * outcome + side`, coin names use `#{encoding}`, token
|
|
243
|
+
# names use `+{encoding}`, and `asset_id = 100_000_000 + encoding`, so
|
|
244
|
+
# this path will need dedicated outcome mappings instead of reusing the
|
|
245
|
+
# existing perp/spot logic.
|
|
167
246
|
meta_task = self.info.get_perp_meta()
|
|
168
247
|
spot_meta_task = self.info.get_spot_meta()
|
|
169
248
|
all_dex_names_task = self.get_all_dex_name()
|
|
@@ -172,17 +251,36 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
172
251
|
meta_task, spot_meta_task, all_dex_names_task
|
|
173
252
|
)
|
|
174
253
|
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
)
|
|
177
273
|
|
|
274
|
+
dex_index_by_name = {
|
|
275
|
+
name: idx for idx, name in enumerate(all_dex_names) if name is not None
|
|
276
|
+
}
|
|
178
277
|
dex_meta_tasks = []
|
|
179
278
|
dex_indices = []
|
|
180
279
|
for dex in self.perp_dexs:
|
|
181
280
|
if dex == "":
|
|
182
281
|
continue
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
except ValueError:
|
|
282
|
+
idx = dex_index_by_name.get(dex)
|
|
283
|
+
if idx is None:
|
|
186
284
|
continue
|
|
187
285
|
|
|
188
286
|
if idx > 0:
|
|
@@ -193,9 +291,49 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
193
291
|
dex_metas = await asyncio.gather(*dex_meta_tasks)
|
|
194
292
|
for idx, dex_meta in zip(dex_indices, dex_metas):
|
|
195
293
|
dex_asset_offset = PERP_DEX_OFFSET + (idx - 1) * 10000
|
|
196
|
-
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)
|
|
197
334
|
|
|
198
|
-
|
|
335
|
+
async def init_metas(self) -> None:
|
|
336
|
+
await self._run_meta_refresh(only_if_missing=False)
|
|
199
337
|
|
|
200
338
|
async def get_metas(self, perp_only: bool = False) -> Metas:
|
|
201
339
|
metas: Metas = {"perp": {}, "spot": [], "dexs": {}} # type: ignore
|
|
@@ -237,31 +375,45 @@ class AsyncHyperliquidCore(AsyncAPI):
|
|
|
237
375
|
return names
|
|
238
376
|
|
|
239
377
|
async def get_coin_name(self, coin: str) -> str:
|
|
240
|
-
|
|
241
|
-
|
|
378
|
+
coin_name = self._lookup_cached_coin_name(coin)
|
|
379
|
+
if coin_name is not None:
|
|
380
|
+
return coin_name
|
|
242
381
|
|
|
243
|
-
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:
|
|
244
388
|
raise ValueError(f"Coin {coin} not found")
|
|
245
389
|
|
|
246
|
-
return
|
|
390
|
+
return coin_name
|
|
247
391
|
|
|
248
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
|
+
|
|
249
397
|
coin_name = await self.get_coin_name(coin)
|
|
250
|
-
|
|
398
|
+
asset = self.coin_assets.get(coin_name)
|
|
399
|
+
if asset is None:
|
|
251
400
|
raise ValueError(f"Coin {coin}({coin_name}) not found")
|
|
252
401
|
|
|
253
|
-
return
|
|
402
|
+
return asset
|
|
254
403
|
|
|
255
404
|
async def get_coin_symbol(self, coin: str) -> str:
|
|
256
405
|
coin_name = await self.get_coin_name(coin)
|
|
257
406
|
return self.coin_symbols[coin_name]
|
|
258
407
|
|
|
259
408
|
async def get_coin_sz_decimals(self, coin: str) -> int:
|
|
260
|
-
|
|
261
|
-
if
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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")
|
|
265
417
|
return self.asset_sz_decimals[asset]
|
|
266
418
|
|
|
267
419
|
async def get_token_info(self, coin: str) -> SpotTokenMeta:
|
{async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/_async_hyperliquid/info.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import warnings
|
|
3
|
-
from typing import Literal
|
|
3
|
+
from typing import Literal, cast
|
|
4
4
|
|
|
5
5
|
from async_hyperliquid.utils.constants import ONE_HOUR_MS, PERP_DEX_OFFSET, SPOT_OFFSET
|
|
6
6
|
from async_hyperliquid.utils.miscs import get_coin_dex, get_timestamp_ms
|
|
@@ -9,9 +9,13 @@ from async_hyperliquid.utils.types import (
|
|
|
9
9
|
AccountState,
|
|
10
10
|
ClearinghouseState,
|
|
11
11
|
OrderWithStatus,
|
|
12
|
+
PerpMeta,
|
|
13
|
+
PerpMetaCtxItem,
|
|
12
14
|
Portfolio,
|
|
13
15
|
Position,
|
|
16
|
+
SpotMeta,
|
|
14
17
|
SpotClearinghouseState,
|
|
18
|
+
SpotMetaCtxItem,
|
|
15
19
|
UserDeposit,
|
|
16
20
|
UserNonFundingDelta,
|
|
17
21
|
UserOpenOrders,
|
|
@@ -23,13 +27,96 @@ from .core import AsyncHyperliquidCore
|
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
class AsyncHyperliquidInfoClient(AsyncHyperliquidCore):
|
|
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:
|
|
56
|
+
meta_ctx = await self.info.get_perp_meta_ctx(dex)
|
|
57
|
+
meta = cast(PerpMeta, meta_ctx[0])
|
|
58
|
+
asset_ctxs = cast(list[PerpMetaCtxItem], meta_ctx[1])
|
|
59
|
+
|
|
60
|
+
if asset_index is not None and 0 <= asset_index < len(asset_ctxs):
|
|
61
|
+
return float(asset_ctxs[asset_index]["markPx"])
|
|
62
|
+
|
|
63
|
+
for idx, info in enumerate(meta["universe"]):
|
|
64
|
+
if info["name"] == coin_name:
|
|
65
|
+
return float(asset_ctxs[idx]["markPx"])
|
|
66
|
+
|
|
67
|
+
raise ValueError(f"Coin {coin_name} not found in perp dex '{dex}'")
|
|
68
|
+
|
|
69
|
+
async def _get_spot_mark_price(
|
|
70
|
+
self, coin_name: str, asset_index: int | None = None
|
|
71
|
+
) -> float:
|
|
72
|
+
meta_ctx = await self.info.get_spot_meta_ctx()
|
|
73
|
+
meta = cast(SpotMeta, meta_ctx[0])
|
|
74
|
+
asset_ctxs = cast(list[SpotMetaCtxItem], meta_ctx[1])
|
|
75
|
+
|
|
76
|
+
if asset_index is not None and 0 <= asset_index < len(asset_ctxs):
|
|
77
|
+
return float(asset_ctxs[asset_index]["markPx"])
|
|
78
|
+
|
|
79
|
+
for info in meta["universe"]:
|
|
80
|
+
if info["name"] == coin_name:
|
|
81
|
+
asset_index = info["index"]
|
|
82
|
+
return float(asset_ctxs[asset_index]["markPx"])
|
|
83
|
+
|
|
84
|
+
raise ValueError(f"Coin {coin_name} not found in spot metadata")
|
|
85
|
+
|
|
86
|
+
async def get_mark_price(self, coin: str) -> float:
|
|
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
|
+
)
|
|
94
|
+
return await self._get_perp_mark_price(coin, get_coin_dex(coin))
|
|
95
|
+
|
|
96
|
+
coin_name = await self.get_coin_name(coin)
|
|
97
|
+
asset = self._lookup_cached_asset_id(coin_name)
|
|
98
|
+
if asset is None:
|
|
99
|
+
raise ValueError(f"Coin {coin}({coin_name}) not found")
|
|
100
|
+
|
|
101
|
+
is_spot_asset = SPOT_OFFSET <= asset < PERP_DEX_OFFSET
|
|
102
|
+
if is_spot_asset:
|
|
103
|
+
return await self._get_spot_mark_price(
|
|
104
|
+
coin_name, asset_index=asset - SPOT_OFFSET
|
|
105
|
+
)
|
|
106
|
+
|
|
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
|
+
)
|
|
114
|
+
|
|
26
115
|
async def get_market_price(self, coin: str) -> float:
|
|
27
116
|
warnings.warn(
|
|
28
117
|
"get_market_price is deprecated and will remove in the future, use get_mid_price instead"
|
|
29
118
|
)
|
|
30
|
-
|
|
31
|
-
market_prices = await self.get_all_market_prices()
|
|
32
|
-
return market_prices[coin_name]
|
|
119
|
+
return await self.get_mark_price(coin)
|
|
33
120
|
|
|
34
121
|
async def get_all_market_prices(
|
|
35
122
|
self, market: Literal["spot", "perp", "all"] = "all"
|
|
@@ -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": [
|
|
@@ -190,8 +190,8 @@ class InfoAPI(AsyncAPI):
|
|
|
190
190
|
payload = {"type": "meta", "dex": dex}
|
|
191
191
|
return await self.post(payload)
|
|
192
192
|
|
|
193
|
-
async def get_perp_meta_ctx(self) -> PerpMetaCtx:
|
|
194
|
-
payload = {"type": "metaAndAssetCtxs"}
|
|
193
|
+
async def get_perp_meta_ctx(self, dex: str = "") -> PerpMetaCtx:
|
|
194
|
+
payload = {"type": "metaAndAssetCtxs", "dex": dex}
|
|
195
195
|
return await self.post(payload)
|
|
196
196
|
|
|
197
197
|
async def get_perp_dexs(self) -> PerpDexs:
|
|
@@ -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.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/async_hyperliquid.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/constants.py
RENAMED
|
File without changes
|
{async_hyperliquid-0.4.4 → async_hyperliquid-0.4.6}/src/async_hyperliquid/utils/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|