wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.21__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.

Files changed (98) hide show
  1. wayfinder_paths/__init__.py +0 -2
  2. wayfinder_paths/adapters/balance_adapter/README.md +59 -45
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +1 -22
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
  5. wayfinder_paths/adapters/brap_adapter/README.md +61 -184
  6. wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
  7. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -148
  8. wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
  9. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
  10. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +1 -10
  11. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
  12. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
  13. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
  14. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
  15. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
  16. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
  17. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
  18. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
  19. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
  20. wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
  21. wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
  22. wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
  23. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
  24. wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
  25. wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
  26. wayfinder_paths/adapters/moonwell_adapter/adapter.py +10 -122
  27. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
  28. wayfinder_paths/adapters/pool_adapter/README.md +30 -51
  29. wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
  30. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
  31. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
  32. wayfinder_paths/adapters/token_adapter/README.md +41 -49
  33. wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
  34. wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
  35. wayfinder_paths/conftest.py +0 -8
  36. wayfinder_paths/core/__init__.py +0 -2
  37. wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
  38. wayfinder_paths/core/adapters/__init__.py +0 -5
  39. wayfinder_paths/core/adapters/models.py +0 -5
  40. wayfinder_paths/core/analytics/__init__.py +0 -2
  41. wayfinder_paths/core/analytics/bootstrap.py +0 -16
  42. wayfinder_paths/core/analytics/stats.py +0 -7
  43. wayfinder_paths/core/analytics/test_analytics.py +5 -34
  44. wayfinder_paths/core/clients/BRAPClient.py +0 -35
  45. wayfinder_paths/core/clients/ClientManager.py +0 -51
  46. wayfinder_paths/core/clients/HyperlendClient.py +0 -77
  47. wayfinder_paths/core/clients/LedgerClient.py +2 -122
  48. wayfinder_paths/core/clients/PoolClient.py +0 -2
  49. wayfinder_paths/core/clients/TokenClient.py +0 -39
  50. wayfinder_paths/core/clients/WalletClient.py +0 -15
  51. wayfinder_paths/core/clients/WayfinderClient.py +0 -24
  52. wayfinder_paths/core/clients/__init__.py +0 -4
  53. wayfinder_paths/core/clients/protocols.py +25 -98
  54. wayfinder_paths/core/config.py +0 -24
  55. wayfinder_paths/core/constants/__init__.py +0 -7
  56. wayfinder_paths/core/constants/base.py +2 -9
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -5
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
  59. wayfinder_paths/core/constants/moonwell_abi.py +0 -35
  60. wayfinder_paths/core/engine/StrategyJob.py +0 -32
  61. wayfinder_paths/core/strategies/Strategy.py +0 -99
  62. wayfinder_paths/core/strategies/__init__.py +0 -2
  63. wayfinder_paths/core/utils/__init__.py +0 -1
  64. wayfinder_paths/core/utils/evm_helpers.py +0 -50
  65. wayfinder_paths/core/utils/{erc20_service.py → tokens.py} +25 -21
  66. wayfinder_paths/core/utils/transaction.py +0 -1
  67. wayfinder_paths/run_strategy.py +0 -46
  68. wayfinder_paths/scripts/create_strategy.py +0 -17
  69. wayfinder_paths/scripts/make_wallets.py +1 -4
  70. wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
  71. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
  72. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
  73. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
  74. wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
  76. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
  77. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
  78. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
  79. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
  80. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
  81. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
  82. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
  83. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
  84. wayfinder_paths/templates/adapter/README.md +107 -46
  85. wayfinder_paths/templates/adapter/adapter.py +0 -9
  86. wayfinder_paths/templates/adapter/test_adapter.py +0 -19
  87. wayfinder_paths/templates/strategy/README.md +113 -59
  88. wayfinder_paths/templates/strategy/strategy.py +0 -22
  89. wayfinder_paths/templates/strategy/test_strategy.py +0 -28
  90. wayfinder_paths/tests/test_test_coverage.py +2 -12
  91. wayfinder_paths/tests/test_utils.py +1 -31
  92. wayfinder_paths-0.1.21.dist-info/METADATA +355 -0
  93. wayfinder_paths-0.1.21.dist-info/RECORD +129 -0
  94. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/WHEEL +1 -1
  95. wayfinder_paths/core/adapters/base.py +0 -5
  96. wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
  97. wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
  98. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/LICENSE +0 -0
@@ -1,12 +1,3 @@
1
- """
2
- Hyperliquid Executor Protocol and Implementations.
3
-
4
- Defines the interface for Hyperliquid order execution and a local-signing implementation.
5
-
6
- Other execution environments can provide their own `HyperliquidExecutor` that satisfies
7
- the protocol (for example, by delegating signing to a hosted signer).
8
- """
9
-
10
1
  from __future__ import annotations
11
2
 
12
3
  import uuid
@@ -39,18 +30,11 @@ except ImportError:
39
30
 
40
31
 
41
32
  def _new_client_id() -> Cloid:
42
- """Generate a new client order ID as a Cloid object."""
43
33
  cloid_str = "0x" + uuid.uuid4().hex
44
34
  return Cloid(cloid_str)
45
35
 
46
36
 
47
37
  class LocalHyperliquidExecutor:
48
- """
49
- Local Hyperliquid executor using SDK with private key signing.
50
-
51
- Uses the hyperliquid SDK's Exchange class which handles EIP-712 signing internally.
52
- """
53
-
54
38
  def __init__(
55
39
  self,
56
40
  *,
@@ -73,13 +57,11 @@ class LocalHyperliquidExecutor:
73
57
  "Provide strategy_wallet.private_key_hex or strategy_wallet.private_key"
74
58
  )
75
59
 
76
- # Create wallet account
77
60
  pk = self._private_key
78
61
  if not pk.startswith("0x"):
79
62
  pk = "0x" + pk
80
63
  self._wallet = Account.from_key(pk)
81
64
 
82
- # Initialize SDK clients
83
65
  base_url = (
84
66
  constants.MAINNET_API_URL
85
67
  if network == "mainnet"
@@ -94,12 +76,6 @@ class LocalHyperliquidExecutor:
94
76
  )
95
77
 
96
78
  def _get_perp_coin(self, asset_id: int) -> str | None:
97
- """
98
- Resolve a perp coin name from a perp asset_id.
99
-
100
- Note: newer versions of the hyperliquid SDK expose `coin_to_asset` but not
101
- `asset_to_coin`, so we build the reverse mapping when needed.
102
- """
103
79
  if self._asset_id_to_coin is None:
104
80
  mapping: dict[int, str] = {}
105
81
 
@@ -131,7 +107,6 @@ class LocalHyperliquidExecutor:
131
107
  return self._asset_id_to_coin.get(asset_id) if self._asset_id_to_coin else None
132
108
 
133
109
  def _resolve_private_key(self, config: dict[str, Any]) -> str | None:
134
- """Extract private key from config."""
135
110
  # Try strategy_wallet first
136
111
  strategy_wallet = config.get("strategy_wallet", {})
137
112
  if isinstance(strategy_wallet, dict):
@@ -152,7 +127,6 @@ class LocalHyperliquidExecutor:
152
127
 
153
128
  @property
154
129
  def address(self) -> str:
155
- """Get the wallet address."""
156
130
  return self._wallet.address
157
131
 
158
132
  async def place_market_order(
@@ -167,22 +141,15 @@ class LocalHyperliquidExecutor:
167
141
  cloid: Any = None,
168
142
  builder: dict[str, Any] | None = None,
169
143
  ) -> dict[str, Any]:
170
- """Place a market order using the SDK.
171
-
172
- Args:
173
- builder: Optional builder fee config with keys 'b' (address) and 'f' (fee bps)
174
- """
175
144
  if cloid is None:
176
145
  cloid = _new_client_id()
177
146
  elif isinstance(cloid, str):
178
147
  cloid = Cloid(cloid)
179
148
 
180
- # Convert builder dict to BuilderInfo if provided
181
149
  builder_info = None
182
150
  if builder:
183
151
  builder_info = BuilderInfo(b=builder.get("b", ""), f=builder.get("f", 0))
184
152
 
185
- # Validate address matches our wallet
186
153
  if address.lower() != self._wallet.address.lower():
187
154
  return {
188
155
  "status": "err",
@@ -193,7 +160,6 @@ class LocalHyperliquidExecutor:
193
160
  }
194
161
 
195
162
  try:
196
- # The SDK's market_open handles slippage internally
197
163
  # For spot (asset_id >= 10000), use different method
198
164
  is_spot = asset_id >= 10000
199
165
 
@@ -256,7 +222,6 @@ class LocalHyperliquidExecutor:
256
222
  order_id: int,
257
223
  address: str,
258
224
  ) -> dict[str, Any]:
259
- """Cancel an open order."""
260
225
  if address.lower() != self._wallet.address.lower():
261
226
  return {
262
227
  "status": "err",
@@ -299,7 +264,6 @@ class LocalHyperliquidExecutor:
299
264
  is_cross: bool,
300
265
  address: str,
301
266
  ) -> dict[str, Any]:
302
- """Update leverage for an asset."""
303
267
  if address.lower() != self._wallet.address.lower():
304
268
  return {
305
269
  "status": "err",
@@ -338,7 +302,6 @@ class LocalHyperliquidExecutor:
338
302
  amount: float,
339
303
  address: str,
340
304
  ) -> dict[str, Any]:
341
- """Transfer USDC from spot to perp balance."""
342
305
  if address.lower() != self._wallet.address.lower():
343
306
  return {
344
307
  "status": "err",
@@ -366,7 +329,6 @@ class LocalHyperliquidExecutor:
366
329
  amount: float,
367
330
  address: str,
368
331
  ) -> dict[str, Any]:
369
- """Transfer USDC from perp to spot balance."""
370
332
  if address.lower() != self._wallet.address.lower():
371
333
  return {
372
334
  "status": "err",
@@ -397,7 +359,6 @@ class LocalHyperliquidExecutor:
397
359
  size: float,
398
360
  address: str,
399
361
  ) -> dict[str, Any]:
400
- """Place a stop-loss order."""
401
362
  if address.lower() != self._wallet.address.lower():
402
363
  return {
403
364
  "status": "err",
@@ -451,20 +412,6 @@ class LocalHyperliquidExecutor:
451
412
  reduce_only: bool = False,
452
413
  builder: dict[str, Any] | None = None,
453
414
  ) -> dict[str, Any]:
454
- """
455
- Place a limit order (GTC - Good Till Cancelled).
456
-
457
- Used for spot stop-loss orders in basis trading.
458
-
459
- Args:
460
- asset_id: Asset ID (perp or spot)
461
- is_buy: True for buy, False for sell
462
- price: Limit price
463
- size: Order size
464
- address: Wallet address
465
- reduce_only: If True, only reduces existing position
466
- builder: Optional builder fee config
467
- """
468
415
  if address.lower() != self._wallet.address.lower():
469
416
  return {
470
417
  "status": "err",
@@ -488,7 +435,6 @@ class LocalHyperliquidExecutor:
488
435
  },
489
436
  }
490
437
 
491
- # Convert builder dict to BuilderInfo if provided
492
438
  builder_info = None
493
439
  if builder:
494
440
  builder_info = BuilderInfo(
@@ -521,7 +467,6 @@ class LocalHyperliquidExecutor:
521
467
  amount: float,
522
468
  address: str,
523
469
  ) -> dict[str, Any]:
524
- """Withdraw USDC from Hyperliquid to Arbitrum."""
525
470
  if address.lower() != self._wallet.address.lower():
526
471
  return {
527
472
  "status": "err",
@@ -532,7 +477,7 @@ class LocalHyperliquidExecutor:
532
477
  # Use withdraw_from_bridge to withdraw to the wallet's own address on Arbitrum
533
478
  result = self.exchange.withdraw_from_bridge(
534
479
  amount=amount,
535
- destination=address, # Withdraw to same address on Arbitrum
480
+ destination=address,
536
481
  )
537
482
  logger.debug(f"Withdraw result: {result}")
538
483
  return result
@@ -551,20 +496,6 @@ class LocalHyperliquidExecutor:
551
496
  max_fee_rate: str,
552
497
  address: str,
553
498
  ) -> dict[str, Any]:
554
- """
555
- Approve a builder fee for the user.
556
-
557
- This signs and broadcasts an approveBuilderFee action that allows
558
- the specified builder to charge up to max_fee_rate on trades.
559
-
560
- Args:
561
- builder: Builder wallet address
562
- max_fee_rate: Fee rate as percentage string (e.g., "0.030%" for 30 tenths bp)
563
- address: User wallet address (must match executor wallet)
564
-
565
- Returns:
566
- Dict with status "ok" or "err" and response data
567
- """
568
499
  if address.lower() != self._wallet.address.lower():
569
500
  return {
570
501
  "status": "err",
@@ -572,7 +503,6 @@ class LocalHyperliquidExecutor:
572
503
  }
573
504
 
574
505
  try:
575
- # The SDK's approve_builder_fee method handles the signing internally
576
506
  result = self.exchange.approve_builder_fee(
577
507
  builder=builder,
578
508
  max_fee_rate=max_fee_rate,
@@ -1,5 +1,3 @@
1
- """PairedFiller - atomic paired spot+perp order execution with imbalance repair."""
2
-
3
1
  from __future__ import annotations
4
2
 
5
3
  import asyncio
@@ -47,7 +45,6 @@ def _round_up_units(units: float, step: Decimal) -> float:
47
45
  def _parse_oids_and_immediate_fill(
48
46
  resp: dict[str, Any],
49
47
  ) -> tuple[list[int], float, float]:
50
- """Extract order IDs and immediate fill info from API response."""
51
48
  oids: list[int] = []
52
49
  filled_units = 0.0
53
50
  filled_notional = 0.0
@@ -112,8 +109,6 @@ def _parse_oids_and_immediate_fill(
112
109
 
113
110
  @dataclass
114
111
  class FillConfig:
115
- """Configuration for paired order filling."""
116
-
117
112
  max_slip_bps: int = 35
118
113
  max_chunk_usd: float = 7_500.0
119
114
  max_loops: int = 40
@@ -122,8 +117,6 @@ class FillConfig:
122
117
 
123
118
  @dataclass
124
119
  class FillConfirmCfg:
125
- """Configuration for fill confirmation polling."""
126
-
127
120
  max_status_polls: int = 4
128
121
  poll_sleep_s: float = 0.20
129
122
  fills_time_early_ms: int = 3_000
@@ -132,16 +125,12 @@ class FillConfirmCfg:
132
125
 
133
126
  @dataclass
134
127
  class LegFillResult:
135
- """Result of filling a single leg (spot or perp)."""
136
-
137
128
  units: float = 0.0
138
129
  notional: float = 0.0
139
130
 
140
131
 
141
132
  @dataclass
142
133
  class LegSubmitResult:
143
- """Result of submitting a leg order."""
144
-
145
134
  oids: list[int]
146
135
  start_ms: int
147
136
  coin_label: str
@@ -151,8 +140,6 @@ class LegSubmitResult:
151
140
 
152
141
 
153
142
  class LegConfirmer:
154
- """Confirms fill completion for individual legs via polling."""
155
-
156
143
  def __init__(self, adapter: HyperliquidAdapter, cfg: FillConfirmCfg):
157
144
  self.adapter = adapter
158
145
  self.cfg = cfg
@@ -168,7 +155,6 @@ class LegConfirmer:
168
155
  fallback_units: float = 0.0,
169
156
  fallback_notional: float = 0.0,
170
157
  ) -> LegFillResult:
171
- """Confirm that a leg order has been filled."""
172
158
  oids: list[int] = list(initial_oids)
173
159
 
174
160
  if not oids and cloid:
@@ -208,7 +194,6 @@ class LegConfirmer:
208
194
  return result
209
195
 
210
196
  async def _oid_from_cloid(self, cloid: str, address: str) -> int | None:
211
- """Get numeric order ID from client order ID."""
212
197
  try:
213
198
  success, status = await self.adapter.get_order_status(address, cloid)
214
199
  if not success:
@@ -227,7 +212,6 @@ class LegConfirmer:
227
212
  return None
228
213
 
229
214
  async def _ensure_not_open(self, address: str, oids: list[int]) -> None:
230
- """Cancel any orders that are still open."""
231
215
  if not oids:
232
216
  return
233
217
  try:
@@ -266,7 +250,6 @@ class LegConfirmer:
266
250
  logger.info(f"Cancel failed for oid {oid_int}: {exc}")
267
251
 
268
252
  def _resolve_asset_id(self, order: dict[str, Any]) -> int | None:
269
- """Resolve asset ID from order info."""
270
253
  coin = order.get("coin")
271
254
  if not isinstance(coin, str):
272
255
  return None
@@ -281,7 +264,6 @@ class LegConfirmer:
281
264
  return None
282
265
 
283
266
  def _flatten_open_orders(self, obj: Any) -> list[dict[str, Any]]:
284
- """Flatten nested order structures into a flat list."""
285
267
  results: list[dict[str, Any]] = []
286
268
 
287
269
  def walk(node: Any, coin_ctx: str | None = None) -> None:
@@ -315,7 +297,6 @@ class LegConfirmer:
315
297
  start_ms: int,
316
298
  oids: list[int],
317
299
  ) -> LegFillResult:
318
- """Sum fills matching order IDs within time window."""
319
300
  if not oids:
320
301
  return LegFillResult()
321
302
 
@@ -393,7 +374,6 @@ class LegConfirmer:
393
374
 
394
375
  @staticmethod
395
376
  def _to_records(data: Any) -> list[dict[str, Any]]:
396
- """Convert various data formats to list of dicts."""
397
377
  if data is None:
398
378
  return []
399
379
  if hasattr(data, "to_dict"):
@@ -408,17 +388,6 @@ class LegConfirmer:
408
388
 
409
389
 
410
390
  class PairedFiller:
411
- """
412
- Executes atomic paired spot+perp orders with imbalance repair.
413
-
414
- Handles:
415
- - Chunking large orders into $7,500 pieces
416
- - Parallel execution of spot and perp legs
417
- - Imbalance repair if one leg fills more than the other
418
- - Rollback on failure
419
- - Fill confirmation via polling
420
- """
421
-
422
391
  def __init__(
423
392
  self,
424
393
  adapter: HyperliquidAdapter,
@@ -450,21 +419,6 @@ class PairedFiller:
450
419
  list[dict[str, Any]],
451
420
  list[dict[str, Any]],
452
421
  ]:
453
- """
454
- Fill paired spot+perp positions atomically.
455
-
456
- Args:
457
- coin: Asset symbol (e.g., "ETH")
458
- spot_asset_id: Spot asset ID (>= 10000)
459
- perp_asset_id: Perpetual asset ID (< 10000)
460
- total_units: Total units to fill
461
- direction: "long_spot_short_perp" or "short_spot_long_perp"
462
- builder_fee: Optional builder fee configuration
463
-
464
- Returns:
465
- Tuple of (spot_units, perp_units, spot_notional, perp_notional,
466
- spot_pointers, perp_pointers)
467
- """
468
422
  step = self._common_step(spot_asset_id, perp_asset_id)
469
423
  remaining = _round_down_units(total_units, step)
470
424
  if remaining <= 0:
@@ -502,7 +456,6 @@ class PairedFiller:
502
456
  while loops < self.cfg.max_loops and (
503
457
  remaining > 0 or abs(delta_units) >= step_float
504
458
  ):
505
- # Handle imbalance repair
506
459
  if abs(delta_units) >= step_float:
507
460
  loops += 1
508
461
  fix_units = _round_down_units(abs(delta_units), step)
@@ -751,7 +704,6 @@ class PairedFiller:
751
704
  logger.warning("Paired filler made no progress for {}; aborting.", coin)
752
705
  break
753
706
 
754
- # Handle margin rejection - rollback spot if perp rejected
755
707
  if (
756
708
  spot_fill.units > 0.0
757
709
  and perp_fill.units <= 0.0
@@ -794,7 +746,6 @@ class PairedFiller:
794
746
 
795
747
  break
796
748
 
797
- # Handle spot failure - rollback perp if spot failed
798
749
  if (
799
750
  perp_fill.units > 0.0
800
751
  and spot_fill.units <= 0.0
@@ -894,7 +845,6 @@ class PairedFiller:
894
845
 
895
846
  @staticmethod
896
847
  def _is_margin_rejected(resp: dict[str, Any]) -> bool:
897
- """Detect Hyperliquid margin rejection responses."""
898
848
  if not isinstance(resp, dict):
899
849
  return False
900
850
 
@@ -912,7 +862,6 @@ class PairedFiller:
912
862
 
913
863
  @staticmethod
914
864
  def _is_errorish(resp: dict[str, Any]) -> bool:
915
- """Best-effort detection for failed API responses."""
916
865
  if not isinstance(resp, dict):
917
866
  return True
918
867
 
@@ -941,7 +890,6 @@ class PairedFiller:
941
890
  cloid: str,
942
891
  builder_fee: dict[str, Any] | None = None,
943
892
  ) -> LegSubmitResult:
944
- """Submit an IOC order for one leg."""
945
893
  start_ms = _now_ms()
946
894
  rounded_units = self.adapter.get_valid_order_size(asset_id, units)
947
895
  if rounded_units <= 0:
@@ -975,7 +923,6 @@ class PairedFiller:
975
923
  )
976
924
 
977
925
  async def _spot_usdc_available(self) -> float:
978
- """Get available USDC balance for spot trades."""
979
926
  try:
980
927
  success, state = await self.adapter.get_spot_user_state(self.address)
981
928
  if not success:
@@ -1003,7 +950,6 @@ class PairedFiller:
1003
950
  step: Decimal,
1004
951
  min_units: float,
1005
952
  ) -> float:
1006
- """Calculate maximum spot units based on available USDC."""
1007
953
  if desired_units <= 0 or mid_price <= 0:
1008
954
  return 0.0
1009
955
  available = await self._spot_usdc_available()
@@ -1019,7 +965,6 @@ class PairedFiller:
1019
965
  return min(desired_units, max_units)
1020
966
 
1021
967
  def _min_units_for_notional(self, mid_price: float, step: Decimal) -> float:
1022
- """Calculate minimum units to meet notional threshold."""
1023
968
  if mid_price <= 0:
1024
969
  return max(float(step), 0.0)
1025
970
  raw_units = MIN_NOTIONAL_USD / mid_price
@@ -1029,7 +974,6 @@ class PairedFiller:
1029
974
  return max(float(step), quantized)
1030
975
 
1031
976
  def _common_step(self, spot_asset_id: int, perp_asset_id: int) -> Decimal:
1032
- """Get common step size for both assets."""
1033
977
  spot_decimals = self.adapter.get_sz_decimals(spot_asset_id)
1034
978
  perp_decimals = self.adapter.get_sz_decimals(perp_asset_id)
1035
979
  return Decimal(10) ** -min(spot_decimals, perp_decimals)
@@ -1040,7 +984,6 @@ class PairedFiller:
1040
984
  reason: str,
1041
985
  metadata: dict[str, Any],
1042
986
  ) -> dict[str, Any] | None:
1043
- """Build order pointer for tracking."""
1044
987
  if not response or response.get("status") != "ok":
1045
988
  return None
1046
989
  return {
@@ -1,5 +1,3 @@
1
- """Tests for HyperliquidAdapter."""
2
-
3
1
  from types import SimpleNamespace
4
2
  from unittest.mock import MagicMock, patch
5
3
 
@@ -7,11 +5,8 @@ import pytest
7
5
 
8
6
 
9
7
  class TestHyperliquidAdapter:
10
- """Tests for HyperliquidAdapter functionality."""
11
-
12
8
  @pytest.fixture
13
9
  def mock_info(self):
14
- """Create a mock Info client."""
15
10
  mock = MagicMock()
16
11
  mock.meta_and_asset_ctxs.return_value = [
17
12
  {"universe": [{"name": "BTC"}, {"name": "ETH"}]},
@@ -36,12 +31,10 @@ class TestHyperliquidAdapter:
36
31
 
37
32
  @pytest.fixture
38
33
  def mock_constants(self):
39
- """Create mock constants module."""
40
34
  return SimpleNamespace(MAINNET_API_URL="https://api.hyperliquid.xyz")
41
35
 
42
36
  @pytest.fixture
43
37
  def adapter(self, mock_info, mock_constants):
44
- """Create adapter with mocked Info client."""
45
38
  with patch(
46
39
  "wayfinder_paths.adapters.hyperliquid_adapter.adapter.Info",
47
40
  return_value=mock_info,
@@ -64,63 +57,53 @@ class TestHyperliquidAdapter:
64
57
 
65
58
  @pytest.mark.asyncio
66
59
  async def test_connect(self, adapter):
67
- """Test connection verification."""
68
60
  result = await adapter.connect()
69
61
  assert result is True
70
62
 
71
63
  @pytest.mark.asyncio
72
64
  async def test_get_meta_and_asset_ctxs(self, adapter):
73
- """Test fetching market metadata."""
74
65
  success, data = await adapter.get_meta_and_asset_ctxs()
75
66
  assert success
76
67
  assert "universe" in data[0]
77
68
 
78
69
  @pytest.mark.asyncio
79
70
  async def test_get_spot_meta(self, adapter):
80
- """Test fetching spot metadata."""
81
71
  success, data = await adapter.get_spot_meta()
82
72
  assert success
83
73
 
84
74
  @pytest.mark.asyncio
85
75
  async def test_get_funding_history(self, adapter):
86
- """Test fetching funding history."""
87
76
  success, data = await adapter.get_funding_history("ETH", 1700000000000)
88
77
  assert success
89
78
  assert isinstance(data, list)
90
79
 
91
80
  @pytest.mark.asyncio
92
81
  async def test_get_candles(self, adapter):
93
- """Test fetching candle data."""
94
82
  success, data = await adapter.get_candles("ETH", "1h", 1700000000000)
95
83
  assert success
96
84
  assert isinstance(data, list)
97
85
 
98
86
  @pytest.mark.asyncio
99
87
  async def test_get_l2_book(self, adapter):
100
- """Test fetching order book."""
101
88
  success, data = await adapter.get_l2_book("ETH")
102
89
  assert success
103
90
  assert "levels" in data
104
91
 
105
92
  @pytest.mark.asyncio
106
93
  async def test_get_user_state(self, adapter):
107
- """Test fetching user state."""
108
94
  success, data = await adapter.get_user_state("0x1234")
109
95
  assert success
110
96
  assert "assetPositions" in data
111
97
 
112
98
  @pytest.mark.asyncio
113
99
  async def test_health_check(self, adapter):
114
- """Test health check."""
115
100
  result = await adapter.health_check()
116
101
  assert result["status"] == "healthy"
117
102
 
118
103
  def test_get_sz_decimals(self, adapter):
119
- """Test getting size decimals."""
120
104
  decimals = adapter.get_sz_decimals(0)
121
105
  assert decimals == 4
122
106
 
123
107
  def test_get_sz_decimals_unknown_asset(self, adapter):
124
- """Test error on unknown asset."""
125
108
  with pytest.raises(ValueError, match="Unknown asset_id"):
126
109
  adapter.get_sz_decimals(99999)