mirror of
https://github.com/chainstacklabs/pumpfun-bonkfun-bot.git
synced 2026-07-27 15:27:44 +00:00
Reduce RPC delays when selling (#150)
* feat(trading): enhance sell execution with token amount and price parameters to reduce RPC delays * feat(trading): add method to fetch actual token balance after transaction to improve accuracy * feat(trading): update account data size limit and enhance price calculations
This commit is contained in:
@@ -8,7 +8,7 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
|
||||
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
|
||||
private_key: "${SOLANA_PRIVATE_KEY}"
|
||||
|
||||
enabled: false # You can turn off the bot w/o removing its config
|
||||
enabled: true # You can turn off the bot w/o removing its config
|
||||
separate_process: true
|
||||
|
||||
# Options: "pump_fun" (default), "lets_bonk"
|
||||
@@ -31,7 +31,7 @@ trade:
|
||||
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
|
||||
#take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
|
||||
#stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
|
||||
max_hold_time: 15 # Maximum hold time in seconds
|
||||
max_hold_time: 5 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
|
||||
#price_check_interval: 2 # Check price every 2 seconds
|
||||
|
||||
# EXTREME FAST mode configuration
|
||||
@@ -61,8 +61,9 @@ compute_units:
|
||||
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
|
||||
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
|
||||
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
|
||||
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
|
||||
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
|
||||
account_data_size: 512_000 # 512KB limit
|
||||
account_data_size: 12_500_000
|
||||
|
||||
# Filters for token selection
|
||||
filters:
|
||||
@@ -77,7 +78,7 @@ filters:
|
||||
retries:
|
||||
max_attempts: 1 # Number of attempts for transaction submission
|
||||
wait_after_creation: 15 # Seconds to wait after token creation (only if EXTREME FAST is disabled)
|
||||
wait_after_buy: 15 # Holding period after buy transaction
|
||||
wait_after_buy: 5 # Holding period after buy transaction
|
||||
wait_before_new_token: 15 # Pause between token trades
|
||||
|
||||
# Token and account management
|
||||
|
||||
@@ -24,14 +24,14 @@ geyser:
|
||||
# Control trade execution: amount of SOL per trade and acceptable price deviation
|
||||
trade:
|
||||
buy_amount: 0.0001 # Amount of SOL to spend when buying (in SOL)
|
||||
buy_slippage: 0.2 # Maximum acceptable price deviation (0.2 = 20%)
|
||||
buy_slippage: 0.3 # Maximum acceptable price deviation (0.3 = 30%)
|
||||
sell_slippage: 0.3
|
||||
|
||||
# Exit strategy configuration
|
||||
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
|
||||
take_profit_percentage: 0.4 # Take profit at 40% gain (0.4 = 40%)
|
||||
stop_loss_percentage: 0.4 # Stop loss at 40% loss (0.4 = 40%)
|
||||
max_hold_time: 60 # Maximum hold time in seconds
|
||||
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
|
||||
price_check_interval: 2 # Check price every 2 seconds
|
||||
|
||||
# EXTREME FAST mode configuration
|
||||
@@ -61,8 +61,9 @@ compute_units:
|
||||
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
|
||||
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
|
||||
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
|
||||
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
|
||||
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
|
||||
account_data_size: 512_000 # 512KB limit
|
||||
account_data_size: 12_500_000
|
||||
|
||||
# Filters for token selection
|
||||
filters:
|
||||
|
||||
@@ -8,7 +8,7 @@ rpc_endpoint: "${SOLANA_NODE_RPC_ENDPOINT}"
|
||||
wss_endpoint: "${SOLANA_NODE_WSS_ENDPOINT}"
|
||||
private_key: "${SOLANA_PRIVATE_KEY}"
|
||||
|
||||
enabled: true # You can turn off the bot w/o removing its config
|
||||
enabled: false # You can turn off the bot w/o removing its config
|
||||
separate_process: true
|
||||
|
||||
# Options: "pump_fun" (default), "lets_bonk"
|
||||
@@ -31,7 +31,7 @@ trade:
|
||||
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
|
||||
#take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
|
||||
#stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
|
||||
max_hold_time: 15 # Maximum hold time in seconds
|
||||
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
|
||||
#price_check_interval: 2 # Check price every 2 seconds
|
||||
|
||||
# EXTREME FAST mode configuration
|
||||
@@ -61,8 +61,9 @@ compute_units:
|
||||
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
|
||||
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
|
||||
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
|
||||
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
|
||||
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
|
||||
account_data_size: 512_000 # 512KB limit
|
||||
account_data_size: 12_500_000
|
||||
|
||||
# Filters for token selection
|
||||
filters:
|
||||
|
||||
@@ -29,7 +29,7 @@ trade:
|
||||
exit_strategy: "time_based" # Options: "time_based", "tp_sl", "manual"
|
||||
take_profit_percentage: 0.1 # Take profit at 10% gain (0.1 = 10%)
|
||||
stop_loss_percentage: 0.1 # Stop loss at 10% loss (0.1 = 10%)
|
||||
max_hold_time: 600 # Maximum hold time in seconds (600 = 10 minutes)
|
||||
max_hold_time: 15 # Maximum hold time in seconds for TP/SL strategy, for time_based - see wait_after_buy
|
||||
price_check_interval: 2 # Check price every 2 seconds
|
||||
|
||||
# EXTREME FAST mode configuration
|
||||
@@ -59,8 +59,9 @@ compute_units:
|
||||
# Reduces CU cost from 16k to ~128 CU by limiting loaded account data.
|
||||
# Default is 64MB (16k CU). Setting to 512KB significantly reduces overhead.
|
||||
# Note: Savings don't show in "consumed CU" but improve tx priority/cost.
|
||||
# Note (Nov 23, 2025): with data size set to 512KB, transactions fail - increasing to 12.5MB resolves the issue.
|
||||
# Reference: https://www.anza.xyz/blog/cu-optimization-with-setloadedaccountsdatasizelimit
|
||||
account_data_size: 512_000 # 512KB limit
|
||||
account_data_size: 12_500_000
|
||||
|
||||
# Filters for token selection
|
||||
filters:
|
||||
|
||||
@@ -271,6 +271,152 @@ class SolanaClient:
|
||||
logger.exception(f"Failed to confirm transaction {signature}")
|
||||
return False
|
||||
|
||||
async def get_transaction_token_balance(
|
||||
self, signature: str, user_pubkey: Pubkey, mint: Pubkey
|
||||
) -> int | None:
|
||||
"""Get the user's token balance after a transaction from postTokenBalances.
|
||||
|
||||
Args:
|
||||
signature: Transaction signature
|
||||
user_pubkey: User's wallet public key
|
||||
mint: Token mint address
|
||||
|
||||
Returns:
|
||||
Token balance (raw amount) after transaction, or None if not found
|
||||
"""
|
||||
result = await self._get_transaction_result(signature)
|
||||
if not result:
|
||||
return None
|
||||
|
||||
meta = result.get("meta", {})
|
||||
post_token_balances = meta.get("postTokenBalances", [])
|
||||
|
||||
user_str = str(user_pubkey)
|
||||
mint_str = str(mint)
|
||||
|
||||
for balance in post_token_balances:
|
||||
if balance.get("owner") == user_str and balance.get("mint") == mint_str:
|
||||
ui_amount = balance.get("uiTokenAmount", {})
|
||||
amount_str = ui_amount.get("amount")
|
||||
if amount_str:
|
||||
return int(amount_str)
|
||||
|
||||
return None
|
||||
|
||||
async def get_buy_transaction_details(
|
||||
self, signature: str, mint: Pubkey, sol_destination: Pubkey
|
||||
) -> tuple[int | None, int | None]:
|
||||
"""Get actual tokens received and SOL spent from a buy transaction.
|
||||
|
||||
Uses preBalances/postBalances to find exact SOL transferred to the
|
||||
pool/curve and pre/post token balance diff to find tokens received.
|
||||
|
||||
Args:
|
||||
signature: Transaction signature
|
||||
mint: Token mint address
|
||||
sol_destination: Address where SOL is sent (bonding curve for pump.fun,
|
||||
quote_vault for letsbonk)
|
||||
|
||||
Returns:
|
||||
Tuple of (tokens_received_raw, sol_spent_lamports), or (None, None)
|
||||
"""
|
||||
result = await self._get_transaction_result(signature)
|
||||
if not result:
|
||||
return None, None
|
||||
|
||||
meta = result.get("meta", {})
|
||||
mint_str = str(mint)
|
||||
|
||||
# Get tokens received from pre/post token balance diff
|
||||
# This works for Token2022 where owner might be different
|
||||
tokens_received = None
|
||||
pre_token_balances = meta.get("preTokenBalances", [])
|
||||
post_token_balances = meta.get("postTokenBalances", [])
|
||||
|
||||
# Build lookup by account index
|
||||
pre_by_idx = {b.get("accountIndex"): b for b in pre_token_balances}
|
||||
post_by_idx = {b.get("accountIndex"): b for b in post_token_balances}
|
||||
|
||||
# Find positive token diff for our mint (user receiving tokens)
|
||||
all_indices = set(pre_by_idx.keys()) | set(post_by_idx.keys())
|
||||
for idx in all_indices:
|
||||
pre = pre_by_idx.get(idx)
|
||||
post = post_by_idx.get(idx)
|
||||
|
||||
# Check if this is our mint
|
||||
balance_mint = (post or pre).get("mint", "")
|
||||
if balance_mint != mint_str:
|
||||
continue
|
||||
|
||||
pre_amount = (
|
||||
int(pre.get("uiTokenAmount", {}).get("amount", 0)) if pre else 0
|
||||
)
|
||||
post_amount = (
|
||||
int(post.get("uiTokenAmount", {}).get("amount", 0)) if post else 0
|
||||
)
|
||||
diff = post_amount - pre_amount
|
||||
|
||||
# Positive diff means tokens received (not the bonding curve's negative)
|
||||
if diff > 0:
|
||||
tokens_received = diff
|
||||
logger.info(f"Tokens received from tx: {tokens_received}")
|
||||
break
|
||||
|
||||
# Get SOL spent from preBalances/postBalances at sol_destination
|
||||
sol_destination_str = str(sol_destination)
|
||||
sol_spent = None
|
||||
pre_balances = meta.get("preBalances", [])
|
||||
post_balances = meta.get("postBalances", [])
|
||||
account_keys = (
|
||||
result.get("transaction", {}).get("message", {}).get("accountKeys", [])
|
||||
)
|
||||
|
||||
for i, key in enumerate(account_keys):
|
||||
key_str = key if isinstance(key, str) else key.get("pubkey", "")
|
||||
if key_str == sol_destination_str:
|
||||
if i < len(pre_balances) and i < len(post_balances):
|
||||
sol_spent = post_balances[i] - pre_balances[i]
|
||||
if sol_spent > 0:
|
||||
logger.info(f"SOL to pool/curve: {sol_spent} lamports")
|
||||
else:
|
||||
logger.warning(
|
||||
f"SOL destination balance change not positive: {sol_spent}"
|
||||
)
|
||||
sol_spent = None
|
||||
break
|
||||
|
||||
return tokens_received, sol_spent
|
||||
|
||||
async def _get_transaction_result(self, signature: str) -> dict | None:
|
||||
"""Fetch transaction result from RPC.
|
||||
|
||||
Args:
|
||||
signature: Transaction signature
|
||||
|
||||
Returns:
|
||||
Transaction result dict or None
|
||||
"""
|
||||
body = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "getTransaction",
|
||||
"params": [
|
||||
signature,
|
||||
{"encoding": "jsonParsed", "commitment": "confirmed"},
|
||||
],
|
||||
}
|
||||
|
||||
response = await self.post_rpc(body)
|
||||
if not response or "result" not in response:
|
||||
logger.warning(f"Failed to get transaction {signature}")
|
||||
return None
|
||||
|
||||
result = response["result"]
|
||||
if not result or "meta" not in result:
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
async def post_rpc(self, body: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"""
|
||||
Send a raw RPC request to the Solana node.
|
||||
|
||||
+107
-39
@@ -130,6 +130,35 @@ class PlatformAwareBuyer(Trader):
|
||||
|
||||
if success:
|
||||
logger.info(f"Buy transaction confirmed: {tx_signature}")
|
||||
|
||||
# Fetch actual tokens and SOL spent from transaction
|
||||
# Uses preBalances/postBalances to get exact amounts
|
||||
sol_destination = self._get_sol_destination(
|
||||
token_info, address_provider
|
||||
)
|
||||
tokens_raw, sol_spent = await self.client.get_buy_transaction_details(
|
||||
str(tx_signature), token_info.mint, sol_destination
|
||||
)
|
||||
|
||||
if tokens_raw is not None and sol_spent is not None:
|
||||
actual_amount = tokens_raw / 10**TOKEN_DECIMALS
|
||||
actual_price = (sol_spent / LAMPORTS_PER_SOL) / actual_amount
|
||||
logger.info(
|
||||
f"Actual tokens received: {actual_amount:.6f} "
|
||||
f"(expected: {token_amount:.6f})"
|
||||
)
|
||||
logger.info(
|
||||
f"Actual SOL spent: {sol_spent / LAMPORTS_PER_SOL:.10f} SOL"
|
||||
)
|
||||
logger.info(f"Actual price: {actual_price:.10f} SOL/token")
|
||||
token_amount = actual_amount
|
||||
token_price_sol = actual_price
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Failed to parse transaction details: tokens={tokens_raw}, "
|
||||
f"sol_spent={sol_spent}"
|
||||
)
|
||||
|
||||
return TradeResult(
|
||||
success=True,
|
||||
platform=token_info.platform,
|
||||
@@ -165,6 +194,42 @@ class PlatformAwareBuyer(Trader):
|
||||
# Fallback to deriving the address using platform provider
|
||||
return address_provider.derive_pool_address(token_info.mint)
|
||||
|
||||
def _get_sol_destination(
|
||||
self, token_info: TokenInfo, address_provider: AddressProvider
|
||||
) -> Pubkey:
|
||||
"""Get the address where SOL is sent during a buy transaction.
|
||||
|
||||
For pump.fun: SOL goes to the bonding curve
|
||||
For letsbonk: SOL goes to the quote_vault (WSOL vault)
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
address_provider: Platform-specific address provider
|
||||
|
||||
Returns:
|
||||
Address where SOL is transferred during buy
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If platform SOL destination is not implemented
|
||||
"""
|
||||
if token_info.platform == Platform.PUMP_FUN:
|
||||
# For pump.fun, SOL goes directly to bonding curve
|
||||
if hasattr(token_info, "bonding_curve") and token_info.bonding_curve:
|
||||
return token_info.bonding_curve
|
||||
return address_provider.derive_pool_address(token_info.mint)
|
||||
elif token_info.platform == Platform.LETS_BONK:
|
||||
# For letsbonk, SOL goes to quote_vault (WSOL vault)
|
||||
if hasattr(token_info, "quote_vault") and token_info.quote_vault:
|
||||
return token_info.quote_vault
|
||||
# Derive quote_vault if not available
|
||||
return address_provider.derive_quote_vault(token_info.mint)
|
||||
|
||||
raise NotImplementedError(
|
||||
f"SOL destination not implemented for platform {token_info.platform.value}. "
|
||||
f"Add platform-specific logic to _get_sol_destination() to specify where "
|
||||
f"SOL is transferred during buy transactions for this platform."
|
||||
)
|
||||
|
||||
def _get_cu_override(self, operation: str, platform: Platform) -> int | None:
|
||||
"""Get compute unit override from configuration.
|
||||
|
||||
@@ -202,8 +267,35 @@ class PlatformAwareSeller(Trader):
|
||||
self.max_retries = max_retries
|
||||
self.compute_units = compute_units or {}
|
||||
|
||||
async def execute(self, token_info: TokenInfo) -> TradeResult:
|
||||
"""Execute sell operation using platform-specific implementations."""
|
||||
async def execute(
|
||||
self, token_info: TokenInfo, token_amount: float, token_price: float
|
||||
) -> TradeResult:
|
||||
"""Execute sell operation using platform-specific implementations.
|
||||
|
||||
Args:
|
||||
token_info: Token information for the sell operation
|
||||
token_amount: Token amount to sell (from buy result). Required to avoid
|
||||
RPC balance query delays.
|
||||
token_price: Token price in SOL (from buy result). Required to avoid
|
||||
RPC pool state query delays.
|
||||
|
||||
Returns:
|
||||
TradeResult with operation outcome
|
||||
|
||||
Raises:
|
||||
ValueError: If required parameters are not provided
|
||||
"""
|
||||
if token_amount is None:
|
||||
raise ValueError(
|
||||
"token_amount is required for sell operation. "
|
||||
"Pass the amount from buy result to avoid RPC delays."
|
||||
)
|
||||
if token_price is None or token_price <= 0:
|
||||
raise ValueError(
|
||||
"token_price is required for sell operation and must be positive. "
|
||||
"Pass the price from buy result to avoid RPC delays."
|
||||
)
|
||||
|
||||
try:
|
||||
# Get platform-specific implementations
|
||||
implementations = get_platform_implementations(
|
||||
@@ -211,19 +303,14 @@ class PlatformAwareSeller(Trader):
|
||||
)
|
||||
address_provider = implementations.address_provider
|
||||
instruction_builder = implementations.instruction_builder
|
||||
curve_manager = implementations.curve_manager
|
||||
|
||||
# Get user's token account and balance
|
||||
user_token_account = address_provider.derive_user_token_account(
|
||||
self.wallet.pubkey, token_info.mint
|
||||
)
|
||||
# Use pre-known amount and price (no RPC delay)
|
||||
token_balance_decimal = token_amount
|
||||
token_balance = int(token_amount * 10**TOKEN_DECIMALS)
|
||||
token_price_sol = token_price
|
||||
|
||||
token_balance = await self.client.get_token_account_balance(
|
||||
user_token_account
|
||||
)
|
||||
token_balance_decimal = token_balance / 10**TOKEN_DECIMALS
|
||||
|
||||
logger.info(f"Token balance: {token_balance_decimal}")
|
||||
logger.info(f"Token balance: {token_balance_decimal:.6f}")
|
||||
logger.info(f"Price per Token (from buy): {token_price_sol:.8f} SOL")
|
||||
|
||||
if token_balance == 0:
|
||||
logger.info("No tokens to sell.")
|
||||
@@ -233,38 +320,19 @@ class PlatformAwareSeller(Trader):
|
||||
error_message="No tokens to sell",
|
||||
)
|
||||
|
||||
# Get pool address and current price using platform-agnostic method
|
||||
pool_address = self._get_pool_address(token_info, address_provider)
|
||||
# Fetch pool state to get price and mayhem mode status
|
||||
pool_state = await curve_manager.get_pool_state(pool_address)
|
||||
token_price_sol = pool_state.get("price_per_token")
|
||||
|
||||
# Validate price_per_token is present and positive
|
||||
if token_price_sol is None or token_price_sol <= 0:
|
||||
raise ValueError(
|
||||
f"Invalid price_per_token: {token_price_sol} for pool {pool_address} "
|
||||
f"(mint: {token_info.mint}) - cannot execute sell with zero/invalid price"
|
||||
)
|
||||
|
||||
# Set is_mayhem_mode from bonding curve state
|
||||
token_info.is_mayhem_mode = pool_state.get("is_mayhem_mode", False)
|
||||
|
||||
logger.info(f"Price per Token: {token_price_sol:.8f} SOL")
|
||||
|
||||
# Calculate expected SOL output
|
||||
# Calculate expected SOL output with slippage protection
|
||||
expected_sol_output = token_balance_decimal * token_price_sol
|
||||
|
||||
# Calculate minimum SOL output with slippage protection
|
||||
min_sol_output = int(
|
||||
(expected_sol_output * (1 - self.slippage)) * LAMPORTS_PER_SOL
|
||||
min_sol_output = max(
|
||||
1,
|
||||
int((expected_sol_output * (1 - self.slippage)) * LAMPORTS_PER_SOL),
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Selling {token_balance_decimal} tokens on {token_info.platform.value}"
|
||||
)
|
||||
logger.info(f"Expected SOL output: {expected_sol_output:.8f} SOL")
|
||||
logger.info(f"Expected SOL output: {expected_sol_output:.10f} SOL")
|
||||
logger.info(
|
||||
f"Minimum SOL output (with {self.slippage * 100:.1f}% slippage): {min_sol_output / LAMPORTS_PER_SOL:.8f} SOL"
|
||||
f"Minimum SOL output (with {self.slippage * 100:.1f}% slippage): "
|
||||
f"{min_sol_output / LAMPORTS_PER_SOL:.10f} SOL ({min_sol_output} lamports)"
|
||||
)
|
||||
|
||||
# Build sell instructions using platform-specific builder
|
||||
|
||||
@@ -193,7 +193,9 @@ class UniversalTrader:
|
||||
|
||||
# State tracking
|
||||
self.traded_mints: set[Pubkey] = set()
|
||||
self.traded_token_programs: dict[str, Pubkey] = {} # Maps mint (as string) to token_program_id
|
||||
self.traded_token_programs: dict[
|
||||
str, Pubkey
|
||||
] = {} # Maps mint (as string) to token_program_id
|
||||
self.token_queue: asyncio.Queue = asyncio.Queue()
|
||||
self.processing: bool = False
|
||||
self.processed_tokens: set[str] = set()
|
||||
@@ -329,8 +331,7 @@ class UniversalTrader:
|
||||
# Build parallel lists of mints and token_program_ids
|
||||
mints_list = list(self.traded_mints)
|
||||
token_program_ids = [
|
||||
self.traded_token_programs.get(str(mint))
|
||||
for mint in mints_list
|
||||
self.traded_token_programs.get(str(mint)) for mint in mints_list
|
||||
]
|
||||
await handle_cleanup_post_session(
|
||||
self.solana_client,
|
||||
@@ -465,7 +466,7 @@ class UniversalTrader:
|
||||
if self.exit_strategy == "tp_sl":
|
||||
await self._handle_tp_sl_exit(token_info, buy_result)
|
||||
elif self.exit_strategy == "time_based":
|
||||
await self._handle_time_based_exit(token_info)
|
||||
await self._handle_time_based_exit(token_info, buy_result)
|
||||
elif self.exit_strategy == "manual":
|
||||
logger.info("Manual exit strategy - position will remain open")
|
||||
else:
|
||||
@@ -512,13 +513,23 @@ class UniversalTrader:
|
||||
# Monitor position until exit condition is met
|
||||
await self._monitor_position_until_exit(token_info, position)
|
||||
|
||||
async def _handle_time_based_exit(self, token_info: TokenInfo) -> None:
|
||||
"""Handle legacy time-based exit strategy."""
|
||||
async def _handle_time_based_exit(
|
||||
self, token_info: TokenInfo, buy_result: TradeResult
|
||||
) -> None:
|
||||
"""Handle legacy time-based exit strategy.
|
||||
|
||||
Args:
|
||||
token_info: Token information
|
||||
buy_result: Result from the buy operation (contains token amount)
|
||||
"""
|
||||
logger.info(f"Waiting for {self.wait_time_after_buy} seconds before selling...")
|
||||
await asyncio.sleep(self.wait_time_after_buy)
|
||||
|
||||
logger.info(f"Selling {token_info.symbol}...")
|
||||
sell_result: TradeResult = await self.seller.execute(token_info)
|
||||
# Pass token amount and price from buy result to avoid RPC delays
|
||||
sell_result: TradeResult = await self.seller.execute(
|
||||
token_info, token_amount=buy_result.amount, token_price=buy_result.price
|
||||
)
|
||||
|
||||
if sell_result.success:
|
||||
logger.info(f"Successfully sold {token_info.symbol}")
|
||||
@@ -575,8 +586,12 @@ class UniversalTrader:
|
||||
f"Position PnL: {pnl['price_change_pct']:.2f}% ({pnl['unrealized_pnl_sol']:.6f} SOL)"
|
||||
)
|
||||
|
||||
# Execute sell
|
||||
sell_result = await self.seller.execute(token_info)
|
||||
# Execute sell with position quantity and entry price to avoid RPC delays
|
||||
sell_result = await self.seller.execute(
|
||||
token_info,
|
||||
token_amount=position.quantity,
|
||||
token_price=position.entry_price,
|
||||
)
|
||||
|
||||
if sell_result.success:
|
||||
# Close position with actual exit price
|
||||
|
||||
Reference in New Issue
Block a user