wayfinder-paths 0.1.14__py3-none-any.whl → 0.1.16__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.
- wayfinder_paths/adapters/balance_adapter/README.md +19 -20
- wayfinder_paths/adapters/balance_adapter/adapter.py +91 -22
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +5 -11
- wayfinder_paths/adapters/brap_adapter/README.md +22 -19
- wayfinder_paths/adapters/brap_adapter/adapter.py +95 -45
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +8 -24
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +40 -42
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +8 -15
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/moonwell_adapter/README.md +29 -31
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +326 -364
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +285 -189
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/token_adapter/test_adapter.py +4 -4
- wayfinder_paths/core/config.py +8 -47
- wayfinder_paths/core/constants/base.py +0 -1
- wayfinder_paths/core/constants/erc20_abi.py +13 -24
- wayfinder_paths/core/engine/StrategyJob.py +3 -1
- wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
- wayfinder_paths/core/strategies/Strategy.py +22 -4
- wayfinder_paths/core/utils/erc20_service.py +100 -0
- wayfinder_paths/core/utils/evm_helpers.py +1 -8
- wayfinder_paths/core/utils/transaction.py +191 -0
- wayfinder_paths/core/utils/web3.py +66 -0
- wayfinder_paths/policies/erc20.py +1 -1
- wayfinder_paths/run_strategy.py +42 -6
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +263 -220
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +132 -155
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +123 -80
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -12
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -6
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2270 -1328
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +282 -121
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -1
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +107 -85
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -8
- wayfinder_paths/templates/adapter/README.md +1 -1
- wayfinder_paths/templates/strategy/README.md +1 -5
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/METADATA +3 -41
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/RECORD +45 -54
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/WHEEL +1 -1
- wayfinder_paths/abis/generic/erc20.json +0 -383
- wayfinder_paths/core/clients/sdk_example.py +0 -125
- wayfinder_paths/core/engine/__init__.py +0 -5
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +0 -130
- wayfinder_paths/core/services/local_evm_txn.py +0 -334
- wayfinder_paths/core/services/local_token_txn.py +0 -242
- wayfinder_paths/core/services/web3_service.py +0 -43
- wayfinder_paths/core/wallets/README.md +0 -88
- wayfinder_paths/core/wallets/WalletManager.py +0 -56
- wayfinder_paths/core/wallets/__init__.py +0 -7
- wayfinder_paths/scripts/run_strategy.py +0 -152
- wayfinder_paths/strategies/config.py +0 -85
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from web3 import AsyncWeb3
|
|
6
|
+
|
|
7
|
+
from wayfinder_paths.core.utils.web3 import (
|
|
8
|
+
get_transaction_chain_id,
|
|
9
|
+
web3_from_chain_id,
|
|
10
|
+
web3s_from_chain_id,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
PRE_EIP_1559_CHAIN_IDS: set = {56, 42161}
|
|
14
|
+
|
|
15
|
+
SUGGESTED_PRIORITY_FEE_MULTIPLIER = 1.5
|
|
16
|
+
SUGGESTED_GAS_PRICE_MULTIPLIER = 1.5
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get_transaction_from_address(transaction: dict) -> str:
|
|
20
|
+
if "from" not in transaction:
|
|
21
|
+
raise ValueError("Transaction does not contain from address")
|
|
22
|
+
return AsyncWeb3.to_checksum_address(transaction["from"])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def nonce_transaction(transaction: dict):
|
|
26
|
+
transaction = transaction.copy()
|
|
27
|
+
|
|
28
|
+
from_address = _get_transaction_from_address(transaction)
|
|
29
|
+
|
|
30
|
+
async def _get_nonce(web3: AsyncWeb3, from_address: str) -> int:
|
|
31
|
+
return await web3.eth.get_transaction_count(from_address)
|
|
32
|
+
|
|
33
|
+
async with web3s_from_chain_id(get_transaction_chain_id(transaction)) as web3s:
|
|
34
|
+
nonces = await asyncio.gather(
|
|
35
|
+
*[_get_nonce(web3, from_address) for web3 in web3s]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
nonce = max(nonces)
|
|
39
|
+
transaction["nonce"] = nonce
|
|
40
|
+
|
|
41
|
+
return transaction
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def gas_price_transaction(
|
|
45
|
+
transaction: dict,
|
|
46
|
+
gas_price_multiplier: float = SUGGESTED_GAS_PRICE_MULTIPLIER,
|
|
47
|
+
priority_fee_multiplier: float = SUGGESTED_PRIORITY_FEE_MULTIPLIER,
|
|
48
|
+
):
|
|
49
|
+
transaction = transaction.copy()
|
|
50
|
+
|
|
51
|
+
async def _get_gas_price(web3: AsyncWeb3) -> int:
|
|
52
|
+
return await web3.eth.gas_price
|
|
53
|
+
|
|
54
|
+
async def _get_base_fee(web3: AsyncWeb3) -> int:
|
|
55
|
+
latest_block = await web3.eth.get_block("latest")
|
|
56
|
+
return latest_block.baseFeePerGas
|
|
57
|
+
|
|
58
|
+
async def _get_priority_fee(web3: AsyncWeb3) -> int:
|
|
59
|
+
lookback_blocks = 10
|
|
60
|
+
percentile = 80
|
|
61
|
+
fee_history = await web3.eth.fee_history(
|
|
62
|
+
lookback_blocks, "latest", [percentile]
|
|
63
|
+
)
|
|
64
|
+
historical_priority_fees = [i[0] for i in fee_history.reward]
|
|
65
|
+
return sum(historical_priority_fees) // len(historical_priority_fees)
|
|
66
|
+
|
|
67
|
+
chain_id = get_transaction_chain_id(transaction)
|
|
68
|
+
async with web3s_from_chain_id(chain_id) as web3s:
|
|
69
|
+
if chain_id in PRE_EIP_1559_CHAIN_IDS:
|
|
70
|
+
gas_prices = await asyncio.gather(*[_get_gas_price(web3) for web3 in web3s])
|
|
71
|
+
gas_price = max(gas_prices)
|
|
72
|
+
|
|
73
|
+
transaction["gasPrice"] = int(gas_price * gas_price_multiplier)
|
|
74
|
+
elif chain_id == 999:
|
|
75
|
+
# HyperEVM big blocks fetch base gas price from a different RPC method. Priority fee = 0 is # grandfathered in from Django, not sure what's right here.
|
|
76
|
+
big_block_gas_prices = await asyncio.gather(
|
|
77
|
+
*[web3.hype.big_block_gas_price() for web3 in web3s]
|
|
78
|
+
)
|
|
79
|
+
big_block_gas_price = max(big_block_gas_prices)
|
|
80
|
+
|
|
81
|
+
transaction["maxFeePerGas"] = int(
|
|
82
|
+
big_block_gas_price * priority_fee_multiplier
|
|
83
|
+
)
|
|
84
|
+
transaction["maxPriorityFeePerGas"] = 0
|
|
85
|
+
else:
|
|
86
|
+
base_fees = await asyncio.gather(*[_get_base_fee(web3) for web3 in web3s])
|
|
87
|
+
priority_fees = await asyncio.gather(
|
|
88
|
+
*[_get_priority_fee(web3) for web3 in web3s]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
base_fee = max(base_fees)
|
|
92
|
+
priority_fee = max(priority_fees)
|
|
93
|
+
|
|
94
|
+
# The next block can grow base fee by up to 12.5%, we give a flew blocks of landing room. log_1.125(2) ~ 6 blocks of landing room. GPT says this is also what Metamask does.
|
|
95
|
+
max_base_fee_growth_multiplier = 2
|
|
96
|
+
transaction["maxFeePerGas"] = int(
|
|
97
|
+
base_fee * max_base_fee_growth_multiplier
|
|
98
|
+
+ priority_fee * priority_fee_multiplier
|
|
99
|
+
)
|
|
100
|
+
transaction["maxPriorityFeePerGas"] = int(
|
|
101
|
+
priority_fee * priority_fee_multiplier
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return transaction
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def gas_limit_transaction(transaction: dict):
|
|
108
|
+
transaction = transaction.copy()
|
|
109
|
+
|
|
110
|
+
# prevents RPCs from taking this as a serious limit
|
|
111
|
+
transaction.pop("gas", None)
|
|
112
|
+
|
|
113
|
+
async def _estimate_gas(web3: AsyncWeb3, transaction: dict) -> int:
|
|
114
|
+
try:
|
|
115
|
+
return await web3.eth.estimate_gas(transaction, block_identifier="pending")
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.info(
|
|
118
|
+
f"Failed to estimate gas using {web3.provider.endpoint_uri}. Error: {e}"
|
|
119
|
+
)
|
|
120
|
+
return 0
|
|
121
|
+
|
|
122
|
+
async with web3s_from_chain_id(get_transaction_chain_id(transaction)) as web3s:
|
|
123
|
+
gas_limits = await asyncio.gather(
|
|
124
|
+
*[_estimate_gas(web3, transaction) for web3 in web3s]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
gas_limit = max(gas_limits)
|
|
128
|
+
if gas_limit == 0:
|
|
129
|
+
logger.error("Gas estimation failed on all RPCs")
|
|
130
|
+
raise Exception("Gas estimation failed on all RPCs")
|
|
131
|
+
transaction["gas"] = gas_limit
|
|
132
|
+
|
|
133
|
+
return transaction
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def broadcast_transaction(chain_id, signed_transaction: bytes) -> str:
|
|
137
|
+
async with web3_from_chain_id(chain_id) as web3:
|
|
138
|
+
tx_hash = await web3.eth.send_raw_transaction(signed_transaction)
|
|
139
|
+
return tx_hash.hex()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def wait_for_transaction_receipt(
|
|
143
|
+
chain_id: int,
|
|
144
|
+
txn_hash: str,
|
|
145
|
+
poll_interval: float = 0.1,
|
|
146
|
+
timeout: int = 300,
|
|
147
|
+
confirmations: int = 3,
|
|
148
|
+
) -> dict:
|
|
149
|
+
async def _wait_for_receipt(web3: AsyncWeb3, tx_hash: str) -> dict:
|
|
150
|
+
return await web3.eth.wait_for_transaction_receipt(
|
|
151
|
+
tx_hash, poll_latency=poll_interval, timeout=timeout
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
async def _get_block_number(web3: AsyncWeb3) -> int:
|
|
155
|
+
return await web3.eth.block_number
|
|
156
|
+
|
|
157
|
+
async with web3s_from_chain_id(chain_id) as web3s:
|
|
158
|
+
tasks = [
|
|
159
|
+
asyncio.create_task(_wait_for_receipt(web3, txn_hash)) for web3 in web3s
|
|
160
|
+
]
|
|
161
|
+
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
|
162
|
+
for task in pending:
|
|
163
|
+
task.cancel()
|
|
164
|
+
receipt = done.pop().result()
|
|
165
|
+
|
|
166
|
+
target_block = receipt["blockNumber"] + confirmations - 1
|
|
167
|
+
while (
|
|
168
|
+
max(await asyncio.gather(*[_get_block_number(w) for w in web3s]))
|
|
169
|
+
< target_block
|
|
170
|
+
):
|
|
171
|
+
await asyncio.sleep(poll_interval)
|
|
172
|
+
return receipt
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def send_transaction(
|
|
176
|
+
transaction: dict, sign_callback: Callable, wait_for_receipt=True
|
|
177
|
+
) -> str:
|
|
178
|
+
logger.info(f"Broadcasting transaction {transaction.get('to', 'unknown')[:10]}...")
|
|
179
|
+
chain_id = get_transaction_chain_id(transaction)
|
|
180
|
+
transaction = await gas_limit_transaction(transaction)
|
|
181
|
+
transaction = await nonce_transaction(transaction)
|
|
182
|
+
transaction = await gas_price_transaction(transaction)
|
|
183
|
+
signed_transaction = await sign_callback(transaction)
|
|
184
|
+
txn_hash = await broadcast_transaction(chain_id, signed_transaction)
|
|
185
|
+
logger.info(f"Transaction broadcasted: {txn_hash}")
|
|
186
|
+
if wait_for_receipt:
|
|
187
|
+
await wait_for_transaction_receipt(chain_id, txn_hash)
|
|
188
|
+
return txn_hash
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# TODO: HypeEVM Big Blocks: Setting and detecting
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
|
|
3
|
+
from web3 import AsyncHTTPProvider, AsyncWeb3
|
|
4
|
+
from web3.middleware import ExtraDataToPOAMiddleware
|
|
5
|
+
from web3.module import Module
|
|
6
|
+
|
|
7
|
+
from wayfinder_paths.core.config import CONFIG
|
|
8
|
+
|
|
9
|
+
POA_MIDDLEWARE_CHAIN_IDS: set = {56, 137, 43114}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HyperModule(Module):
|
|
13
|
+
def __init__(self, w3):
|
|
14
|
+
super().__init__(w3)
|
|
15
|
+
|
|
16
|
+
async def big_block_gas_price(self):
|
|
17
|
+
big_block_gas_price = await self.w3.manager.coro_request(
|
|
18
|
+
"eth_bigBlockGasPrice", []
|
|
19
|
+
)
|
|
20
|
+
return int(big_block_gas_price, 16)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_rpcs_for_chain_id(chain_id: int) -> list:
|
|
24
|
+
rpcs = CONFIG.get("strategy", {}).get("rpc_urls", {}).get(str(chain_id))
|
|
25
|
+
if rpcs is None:
|
|
26
|
+
raise ValueError(f"No RPCs configured for chain ID {chain_id}")
|
|
27
|
+
return rpcs
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_web3(rpc: str, chain_id: int) -> AsyncWeb3:
|
|
31
|
+
web3 = AsyncWeb3(AsyncHTTPProvider(rpc))
|
|
32
|
+
if chain_id in POA_MIDDLEWARE_CHAIN_IDS:
|
|
33
|
+
web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
|
|
34
|
+
if chain_id == 999:
|
|
35
|
+
web3.attach_modules({"hype": (HyperModule)})
|
|
36
|
+
return web3
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_transaction_chain_id(transaction: dict) -> int:
|
|
40
|
+
if "chainId" not in transaction:
|
|
41
|
+
raise ValueError("Transaction does not contain chainId")
|
|
42
|
+
return int(transaction["chainId"])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_web3s_from_chain_id(chain_id: int) -> list[AsyncWeb3]:
|
|
46
|
+
rpcs = _get_rpcs_for_chain_id(chain_id)
|
|
47
|
+
return [_get_web3(rpc, chain_id) for rpc in rpcs]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@asynccontextmanager
|
|
51
|
+
async def web3s_from_chain_id(chain_id: int):
|
|
52
|
+
web3s = get_web3s_from_chain_id(chain_id)
|
|
53
|
+
try:
|
|
54
|
+
yield web3s
|
|
55
|
+
finally:
|
|
56
|
+
for web3 in web3s:
|
|
57
|
+
await web3.provider.disconnect()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@asynccontextmanager
|
|
61
|
+
async def web3_from_chain_id(chain_id: int):
|
|
62
|
+
web3s = get_web3s_from_chain_id(chain_id)
|
|
63
|
+
try:
|
|
64
|
+
yield web3s[0]
|
|
65
|
+
finally:
|
|
66
|
+
await web3s[0].provider.disconnect()
|
wayfinder_paths/run_strategy.py
CHANGED
|
@@ -14,19 +14,21 @@ from loguru import logger
|
|
|
14
14
|
|
|
15
15
|
from wayfinder_paths.core.config import StrategyJobConfig
|
|
16
16
|
from wayfinder_paths.core.engine.StrategyJob import StrategyJob
|
|
17
|
+
from wayfinder_paths.core.utils.evm_helpers import resolve_private_key_for_from_address
|
|
18
|
+
from wayfinder_paths.core.utils.web3 import get_transaction_chain_id, web3_from_chain_id
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def load_strategy(
|
|
20
22
|
strategy_name: str,
|
|
21
23
|
*,
|
|
22
|
-
|
|
24
|
+
config: StrategyJobConfig,
|
|
23
25
|
):
|
|
24
26
|
"""
|
|
25
27
|
Dynamically load a strategy by name
|
|
26
28
|
|
|
27
29
|
Args:
|
|
28
30
|
strategy_name: Name of the strategy to load (directory name in strategies/)
|
|
29
|
-
|
|
31
|
+
config: StrategyJobConfig instance containing user and strategy configuration
|
|
30
32
|
|
|
31
33
|
Returns:
|
|
32
34
|
Strategy instance
|
|
@@ -68,7 +70,34 @@ def load_strategy(
|
|
|
68
70
|
if strategy_class is None:
|
|
69
71
|
raise ValueError(f"No Strategy class found in {module_path}")
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
# Get wallet addresses from strategy_config (enriched from wallets array in config.json)
|
|
74
|
+
main_wallet = config.strategy_config.get("main_wallet") or {}
|
|
75
|
+
strategy_wallet = config.strategy_config.get("strategy_wallet") or {}
|
|
76
|
+
main_wallet_address = main_wallet.get("address")
|
|
77
|
+
strategy_wallet_address = strategy_wallet.get("address")
|
|
78
|
+
|
|
79
|
+
async def main_wallet_signing_callback(transaction):
|
|
80
|
+
private_key = resolve_private_key_for_from_address(
|
|
81
|
+
main_wallet_address, config.strategy_config
|
|
82
|
+
)
|
|
83
|
+
async with web3_from_chain_id(get_transaction_chain_id(transaction)) as web3:
|
|
84
|
+
signed = web3.eth.account.sign_transaction(transaction, private_key)
|
|
85
|
+
return signed.raw_transaction.hex()
|
|
86
|
+
|
|
87
|
+
async def strategy_wallet_signing_callback(transaction):
|
|
88
|
+
private_key = resolve_private_key_for_from_address(
|
|
89
|
+
strategy_wallet_address,
|
|
90
|
+
config.strategy_config,
|
|
91
|
+
)
|
|
92
|
+
async with web3_from_chain_id(get_transaction_chain_id(transaction)) as web3:
|
|
93
|
+
signed = web3.eth.account.sign_transaction(transaction, private_key)
|
|
94
|
+
return signed.raw_transaction.hex()
|
|
95
|
+
|
|
96
|
+
return strategy_class(
|
|
97
|
+
config=config.strategy_config,
|
|
98
|
+
main_wallet_signing_callback=main_wallet_signing_callback,
|
|
99
|
+
strategy_wallet_signing_callback=strategy_wallet_signing_callback,
|
|
100
|
+
)
|
|
72
101
|
|
|
73
102
|
|
|
74
103
|
def load_config(
|
|
@@ -133,10 +162,12 @@ async def run_strategy(
|
|
|
133
162
|
# Load configuration with strategy name for wallet lookup
|
|
134
163
|
logger.debug(f"Config path provided: {config_path}")
|
|
135
164
|
config = load_config(config_path, strategy_name=strategy_name)
|
|
165
|
+
main_wallet_cfg = config.strategy_config.get("main_wallet") or {}
|
|
166
|
+
strategy_wallet_cfg = config.strategy_config.get("strategy_wallet") or {}
|
|
136
167
|
logger.debug(
|
|
137
168
|
"Loaded config: wallets(main={} strategy={})",
|
|
138
|
-
|
|
139
|
-
|
|
169
|
+
main_wallet_cfg.get("address") or "none",
|
|
170
|
+
strategy_wallet_cfg.get("address") or "none",
|
|
140
171
|
)
|
|
141
172
|
|
|
142
173
|
# Validate required configuration
|
|
@@ -145,7 +176,7 @@ async def run_strategy(
|
|
|
145
176
|
# Load strategy with the enriched config
|
|
146
177
|
strategy = load_strategy(
|
|
147
178
|
strategy_name,
|
|
148
|
-
|
|
179
|
+
config=config,
|
|
149
180
|
)
|
|
150
181
|
logger.info(f"Loaded strategy: {strategy.name}")
|
|
151
182
|
|
|
@@ -197,6 +228,10 @@ async def run_strategy(
|
|
|
197
228
|
result = await strategy_job.execute_strategy("update")
|
|
198
229
|
logger.info(f"Update result: {result}")
|
|
199
230
|
|
|
231
|
+
elif action == "exit":
|
|
232
|
+
result = await strategy_job.execute_strategy("exit")
|
|
233
|
+
logger.info(f"Exit result: {result}")
|
|
234
|
+
|
|
200
235
|
elif action == "partial-liquidate":
|
|
201
236
|
usd_value = kwargs.get("amount")
|
|
202
237
|
if not usd_value:
|
|
@@ -292,6 +327,7 @@ def main():
|
|
|
292
327
|
"withdraw",
|
|
293
328
|
"status",
|
|
294
329
|
"update",
|
|
330
|
+
"exit",
|
|
295
331
|
"policy",
|
|
296
332
|
"script",
|
|
297
333
|
"partial-liquidate",
|