wayfinder-paths 0.1.8__py3-none-any.whl → 0.1.9__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.
Files changed (47) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +5 -14
  2. wayfinder_paths/adapters/brap_adapter/README.md +1 -1
  3. wayfinder_paths/adapters/brap_adapter/adapter.py +0 -51
  4. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  5. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  6. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  7. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  8. wayfinder_paths/adapters/pool_adapter/README.md +1 -77
  9. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
  10. wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
  11. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
  12. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  13. wayfinder_paths/core/clients/AuthClient.py +0 -3
  14. wayfinder_paths/core/clients/ClientManager.py +1 -22
  15. wayfinder_paths/core/clients/WalletClient.py +0 -8
  16. wayfinder_paths/core/clients/WayfinderClient.py +9 -14
  17. wayfinder_paths/core/clients/__init__.py +0 -8
  18. wayfinder_paths/core/clients/protocols.py +0 -60
  19. wayfinder_paths/core/config.py +5 -45
  20. wayfinder_paths/core/engine/StrategyJob.py +0 -3
  21. wayfinder_paths/core/services/base.py +0 -49
  22. wayfinder_paths/core/services/local_evm_txn.py +3 -82
  23. wayfinder_paths/core/services/local_token_txn.py +61 -70
  24. wayfinder_paths/core/services/web3_service.py +0 -2
  25. wayfinder_paths/core/settings.py +8 -8
  26. wayfinder_paths/core/strategies/Strategy.py +1 -5
  27. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  28. wayfinder_paths/core/wallets/README.md +3 -6
  29. wayfinder_paths/run_strategy.py +29 -32
  30. wayfinder_paths/scripts/make_wallets.py +1 -25
  31. wayfinder_paths/scripts/run_strategy.py +0 -2
  32. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  33. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
  34. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  35. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  36. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  37. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
  38. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  39. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
  40. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
  41. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  42. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +5 -15
  43. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +45 -47
  44. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  45. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  46. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
  47. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
@@ -208,7 +208,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
208
208
  *,
209
209
  main_wallet: dict[str, Any] | None = None,
210
210
  strategy_wallet: dict[str, Any] | None = None,
211
- simulation: bool = False,
212
211
  web3_service: Web3Service | None = None,
213
212
  hyperliquid_executor: HyperliquidExecutor | None = None,
214
213
  api_key: str | None = None,
@@ -221,7 +220,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
221
220
  if strategy_wallet:
222
221
  merged_config["strategy_wallet"] = strategy_wallet
223
222
  self.config = merged_config
224
- self.simulation = simulation
225
223
 
226
224
  # Position tracking
227
225
  self.current_position: BasisPosition | None = None
@@ -249,10 +247,10 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
249
247
  "strategy": self.config,
250
248
  }
251
249
 
252
- # Create Hyperliquid executor if not provided and not in simulation.
250
+ # Create Hyperliquid executor if not provided.
253
251
  # This is only required for placing/canceling orders (not market reads).
254
252
  hl_executor = hyperliquid_executor
255
- if hl_executor is None and not self.simulation:
253
+ if hl_executor is None:
256
254
  try:
257
255
  hl_executor = LocalHyperliquidExecutor(config=adapter_config)
258
256
  self.logger.info("Created LocalHyperliquidExecutor for real execution")
@@ -267,7 +265,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
267
265
  try:
268
266
  self.hyperliquid_adapter = HyperliquidAdapter(
269
267
  config=adapter_config,
270
- simulation=self.simulation,
271
268
  executor=hl_executor,
272
269
  )
273
270
  except Exception as e:
@@ -280,7 +277,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
280
277
  tx_adapter = LocalTokenTxnService(
281
278
  adapter_config,
282
279
  wallet_provider=wallet_provider,
283
- simulation=self.simulation,
284
280
  )
285
281
  web3_service = DefaultWeb3Service(
286
282
  wallet_provider=wallet_provider, evm_transactions=tx_adapter
@@ -502,18 +498,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
502
498
  return (False, f"Failed to transfer ETH for gas: {gas_res}")
503
499
  self.logger.info(f"Gas transfer successful: {gas_res}")
504
500
 
505
- # Simulation mode - just track the deposit
506
- if self.simulation:
507
- self.logger.info(
508
- f"[SIMULATION] Would send {main_token_amount} USDC to Hyperliquid bridge"
509
- )
510
- self.deposit_amount = main_token_amount
511
- return (
512
- True,
513
- f"[SIMULATION] Deposited {main_token_amount} USDC. "
514
- f"Call update() to analyze and open positions.",
515
- )
516
-
517
501
  # Real deposit: ensure funds are in the strategy wallet, then send USDC to bridge.
518
502
  try:
519
503
  main_address = self._get_main_wallet_address()
@@ -926,11 +910,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
926
910
  if not close_success:
927
911
  return (False, f"Failed to close position: {close_msg}")
928
912
 
929
- if self.simulation:
930
- withdrawn = self.deposit_amount
931
- self.deposit_amount = 0
932
- return (True, f"[SIMULATION] Withdrew {withdrawn} USDC to main wallet")
933
-
934
913
  # Step 1: Transfer any spot USDC to perp for withdrawal
935
914
  success, spot_state = await self.hyperliquid_adapter.get_spot_user_state(
936
915
  address
@@ -1168,9 +1147,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1168
1147
  if not self.builder_fee:
1169
1148
  return True, "No builder fee configured"
1170
1149
 
1171
- if self.simulation:
1172
- return True, "[SIMULATION] Builder fee approval skipped"
1173
-
1174
1150
  address = self._get_strategy_wallet_address()
1175
1151
  builder = self.builder_fee.get("b", "")
1176
1152
  required_fee = self.builder_fee.get("f", 0)
@@ -1357,112 +1333,98 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1357
1333
  Decimal(str(order_usd)).quantize(Decimal("0.01"), rounding=ROUND_UP)
1358
1334
  )
1359
1335
 
1360
- if self.simulation:
1361
- self.logger.info(
1362
- f"[SIMULATION] Would open {target_qty} {coin} basis position"
1363
- )
1364
- spot_filled = target_qty
1365
- perp_filled = target_qty
1366
- spot_notional = order_usd
1367
- perp_notional = order_usd
1368
- entry_price = float(best.get("mark_price", 0.0) or 100.0)
1369
- else:
1370
- # Step 1: Ensure builder fee is approved
1371
- fee_success, fee_msg = await self.ensure_builder_fee_approved()
1372
- if not fee_success:
1373
- return (False, f"Builder fee approval failed: {fee_msg}")
1374
-
1375
- # Step 2: Update leverage for the perp asset
1376
- self.logger.info(f"Setting leverage to {leverage}x for {coin}")
1377
- success, lev_result = await self.hyperliquid_adapter.update_leverage(
1378
- asset_id=perp_asset_id,
1379
- leverage=leverage,
1380
- is_cross=True,
1381
- address=address,
1382
- )
1383
- if not success:
1384
- self.logger.warning(f"Failed to set leverage: {lev_result}")
1385
- # Continue anyway - leverage might already be set
1336
+ # Step 1: Ensure builder fee is approved
1337
+ fee_success, fee_msg = await self.ensure_builder_fee_approved()
1338
+ if not fee_success:
1339
+ return (False, f"Builder fee approval failed: {fee_msg}")
1386
1340
 
1387
- # Step 3: Transfer USDC from perp to spot for spot purchase
1388
- # We need approximately order_usd in spot to buy the asset
1389
- self.logger.info(
1390
- f"Transferring ${order_usd:.2f} from perp to spot for {coin}"
1391
- )
1392
- (
1393
- success,
1394
- transfer_result,
1395
- ) = await self.hyperliquid_adapter.transfer_perp_to_spot(
1396
- amount=order_usd,
1397
- address=address,
1398
- )
1399
- if not success:
1400
- self.logger.warning(
1401
- f"Perp to spot transfer failed: {transfer_result}"
1402
- )
1403
- # May fail if already in spot, continue
1341
+ # Step 2: Update leverage for the perp asset
1342
+ self.logger.info(f"Setting leverage to {leverage}x for {coin}")
1343
+ success, lev_result = await self.hyperliquid_adapter.update_leverage(
1344
+ asset_id=perp_asset_id,
1345
+ leverage=leverage,
1346
+ is_cross=True,
1347
+ address=address,
1348
+ )
1349
+ if not success:
1350
+ self.logger.warning(f"Failed to set leverage: {lev_result}")
1351
+ # Continue anyway - leverage might already be set
1404
1352
 
1405
- # Step 4: Execute paired fill
1406
- filler = PairedFiller(
1407
- adapter=self.hyperliquid_adapter,
1408
- address=address,
1409
- cfg=FillConfig(max_slip_bps=35, max_chunk_usd=7500.0),
1410
- )
1353
+ # Step 3: Transfer USDC from perp to spot for spot purchase
1354
+ # We need approximately order_usd in spot to buy the asset
1355
+ self.logger.info(
1356
+ f"Transferring ${order_usd:.2f} from perp to spot for {coin}"
1357
+ )
1358
+ (
1359
+ success,
1360
+ transfer_result,
1361
+ ) = await self.hyperliquid_adapter.transfer_perp_to_spot(
1362
+ amount=order_usd,
1363
+ address=address,
1364
+ )
1365
+ if not success:
1366
+ self.logger.warning(f"Perp to spot transfer failed: {transfer_result}")
1367
+ # May fail if already in spot, continue
1411
1368
 
1412
- (
1413
- spot_filled,
1414
- perp_filled,
1415
- spot_notional,
1416
- perp_notional,
1417
- spot_pointers,
1418
- perp_pointers,
1419
- ) = await filler.fill_pair_units(
1420
- coin=coin,
1421
- spot_asset_id=spot_asset_id,
1422
- perp_asset_id=perp_asset_id,
1423
- total_units=target_qty,
1424
- direction="long_spot_short_perp",
1425
- builder_fee=self.builder_fee,
1426
- )
1369
+ # Step 4: Execute paired fill
1370
+ filler = PairedFiller(
1371
+ adapter=self.hyperliquid_adapter,
1372
+ address=address,
1373
+ cfg=FillConfig(max_slip_bps=35, max_chunk_usd=7500.0),
1374
+ )
1427
1375
 
1428
- if spot_filled <= 0 or perp_filled <= 0:
1429
- return (False, f"Failed to fill basis position on {coin}")
1376
+ (
1377
+ spot_filled,
1378
+ perp_filled,
1379
+ spot_notional,
1380
+ perp_notional,
1381
+ spot_pointers,
1382
+ perp_pointers,
1383
+ ) = await filler.fill_pair_units(
1384
+ coin=coin,
1385
+ spot_asset_id=spot_asset_id,
1386
+ perp_asset_id=perp_asset_id,
1387
+ total_units=target_qty,
1388
+ direction="long_spot_short_perp",
1389
+ builder_fee=self.builder_fee,
1390
+ )
1430
1391
 
1431
- self.logger.info(
1432
- f"Filled basis position: spot={spot_filled:.6f}, perp={perp_filled:.6f}, "
1433
- f"notional=${spot_notional:.2f}/${perp_notional:.2f}"
1434
- )
1392
+ if spot_filled <= 0 or perp_filled <= 0:
1393
+ return (False, f"Failed to fill basis position on {coin}")
1435
1394
 
1436
- # Get entry price from current mid
1437
- success, mids = await self.hyperliquid_adapter.get_all_mid_prices()
1438
- entry_price = mids.get(coin, 0.0) if success else 0.0
1395
+ self.logger.info(
1396
+ f"Filled basis position: spot={spot_filled:.6f}, perp={perp_filled:.6f}, "
1397
+ f"notional=${spot_notional:.2f}/${perp_notional:.2f}"
1398
+ )
1439
1399
 
1440
- # Step 5: Get liquidation price and place stop-loss
1441
- success, user_state = await self.hyperliquid_adapter.get_user_state(
1442
- address
1443
- )
1444
- liquidation_price = None
1445
- if success:
1446
- for pos_wrapper in user_state.get("assetPositions", []):
1447
- pos = pos_wrapper.get("position", {})
1448
- if pos.get("coin") == coin:
1449
- liquidation_price = float(pos.get("liquidationPx", 0))
1450
- break
1400
+ # Get entry price from current mid
1401
+ success, mids = await self.hyperliquid_adapter.get_all_mid_prices()
1402
+ entry_price = mids.get(coin, 0.0) if success else 0.0
1451
1403
 
1452
- if liquidation_price and liquidation_price > 0:
1453
- sl_success, sl_msg = await self._place_stop_loss_orders(
1454
- coin=coin,
1455
- perp_asset_id=perp_asset_id,
1456
- position_size=perp_filled,
1457
- entry_price=entry_price,
1458
- liquidation_price=liquidation_price,
1459
- spot_asset_id=spot_asset_id,
1460
- spot_position_size=spot_filled,
1461
- )
1462
- if not sl_success:
1463
- self.logger.warning(f"Stop-loss placement failed: {sl_msg}")
1464
- else:
1465
- self.logger.warning("Could not get liquidation price for stop-loss")
1404
+ # Step 5: Get liquidation price and place stop-loss
1405
+ success, user_state = await self.hyperliquid_adapter.get_user_state(address)
1406
+ liquidation_price = None
1407
+ if success:
1408
+ for pos_wrapper in user_state.get("assetPositions", []):
1409
+ pos = pos_wrapper.get("position", {})
1410
+ if pos.get("coin") == coin:
1411
+ liquidation_price = float(pos.get("liquidationPx", 0))
1412
+ break
1413
+
1414
+ if liquidation_price and liquidation_price > 0:
1415
+ sl_success, sl_msg = await self._place_stop_loss_orders(
1416
+ coin=coin,
1417
+ perp_asset_id=perp_asset_id,
1418
+ position_size=perp_filled,
1419
+ entry_price=entry_price,
1420
+ liquidation_price=liquidation_price,
1421
+ spot_asset_id=spot_asset_id,
1422
+ spot_position_size=spot_filled,
1423
+ )
1424
+ if not sl_success:
1425
+ self.logger.warning(f"Stop-loss placement failed: {sl_msg}")
1426
+ else:
1427
+ self.logger.warning("Could not get liquidation price for stop-loss")
1466
1428
 
1467
1429
  # Create position record
1468
1430
  self.current_position = BasisPosition(
@@ -1836,9 +1798,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1836
1798
  if self.current_position is None:
1837
1799
  return True, "No position"
1838
1800
 
1839
- if self.simulation:
1840
- return True, "[SIMULATION] Would repair leg imbalance"
1841
-
1842
1801
  pos = self.current_position
1843
1802
  coin = pos.coin
1844
1803
  address = self._get_strategy_wallet_address()
@@ -1930,9 +1889,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1930
1889
  if self.current_position is None:
1931
1890
  return True, "No position"
1932
1891
 
1933
- if self.simulation:
1934
- return True, "[SIMULATION] Stop-loss check skipped"
1935
-
1936
1892
  pos = self.current_position
1937
1893
  coin = pos.coin
1938
1894
 
@@ -2026,13 +1982,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2026
1982
  pos = self.current_position
2027
1983
  self.logger.info(f"Closing position on {pos.coin}")
2028
1984
 
2029
- if self.simulation:
2030
- self.logger.info(
2031
- f"[SIMULATION] Would close {pos.spot_amount} {pos.coin} basis position"
2032
- )
2033
- self.current_position = None
2034
- return (True, "Position closed (simulation)")
2035
-
2036
1985
  # Cancel all stop-loss and limit orders first
2037
1986
  await self._cancel_all_position_orders()
2038
1987
 
@@ -119,12 +119,32 @@ class TestBasisTradingStrategy:
119
119
  return_value=(
120
120
  True,
121
121
  {
122
- "marginSummary": {"accountValue": "0", "withdrawable": "0"},
122
+ "marginSummary": {"accountValue": "0"},
123
+ "withdrawable": "100.0", # Top-level withdrawable for withdraw() method
123
124
  "assetPositions": [],
124
125
  },
125
126
  )
126
127
  )
127
128
  mock.get_spot_user_state = AsyncMock(return_value=(True, {"balances": []}))
129
+ mock.get_max_builder_fee = AsyncMock(return_value=(True, 0))
130
+ mock.approve_builder_fee = AsyncMock(return_value=(True, {"status": "ok"}))
131
+ mock.update_leverage = AsyncMock(return_value=(True, {"status": "ok"}))
132
+ mock.transfer_perp_to_spot = AsyncMock(return_value=(True, {"status": "ok"}))
133
+ mock.transfer_spot_to_perp = AsyncMock(return_value=(True, {"status": "ok"}))
134
+ mock.place_market_order = AsyncMock(return_value=(True, {"status": "ok"}))
135
+ mock.place_limit_order = AsyncMock(return_value=(True, {"status": "ok"}))
136
+ mock.place_stop_loss = AsyncMock(return_value=(True, {"status": "ok"}))
137
+ mock.cancel_order = AsyncMock(return_value=(True, {"status": "ok"}))
138
+ mock.withdraw = AsyncMock(return_value=(True, {"status": "ok"}))
139
+ mock.get_open_orders = AsyncMock(return_value=(True, []))
140
+ mock.get_frontend_open_orders = AsyncMock(return_value=(True, []))
141
+ mock.get_valid_order_size = MagicMock(side_effect=lambda _asset, size: size)
142
+ mock.wait_for_deposit = AsyncMock(
143
+ return_value=(True, 100.0) # (deposit_confirmed, final_balance)
144
+ )
145
+ mock.wait_for_withdrawal = AsyncMock(
146
+ return_value=(True, {"0x123456": 100.0}) # tx_hash -> amount (float)
147
+ )
128
148
  return mock
129
149
 
130
150
  @pytest.fixture
@@ -162,7 +182,6 @@ class TestBasisTradingStrategy:
162
182
  "main_wallet": {"address": "0x1234"},
163
183
  "strategy_wallet": {"address": "0x5678"},
164
184
  },
165
- simulation=True,
166
185
  )
167
186
  s.hyperliquid_adapter = mock_hyperliquid_adapter
168
187
  s.ledger_adapter = ledger_adapter
@@ -173,9 +192,45 @@ class TestBasisTradingStrategy:
173
192
  s.balance_adapter.move_from_main_wallet_to_strategy_wallet = AsyncMock(
174
193
  return_value=(True, {})
175
194
  )
195
+ s.balance_adapter.move_from_strategy_wallet_to_main_wallet = AsyncMock(
196
+ return_value=(True, {})
197
+ )
176
198
  s.balance_adapter.send_to_address = AsyncMock(
177
199
  return_value=(True, {"tx_hash": "0x123"})
178
200
  )
201
+ # Mock internal dependencies to prevent MagicMock await errors
202
+ # These are needed if the real method somehow gets called
203
+ s.balance_adapter.token_client = AsyncMock()
204
+ s.balance_adapter.token_client.get_token_details = (
205
+ AsyncMock(
206
+ return_value={
207
+ "id": "usdc",
208
+ "address": "0x1234",
209
+ "decimals": 6,
210
+ }
211
+ )
212
+ )
213
+ s.balance_adapter.token_transactions = AsyncMock()
214
+ s.balance_adapter.token_transactions.build_send = AsyncMock(
215
+ return_value=(True, {"transaction": "0xMOCK"})
216
+ )
217
+ s.balance_adapter.wallet_provider = AsyncMock()
218
+ s.balance_adapter.wallet_provider.broadcast_transaction = (
219
+ AsyncMock(
220
+ return_value=(True, {"transaction_hash": "0x123"})
221
+ )
222
+ )
223
+ s.balance_adapter.token_adapter = AsyncMock()
224
+ s.balance_adapter.token_adapter.get_token_price = AsyncMock(
225
+ return_value=(True, {"current_price": 1.0})
226
+ )
227
+ # ledger_adapter is real, but ensure its methods are async-mockable
228
+ s.balance_adapter.ledger_adapter = ledger_adapter
229
+ # Also ensure the balance_adapter's _move_between_wallets won't call real methods
230
+ # by making sure all its dependencies return AsyncMock
231
+ s.balance_adapter._move_between_wallets = AsyncMock(
232
+ return_value=(True, {"transaction_hash": "0x123"})
233
+ )
179
234
  return s
180
235
 
181
236
  @pytest.mark.asyncio
@@ -185,22 +240,32 @@ class TestBasisTradingStrategy:
185
240
  examples = load_examples()
186
241
  smoke = examples["smoke"]
187
242
 
188
- # Deposit
189
- deposit_params = smoke.get("deposit", {})
190
- success, msg = await strategy.deposit(**deposit_params)
191
- assert success, f"Deposit failed: {msg}"
243
+ # Mock PairedFiller for update() and withdraw() to work
244
+ with patch(
245
+ "wayfinder_paths.strategies.basis_trading_strategy.strategy.PairedFiller"
246
+ ) as mock_filler_class:
247
+ mock_filler = MagicMock()
248
+ mock_filler.fill_pair_units = AsyncMock(
249
+ return_value=(0.5, 0.5, 1000.0, 1000.0, [], [])
250
+ )
251
+ mock_filler_class.return_value = mock_filler
252
+
253
+ # Deposit
254
+ deposit_params = smoke.get("deposit", {})
255
+ success, msg = await strategy.deposit(**deposit_params)
256
+ assert success, f"Deposit failed: {msg}"
192
257
 
193
- # Update
194
- success, msg = await strategy.update()
195
- assert success, f"Update failed: {msg}"
258
+ # Update
259
+ success, msg = await strategy.update()
260
+ assert success, f"Update failed: {msg}"
196
261
 
197
- # Status
198
- status = await strategy.status()
199
- assert "portfolio_value" in status
262
+ # Status
263
+ status = await strategy.status()
264
+ assert "portfolio_value" in status
200
265
 
201
- # Withdraw
202
- success, msg = await strategy.withdraw()
203
- assert success, f"Withdraw failed: {msg}"
266
+ # Withdraw (needs PairedFiller mock for _close_position)
267
+ success, msg = await strategy.withdraw()
268
+ assert success, f"Withdraw failed: {msg}"
204
269
 
205
270
  @pytest.mark.asyncio
206
271
  async def test_deposit_minimum(self, strategy):
@@ -311,8 +376,23 @@ class TestBasisTradingStrategy:
311
376
  assert opps[0]["selection"]["net_apy"] is not None
312
377
 
313
378
  @pytest.mark.asyncio
314
- async def test_get_undeployed_capital_empty(self, strategy):
379
+ async def test_get_undeployed_capital_empty(
380
+ self, strategy, mock_hyperliquid_adapter
381
+ ):
315
382
  """Test _get_undeployed_capital with no capital."""
383
+ mock_hyperliquid_adapter.get_user_state = AsyncMock(
384
+ return_value=(
385
+ True,
386
+ {
387
+ "marginSummary": {"accountValue": "0", "withdrawable": "0"},
388
+ "withdrawable": "0",
389
+ "assetPositions": [],
390
+ },
391
+ )
392
+ )
393
+ mock_hyperliquid_adapter.get_spot_user_state = AsyncMock(
394
+ return_value=(True, {"balances": []})
395
+ )
316
396
  perp_margin, spot_usdc = await strategy._get_undeployed_capital()
317
397
  assert perp_margin == 0.0
318
398
  assert spot_usdc == 0.0
@@ -508,7 +588,6 @@ class TestBasisTradingStrategy:
508
588
  "main_wallet": {"address": "0x1234"},
509
589
  "strategy_wallet": {"address": "0x5678"},
510
590
  },
511
- simulation=False, # Not simulation mode
512
591
  )
513
592
  s.hyperliquid_adapter = mock_hyperliquid_adapter
514
593
  s.ledger_adapter = ledger_adapter
@@ -561,7 +640,6 @@ class TestBasisTradingStrategy:
561
640
  "main_wallet": {"address": "0x1234"},
562
641
  "strategy_wallet": {"address": "0x5678"},
563
642
  },
564
- simulation=False, # Not simulation mode
565
643
  )
566
644
  s.hyperliquid_adapter = mock_hyperliquid_adapter
567
645
  s.ledger_adapter = ledger_adapter
@@ -580,46 +658,6 @@ class TestBasisTradingStrategy:
580
658
  # Should have called approve_builder_fee
581
659
  mock_hyperliquid_adapter.approve_builder_fee.assert_called_once()
582
660
 
583
- @pytest.mark.asyncio
584
- async def test_ensure_builder_fee_simulation_mode(
585
- self, mock_hyperliquid_adapter, ledger_adapter
586
- ):
587
- """Test ensure_builder_fee_approved in simulation mode."""
588
- with patch(
589
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.HyperliquidAdapter",
590
- return_value=mock_hyperliquid_adapter,
591
- ):
592
- with patch(
593
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.BalanceAdapter"
594
- ):
595
- with patch(
596
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.TokenAdapter"
597
- ):
598
- with patch(
599
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.LedgerAdapter",
600
- return_value=ledger_adapter,
601
- ):
602
- with patch(
603
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.WalletManager"
604
- ):
605
- from wayfinder_paths.strategies.basis_trading_strategy.strategy import (
606
- BasisTradingStrategy,
607
- )
608
-
609
- s = BasisTradingStrategy(
610
- config={
611
- "main_wallet": {"address": "0x1234"},
612
- "strategy_wallet": {"address": "0x5678"},
613
- },
614
- simulation=True, # Simulation mode
615
- )
616
- s.hyperliquid_adapter = mock_hyperliquid_adapter
617
- s.ledger_adapter = ledger_adapter
618
-
619
- success, msg = await s.ensure_builder_fee_approved()
620
- assert success is True
621
- assert "simulation" in msg.lower()
622
-
623
661
  @pytest.mark.asyncio
624
662
  async def test_portfolio_value_includes_spot_holdings(
625
663
  self, strategy, mock_hyperliquid_adapter
@@ -1,4 +1,4 @@
1
- # Hyperlend Stable Yield Strategy
1
+ # Hyperlend Stable Yield Strategy
2
2
 
3
3
  - Entrypoint: `strategies.hyperlend_stable_yield_strategy.strategy.HyperlendStableYieldStrategy`
4
4
  - Manifest: `manifest.yaml`
@@ -79,7 +79,7 @@ poetry install
79
79
  # Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
80
80
  poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
81
81
 
82
- # Copy config and edit credentials (or rely on env vars)
82
+ # Copy config and edit credentials
83
83
  cp wayfinder_paths/config.example.json config.json
84
84
 
85
85
  # Check status / health
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "smoke": {
3
- "deposit": {},
3
+ "deposit": {
4
+ "main_token_amount": 10.0,
5
+ "gas_token_amount": 0.01
6
+ },
4
7
  "update": {},
5
8
  "status": {},
6
9
  "withdraw": {}