wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.20__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.
Potentially problematic release.
This version of wayfinder-paths might be problematic. Click here for more details.
- wayfinder_paths/__init__.py +0 -2
- wayfinder_paths/adapters/balance_adapter/README.md +59 -45
- wayfinder_paths/adapters/balance_adapter/adapter.py +0 -21
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
- wayfinder_paths/adapters/brap_adapter/README.md +61 -184
- wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/brap_adapter/adapter.py +0 -147
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -9
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
- wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
- wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
- wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
- wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
- wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +9 -121
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
- wayfinder_paths/adapters/pool_adapter/README.md +30 -51
- wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
- wayfinder_paths/adapters/token_adapter/README.md +41 -49
- wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
- wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
- wayfinder_paths/conftest.py +0 -8
- wayfinder_paths/core/__init__.py +0 -2
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
- wayfinder_paths/core/adapters/__init__.py +0 -5
- wayfinder_paths/core/adapters/models.py +0 -5
- wayfinder_paths/core/analytics/__init__.py +0 -2
- wayfinder_paths/core/analytics/bootstrap.py +0 -16
- wayfinder_paths/core/analytics/stats.py +0 -7
- wayfinder_paths/core/analytics/test_analytics.py +5 -34
- wayfinder_paths/core/clients/BRAPClient.py +0 -35
- wayfinder_paths/core/clients/ClientManager.py +0 -51
- wayfinder_paths/core/clients/HyperlendClient.py +0 -77
- wayfinder_paths/core/clients/LedgerClient.py +2 -122
- wayfinder_paths/core/clients/PoolClient.py +0 -2
- wayfinder_paths/core/clients/TokenClient.py +0 -39
- wayfinder_paths/core/clients/WalletClient.py +0 -15
- wayfinder_paths/core/clients/WayfinderClient.py +0 -24
- wayfinder_paths/core/clients/__init__.py +0 -4
- wayfinder_paths/core/clients/protocols.py +25 -98
- wayfinder_paths/core/config.py +0 -24
- wayfinder_paths/core/constants/__init__.py +0 -7
- wayfinder_paths/core/constants/base.py +2 -9
- wayfinder_paths/core/constants/erc20_abi.py +0 -5
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
- wayfinder_paths/core/constants/moonwell_abi.py +0 -35
- wayfinder_paths/core/engine/StrategyJob.py +0 -32
- wayfinder_paths/core/strategies/Strategy.py +0 -99
- wayfinder_paths/core/strategies/__init__.py +0 -2
- wayfinder_paths/core/utils/__init__.py +0 -1
- wayfinder_paths/core/utils/erc20_service.py +0 -1
- wayfinder_paths/core/utils/evm_helpers.py +0 -50
- wayfinder_paths/core/utils/transaction.py +0 -1
- wayfinder_paths/run_strategy.py +0 -46
- wayfinder_paths/scripts/create_strategy.py +0 -17
- wayfinder_paths/scripts/make_wallets.py +1 -4
- wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
- wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
- wayfinder_paths/templates/adapter/README.md +107 -46
- wayfinder_paths/templates/adapter/adapter.py +0 -9
- wayfinder_paths/templates/adapter/test_adapter.py +0 -19
- wayfinder_paths/templates/strategy/README.md +113 -59
- wayfinder_paths/templates/strategy/strategy.py +0 -22
- wayfinder_paths/templates/strategy/test_strategy.py +0 -28
- wayfinder_paths/tests/test_test_coverage.py +2 -12
- wayfinder_paths/tests/test_utils.py +1 -31
- wayfinder_paths-0.1.20.dist-info/METADATA +355 -0
- wayfinder_paths-0.1.20.dist-info/RECORD +129 -0
- wayfinder_paths/core/adapters/base.py +0 -5
- wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
- wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/WHEEL +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""HyperliquidAdapter - wraps hyperliquid SDK for market data and order execution."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
import asyncio
|
|
@@ -31,8 +29,6 @@ except ImportError:
|
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
class SimpleCache:
|
|
34
|
-
"""Simple in-memory cache with TTL for local caching."""
|
|
35
|
-
|
|
36
32
|
def __init__(self):
|
|
37
33
|
self._cache: dict[str, Any] = {}
|
|
38
34
|
self._expiry: dict[str, float] = {}
|
|
@@ -56,18 +52,6 @@ class SimpleCache:
|
|
|
56
52
|
|
|
57
53
|
|
|
58
54
|
class HyperliquidAdapter(BaseAdapter):
|
|
59
|
-
"""
|
|
60
|
-
Adapter for Hyperliquid exchange operations.
|
|
61
|
-
|
|
62
|
-
Wraps the hyperliquid SDK directly for market data access.
|
|
63
|
-
Uses Hyperliquid's public API for:
|
|
64
|
-
- Market metadata (perp and spot)
|
|
65
|
-
- Funding rate history
|
|
66
|
-
- Price candles
|
|
67
|
-
- Order book snapshots
|
|
68
|
-
- User positions and balances
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
55
|
adapter_type = "HYPERLIQUID"
|
|
72
56
|
|
|
73
57
|
def __init__(
|
|
@@ -87,7 +71,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
87
71
|
self._cache = SimpleCache()
|
|
88
72
|
self._executor = executor
|
|
89
73
|
|
|
90
|
-
# Initialize Hyperliquid Info client
|
|
91
74
|
self.info = Info(constants.MAINNET_API_URL, skip_ws=True)
|
|
92
75
|
|
|
93
76
|
# Cache asset mappings after first fetch
|
|
@@ -95,7 +78,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
95
78
|
self._coin_to_asset: dict[str, int] | None = None
|
|
96
79
|
|
|
97
80
|
async def connect(self) -> bool:
|
|
98
|
-
"""Verify connection by fetching market metadata."""
|
|
99
81
|
try:
|
|
100
82
|
meta = self.info.meta_and_asset_ctxs()
|
|
101
83
|
if meta:
|
|
@@ -111,11 +93,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
111
93
|
# ------------------------------------------------------------------ #
|
|
112
94
|
|
|
113
95
|
async def get_meta_and_asset_ctxs(self) -> tuple[bool, Any]:
|
|
114
|
-
"""
|
|
115
|
-
Get perpetual market metadata and asset contexts.
|
|
116
|
-
|
|
117
|
-
Returns combined [meta, assetCtxs] from Hyperliquid API.
|
|
118
|
-
"""
|
|
119
96
|
cache_key = "hl_meta_and_asset_ctxs"
|
|
120
97
|
cached = self._cache.get(cache_key)
|
|
121
98
|
if cached:
|
|
@@ -123,25 +100,19 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
123
100
|
|
|
124
101
|
try:
|
|
125
102
|
data = self.info.meta_and_asset_ctxs()
|
|
126
|
-
self._cache.set(cache_key, data, timeout=60)
|
|
103
|
+
self._cache.set(cache_key, data, timeout=60)
|
|
127
104
|
return True, data
|
|
128
105
|
except Exception as exc:
|
|
129
106
|
self.logger.error(f"Failed to fetch meta_and_asset_ctxs: {exc}")
|
|
130
107
|
return False, str(exc)
|
|
131
108
|
|
|
132
109
|
async def get_spot_meta(self) -> tuple[bool, Any]:
|
|
133
|
-
"""
|
|
134
|
-
Get spot market metadata.
|
|
135
|
-
|
|
136
|
-
Returns spot market information including tokens and pairs.
|
|
137
|
-
"""
|
|
138
110
|
cache_key = "hl_spot_meta"
|
|
139
111
|
cached = self._cache.get(cache_key)
|
|
140
112
|
if cached:
|
|
141
113
|
return True, cached
|
|
142
114
|
|
|
143
115
|
try:
|
|
144
|
-
# Handle both callable and property access patterns
|
|
145
116
|
spot_meta = self.info.spot_meta
|
|
146
117
|
if callable(spot_meta):
|
|
147
118
|
data = spot_meta()
|
|
@@ -154,13 +125,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
154
125
|
return False, str(exc)
|
|
155
126
|
|
|
156
127
|
async def get_spot_assets(self) -> tuple[bool, dict[str, int]]:
|
|
157
|
-
"""
|
|
158
|
-
Get mapping of spot pair names to asset IDs.
|
|
159
|
-
|
|
160
|
-
Returns:
|
|
161
|
-
Dict mapping "BASE/QUOTE" names to spot asset IDs (index + 10000).
|
|
162
|
-
Example: {"PURR/USDC": 10000, "HYPE/USDC": 10107, ...}
|
|
163
|
-
"""
|
|
164
128
|
cache_key = "hl_spot_assets"
|
|
165
129
|
cached = self._cache.get(cache_key)
|
|
166
130
|
if cached:
|
|
@@ -182,7 +146,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
182
146
|
|
|
183
147
|
base_idx, quote_idx = pair_tokens[0], pair_tokens[1]
|
|
184
148
|
|
|
185
|
-
# Get token names
|
|
186
149
|
base_info = tokens[base_idx] if base_idx < len(tokens) else {}
|
|
187
150
|
quote_info = tokens[quote_idx] if quote_idx < len(tokens) else {}
|
|
188
151
|
|
|
@@ -193,7 +156,7 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
193
156
|
spot_asset_id = pair.get("index", 0) + 10000
|
|
194
157
|
response[name] = spot_asset_id
|
|
195
158
|
|
|
196
|
-
self._cache.set(cache_key, response, timeout=300)
|
|
159
|
+
self._cache.set(cache_key, response, timeout=300)
|
|
197
160
|
return True, response
|
|
198
161
|
|
|
199
162
|
except Exception as exc:
|
|
@@ -201,16 +164,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
201
164
|
return False, {}
|
|
202
165
|
|
|
203
166
|
def get_spot_asset_id(self, base_coin: str, quote_coin: str = "USDC") -> int | None:
|
|
204
|
-
"""
|
|
205
|
-
Synchronous helper to get spot asset ID from cached data.
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
base_coin: Base token name (e.g., "HYPE", "ETH", "BTC")
|
|
209
|
-
quote_coin: Quote token name (default: "USDC")
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
Spot asset ID or None if not found.
|
|
213
|
-
"""
|
|
214
167
|
cache_key = "hl_spot_assets"
|
|
215
168
|
cached = self._cache.get(cache_key)
|
|
216
169
|
if cached:
|
|
@@ -224,17 +177,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
224
177
|
start_time_ms: int,
|
|
225
178
|
end_time_ms: int | None = None,
|
|
226
179
|
) -> tuple[bool, list[dict[str, Any]]]:
|
|
227
|
-
"""
|
|
228
|
-
Get funding rate history for a perpetual.
|
|
229
|
-
|
|
230
|
-
Args:
|
|
231
|
-
coin: Coin symbol (e.g., "ETH", "BTC")
|
|
232
|
-
start_time_ms: Start time in milliseconds
|
|
233
|
-
end_time_ms: End time in milliseconds (optional)
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
List of funding rate records with time and fundingRate fields.
|
|
237
|
-
"""
|
|
238
180
|
try:
|
|
239
181
|
data = self.info.funding_history(coin, start_time_ms, end_time_ms)
|
|
240
182
|
return True, data
|
|
@@ -249,18 +191,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
249
191
|
start_time_ms: int,
|
|
250
192
|
end_time_ms: int | None = None,
|
|
251
193
|
) -> tuple[bool, list[dict[str, Any]]]:
|
|
252
|
-
"""
|
|
253
|
-
Get OHLCV candle data.
|
|
254
|
-
|
|
255
|
-
Args:
|
|
256
|
-
coin: Coin symbol (e.g., "ETH", "BTC")
|
|
257
|
-
interval: Candle interval (e.g., "1h", "4h", "1d")
|
|
258
|
-
start_time_ms: Start time in milliseconds
|
|
259
|
-
end_time_ms: End time in milliseconds (optional)
|
|
260
|
-
|
|
261
|
-
Returns:
|
|
262
|
-
List of candle records with t, o, h, l, c, v fields.
|
|
263
|
-
"""
|
|
264
194
|
try:
|
|
265
195
|
data = self.info.candles_snapshot(
|
|
266
196
|
coin, interval, start_time_ms, end_time_ms
|
|
@@ -275,16 +205,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
275
205
|
coin: str,
|
|
276
206
|
n_levels: int = 20,
|
|
277
207
|
) -> tuple[bool, dict[str, Any]]:
|
|
278
|
-
"""
|
|
279
|
-
Get L2 order book snapshot.
|
|
280
|
-
|
|
281
|
-
Args:
|
|
282
|
-
coin: Coin symbol (e.g., "ETH", "BTC", or spot pair like "HYPE/USDC")
|
|
283
|
-
n_levels: Number of price levels to fetch
|
|
284
|
-
|
|
285
|
-
Returns:
|
|
286
|
-
Order book with levels containing px, sz, n fields.
|
|
287
|
-
"""
|
|
288
208
|
try:
|
|
289
209
|
data = self.info.l2_snapshot(coin)
|
|
290
210
|
return True, data
|
|
@@ -293,15 +213,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
293
213
|
return False, str(exc)
|
|
294
214
|
|
|
295
215
|
async def get_user_state(self, address: str) -> tuple[bool, dict[str, Any]]:
|
|
296
|
-
"""
|
|
297
|
-
Get user's perpetual account state including positions and margin.
|
|
298
|
-
|
|
299
|
-
Args:
|
|
300
|
-
address: Wallet address
|
|
301
|
-
|
|
302
|
-
Returns:
|
|
303
|
-
User state with assetPositions, crossMarginSummary, etc.
|
|
304
|
-
"""
|
|
305
216
|
try:
|
|
306
217
|
data = self.info.user_state(address)
|
|
307
218
|
return True, data
|
|
@@ -310,15 +221,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
310
221
|
return False, str(exc)
|
|
311
222
|
|
|
312
223
|
async def get_spot_user_state(self, address: str) -> tuple[bool, dict[str, Any]]:
|
|
313
|
-
"""
|
|
314
|
-
Get user's spot account balances.
|
|
315
|
-
|
|
316
|
-
Args:
|
|
317
|
-
address: Wallet address
|
|
318
|
-
|
|
319
|
-
Returns:
|
|
320
|
-
Spot balances for the user.
|
|
321
|
-
"""
|
|
322
224
|
try:
|
|
323
225
|
data = self.info.spot_user_state(address)
|
|
324
226
|
return True, data
|
|
@@ -327,15 +229,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
327
229
|
return False, str(exc)
|
|
328
230
|
|
|
329
231
|
async def get_margin_table(self, margin_table_id: int) -> tuple[bool, list[dict]]:
|
|
330
|
-
"""
|
|
331
|
-
Get tiered margin table for an asset.
|
|
332
|
-
|
|
333
|
-
Args:
|
|
334
|
-
margin_table_id: Margin table ID from asset context
|
|
335
|
-
|
|
336
|
-
Returns:
|
|
337
|
-
List of margin tiers with notional and maintenance rate.
|
|
338
|
-
"""
|
|
339
232
|
cache_key = f"hl_margin_table_{margin_table_id}"
|
|
340
233
|
cached = self._cache.get(cache_key)
|
|
341
234
|
if cached:
|
|
@@ -350,22 +243,13 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
350
243
|
except Exception: # noqa: BLE001 - try alternate payload key
|
|
351
244
|
body = {"type": "marginTable", "marginTableId": int(margin_table_id)}
|
|
352
245
|
data = self.info.post("/info", body)
|
|
353
|
-
self._cache.set(cache_key, data, timeout=86400)
|
|
246
|
+
self._cache.set(cache_key, data, timeout=86400)
|
|
354
247
|
return True, data
|
|
355
248
|
except Exception as exc:
|
|
356
249
|
self.logger.error(f"Failed to fetch margin_table {margin_table_id}: {exc}")
|
|
357
250
|
return False, str(exc)
|
|
358
251
|
|
|
359
252
|
async def get_spot_l2_book(self, spot_asset_id: int) -> tuple[bool, dict[str, Any]]:
|
|
360
|
-
"""
|
|
361
|
-
Get L2 order book for a spot market by asset ID.
|
|
362
|
-
|
|
363
|
-
Args:
|
|
364
|
-
spot_asset_id: Spot asset ID (>= 10000)
|
|
365
|
-
|
|
366
|
-
Returns:
|
|
367
|
-
Order book with levels.
|
|
368
|
-
"""
|
|
369
253
|
try:
|
|
370
254
|
# Spot L2 uses different coin names based on spot index:
|
|
371
255
|
# - Index 0 (PURR): use "PURR/USDC"
|
|
@@ -394,20 +278,17 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
394
278
|
|
|
395
279
|
@property
|
|
396
280
|
def asset_to_sz_decimals(self) -> dict[int, int]:
|
|
397
|
-
"""Get asset ID to size decimals mapping."""
|
|
398
281
|
if self._asset_to_sz_decimals is None:
|
|
399
282
|
self._asset_to_sz_decimals = dict(self.info.asset_to_sz_decimals)
|
|
400
283
|
return self._asset_to_sz_decimals
|
|
401
284
|
|
|
402
285
|
@property
|
|
403
286
|
def coin_to_asset(self) -> dict[str, int]:
|
|
404
|
-
"""Get coin name to asset ID mapping (perps only)."""
|
|
405
287
|
if self._coin_to_asset is None:
|
|
406
288
|
self._coin_to_asset = dict(self.info.coin_to_asset)
|
|
407
289
|
return self._coin_to_asset
|
|
408
290
|
|
|
409
291
|
def get_sz_decimals(self, asset_id: int) -> int:
|
|
410
|
-
"""Get size decimals for an asset."""
|
|
411
292
|
try:
|
|
412
293
|
return self.asset_to_sz_decimals[asset_id]
|
|
413
294
|
except KeyError:
|
|
@@ -416,7 +297,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
416
297
|
) from None
|
|
417
298
|
|
|
418
299
|
def refresh_mappings(self) -> None:
|
|
419
|
-
"""Force refresh of cached asset mappings."""
|
|
420
300
|
self._asset_to_sz_decimals = None
|
|
421
301
|
self._coin_to_asset = None
|
|
422
302
|
self._cache.clear()
|
|
@@ -426,7 +306,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
426
306
|
# ------------------------------------------------------------------ #
|
|
427
307
|
|
|
428
308
|
async def get_all_mid_prices(self) -> tuple[bool, dict[str, float]]:
|
|
429
|
-
"""Get mid prices for all markets."""
|
|
430
309
|
try:
|
|
431
310
|
data = self.info.all_mids()
|
|
432
311
|
return True, {k: float(v) for k, v in data.items()}
|
|
@@ -435,7 +314,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
435
314
|
return False, str(exc)
|
|
436
315
|
|
|
437
316
|
def get_valid_order_size(self, asset_id: int, size: float) -> float:
|
|
438
|
-
"""Round size to valid lot size for asset."""
|
|
439
317
|
decimals = self.get_sz_decimals(asset_id)
|
|
440
318
|
from decimal import ROUND_DOWN, Decimal
|
|
441
319
|
|
|
@@ -463,22 +341,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
463
341
|
cloid: str | None = None,
|
|
464
342
|
builder: dict[str, Any] | None = None,
|
|
465
343
|
) -> tuple[bool, dict[str, Any]]:
|
|
466
|
-
"""
|
|
467
|
-
Place a market order (IOC with slippage).
|
|
468
|
-
|
|
469
|
-
Args:
|
|
470
|
-
asset_id: Asset ID (perp < 10000, spot >= 10000)
|
|
471
|
-
is_buy: True for buy, False for sell
|
|
472
|
-
slippage: Slippage tolerance (0.0 to 1.0)
|
|
473
|
-
size: Order size in base units
|
|
474
|
-
address: Wallet address
|
|
475
|
-
reduce_only: If True, only reduce existing position
|
|
476
|
-
cloid: Client order ID (optional)
|
|
477
|
-
builder: Optional builder fee config with keys 'b' (address) and 'f' (fee bps)
|
|
478
|
-
|
|
479
|
-
Returns:
|
|
480
|
-
(success, response_data or error_message)
|
|
481
|
-
"""
|
|
482
344
|
if not self._executor:
|
|
483
345
|
raise NotImplementedError(
|
|
484
346
|
"No Hyperliquid executor configured. "
|
|
@@ -505,17 +367,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
505
367
|
order_id: int | str,
|
|
506
368
|
address: str,
|
|
507
369
|
) -> tuple[bool, dict[str, Any]]:
|
|
508
|
-
"""
|
|
509
|
-
Cancel an open order.
|
|
510
|
-
|
|
511
|
-
Args:
|
|
512
|
-
asset_id: Asset ID
|
|
513
|
-
order_id: Order ID to cancel
|
|
514
|
-
address: Wallet address
|
|
515
|
-
|
|
516
|
-
Returns:
|
|
517
|
-
(success, response_data or error_message)
|
|
518
|
-
"""
|
|
519
370
|
if not self._executor:
|
|
520
371
|
raise NotImplementedError(
|
|
521
372
|
"No Hyperliquid executor configured. "
|
|
@@ -538,18 +389,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
538
389
|
is_cross: bool,
|
|
539
390
|
address: str,
|
|
540
391
|
) -> tuple[bool, dict[str, Any]]:
|
|
541
|
-
"""
|
|
542
|
-
Update leverage for an asset.
|
|
543
|
-
|
|
544
|
-
Args:
|
|
545
|
-
asset_id: Asset ID
|
|
546
|
-
leverage: Target leverage
|
|
547
|
-
is_cross: True for cross margin, False for isolated
|
|
548
|
-
address: Wallet address
|
|
549
|
-
|
|
550
|
-
Returns:
|
|
551
|
-
(success, response_data or error_message)
|
|
552
|
-
"""
|
|
553
392
|
if not self._executor:
|
|
554
393
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
555
394
|
|
|
@@ -568,7 +407,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
568
407
|
amount: float,
|
|
569
408
|
address: str,
|
|
570
409
|
) -> tuple[bool, dict[str, Any]]:
|
|
571
|
-
"""Transfer USDC from spot to perp balance."""
|
|
572
410
|
if not self._executor:
|
|
573
411
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
574
412
|
|
|
@@ -585,7 +423,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
585
423
|
amount: float,
|
|
586
424
|
address: str,
|
|
587
425
|
) -> tuple[bool, dict[str, Any]]:
|
|
588
|
-
"""Transfer USDC from perp to spot balance."""
|
|
589
426
|
if not self._executor:
|
|
590
427
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
591
428
|
|
|
@@ -605,19 +442,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
605
442
|
size: float,
|
|
606
443
|
address: str,
|
|
607
444
|
) -> tuple[bool, dict[str, Any]]:
|
|
608
|
-
"""
|
|
609
|
-
Place a stop-loss order.
|
|
610
|
-
|
|
611
|
-
Args:
|
|
612
|
-
asset_id: Asset ID
|
|
613
|
-
is_buy: True to buy (close short), False to sell (close long)
|
|
614
|
-
trigger_price: Price at which to trigger
|
|
615
|
-
size: Order size
|
|
616
|
-
address: Wallet address
|
|
617
|
-
|
|
618
|
-
Returns:
|
|
619
|
-
(success, response_data or error_message)
|
|
620
|
-
"""
|
|
621
445
|
if not self._executor:
|
|
622
446
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
623
447
|
|
|
@@ -633,15 +457,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
633
457
|
return success, result
|
|
634
458
|
|
|
635
459
|
async def get_user_fills(self, address: str) -> tuple[bool, list[dict[str, Any]]]:
|
|
636
|
-
"""
|
|
637
|
-
Get recent fills for a user.
|
|
638
|
-
|
|
639
|
-
Args:
|
|
640
|
-
address: Wallet address
|
|
641
|
-
|
|
642
|
-
Returns:
|
|
643
|
-
List of fill records
|
|
644
|
-
"""
|
|
645
460
|
try:
|
|
646
461
|
data = self.info.user_fills(address)
|
|
647
462
|
return True, data if isinstance(data, list) else []
|
|
@@ -652,16 +467,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
652
467
|
async def get_order_status(
|
|
653
468
|
self, address: str, order_id: int | str
|
|
654
469
|
) -> tuple[bool, dict[str, Any]]:
|
|
655
|
-
"""
|
|
656
|
-
Get status of a specific order.
|
|
657
|
-
|
|
658
|
-
Args:
|
|
659
|
-
address: Wallet address
|
|
660
|
-
order_id: Order ID (numeric) or client order ID (string)
|
|
661
|
-
|
|
662
|
-
Returns:
|
|
663
|
-
Order status data
|
|
664
|
-
"""
|
|
665
470
|
try:
|
|
666
471
|
body = {"type": "orderStatus", "user": address, "oid": order_id}
|
|
667
472
|
data = self.info.post("/info", body)
|
|
@@ -671,15 +476,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
671
476
|
return False, str(exc)
|
|
672
477
|
|
|
673
478
|
async def get_open_orders(self, address: str) -> tuple[bool, list[dict[str, Any]]]:
|
|
674
|
-
"""
|
|
675
|
-
Get open orders for a user.
|
|
676
|
-
|
|
677
|
-
Args:
|
|
678
|
-
address: Wallet address
|
|
679
|
-
|
|
680
|
-
Returns:
|
|
681
|
-
List of open order records
|
|
682
|
-
"""
|
|
683
479
|
try:
|
|
684
480
|
data = self.info.open_orders(address)
|
|
685
481
|
return True, data if isinstance(data, list) else []
|
|
@@ -690,18 +486,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
690
486
|
async def get_frontend_open_orders(
|
|
691
487
|
self, address: str
|
|
692
488
|
) -> tuple[bool, list[dict[str, Any]]]:
|
|
693
|
-
"""
|
|
694
|
-
Get all open orders including trigger orders (stop-loss, take-profit).
|
|
695
|
-
|
|
696
|
-
Uses frontendOpenOrders endpoint which returns both limit and trigger orders
|
|
697
|
-
with full order details including orderType and triggerPx.
|
|
698
|
-
|
|
699
|
-
Args:
|
|
700
|
-
address: Wallet address
|
|
701
|
-
|
|
702
|
-
Returns:
|
|
703
|
-
List of open order records including trigger orders
|
|
704
|
-
"""
|
|
705
489
|
try:
|
|
706
490
|
data = self.info.frontend_open_orders(address)
|
|
707
491
|
return True, data if isinstance(data, list) else []
|
|
@@ -717,11 +501,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
717
501
|
amount: float,
|
|
718
502
|
address: str,
|
|
719
503
|
) -> tuple[bool, dict[str, Any]]:
|
|
720
|
-
"""
|
|
721
|
-
Withdraw USDC from Hyperliquid to Arbitrum.
|
|
722
|
-
|
|
723
|
-
Note: This is an L1 withdrawal handled by the Hyperliquid executor (signing required).
|
|
724
|
-
"""
|
|
725
504
|
if not self._executor:
|
|
726
505
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
727
506
|
|
|
@@ -737,7 +516,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
737
516
|
# ------------------------------------------------------------------ #
|
|
738
517
|
|
|
739
518
|
async def health_check(self) -> dict[str, Any]:
|
|
740
|
-
"""Check adapter health by verifying API connectivity."""
|
|
741
519
|
try:
|
|
742
520
|
success, meta = await self.get_meta_and_asset_ctxs()
|
|
743
521
|
if success and meta:
|
|
@@ -754,15 +532,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
754
532
|
# ------------------------------------------------------------------ #
|
|
755
533
|
|
|
756
534
|
def get_perp_margin_amount(self, user_state: dict[str, Any]) -> float:
|
|
757
|
-
"""
|
|
758
|
-
Extract perp margin amount from user state.
|
|
759
|
-
|
|
760
|
-
Args:
|
|
761
|
-
user_state: User state from get_user_state()
|
|
762
|
-
|
|
763
|
-
Returns:
|
|
764
|
-
Perp margin balance in USD
|
|
765
|
-
"""
|
|
766
535
|
try:
|
|
767
536
|
margin_summary = user_state.get("marginSummary", {})
|
|
768
537
|
account_value = margin_summary.get("accountValue")
|
|
@@ -779,17 +548,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
779
548
|
user: str,
|
|
780
549
|
builder: str,
|
|
781
550
|
) -> tuple[bool, int]:
|
|
782
|
-
"""
|
|
783
|
-
Get the current max builder fee approval for a user/builder pair.
|
|
784
|
-
|
|
785
|
-
Args:
|
|
786
|
-
user: User wallet address
|
|
787
|
-
builder: Builder wallet address
|
|
788
|
-
|
|
789
|
-
Returns:
|
|
790
|
-
(success, fee_in_tenths_bp) - The approved fee in tenths of basis points.
|
|
791
|
-
Returns 0 if no approval exists.
|
|
792
|
-
"""
|
|
793
551
|
try:
|
|
794
552
|
body = {"type": "maxBuilderFee", "user": user, "builder": builder}
|
|
795
553
|
data = self.info.post("/info", body)
|
|
@@ -805,17 +563,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
805
563
|
max_fee_rate: str,
|
|
806
564
|
address: str,
|
|
807
565
|
) -> tuple[bool, dict[str, Any]]:
|
|
808
|
-
"""
|
|
809
|
-
Approve a builder fee for a user.
|
|
810
|
-
|
|
811
|
-
Args:
|
|
812
|
-
builder: Builder wallet address
|
|
813
|
-
max_fee_rate: Fee rate as percentage string (e.g., "0.030%" for 30 tenths bp)
|
|
814
|
-
address: User wallet address
|
|
815
|
-
|
|
816
|
-
Returns:
|
|
817
|
-
(success, response_data or error_message)
|
|
818
|
-
"""
|
|
819
566
|
if not self._executor:
|
|
820
567
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
821
568
|
|
|
@@ -839,23 +586,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
839
586
|
reduce_only: bool = False,
|
|
840
587
|
builder: dict[str, Any] | None = None,
|
|
841
588
|
) -> tuple[bool, dict[str, Any]]:
|
|
842
|
-
"""
|
|
843
|
-
Place a limit order (GTC - Good Till Cancelled).
|
|
844
|
-
|
|
845
|
-
Used for spot stop-loss orders in basis trading.
|
|
846
|
-
|
|
847
|
-
Args:
|
|
848
|
-
asset_id: Asset ID (perp < 10000, spot >= 10000)
|
|
849
|
-
is_buy: True for buy, False for sell
|
|
850
|
-
price: Limit price
|
|
851
|
-
size: Order size
|
|
852
|
-
address: Wallet address
|
|
853
|
-
reduce_only: If True, only reduces existing position
|
|
854
|
-
builder: Optional builder fee config
|
|
855
|
-
|
|
856
|
-
Returns:
|
|
857
|
-
(success, response_data or error_message)
|
|
858
|
-
"""
|
|
859
589
|
if not self._executor:
|
|
860
590
|
raise NotImplementedError("No Hyperliquid executor configured.")
|
|
861
591
|
|
|
@@ -880,21 +610,8 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
880
610
|
timeout_s: int = 120,
|
|
881
611
|
poll_interval_s: int = 5,
|
|
882
612
|
) -> tuple[bool, float]:
|
|
883
|
-
"""
|
|
884
|
-
Wait for a deposit to be credited on Hyperliquid L1.
|
|
885
|
-
|
|
886
|
-
Args:
|
|
887
|
-
address: Wallet address
|
|
888
|
-
expected_increase: Expected USD amount to be deposited
|
|
889
|
-
timeout_s: Maximum time to wait in seconds
|
|
890
|
-
poll_interval_s: Time between polling attempts
|
|
891
|
-
|
|
892
|
-
Returns:
|
|
893
|
-
(success, final_balance) - True if deposit confirmed within timeout
|
|
894
|
-
"""
|
|
895
613
|
iterations = timeout_s // poll_interval_s
|
|
896
614
|
|
|
897
|
-
# Get initial balance
|
|
898
615
|
success, initial_state = await self.get_user_state(address)
|
|
899
616
|
if not success:
|
|
900
617
|
self.logger.warning(f"Could not fetch initial state: {initial_state}")
|
|
@@ -934,7 +651,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
934
651
|
f"Hyperliquid deposit not confirmed after {timeout_s}s. "
|
|
935
652
|
f"Deposits typically take 1-2 minutes."
|
|
936
653
|
)
|
|
937
|
-
# Return current balance even if not confirmed
|
|
938
654
|
success, state = await self.get_user_state(address)
|
|
939
655
|
final_balance = (
|
|
940
656
|
self.get_perp_margin_amount(state) if success else initial_balance
|
|
@@ -946,16 +662,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
946
662
|
address: str,
|
|
947
663
|
from_timestamp_ms: int,
|
|
948
664
|
) -> tuple[bool, dict[str, float]]:
|
|
949
|
-
"""
|
|
950
|
-
Get user withdrawal history from Hyperliquid.
|
|
951
|
-
|
|
952
|
-
Args:
|
|
953
|
-
address: Wallet address
|
|
954
|
-
from_timestamp_ms: Start time in milliseconds
|
|
955
|
-
|
|
956
|
-
Returns:
|
|
957
|
-
(success, {tx_hash: usdc_amount})
|
|
958
|
-
"""
|
|
959
665
|
try:
|
|
960
666
|
from eth_utils import to_checksum_address
|
|
961
667
|
|
|
@@ -992,21 +698,6 @@ class HyperliquidAdapter(BaseAdapter):
|
|
|
992
698
|
max_poll_time_s: int = 30 * 60,
|
|
993
699
|
poll_interval_s: int = 5,
|
|
994
700
|
) -> tuple[bool, dict[str, float]]:
|
|
995
|
-
"""
|
|
996
|
-
Wait for a withdrawal to appear on-chain.
|
|
997
|
-
|
|
998
|
-
Polls Hyperliquid's ledger updates until a withdrawal is detected.
|
|
999
|
-
Withdrawals typically take 5-15 minutes to process.
|
|
1000
|
-
|
|
1001
|
-
Args:
|
|
1002
|
-
address: Wallet address
|
|
1003
|
-
lookback_s: How far back to look for withdrawals (small buffer for latency)
|
|
1004
|
-
max_poll_time_s: Maximum time to wait (default 30 minutes)
|
|
1005
|
-
poll_interval_s: Time between polls
|
|
1006
|
-
|
|
1007
|
-
Returns:
|
|
1008
|
-
(success, {tx_hash: usdc_amount}) - withdrawals found
|
|
1009
|
-
"""
|
|
1010
701
|
import time
|
|
1011
702
|
|
|
1012
703
|
start_time_ms = time.time() * 1000
|