wayfinder-paths 0.1.7__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 (149) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +399 -0
  2. wayfinder_paths/__init__.py +22 -0
  3. wayfinder_paths/abis/generic/erc20.json +383 -0
  4. wayfinder_paths/adapters/__init__.py +0 -0
  5. wayfinder_paths/adapters/balance_adapter/README.md +94 -0
  6. wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
  7. wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
  8. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  9. wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
  10. wayfinder_paths/adapters/brap_adapter/README.md +249 -0
  11. wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
  13. wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
  15. wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
  19. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
  20. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  21. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  24. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  28. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  29. wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
  30. wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
  31. wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
  32. wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
  33. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
  34. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
  35. wayfinder_paths/adapters/pool_adapter/README.md +206 -0
  36. wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
  37. wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
  38. wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
  39. wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
  40. wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
  41. wayfinder_paths/adapters/token_adapter/README.md +101 -0
  42. wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
  43. wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
  46. wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
  47. wayfinder_paths/config.example.json +22 -0
  48. wayfinder_paths/conftest.py +31 -0
  49. wayfinder_paths/core/__init__.py +18 -0
  50. wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
  51. wayfinder_paths/core/adapters/__init__.py +5 -0
  52. wayfinder_paths/core/adapters/base.py +5 -0
  53. wayfinder_paths/core/adapters/models.py +46 -0
  54. wayfinder_paths/core/analytics/__init__.py +11 -0
  55. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  56. wayfinder_paths/core/analytics/stats.py +48 -0
  57. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  58. wayfinder_paths/core/clients/AuthClient.py +83 -0
  59. wayfinder_paths/core/clients/BRAPClient.py +109 -0
  60. wayfinder_paths/core/clients/ClientManager.py +210 -0
  61. wayfinder_paths/core/clients/HyperlendClient.py +192 -0
  62. wayfinder_paths/core/clients/LedgerClient.py +443 -0
  63. wayfinder_paths/core/clients/PoolClient.py +128 -0
  64. wayfinder_paths/core/clients/SimulationClient.py +192 -0
  65. wayfinder_paths/core/clients/TokenClient.py +89 -0
  66. wayfinder_paths/core/clients/TransactionClient.py +63 -0
  67. wayfinder_paths/core/clients/WalletClient.py +94 -0
  68. wayfinder_paths/core/clients/WayfinderClient.py +269 -0
  69. wayfinder_paths/core/clients/__init__.py +48 -0
  70. wayfinder_paths/core/clients/protocols.py +392 -0
  71. wayfinder_paths/core/clients/sdk_example.py +110 -0
  72. wayfinder_paths/core/config.py +458 -0
  73. wayfinder_paths/core/constants/__init__.py +26 -0
  74. wayfinder_paths/core/constants/base.py +42 -0
  75. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  76. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  77. wayfinder_paths/core/engine/StrategyJob.py +188 -0
  78. wayfinder_paths/core/engine/__init__.py +5 -0
  79. wayfinder_paths/core/engine/manifest.py +97 -0
  80. wayfinder_paths/core/services/__init__.py +0 -0
  81. wayfinder_paths/core/services/base.py +179 -0
  82. wayfinder_paths/core/services/local_evm_txn.py +430 -0
  83. wayfinder_paths/core/services/local_token_txn.py +231 -0
  84. wayfinder_paths/core/services/web3_service.py +45 -0
  85. wayfinder_paths/core/settings.py +61 -0
  86. wayfinder_paths/core/strategies/Strategy.py +280 -0
  87. wayfinder_paths/core/strategies/__init__.py +5 -0
  88. wayfinder_paths/core/strategies/base.py +7 -0
  89. wayfinder_paths/core/strategies/descriptors.py +81 -0
  90. wayfinder_paths/core/utils/__init__.py +1 -0
  91. wayfinder_paths/core/utils/evm_helpers.py +206 -0
  92. wayfinder_paths/core/utils/wallets.py +77 -0
  93. wayfinder_paths/core/wallets/README.md +91 -0
  94. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  95. wayfinder_paths/core/wallets/__init__.py +7 -0
  96. wayfinder_paths/policies/enso.py +17 -0
  97. wayfinder_paths/policies/erc20.py +34 -0
  98. wayfinder_paths/policies/evm.py +21 -0
  99. wayfinder_paths/policies/hyper_evm.py +19 -0
  100. wayfinder_paths/policies/hyperlend.py +12 -0
  101. wayfinder_paths/policies/hyperliquid.py +30 -0
  102. wayfinder_paths/policies/moonwell.py +54 -0
  103. wayfinder_paths/policies/prjx.py +30 -0
  104. wayfinder_paths/policies/util.py +27 -0
  105. wayfinder_paths/run_strategy.py +411 -0
  106. wayfinder_paths/scripts/__init__.py +0 -0
  107. wayfinder_paths/scripts/create_strategy.py +181 -0
  108. wayfinder_paths/scripts/make_wallets.py +169 -0
  109. wayfinder_paths/scripts/run_strategy.py +124 -0
  110. wayfinder_paths/scripts/validate_manifests.py +213 -0
  111. wayfinder_paths/strategies/__init__.py +0 -0
  112. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  113. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  114. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  115. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  116. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  117. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  118. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  119. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  120. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  121. wayfinder_paths/strategies/config.py +85 -0
  122. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
  123. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
  124. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  125. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
  126. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
  127. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
  128. wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
  129. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  130. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
  131. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
  132. wayfinder_paths/templates/adapter/README.md +105 -0
  133. wayfinder_paths/templates/adapter/adapter.py +26 -0
  134. wayfinder_paths/templates/adapter/examples.json +8 -0
  135. wayfinder_paths/templates/adapter/manifest.yaml +6 -0
  136. wayfinder_paths/templates/adapter/test_adapter.py +49 -0
  137. wayfinder_paths/templates/strategy/README.md +153 -0
  138. wayfinder_paths/templates/strategy/examples.json +11 -0
  139. wayfinder_paths/templates/strategy/manifest.yaml +8 -0
  140. wayfinder_paths/templates/strategy/strategy.py +57 -0
  141. wayfinder_paths/templates/strategy/test_strategy.py +197 -0
  142. wayfinder_paths/tests/__init__.py +0 -0
  143. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  144. wayfinder_paths/tests/test_test_coverage.py +212 -0
  145. wayfinder_paths/tests/test_utils.py +64 -0
  146. wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
  147. wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
  148. wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
  149. wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,118 @@
1
+ """
2
+ ERC20 ABI constants for consistent use across the codebase.
3
+ This centralizes ABI definitions to avoid duplication and improve maintainability.
4
+ """
5
+
6
+ # Standard ERC20 ABI - includes common functions needed for token operations
7
+ ERC20_ABI = [
8
+ {
9
+ "constant": True,
10
+ "inputs": [
11
+ {"name": "_owner", "type": "address"},
12
+ {"name": "_spender", "type": "address"},
13
+ ],
14
+ "name": "allowance",
15
+ "outputs": [{"name": "", "type": "uint256"}],
16
+ "type": "function",
17
+ },
18
+ {
19
+ "constant": False,
20
+ "inputs": [
21
+ {"name": "_spender", "type": "address"},
22
+ {"name": "_value", "type": "uint256"},
23
+ ],
24
+ "name": "approve",
25
+ "outputs": [{"name": "", "type": "bool"}],
26
+ "type": "function",
27
+ },
28
+ {
29
+ "constant": True,
30
+ "inputs": [{"name": "account", "type": "address"}],
31
+ "name": "balanceOf",
32
+ "outputs": [{"name": "", "type": "uint256"}],
33
+ "type": "function",
34
+ },
35
+ {
36
+ "constant": True,
37
+ "inputs": [],
38
+ "name": "decimals",
39
+ "outputs": [{"name": "", "type": "uint8"}],
40
+ "type": "function",
41
+ },
42
+ {
43
+ "constant": True,
44
+ "inputs": [],
45
+ "name": "name",
46
+ "outputs": [{"name": "", "type": "string"}],
47
+ "type": "function",
48
+ },
49
+ {
50
+ "constant": True,
51
+ "inputs": [],
52
+ "name": "symbol",
53
+ "outputs": [{"name": "", "type": "string"}],
54
+ "type": "function",
55
+ },
56
+ {
57
+ "constant": True,
58
+ "inputs": [],
59
+ "name": "totalSupply",
60
+ "outputs": [{"name": "", "type": "uint256"}],
61
+ "type": "function",
62
+ },
63
+ {
64
+ "constant": False,
65
+ "inputs": [
66
+ {"name": "_to", "type": "address"},
67
+ {"name": "_value", "type": "uint256"},
68
+ ],
69
+ "name": "transfer",
70
+ "outputs": [{"name": "", "type": "bool"}],
71
+ "type": "function",
72
+ },
73
+ {
74
+ "constant": False,
75
+ "inputs": [
76
+ {"name": "_from", "type": "address"},
77
+ {"name": "_to", "type": "address"},
78
+ {"name": "_value", "type": "uint256"},
79
+ ],
80
+ "name": "transferFrom",
81
+ "outputs": [{"name": "", "type": "bool"}],
82
+ "type": "function",
83
+ },
84
+ ]
85
+
86
+ # Minimal ABI for specific use cases (e.g., when you only need certain functions)
87
+ ERC20_MINIMAL_ABI = [
88
+ {
89
+ "constant": True,
90
+ "inputs": [{"name": "account", "type": "address"}],
91
+ "name": "balanceOf",
92
+ "outputs": [{"name": "", "type": "uint256"}],
93
+ "type": "function",
94
+ }
95
+ ]
96
+
97
+ ERC20_APPROVAL_ABI = [
98
+ {
99
+ "constant": True,
100
+ "inputs": [
101
+ {"name": "_owner", "type": "address"},
102
+ {"name": "_spender", "type": "address"},
103
+ ],
104
+ "name": "allowance",
105
+ "outputs": [{"name": "", "type": "uint256"}],
106
+ "type": "function",
107
+ },
108
+ {
109
+ "constant": False,
110
+ "inputs": [
111
+ {"name": "_spender", "type": "address"},
112
+ {"name": "_value", "type": "uint256"},
113
+ ],
114
+ "name": "approve",
115
+ "outputs": [{"name": "", "type": "bool"}],
116
+ "type": "function",
117
+ },
118
+ ]
@@ -0,0 +1,152 @@
1
+ """
2
+ HyperLend ABI constants for smart contract interactions.
3
+
4
+ This module contains ABI definitions for HyperLend protocol contracts,
5
+ including Pool, Protocol Data Provider, Wrapped Token Gateway, and WETH contracts.
6
+ """
7
+
8
+ # Minimal Pool ABI for supply and deposit operations
9
+ POOL_ABI = [
10
+ {
11
+ "name": "supply",
12
+ "type": "function",
13
+ "stateMutability": "nonpayable",
14
+ "inputs": [
15
+ {"name": "asset", "type": "address"},
16
+ {"name": "amount", "type": "uint256"},
17
+ {"name": "onBehalfOf", "type": "address"},
18
+ {"name": "referralCode", "type": "uint16"},
19
+ ],
20
+ "outputs": [],
21
+ },
22
+ {
23
+ "name": "deposit",
24
+ "type": "function",
25
+ "stateMutability": "nonpayable",
26
+ "inputs": [
27
+ {"name": "asset", "type": "address"},
28
+ {"name": "amount", "type": "uint256"},
29
+ {"name": "onBehalfOf", "type": "address"},
30
+ {"name": "referralCode", "type": "uint16"},
31
+ ],
32
+ "outputs": [],
33
+ },
34
+ {
35
+ "name": "withdraw",
36
+ "type": "function",
37
+ "stateMutability": "nonpayable",
38
+ "inputs": [
39
+ {"name": "asset", "type": "address"},
40
+ {"name": "amount", "type": "uint256"},
41
+ {"name": "to", "type": "address"},
42
+ ],
43
+ "outputs": [],
44
+ },
45
+ ]
46
+
47
+ # Protocol Data Provider ABI for reserve token addresses and user data
48
+ PROTOCOL_DATA_PROVIDER_ABI = [
49
+ {
50
+ "type": "function",
51
+ "stateMutability": "view",
52
+ "name": "getReserveTokensAddresses",
53
+ "inputs": [{"name": "asset", "type": "address"}],
54
+ "outputs": [
55
+ {"name": "aTokenAddress", "type": "address"},
56
+ {"name": "stableDebtTokenAddress", "type": "address"},
57
+ {"name": "variableDebtTokenAddress", "type": "address"},
58
+ ],
59
+ },
60
+ {
61
+ "type": "function",
62
+ "stateMutability": "view",
63
+ "name": "getUserReserveData",
64
+ "inputs": [
65
+ {"name": "asset", "type": "address"},
66
+ {"name": "user", "type": "address"},
67
+ ],
68
+ "outputs": [
69
+ {"name": "currentATokenBalance", "type": "uint256"},
70
+ {"name": "currentStableDebt", "type": "uint256"},
71
+ {"name": "currentVariableDebt", "type": "uint256"},
72
+ {"name": "liquidityRate", "type": "uint256"},
73
+ {"name": "stableBorrowRate", "type": "uint256"},
74
+ {"name": "variableBorrowRate", "type": "uint256"},
75
+ {"name": "liquidityIndex", "type": "uint256"},
76
+ {"name": "healthFactor", "type": "uint256"},
77
+ ],
78
+ },
79
+ ]
80
+
81
+ # Wrapped Token Gateway ABI for native token operations
82
+ WRAPPED_TOKEN_GATEWAY_ABI = [
83
+ {
84
+ "type": "function",
85
+ "stateMutability": "view",
86
+ "name": "getWETHAddress",
87
+ "inputs": [],
88
+ "outputs": [{"type": "address"}],
89
+ },
90
+ {
91
+ "type": "function",
92
+ "stateMutability": "payable",
93
+ "name": "depositETH",
94
+ "inputs": [
95
+ {"type": "address"},
96
+ {"type": "address"},
97
+ {"type": "uint16"},
98
+ ],
99
+ "outputs": [],
100
+ },
101
+ {
102
+ "type": "function",
103
+ "stateMutability": "nonpayable",
104
+ "name": "withdrawETH",
105
+ "inputs": [
106
+ {"type": "address"},
107
+ {"type": "uint256"},
108
+ {"type": "address"},
109
+ ],
110
+ "outputs": [],
111
+ },
112
+ {
113
+ "type": "function",
114
+ "stateMutability": "payable",
115
+ "name": "repayETH",
116
+ "inputs": [
117
+ {"type": "address"},
118
+ {"type": "uint256"},
119
+ {"type": "address"},
120
+ ],
121
+ "outputs": [],
122
+ },
123
+ {
124
+ "type": "function",
125
+ "stateMutability": "nonpayable",
126
+ "name": "borrowETH",
127
+ "inputs": [
128
+ {"type": "address"},
129
+ {"type": "uint256"},
130
+ {"type": "uint16"},
131
+ ],
132
+ "outputs": [],
133
+ },
134
+ ]
135
+
136
+ # WETH ABI for native token wrapping
137
+ WETH_ABI = [
138
+ {
139
+ "inputs": [],
140
+ "name": "deposit",
141
+ "outputs": [],
142
+ "stateMutability": "payable",
143
+ "type": "function",
144
+ },
145
+ {
146
+ "inputs": [{"name": "wad", "type": "uint256"}],
147
+ "name": "withdraw",
148
+ "outputs": [],
149
+ "stateMutability": "nonpayable",
150
+ "type": "function",
151
+ },
152
+ ]
@@ -0,0 +1,188 @@
1
+ import asyncio
2
+ import os
3
+ from typing import Any
4
+
5
+ from loguru import logger
6
+
7
+ from wayfinder_paths.core.clients.ClientManager import ClientManager
8
+ from wayfinder_paths.core.config import StrategyJobConfig
9
+ from wayfinder_paths.core.strategies.Strategy import Strategy
10
+
11
+
12
+ class StrategyJob:
13
+ def __init__(
14
+ self,
15
+ strategy: Strategy,
16
+ config: StrategyJobConfig,
17
+ clients: dict[str, Any] | None = None,
18
+ skip_auth: bool = False,
19
+ api_key: str | None = None,
20
+ ):
21
+ """
22
+ Initialize a StrategyJob.
23
+
24
+ Args:
25
+ strategy: The strategy to execute.
26
+ config: Strategy job configuration.
27
+ clients: Optional dict of pre-instantiated clients to inject directly.
28
+ skip_auth: If True, skips authentication (for SDK usage).
29
+ api_key: Optional API key for service account authentication.
30
+ If provided, will be passed to ClientManager and strategy.
31
+ """
32
+ self.strategy = strategy
33
+ self.config = config
34
+
35
+ self.job_id = strategy.name or "unknown"
36
+ self.clients = ClientManager(
37
+ clients=clients, skip_auth=skip_auth, api_key=api_key
38
+ )
39
+
40
+ def _setup_strategy(self):
41
+ """Setup the strategy instance"""
42
+ if not self.strategy:
43
+ raise ValueError("No strategy provided to StrategyJob")
44
+
45
+ self.strategy.log = self.log
46
+
47
+ def _is_using_api_key(self) -> bool:
48
+ """Check if API key authentication is being used."""
49
+ if self.clients._api_key:
50
+ return True
51
+
52
+ if self.clients.auth:
53
+ try:
54
+ creds = self.clients.auth._load_config_credentials()
55
+ if creds.get("api_key"):
56
+ return True
57
+ if os.getenv("WAYFINDER_API_KEY"):
58
+ return True
59
+ except Exception:
60
+ pass
61
+
62
+ return False
63
+
64
+ async def setup(self):
65
+ """
66
+ Initialize the strategy job and strategy.
67
+
68
+ Sets up authentication and initializes the strategy with merged configuration.
69
+ """
70
+ self._setup_strategy()
71
+
72
+ # Ensure auth token is set for API calls
73
+ if not self.clients._skip_auth:
74
+ is_api_key_auth = self._is_using_api_key()
75
+
76
+ if is_api_key_auth:
77
+ logger.debug("Using API key authentication")
78
+ if self.clients.auth:
79
+ await self.clients.auth._ensure_bearer_token()
80
+ else:
81
+ # Try to ensure bearer token is set, authenticate if needed
82
+ try:
83
+ if self.clients.auth:
84
+ await self.clients.auth._ensure_bearer_token()
85
+ except (PermissionError, Exception) as e:
86
+ if not isinstance(e, PermissionError):
87
+ logger.warning(
88
+ f"Authentication failed: {e}, trying OAuth fallback"
89
+ )
90
+ username = self.config.user.username
91
+ password = self.config.user.password
92
+ refresh_token = self.config.user.refresh_token
93
+ if refresh_token or (username and password):
94
+ await self.clients.authenticate(
95
+ username=username,
96
+ password=password,
97
+ refresh_token=refresh_token,
98
+ )
99
+ else:
100
+ raise ValueError(
101
+ "Authentication required: provide api_key parameter for service account auth, "
102
+ "or username+password/refresh_token in config.json for personal access"
103
+ ) from e
104
+
105
+ existing_cfg = dict(getattr(self.strategy, "config", {}) or {})
106
+ strategy_cfg = dict(self.config.strategy_config or {})
107
+ merged_cfg = {**strategy_cfg, **existing_cfg}
108
+ self.strategy.config = merged_cfg
109
+ self.strategy.clients = self.clients
110
+ await self.strategy.setup()
111
+
112
+ async def execute_strategy(self, action: str, **kwargs) -> dict[str, Any]:
113
+ """Execute a strategy action (deposit, withdraw, update, status, partial_liquidate)"""
114
+ try:
115
+ if action == "deposit":
116
+ result = await self.strategy.deposit(**kwargs)
117
+ elif action == "withdraw":
118
+ result = await self.strategy.withdraw(**kwargs)
119
+ elif action == "update":
120
+ result = await self.strategy.update()
121
+ elif action == "status":
122
+ result = await self.strategy.status()
123
+ elif action == "partial_liquidate":
124
+ usd_value = kwargs.get("usd_value")
125
+ if usd_value is None:
126
+ result = (
127
+ False,
128
+ "usd_value parameter is required for partial_liquidate",
129
+ )
130
+ else:
131
+ result = await self.strategy.partial_liquidate(usd_value)
132
+ else:
133
+ result = {"success": False, "message": f"Unknown action: {action}"}
134
+
135
+ await self.log(f"Strategy action '{action}' completed: {result}")
136
+ return result
137
+
138
+ except Exception as e:
139
+ error_msg = f"Strategy action '{action}' failed: {str(e)}"
140
+ await self.log(error_msg)
141
+ await self.handle_error({"error": str(e), "action": action})
142
+ return {"success": False, "error": str(e)}
143
+
144
+ async def run_continuous(self, interval_seconds: int | None = None):
145
+ """Run the strategy continuously at specified intervals"""
146
+ interval = interval_seconds or self.config.system.update_interval
147
+ logger.info(
148
+ f"Starting continuous execution for strategy: {self.strategy.name} with interval {interval}s"
149
+ )
150
+
151
+ while True:
152
+ try:
153
+ await self.execute_strategy("update")
154
+ await asyncio.sleep(interval)
155
+
156
+ except asyncio.CancelledError:
157
+ logger.info("Continuous execution cancelled")
158
+ break
159
+ except Exception as e:
160
+ logger.error(f"Error in continuous execution: {str(e)}")
161
+ await asyncio.sleep(interval)
162
+
163
+ async def log(self, msg: str):
164
+ """Log messages for the job"""
165
+ logger.info(f"Job {self.job_id}: {msg}")
166
+
167
+ async def handle_error(self, error_data: dict[str, Any]) -> None:
168
+ """
169
+ Handle errors that occur during strategy execution.
170
+
171
+ Args:
172
+ error_data: Dictionary containing error information. Expected keys:
173
+ - error: Error message or exception string
174
+ - action: Strategy action that failed (e.g., "deposit", "update")
175
+
176
+ Note:
177
+ Base implementation is a no-op. Subclasses or external systems
178
+ can override this method to implement custom error handling,
179
+ logging, alerting, or recovery logic.
180
+ """
181
+ pass
182
+
183
+ async def stop(self):
184
+ """Stop the strategy job and cleanup"""
185
+ if hasattr(self.strategy, "stop"):
186
+ await self.strategy.stop()
187
+
188
+ logger.info(f"Strategy job {self.job_id} stopped")
@@ -0,0 +1,5 @@
1
+ """Core Engine Module"""
2
+
3
+ from .StrategyJob import StrategyJob
4
+
5
+ __all__ = ["StrategyJob", "GLOBAL_IMPORTS"]
@@ -0,0 +1,97 @@
1
+ from typing import Any
2
+
3
+ import yaml
4
+ from pydantic import BaseModel, Field, validator
5
+
6
+
7
+ class AdapterRequirement(BaseModel):
8
+ name: str = Field(
9
+ ..., description="Adapter symbolic name (e.g., BALANCE, HYPERLIQUID)"
10
+ )
11
+ capabilities: list[str] = Field(default_factory=list)
12
+
13
+
14
+ class AdapterManifest(BaseModel):
15
+ schema_version: str = Field(default="0.1")
16
+ entrypoint: str
17
+ capabilities: list[str] = Field(default_factory=list)
18
+ dependencies: list[str] = Field(default_factory=list)
19
+
20
+ @validator("entrypoint")
21
+ def validate_entrypoint(cls, v: str) -> str:
22
+ if "." not in v:
23
+ raise ValueError("entrypoint must be a full import path")
24
+ return v
25
+
26
+ @validator("capabilities")
27
+ def validate_capabilities(cls, v: list[str]) -> list[str]:
28
+ if not v:
29
+ raise ValueError("capabilities cannot be empty")
30
+ return v
31
+
32
+ @validator("dependencies")
33
+ def validate_dependencies(cls, v: list[str]) -> list[str]:
34
+ if not v:
35
+ raise ValueError("dependencies cannot be empty")
36
+ return v
37
+
38
+
39
+ class StrategyManifest(BaseModel):
40
+ schema_version: str = Field(default="0.1")
41
+ entrypoint: str = Field(
42
+ ...,
43
+ description="Python path to class, e.g. strategies.funding_rate_strategy.FundingRateStrategy",
44
+ )
45
+ name: str | None = Field(
46
+ default=None,
47
+ description="Unique name identifier for this strategy instance. Used to look up dedicated wallet in wallets.json by label.",
48
+ )
49
+ permissions: dict[str, Any] = Field(default_factory=dict)
50
+ adapters: list[AdapterRequirement] = Field(default_factory=list)
51
+
52
+ @validator("entrypoint")
53
+ def validate_entrypoint(cls, v: str) -> str:
54
+ if "." not in v:
55
+ raise ValueError(
56
+ "entrypoint must be a full import path to a Strategy class"
57
+ )
58
+ return v
59
+
60
+ @validator("permissions")
61
+ def validate_permissions(cls, v: dict) -> dict:
62
+ if "policy" not in v:
63
+ raise ValueError("permissions.policy is required")
64
+ if not v["policy"]:
65
+ raise ValueError("permissions.policy cannot be empty")
66
+ return v
67
+
68
+ @validator("adapters")
69
+ def validate_adapters(cls, v: list) -> list:
70
+ if not v:
71
+ raise ValueError("adapters cannot be empty")
72
+ return v
73
+
74
+
75
+ def load_adapter_manifest(path: str) -> AdapterManifest:
76
+ with open(path) as f:
77
+ data = yaml.safe_load(f)
78
+ return AdapterManifest(**data)
79
+
80
+
81
+ def load_strategy_manifest(path: str) -> StrategyManifest:
82
+ with open(path) as f:
83
+ data = yaml.safe_load(f)
84
+ return StrategyManifest(**data)
85
+
86
+
87
+ def load_manifest(path: str) -> StrategyManifest:
88
+ """Legacy function for backward compatibility."""
89
+ return load_strategy_manifest(path)
90
+
91
+
92
+ def validate_manifest(manifest: StrategyManifest) -> None:
93
+ # Simple v0.1 rules: require at least one adapter and permissions.policy
94
+ if not manifest.adapters:
95
+ raise ValueError("Manifest must declare at least one adapter")
96
+ if "policy" not in manifest.permissions:
97
+ raise ValueError("Manifest.permissions must include 'policy'")
File without changes