wayfinder-paths 0.1.6__py3-none-any.whl → 0.1.8__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 (50) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +0 -10
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +0 -20
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -30
  4. wayfinder_paths/adapters/brap_adapter/adapter.py +3 -2
  5. wayfinder_paths/adapters/brap_adapter/test_adapter.py +9 -13
  6. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +14 -7
  7. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  8. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  9. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  10. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  11. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  12. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  13. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  14. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  15. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  16. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +7 -6
  17. wayfinder_paths/adapters/pool_adapter/README.md +3 -28
  18. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -72
  19. wayfinder_paths/adapters/pool_adapter/examples.json +0 -43
  20. wayfinder_paths/adapters/pool_adapter/test_adapter.py +4 -54
  21. wayfinder_paths/adapters/token_adapter/test_adapter.py +4 -14
  22. wayfinder_paths/core/adapters/models.py +9 -4
  23. wayfinder_paths/core/analytics/__init__.py +11 -0
  24. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  25. wayfinder_paths/core/analytics/stats.py +48 -0
  26. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  27. wayfinder_paths/core/clients/BRAPClient.py +1 -0
  28. wayfinder_paths/core/clients/LedgerClient.py +2 -7
  29. wayfinder_paths/core/clients/PoolClient.py +0 -16
  30. wayfinder_paths/core/clients/WalletClient.py +0 -27
  31. wayfinder_paths/core/clients/protocols.py +104 -18
  32. wayfinder_paths/scripts/make_wallets.py +9 -0
  33. wayfinder_paths/scripts/run_strategy.py +124 -0
  34. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  35. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  36. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  37. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  38. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  39. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  40. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  41. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  42. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  43. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +1 -9
  44. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +36 -5
  45. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +367 -278
  46. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +204 -7
  47. {wayfinder_paths-0.1.6.dist-info → wayfinder_paths-0.1.8.dist-info}/METADATA +32 -3
  48. {wayfinder_paths-0.1.6.dist-info → wayfinder_paths-0.1.8.dist-info}/RECORD +50 -27
  49. {wayfinder_paths-0.1.6.dist-info → wayfinder_paths-0.1.8.dist-info}/LICENSE +0 -0
  50. {wayfinder_paths-0.1.6.dist-info → wayfinder_paths-0.1.8.dist-info}/WHEEL +0 -0
@@ -176,7 +176,7 @@ class LedgerClient:
176
176
  "offset": offset,
177
177
  }
178
178
 
179
- async def get_strategy_net_deposit(self, *, wallet_address: str) -> NetDeposit:
179
+ async def get_strategy_net_deposit(self, *, wallet_address: str) -> float:
180
180
  """
181
181
  Calculate the net deposit (deposits - withdrawals) for a strategy wallet.
182
182
 
@@ -210,12 +210,7 @@ class LedgerClient:
210
210
 
211
211
  net_deposit = total_deposits - total_withdrawals
212
212
 
213
- return {
214
- "net_deposit": str(net_deposit),
215
- "total_deposits": str(total_deposits),
216
- "total_withdrawals": str(total_withdrawals),
217
- "wallet_address": wallet_address,
218
- }
213
+ return float(net_deposit)
219
214
 
220
215
  async def get_strategy_latest_transactions(
221
216
  self, *, wallet_address: str, limit: int = 10
@@ -85,22 +85,6 @@ class PoolClient(WayfinderClient):
85
85
  data = response.json()
86
86
  return data.get("data", data)
87
87
 
88
- async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
89
- """
90
- Fetch all pools.
91
-
92
- Example:
93
- GET /api/v1/public/pools/?merge_external=false
94
- """
95
- url = f"{self.api_base_url}/public/pools/"
96
- params: dict[str, Any] = {}
97
- if merge_external is not None:
98
- params["merge_external"] = "true" if merge_external else "false"
99
- response = await self._request("GET", url, params=params, headers={})
100
- response.raise_for_status()
101
- data = response.json()
102
- return data.get("data", data)
103
-
104
88
  async def get_llama_matches(self) -> dict[str, LlamaMatch]:
105
89
  """
106
90
  Fetch Llama matches for pools.
@@ -92,30 +92,3 @@ class WalletClient(WayfinderClient):
92
92
  response = await self._authed_request("POST", url, json=payload)
93
93
  data = response.json()
94
94
  return data.get("data", data)
95
-
96
- async def get_all_enriched_token_balances_for_wallet(
97
- self,
98
- *,
99
- wallet_address: str,
100
- enrich: bool = True,
101
- from_cache: bool = False,
102
- add_llama: bool = True,
103
- ) -> EnrichedBalances:
104
- """
105
- Fetch all token balances for a wallet with enrichment via the enriched endpoint.
106
-
107
- Mirrors POST /api/v1/public/balances/enriched/
108
- """
109
- url = f"{self.api_base_url}/public/balances/enriched/"
110
- payload = {
111
- "wallet_address": wallet_address,
112
- "enrich": enrich,
113
- "from_cache": from_cache,
114
- "add_llama": add_llama,
115
- }
116
- try:
117
- response = await self._authed_request("POST", url, json=payload)
118
- data = response.json()
119
- return data.get("data", data)
120
- except Exception:
121
- raise
@@ -20,7 +20,6 @@ if TYPE_CHECKING:
20
20
  StableMarket,
21
21
  )
22
22
  from wayfinder_paths.core.clients.LedgerClient import (
23
- NetDeposit,
24
23
  StrategyTransactionList,
25
24
  TransactionRecord,
26
25
  )
@@ -36,7 +35,6 @@ if TYPE_CHECKING:
36
35
  )
37
36
  from wayfinder_paths.core.clients.TransactionClient import TransactionPayload
38
37
  from wayfinder_paths.core.clients.WalletClient import (
39
- EnrichedBalances,
40
38
  PoolBalance,
41
39
  TokenBalance,
42
40
  )
@@ -113,7 +111,7 @@ class LedgerClientProtocol(Protocol):
113
111
  """Fetch a paginated list of transactions for a given strategy wallet"""
114
112
  ...
115
113
 
116
- async def get_strategy_net_deposit(self, *, wallet_address: str) -> NetDeposit:
114
+ async def get_strategy_net_deposit(self, *, wallet_address: str) -> float:
117
115
  """Fetch the net deposit (deposits - withdrawals) for a strategy"""
118
116
  ...
119
117
 
@@ -187,17 +185,6 @@ class WalletClientProtocol(Protocol):
187
185
  """Fetch a wallet's LP/share balance for a given pool address and chain"""
188
186
  ...
189
187
 
190
- async def get_all_enriched_token_balances_for_wallet(
191
- self,
192
- *,
193
- wallet_address: str,
194
- enrich: bool = True,
195
- from_cache: bool = False,
196
- add_llama: bool = True,
197
- ) -> EnrichedBalances:
198
- """Fetch all token balances for a wallet with enrichment"""
199
- ...
200
-
201
188
 
202
189
  class TransactionClientProtocol(Protocol):
203
190
  """Protocol for transaction operations"""
@@ -226,10 +213,6 @@ class PoolClientProtocol(Protocol):
226
213
  """Fetch pools by comma-separated pool ids"""
227
214
  ...
228
215
 
229
- async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
230
- """Fetch all pools"""
231
- ...
232
-
233
216
  async def get_llama_matches(self) -> dict[str, LlamaMatch]:
234
217
  """Fetch Llama matches for pools"""
235
218
  ...
@@ -300,3 +283,106 @@ class SimulationClientProtocol(Protocol):
300
283
  ) -> SimulationResult:
301
284
  """Simulate token swap operation"""
302
285
  ...
286
+
287
+
288
+ class HyperliquidExecutorProtocol(Protocol):
289
+ """Protocol for Hyperliquid order execution operations."""
290
+
291
+ async def place_market_order(
292
+ self,
293
+ *,
294
+ asset_id: int,
295
+ is_buy: bool,
296
+ slippage: float,
297
+ size: float,
298
+ address: str,
299
+ reduce_only: bool = False,
300
+ cloid: Any = None,
301
+ builder: dict[str, Any] | None = None,
302
+ ) -> dict[str, Any]:
303
+ """Place a market order."""
304
+ ...
305
+
306
+ async def cancel_order(
307
+ self,
308
+ *,
309
+ asset_id: int,
310
+ order_id: int,
311
+ address: str,
312
+ ) -> dict[str, Any]:
313
+ """Cancel an open order."""
314
+ ...
315
+
316
+ async def update_leverage(
317
+ self,
318
+ *,
319
+ asset_id: int,
320
+ leverage: int,
321
+ is_cross: bool,
322
+ address: str,
323
+ ) -> dict[str, Any]:
324
+ """Update leverage for an asset."""
325
+ ...
326
+
327
+ async def transfer_spot_to_perp(
328
+ self,
329
+ *,
330
+ amount: float,
331
+ address: str,
332
+ ) -> dict[str, Any]:
333
+ """Transfer USDC from spot to perp balance."""
334
+ ...
335
+
336
+ async def transfer_perp_to_spot(
337
+ self,
338
+ *,
339
+ amount: float,
340
+ address: str,
341
+ ) -> dict[str, Any]:
342
+ """Transfer USDC from perp to spot balance."""
343
+ ...
344
+
345
+ async def place_stop_loss(
346
+ self,
347
+ *,
348
+ asset_id: int,
349
+ is_buy: bool,
350
+ trigger_price: float,
351
+ size: float,
352
+ address: str,
353
+ ) -> dict[str, Any]:
354
+ """Place a stop-loss order."""
355
+ ...
356
+
357
+ async def place_limit_order(
358
+ self,
359
+ *,
360
+ asset_id: int,
361
+ is_buy: bool,
362
+ price: float,
363
+ size: float,
364
+ address: str,
365
+ reduce_only: bool = False,
366
+ builder: dict[str, Any] | None = None,
367
+ ) -> dict[str, Any]:
368
+ """Place a limit order."""
369
+ ...
370
+
371
+ async def withdraw(
372
+ self,
373
+ *,
374
+ amount: float,
375
+ address: str,
376
+ ) -> dict[str, Any]:
377
+ """Withdraw USDC from Hyperliquid to Arbitrum."""
378
+ ...
379
+
380
+ async def approve_builder_fee(
381
+ self,
382
+ *,
383
+ builder: str,
384
+ max_fee_rate: str,
385
+ address: str,
386
+ ) -> dict[str, Any]:
387
+ """Approve a builder fee for the user."""
388
+ ...
@@ -62,8 +62,17 @@ def main():
62
62
  default=None,
63
63
  help="Create a wallet with a custom label (e.g., strategy name). If not provided, auto-generates labels.",
64
64
  )
65
+ parser.add_argument(
66
+ "--default",
67
+ action="store_true",
68
+ help="Create a default 'main' wallet if none exists (used by CI)",
69
+ )
65
70
  args = parser.parse_args()
66
71
 
72
+ # --default is equivalent to -n 1 (create main wallet if needed)
73
+ if args.default and args.n == 0 and not args.label:
74
+ args.n = 1
75
+
67
76
  args.out_dir.mkdir(parents=True, exist_ok=True)
68
77
 
69
78
  # Load existing wallets
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ def _load_wallets(path: Path) -> list[dict[str, Any]]:
11
+ data = json.loads(path.read_text(encoding="utf-8"))
12
+ if not isinstance(data, list):
13
+ raise ValueError(f"Expected a list in {path}")
14
+ return [w for w in data if isinstance(w, dict)]
15
+
16
+
17
+ def _find_wallet(wallets: list[dict[str, Any]], label: str) -> dict[str, Any]:
18
+ for w in wallets:
19
+ if w.get("label") == label:
20
+ return w
21
+ raise ValueError(f"Wallet label not found in wallets.json: {label}")
22
+
23
+
24
+ def _get_strategy_class(strategy: str):
25
+ if strategy == "basis_trading_strategy":
26
+ from wayfinder_paths.strategies.basis_trading_strategy.strategy import (
27
+ BasisTradingStrategy,
28
+ )
29
+
30
+ return BasisTradingStrategy
31
+
32
+ if strategy == "hyperlend_stable_yield_strategy":
33
+ from wayfinder_paths.strategies.hyperlend_stable_yield_strategy.strategy import (
34
+ HyperlendStableYieldStrategy,
35
+ )
36
+
37
+ return HyperlendStableYieldStrategy
38
+
39
+ raise ValueError(f"Unknown strategy: {strategy}")
40
+
41
+
42
+ async def _run(args: argparse.Namespace) -> int:
43
+ repo_root = Path(__file__).resolve().parents[2]
44
+ wallets_path = (
45
+ Path(args.wallets).resolve() if args.wallets else repo_root / "wallets.json"
46
+ )
47
+ wallets = _load_wallets(wallets_path)
48
+
49
+ main_wallet = _find_wallet(wallets, args.main_wallet_label)
50
+ strategy_wallet = _find_wallet(wallets, args.strategy_wallet_label)
51
+
52
+ strategy_class = _get_strategy_class(args.strategy)
53
+ s = strategy_class(
54
+ {
55
+ "main_wallet": main_wallet,
56
+ "strategy_wallet": strategy_wallet,
57
+ },
58
+ simulation=args.simulation,
59
+ )
60
+
61
+ await s.setup()
62
+
63
+ if args.command == "deposit":
64
+ ok, msg = await s.deposit(
65
+ main_token_amount=float(args.usdc), gas_token_amount=float(args.eth)
66
+ )
67
+ print(msg)
68
+ return 0 if ok else 1
69
+
70
+ if args.command == "update":
71
+ ok, msg = await s.update()
72
+ print(msg)
73
+ return 0 if ok else 1
74
+
75
+ if args.command == "withdraw":
76
+ ok, msg = await s.withdraw(
77
+ amount=float(args.amount) if args.amount is not None else None
78
+ )
79
+ print(msg)
80
+ return 0 if ok else 1
81
+
82
+ if args.command == "status":
83
+ st = await s.status()
84
+ print(json.dumps(st, indent=2, sort_keys=True))
85
+ return 0
86
+
87
+ raise ValueError(f"Unknown command: {args.command}")
88
+
89
+
90
+ def main() -> int:
91
+ p = argparse.ArgumentParser(
92
+ description="Run a strategy locally (deposit/update/withdraw/status)."
93
+ )
94
+ p.add_argument(
95
+ "--strategy",
96
+ default="basis_trading_strategy",
97
+ choices=["basis_trading_strategy", "hyperlend_stable_yield_strategy"],
98
+ )
99
+ p.add_argument(
100
+ "--wallets", default=None, help="Path to wallets.json (default: repo root)"
101
+ )
102
+ p.add_argument("--main-wallet-label", default="main")
103
+ p.add_argument("--strategy-wallet-label", default="basis_trading_strategy")
104
+ p.add_argument("--simulation", action="store_true")
105
+
106
+ sub = p.add_subparsers(dest="command", required=True)
107
+
108
+ dep = sub.add_parser("deposit")
109
+ dep.add_argument("--usdc", required=True, type=float)
110
+ dep.add_argument("--eth", default=0.0, type=float)
111
+
112
+ sub.add_parser("update")
113
+
114
+ wd = sub.add_parser("withdraw")
115
+ wd.add_argument("--amount", default=None, type=float)
116
+
117
+ sub.add_parser("status")
118
+
119
+ args = p.parse_args()
120
+ return asyncio.run(_run(args))
121
+
122
+
123
+ if __name__ == "__main__":
124
+ raise SystemExit(main())
@@ -0,0 +1,213 @@
1
+ # Basis Trading Strategy
2
+
3
+ Delta-neutral basis trading on Hyperliquid that captures funding rate payments through matched spot long and perpetual short positions.
4
+
5
+ ## How It Works
6
+
7
+ ### Delta-Neutral Basis Trading
8
+
9
+ The strategy maintains market neutrality by holding equal-and-opposite positions:
10
+ - **Long Spot**: Buy the underlying asset (e.g., HYPE)
11
+ - **Short Perp**: Short the perpetual contract for the same asset
12
+
13
+ This creates a "basis trade" where:
14
+ - Price movements cancel out (if HYPE goes up 10%, spot gains +10%, perp loses -10%)
15
+ - You collect funding payments when longs pay shorts (positive funding rate)
16
+ - The position is "delta-neutral" - profit comes from funding, not price direction
17
+
18
+ ### Position Sizing with Leverage
19
+
20
+ Given a deposit of `D` USDC and leverage `L`:
21
+ - **Order Size**: `order_usd = D * (L / (L + 1))`
22
+ - **Margin Reserved**: `D / (L + 1)`
23
+
24
+ Example with $100 deposit at 2x leverage:
25
+ - Order size: $100 * (2/3) = $66.67 per leg
26
+ - Margin: $100 / 3 = $33.33
27
+
28
+ ## Opportunity Selection
29
+
30
+ ### 1. Candidate Discovery
31
+
32
+ The strategy scans all Hyperliquid markets to find spot-perp pairs:
33
+ - Spots quoted in USDC that have matching perpetual contracts
34
+ - Filters: minimum open interest, daily volume, order book depth
35
+
36
+ ### 2. Historical Analysis (Backtesting)
37
+
38
+ For each candidate, fetches up to 180 days of hourly data:
39
+ - **Funding rates**: Mean, volatility, negative hour fraction, worst 24h/7d sums
40
+ - **Price candles**: Hourly closes and highs for volatility calculation
41
+
42
+ ### 3. Safe Leverage Calculation
43
+
44
+ Uses a deterministic "stress test" approach over rolling historical windows:
45
+
46
+ ```
47
+ For each window of N hours:
48
+ - Track cumulative negative funding (adjusted for price run-up)
49
+ - Track maximum price run-up (high / entry - 1)
50
+ - Calculate buffer requirement:
51
+ buffer = maintenance_margin * (1 + runup) + runup + cum_neg_funding + fees
52
+ ```
53
+
54
+ The worst-case buffer across all windows determines the maximum safe leverage:
55
+ - If buffer requirement is 50%, max safe leverage = 2x
56
+ - If buffer requirement is 33%, max safe leverage = 3x
57
+
58
+ ### 4. Bootstrap Simulation (Optional)
59
+
60
+ For additional statistical confidence, the strategy can run Monte Carlo simulations:
61
+ - Resamples historical funding/price data in blocks (default 24h blocks)
62
+ - Runs N simulations (configurable, e.g., 1000)
63
+ - Calculates VaR at specified confidence level (default 97.5%)
64
+
65
+ Configure via:
66
+ ```json
67
+ {
68
+ "strategy_config": {
69
+ "bootstrap_sims": 1000,
70
+ "bootstrap_block_hours": 24
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### 5. Ranking
76
+
77
+ Opportunities are ranked by expected APY:
78
+ ```
79
+ expected_apy = mean_hourly_funding * 24 * 365 * safe_leverage
80
+ ```
81
+
82
+ ## Position Management
83
+
84
+ ### Opening a Position
85
+
86
+ 1. Transfers USDC from main wallet to strategy wallet
87
+ 2. Bridges USDC to Hyperliquid via Arbitrum
88
+ 3. Splits between perp margin and spot
89
+ 4. Uses `PairedFiller` to atomically execute both legs (buy spot + sell perp)
90
+ 5. Places protective orders:
91
+ - **Stop-loss**: Triggers if price approaches liquidation (default 65% of distance)
92
+ - **Limit sell**: Closes spot if funding flips negative
93
+
94
+ ### Incremental Scaling
95
+
96
+ When you deposit additional funds with an existing position:
97
+ - Detects idle capital (undeployed USDC on Hyperliquid)
98
+ - Calculates additional units to add to each leg
99
+ - Uses `PairedFiller` to atomically add to both positions
100
+ - Maintains delta neutrality throughout
101
+
102
+ ### Monitoring (update)
103
+
104
+ The `update` action:
105
+ 1. Checks if position needs rebalancing (funding flipped, leverage drift, etc.)
106
+ 2. Deploys any idle capital via scale-up
107
+ 3. Verifies leg balance (spot amount ≈ perp amount)
108
+ 4. Updates stop-loss/limit orders if liquidation price changed
109
+
110
+ ### Closing a Position
111
+
112
+ 1. Cancels all open orders
113
+ 2. Uses `PairedFiller` to atomically close both legs (sell spot + buy perp)
114
+ 3. Withdraws USDC from Hyperliquid to Arbitrum
115
+ 4. Sends funds back to main wallet
116
+
117
+ ## CLI Usage
118
+
119
+ ```bash
120
+ # Analyze opportunities for a $1000 deposit (doesn't open position)
121
+ poetry run python wayfinder_paths/run_strategy.py basis_trading_strategy \
122
+ --action analyze --amount 1000 --config config.json
123
+
124
+ # Deposit $100 USDC from main wallet
125
+ poetry run python wayfinder_paths/run_strategy.py basis_trading_strategy \
126
+ --action deposit --main-token-amount 100 --config config.json
127
+
128
+ # Analyze and open/manage position
129
+ poetry run python wayfinder_paths/run_strategy.py basis_trading_strategy \
130
+ --action update --config config.json
131
+
132
+ # Check current status
133
+ poetry run python wayfinder_paths/run_strategy.py basis_trading_strategy \
134
+ --action status --config config.json
135
+
136
+ # Withdraw all funds back to main wallet
137
+ poetry run python wayfinder_paths/run_strategy.py basis_trading_strategy \
138
+ --action withdraw --config config.json
139
+
140
+ # Generate batch snapshot of all opportunities
141
+ poetry run python wayfinder_paths/run_strategy.py basis_trading_strategy \
142
+ --action snapshot --amount 1000 --config config.json
143
+ ```
144
+
145
+ ## Configuration
146
+
147
+ ```json
148
+ {
149
+ "main_wallet": {
150
+ "address": "0x...",
151
+ "private_key": "0x..."
152
+ },
153
+ "strategy_wallet": {
154
+ "address": "0x...",
155
+ "private_key": "0x..."
156
+ },
157
+ "strategy_config": {
158
+ "max_leverage": 3,
159
+ "lookback_days": 180,
160
+ "bootstrap_sims": 0,
161
+ "bootstrap_block_hours": 24
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### Parameters
167
+
168
+ | Parameter | Default | Description |
169
+ |-----------|---------|-------------|
170
+ | `max_leverage` | 3 | Maximum leverage allowed |
171
+ | `lookback_days` | 180 | Days of historical data for analysis |
172
+ | `confidence` | 0.975 | VaR confidence level (97.5%) |
173
+ | `fee_eps` | 0.003 | Fee buffer (0.3%) |
174
+ | `oi_floor` | 50 | Minimum open interest (USD) |
175
+ | `day_vlm_floor` | 100,000 | Minimum daily volume (USD) |
176
+ | `bootstrap_sims` | 50 | Monte Carlo simulations for VaR estimation |
177
+ | `bootstrap_block_hours` | 24 | Block size for bootstrap resampling |
178
+
179
+ ### Thresholds
180
+
181
+ | Constant | Value | Description |
182
+ |----------|-------|-------------|
183
+ | `MIN_DEPOSIT_USDC` | 50 | Minimum deposit |
184
+ | `LIQUIDATION_REBALANCE_THRESHOLD` | 0.65 | Stop-loss at 65% of liquidation distance |
185
+ | `MIN_UNUSED_USD` | 5.0 | Minimum idle capital to trigger scale-up |
186
+ | `UNUSED_REL_EPS` | 0.05 | Relative threshold (5% of deposit) |
187
+
188
+ ## Adapters Used
189
+
190
+ - **BALANCE**: Wallet balances and ERC20 transfers
191
+ - **LEDGER**: Transaction recording for deposit/withdraw tracking
192
+ - **TOKEN**: Token metadata (decimals, addresses)
193
+ - **HYPERLIQUID**: Market data, order execution, account state
194
+
195
+ ## Risk Factors
196
+
197
+ 1. **Funding Rate Flips**: Rates can turn negative, causing losses instead of gains
198
+ 2. **Liquidation Risk**: High leverage + adverse price movement can liquidate the perp
199
+ 3. **Execution Slippage**: Large orders may move the market
200
+ 4. **Withdrawal Delays**: Hyperliquid withdrawals take ~15-30 minutes
201
+ 5. **Smart Contract Risk**: Funds are held on Hyperliquid's L1
202
+
203
+ ## Architecture
204
+
205
+ ```
206
+ BasisTradingStrategy
207
+ ├── HyperliquidAdapter # Market data, account state
208
+ ├── LocalHyperliquidExecutor # Order execution (spot + perp)
209
+ ├── PairedFiller # Atomic paired order execution
210
+ ├── BalanceAdapter # Arbitrum wallet balances
211
+ ├── LedgerAdapter # Deposit/withdraw tracking
212
+ └── LocalEvmTxn # Arbitrum transaction signing
213
+ ```
@@ -0,0 +1,3 @@
1
+ from .strategy import BasisTradingStrategy
2
+
3
+ __all__ = ["BasisTradingStrategy"]
@@ -0,0 +1 @@
1
+ USDC_ARBITRUM_TOKEN_ID = "usd-coin-arbitrum"
@@ -0,0 +1,16 @@
1
+ {
2
+ "smoke": {
3
+ "deposit": {"main_token_amount": 100, "gas_token_amount": 0.0001},
4
+ "update": {},
5
+ "status": {},
6
+ "withdraw": {}
7
+ },
8
+ "min_deposit_fail": {
9
+ "deposit": {"main_token_amount": 10, "gas_token_amount": 0.0},
10
+ "expect": {"success": false, "message_contains": "Minimum deposit"}
11
+ },
12
+ "analysis_only": {
13
+ "deposit": {"main_token_amount": 200, "gas_token_amount": 0.0001},
14
+ "update": {}
15
+ }
16
+ }
@@ -0,0 +1,23 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "wayfinder_paths.strategies.basis_trading_strategy.strategy.BasisTradingStrategy"
3
+ permissions:
4
+ policy: |
5
+ (wallet.id == 'FORMAT_WALLET_ID') AND (
6
+ # Allow Hyperliquid EIP-712 order actions
7
+ (action.type == 'hyperliquid_order') OR
8
+ (action.type == 'hyperliquid_cancel') OR
9
+ (action.type == 'hyperliquid_transfer') OR
10
+ # Allow USDC transfers to Hyperliquid bridge
11
+ (action.type == 'erc20_transfer' AND action.to == '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7') OR
12
+ # Allow USDC withdraw to main wallet
13
+ (action.type == 'erc20_transfer' AND action.to == main_wallet.address)
14
+ )
15
+ adapters:
16
+ - name: "BALANCE"
17
+ capabilities: ["wallet_read", "wallet_transfer"]
18
+ - name: "LEDGER"
19
+ capabilities: ["ledger.read", "ledger.write", "strategy.transactions"]
20
+ - name: "TOKEN"
21
+ capabilities: ["token.read"]
22
+ - name: "HYPERLIQUID"
23
+ capabilities: ["market.read", "market.meta", "market.funding", "market.candles", "market.orderbook", "order.execute", "order.cancel", "position.manage", "transfer"]