Selling strategy - deniyuda348/pump-fun-pump-swap-sniper-copy-bot GitHub Wiki

The bot implements a sophisticated multi-faceted selling strategy for tokens on Solana DEXes (PumpFun and PumpSwap protocols).

Core components of the selling strategy include:

  • Price-based analysis (take profit targets, stop loss, trailing stops, EMA crossovers)
  • Liquidity monitoring (minimum thresholds, relative drop detection)
  • Volume analysis (spike detection, drop detection, moving averages)
  • Time-based exits (max/min hold times)
  • Manipulation detection (wash trading, creator selling)
  • Progressive selling in chunks

The TokenMetrics struct tracks important metrics for each token:

  • Entry price, highest price, lowest price, current price
  • Time held, buy timestamp
  • Amount held, cost basis
  • Price history, volume history
  • Liquidity at entry

sophisticated decision framework for determining when to sell:

  • Various conditions are evaluated through methods like evaluate_sell_conditions
  • Triggers include: take profit reached, stop loss hit, excessive retracement, low liquidity
  • Time-based rules like maximum hold time

The progressive_sell method implements selling tokens in chunks rather than all at once:

  • Divides the total amount into smaller chunks
  • Sells each chunk with a delay between sales
  • Uses dynamic slippage based on token value

The selling strategy can be adjusted based on market conditions:

  • Different approaches for bullish, bearish, volatile, or stable markets
  • Parameters like profit targets and trail percentages are dynamically adjusted

In copy_trading.rs, there's integration with the selling strategy:

  • A dedicated function setup_selling_strategy starts monitoring tokens
  • Periodically checks if selling conditions are met
  • Executes progressive sells when conditions are favorable

The system includes robust notification and error handling mechanisms:

  • Sends notifications through Telegram
  • Records metrics for future analysis
  • Verifies transactions have executed successfully

Key selling conditions include:

  • Take profit threshold reached
  • Stop loss triggered
  • Excessive retracement from highest price
  • Liquidity dropping too low
  • Maximum hold time exceeded
  • Trailing stop conditions triggered
  • The bot can use different strategies for different market conditions and dynamically adjust parameters based on real-time metrics.

Real implementation

//! # Enhanced Selling Strategy for Solana Trading Bot
//!
//! This module implements a sophisticated selling strategy for tokens on Solana DEXes,
//! specifically targeting PumpFun and PumpSwap protocols. The strategy combines multiple
//! approaches to determine optimal exit points:
//!
//! ## Core Components
//!
//! 1. **Price-Based Analysis**
//!    - Take profit targets
//!    - Stop loss protection
//!    - Trailing stops that activate after reaching profit thresholds
//!    - Technical indicators (EMA crossovers)
//!
//! 2. **Liquidity Monitoring**
//!    - Minimum absolute liquidity thresholds
//!    - Relative liquidity drop detection
//!    - Price impact assessment
//!
//! 3. **Volume Analysis**
//!    - Volume spike detection (potential exit signals)
//!    - Volume drop detection (liquidity drying up)
//!    - Moving averages of volume to identify trends
//!
//! 4. **Time-Based Exits**
//!    - Maximum hold time to prevent bag holding
//!    - Minimum hold time for profitable positions
//!
//! 5. **Manipulation Detection**
//!    - Wash trading detection
//!    - Creator selling detection
//!    - Large holder movement analysis
//!
//! 6. **Strategy Adaptation**
//!    - Market condition assessment (Bullish, Bearish, Volatile, Stable)
//!    - Dynamic adjustment of parameters based on market conditions
//!
//! 7. **Execution Management**
//!    - Progressive selling in chunks
//!    - Dynamic slippage calculation
//!    - Protocol selection between PumpFun and PumpSwap
//!
//! 8. **Backtesting Framework**
//!    - Historical trade analysis
//!    - Performance reporting
//!    - Strategy optimization
//!
//! The strategy maintains comprehensive metrics on each token position and
//! employs a multi-factor decision framework to determine when to exit positions.
use std::collections::{HashMap, HashSet, VecDeque};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use anyhow::{anyhow, Result};
use anchor_client::solana_sdk::{hash::Hash, instruction::Instruction, pubkey::Pubkey, signature::{Keypair, Signature}};
use colored::Colorize;
use tokio::time::sleep;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};

use crate::common::{
    config::{AppState, SwapConfig},
    logger::Logger,
};
use crate::dex::pump_fun::Pump;
use crate::dex::pump_swap::PumpSwap;
use crate::engine::swap::{SwapDirection, SwapInType, SwapProtocol};
use crate::engine::transaction_parser::{ParsedData, DexType, TradeInfoFromToken};

// Global state for token metrics
lazy_static! {
    static ref TOKEN_METRICS: Arc<Mutex<HashMap<String, TokenMetrics>>> = Arc::new(Mutex::new(HashMap::new()));
    static ref TOKEN_TRACKING: Arc<Mutex<HashMap<String, TokenTrackingInfo>>> = Arc::new(Mutex::new(HashMap::new()));
    static ref HISTORICAL_TRADES: Arc<Mutex<VecDeque<TradeExecutionRecord>>> = Arc::new(Mutex::new(VecDeque::with_capacity(100)));
}

/// Token metrics for selling strategy
#[derive(Clone, Debug)]
pub struct TokenMetrics {
    pub entry_price: f64,
    pub highest_price: f64,
    pub lowest_price: f64,
    pub current_price: f64,
    pub volume_24h: f64,
    pub market_cap: f64,
    pub time_held: u64,
    pub last_update: Instant,
    pub buy_timestamp: u64,
    pub amount_held: f64,
    pub cost_basis: f64,
    pub price_history: VecDeque<f64>,     // Rolling window of prices
    pub volume_history: VecDeque<f64>,    // Rolling window of volumes
    pub liquidity_at_entry: f64,
}

/// Token tracking info for progressive selling
pub struct TokenTrackingInfo {
    pub top_pnl: f64,
    pub last_sell_time: Instant,
    pub completed_intervals: HashSet<String>,
    pub sell_attempts: usize,
    pub sell_success: usize,
}

/// Record of executed trades for analytics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeExecutionRecord {
    pub mint: String,
    pub entry_price: f64,
    pub exit_price: f64,
    pub pnl: f64,
    pub reason: String,
    pub timestamp: u64,
    pub amount_sold: f64,
    pub protocol: String,
}

/// Market condition enum for dynamic strategy adjustment
#[derive(Debug, Clone)]
pub enum MarketCondition {
    Bullish,
    Bearish,
    Volatile,
    Stable,
}

/// Configuration for profit taking strategy
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfitTakingConfig {
    pub target_percentage: f64,           // 1.0 = 100%
    pub scale_out_percentages: Vec<f64>,  // [0.5, 0.3, 0.2] for 50%, 30%, 20%
}

impl Default for ProfitTakingConfig {
    fn default() -> Self {
        Self {
            target_percentage: 1.0,       // 100% profit target
            scale_out_percentages: vec![0.5, 0.3, 0.2], // 50%, 30%, 20%
        }
    }
}

/// Configuration for trailing stop strategy
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrailingStopConfig {
    pub activation_percentage: f64,   // 0.2 = 20% from peak
    pub trail_percentage: f64,        // 0.05 = 5% trailing
}

impl Default for TrailingStopConfig {
    fn default() -> Self {
        Self {
            activation_percentage: 0.2,   // 20% activation threshold
            trail_percentage: 0.05,       // 5% trail
        }
    }
}

/// Configuration for liquidity monitoring
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiquidityMonitorConfig {
    pub min_absolute_liquidity: f64,  // Minimum SOL liquidity to hold
    pub max_acceptable_drop: f64,     // 0.5 = 50% drop from entry
}

impl Default for LiquidityMonitorConfig {
    fn default() -> Self {
        Self {
            min_absolute_liquidity: 1.0,  // 1 SOL minimum liquidity
            max_acceptable_drop: 0.5,     // 50% drop from entry
        }
    }
}

/// Configuration for volume analysis
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumeAnalysisConfig {
    pub lookback_period: usize,       // Number of trades to look back
    pub spike_threshold: f64,         // 3.0 = 3x average volume
    pub drop_threshold: f64,          // 0.3 = 30% of average volume
}

impl Default for VolumeAnalysisConfig {
    fn default() -> Self {
        Self {
            lookback_period: 20,      // 20 trades lookback
            spike_threshold: 3.0,     // 3x average volume for spike
            drop_threshold: 0.3,      // 30% of average volume for drop
        }
    }
}

/// Configuration for time-based exits
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeExitConfig {
    pub max_hold_time_secs: u64,      // Maximum time to hold position
    pub min_profit_time_secs: u64,    // Minimum time to hold profitable trades
}

impl Default for TimeExitConfig {
    fn default() -> Self {
        Self {
            max_hold_time_secs: 3600,     // 1 hour max hold time
            min_profit_time_secs: 120,    // 2 minutes min hold for profitable trades
        }
    }
}

/// Configuration for selling strategy
#[derive(Clone, Debug)]
pub struct SellingConfig {
    pub take_profit: f64,       // Percentage (e.g., 2.0 for 2%)
    pub stop_loss: f64,         // Percentage (e.g., -5.0 for -5%)
    pub max_hold_time: u64,     // Seconds
    pub retracement_threshold: f64, // Percentage drop from highest price
    pub min_liquidity: f64,     // Minimum SOL in pool
    pub copy_trade_wallets: Vec<Pubkey>,
    pub progressive_sell_chunks: usize, // Number of chunks for progressive selling
    pub progressive_sell_interval: u64, // Seconds between sells
    
    // Enhanced selling strategy configurations
    pub profit_taking: ProfitTakingConfig,
    pub trailing_stop: TrailingStopConfig,
    pub liquidity_monitor: LiquidityMonitorConfig,
    pub volume_analysis: VolumeAnalysisConfig,
    pub time_based: TimeExitConfig,
}

impl Default for SellingConfig {
    fn default() -> Self {
        Self {
            take_profit: 2.0,                // 2% profit target
            stop_loss: -5.0,                 // 5% stop loss
            max_hold_time: 3600,             // 1 hour max hold time
            retracement_threshold: 5.0,      // 5% retracement threshold
            min_liquidity: 1.0,              // 1 SOL minimum liquidity
            copy_trade_wallets: vec![],      // No specific wallets to copy
            progressive_sell_chunks: 3,      // Sell in 3 chunks
            progressive_sell_interval: 120,  // 2 minutes between sells
            
            // Enhanced selling strategy configurations
            profit_taking: ProfitTakingConfig::default(),
            trailing_stop: TrailingStopConfig::default(),
            liquidity_monitor: LiquidityMonitorConfig::default(),
            volume_analysis: VolumeAnalysisConfig::default(),
            time_based: TimeExitConfig::default(),
        }
    }
}

/// Engine for executing selling strategies
#[derive(Clone)]
pub struct SellingEngine {
    app_state: Arc<AppState>,
    swap_config: Arc<SwapConfig>,
    config: SellingConfig,
    logger: Logger,
}

impl SellingEngine {
    pub fn new(
        app_state: Arc<AppState>,
        swap_config: Arc<SwapConfig>,
        config: SellingConfig,
    ) -> Self {
        Self {
            app_state,
            swap_config,
            config,
            logger: Logger::new("[SELLING-STRATEGY] => ".yellow().to_string()),
        }
    }

    /// Update metrics for a token based on parsed transaction data
    pub fn update_metrics(&self, token_mint: &str, parsed_data: &ParsedData) -> Result<()> {
        let logger = Logger::new("[SELLING-STRATEGY] => ".magenta().to_string());
        
        // Extract data
        let sol_change = parsed_data.sol_change;
        let token_change = parsed_data.token_change;
        let is_buy = parsed_data.is_buy;
        let timestamp = parsed_data.timestamp.unwrap_or_else(|| {
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs()
        });
        
        // Calculate price if possible
        let price = if token_change != 0.0 && sol_change != 0.0 {
            (sol_change / token_change).abs()
        } else {
            0.0
        };
        
        if price <= 0.0 {
            logger.log(format!("Invalid price calculated: {}", price));
            return Err(anyhow::anyhow!("Invalid price calculation"));
        }
        
        // Update token metrics
        let mut metrics_map = TOKEN_METRICS.lock().expect("Failed to lock token metrics");
        let metrics = metrics_map.entry(token_mint.to_string())
            .or_insert_with(|| TokenMetrics {
                entry_price: 0.0,
                highest_price: 0.0,
                lowest_price: 0.0, // Add the lowest_price field
                current_price: 0.0,
                volume_24h: 0.0,
                market_cap: 0.0,
                time_held: 0,
                last_update: Instant::now(),
                buy_timestamp: timestamp,
                amount_held: 0.0,
                cost_basis: 0.0,
                price_history: VecDeque::new(),
                volume_history: VecDeque::new(),
                liquidity_at_entry: 0.0,
            });
        
        // Update metrics based on transaction type
        if is_buy {
            // For buy transactions, update entry price if not set
            if metrics.entry_price == 0.0 {
                metrics.entry_price = price;
                metrics.buy_timestamp = timestamp;
                metrics.last_update = Instant::now();
                
                logger.log(format!("Initialized entry price for {}: {}", token_mint, price));
            }
            
            // Update amount held
            if token_change > 0.0 {
                metrics.amount_held += token_change;
                metrics.cost_basis += sol_change.abs();
                
                logger.log(format!("Updated token balance: {}, cost basis: {}", 
                          metrics.amount_held, metrics.cost_basis));
            }
        } else {
            // For sell transactions, update amount held
            if token_change < 0.0 {
                // Reduce amount held, but don't go below zero
                metrics.amount_held = (metrics.amount_held - token_change.abs()).max(0.0);
                
                // Reduce cost basis proportionally
                if metrics.amount_held > 0.0 && metrics.cost_basis > 0.0 {
                    let sell_percentage = token_change.abs() / (metrics.amount_held + token_change.abs());
                    metrics.cost_basis *= 1.0 - sell_percentage;
                } else {
                    metrics.cost_basis = 0.0;
                }
                
                logger.log(format!("Updated token balance after sell: {}, cost basis: {}", 
                          metrics.amount_held, metrics.cost_basis));
            }
        }
        
        // Always update current price
        metrics.current_price = price;
        
        // Update highest price if applicable
        if price > metrics.highest_price {
            metrics.highest_price = price;
        }
        
        // Update lowest price if applicable (initialize or update if lower)
        if metrics.lowest_price == 0.0 || price < metrics.lowest_price {
            metrics.lowest_price = price;
        }
        
        // Update price history
        metrics.price_history.push_back(price);
        if metrics.price_history.len() > 20 {  // Keep last 20 prices
            metrics.price_history.pop_front();
        }
        
        // Log current metrics
        let pnl = if metrics.entry_price > 0.0 {
            ((price - metrics.entry_price) / metrics.entry_price) * 100.0
        } else {
            0.0
        };
        
        logger.log(format!(
            "Token metrics for {}: Price: {}, Entry: {}, Highest: {}, Lowest: {}, PNL: {:.2}%",
            token_mint, price, metrics.entry_price, metrics.highest_price, metrics.lowest_price, pnl
        ));
        
        Ok(())
    }
    
    /// Record a buy transaction for a token with enhanced metrics tracking
    pub fn record_buy(&self, token_mint: &str, amount: f64, cost: f64) -> Result<()> {
        let logger = Logger::new("[SELLING-STRATEGY-BUY] => ".green().to_string());
        logger.log(format!("Recording buy: {} tokens for {} SOL", amount, cost));
        
        // Get the current timestamp
        let current_timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
            Ok(n) => n.as_secs(),
            Err(_) => 0,
        };
        
        // Get liquidity information asynchronously
        let liquidity = tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(async {
                let protocol = match self.determine_best_protocol_for_token(token_mint).await {
                    Ok(p) => p,
                    Err(_) => return 0.0,
                };
                
                match self.get_pool_liquidity(token_mint).await {
                    Ok(liq) => liq,
                    Err(_) => 0.0,
                }
            })
        });
        
        // Get or create metrics for this token
        let mut metrics_map = TOKEN_METRICS.lock().expect("Failed to lock token metrics");
        let metrics = metrics_map.entry(token_mint.to_string())
            .or_insert_with(|| TokenMetrics {
                entry_price: 0.0,
                highest_price: 0.0,
                lowest_price: 0.0,
                current_price: 0.0,
                volume_24h: 0.0,
                market_cap: 0.0,
                time_held: 0,
                last_update: Instant::now(),
                buy_timestamp: 0,
                amount_held: 0.0,
                cost_basis: 0.0,
                price_history: VecDeque::new(),
                volume_history: VecDeque::new(),
                liquidity_at_entry: liquidity,
            });
        
        // Calculate entry price
        if amount > 0.0 {
            let entry_price = cost / amount;
            
            // Update metrics
            metrics.entry_price = entry_price;
            metrics.current_price = entry_price;
            metrics.highest_price = entry_price; // Reset highest price on new buy
            metrics.lowest_price = entry_price;  // Reset lowest price on new buy
            metrics.buy_timestamp = current_timestamp;
            metrics.amount_held = amount;
            metrics.cost_basis = cost;
            metrics.time_held = 0; // Reset time held
            metrics.liquidity_at_entry = liquidity;
            
            // Initialize price history
            metrics.price_history.clear();
            metrics.price_history.push_back(entry_price);
            
            // Initialize volume history
            metrics.volume_history.clear();
            
            // Log the update
            logger.log(format!(
                "Recorded buy for {}: {} tokens at {} SOL each, total cost: {} SOL, liquidity: {} SOL",
                token_mint, amount, entry_price, cost, liquidity
            ));
            
            // Initialize token tracking
            let mut tracking = TOKEN_TRACKING.lock().expect("Failed to lock token tracking");
            tracking.entry(token_mint.to_string()).or_insert(TokenTrackingInfo {
                top_pnl: 0.0,
                last_sell_time: Instant::now(),
                completed_intervals: HashSet::new(),
                sell_attempts: 0,
                sell_success: 0,
            });
        } else {
            logger.log(format!("Invalid buy amount for {}: {}", token_mint, amount).red().to_string());
        }
        
        Ok(())
    }
    
    /// Evaluate whether we should sell a token based on various conditions
    /// 
    /// This method combines all selling conditions from the enhanced decision framework
    /// into a single evaluation, providing a comprehensive analysis of when to exit a position.
    pub async fn evaluate_sell_conditions(&self, token_mint: &str) -> Result<bool> {
        // Get metrics for the token
        let metrics = {
            let metrics_map = TOKEN_METRICS.lock().expect("Failed to lock metrics for evaluation");
            match metrics_map.get(token_mint) {
                Some(m) => m.clone(), // Clone the metrics to avoid holding the lock
                None => return Ok(false), // No metrics, so nothing to sell
            }
        };
        
        // Remaining code with metrics available outside the lock
        // Use conditions to determine if we should sell
        
        // Calculate time held
        let time_held = metrics.last_update.elapsed().as_secs();
        
        // Check if we've exceeded the max hold time
        if time_held > self.config.max_hold_time {
            self.logger.log(format!("Selling due to max hold time exceeded: {} > {}", 
                             time_held, self.config.max_hold_time).yellow().to_string());
            return Ok(true);
        }
        
        // Calculate current price vs highest price
        let current_price = match self.get_current_price(token_mint).await {
            Ok(price) => price,
            Err(_) => metrics.current_price, // Fall back to last known price
        };
        
        // Calculate percentage change from highest price
        let retracement = if metrics.highest_price > 0.0 {
            (metrics.highest_price - current_price) / metrics.highest_price * 100.0
        } else {
            0.0
        };
        
        // Calculate percentage gain from entry
        let gain = if metrics.entry_price > 0.0 {
            (current_price - metrics.entry_price) / metrics.entry_price * 100.0
        } else {
            0.0
        };
        
        // Log metrics
        self.logger.log(format!(
            "Token: {} - Current: {:.8}, Entry: {:.8}, Highest: {:.8}, Gain: {:.2}%, Retracement: {:.2}%",
            token_mint, current_price, metrics.entry_price, metrics.highest_price, gain, retracement
        ).blue().to_string());
        
        // Check if we've reached take profit
        if gain >= self.config.take_profit {
            self.logger.log(format!("Selling due to take profit reached: {:.2}% >= {:.2}%", 
                             gain, self.config.take_profit).green().to_string());
            return Ok(true);
        }
        
        // Check if we've hit stop loss
        if gain <= self.config.stop_loss {
            self.logger.log(format!("Selling due to stop loss triggered: {:.2}% <= {:.2}%", 
                             gain, self.config.stop_loss).red().to_string());
            return Ok(true);
        }
        
        // Check if price has retraced too much from highest
        if retracement >= self.config.retracement_threshold && gain > 0.0 {
            self.logger.log(format!(
                "Selling due to excessive retracement: {:.2}% >= {:.2}% (still in profit: {:.2}%)",
                retracement, self.config.retracement_threshold, gain
            ).yellow().to_string());
            return Ok(true);
        }
        
        // Check if liquidity is too low
        let liquidity = match self.get_pool_liquidity(token_mint).await {
            Ok(liq) => liq,
            Err(_) => 0.0, // Conservative approach: if we can't check liquidity, assume it's low
        };
        
        if liquidity < self.config.min_liquidity {
            self.logger.log(format!("Selling due to low liquidity: {:.2} < {:.2}", 
                            liquidity, self.config.min_liquidity).red().to_string());
            return Ok(true);
        }
        
        // Check if copy targets are selling
        match self.is_copy_target_selling(token_mint).await {
            Ok(true) => {
                self.logger.log("Selling because copy targets are selling".yellow().to_string());
                return Ok(true);
            },
            _ => {}, // Ignore errors or false result
        }
        
        // If we've reached here, no sell conditions met
        Ok(false)
    }
    
    /// Check if any copy targets are selling this token
    async fn is_copy_target_selling(&self, _token_mint: &str) -> Result<bool> {
        // This would check if any wallets we're copying are selling this token
        // For now, we'll just return false as a placeholder
        Ok(false)
    }
    
    /// Get the current pool liquidity for a token
    async fn get_pool_liquidity(&self, token_mint: &str) -> Result<f64> {
        // Try to get liquidity from PumpSwap first
        let pump_swap = PumpSwap::new(
            self.app_state.wallet.clone(),
            Some(self.app_state.rpc_client.clone()),
            Some(self.app_state.rpc_nonblocking_client.clone()),
        );
        
        match pump_swap.get_token_price(token_mint).await {
            Ok(_) => {
                // If we got a price, we can assume the pool exists
                // In a real implementation, you'd get the actual SOL reserves here
                return Ok(50.0); // Placeholder value
            },
            Err(_) => {
                // Try PumpFun instead
                let pump_fun = Pump::new(
                    self.app_state.rpc_nonblocking_client.clone(),
                    self.app_state.rpc_client.clone(),
                    self.app_state.wallet.clone(),
                );
                
                match pump_fun.get_token_price(token_mint).await {
                    Ok(_) => Ok(30.0), // Placeholder value
                    Err(e) => Err(anyhow!("Failed to get liquidity: {}", e)),
                }
            }
        }
    }
    
    /// Get the current price of a token
    async fn get_current_price(&self, token_mint: &str) -> Result<f64> {
        // Try to get price from PumpSwap first
        let pump_swap = PumpSwap::new(
            self.app_state.wallet.clone(),
            Some(self.app_state.rpc_client.clone()),
            Some(self.app_state.rpc_nonblocking_client.clone()),
        );
        
        match pump_swap.get_token_price(token_mint).await {
            Ok(price) => Ok(price),
            Err(_) => {
                // Try PumpFun instead
                let pump_fun = Pump::new(
                    self.app_state.rpc_nonblocking_client.clone(),
                    self.app_state.rpc_client.clone(),
                    self.app_state.wallet.clone(),
                );
                
                match pump_fun.get_token_price(token_mint).await {
                    Ok(price) => Ok(price),
                    Err(e) => Err(anyhow!("Failed to get price: {}", e)),
                }
            }
        }
    }
    
    /// Calculate dynamic slippage based on liquidity
    fn calculate_dynamic_slippage(&self, token_mint: &str, sell_amount: f64) -> Result<u64> {
        let metrics_map = TOKEN_METRICS.lock().unwrap();
        
        // Check if we have metrics for this token
        let metrics = match metrics_map.get(token_mint) {
            Some(m) => m,
            None => return Err(anyhow!("No metrics found for token {}", token_mint)),
        };
        
        // Base slippage on token amount and price
        let token_value = sell_amount * metrics.current_price;
        
        // More valuable tokens need higher slippage to ensure execution
        // Return basis points (100 = 1%)
        let slippage_bps = if token_value > 10.0 {
            300 // 3% for high value tokens (300 basis points)
        } else if token_value > 1.0 {
            200 // 2% for medium value tokens (200 basis points)
        } else {
            100 // 1% for low value tokens (100 basis points)
        };
        
        Ok(slippage_bps)
    }
    
    /// Calculate the optimal amount to sell based on metrics
    fn calculate_optimal_sell_amount(&self, token_mint: &str) -> Result<f64> {
        let metrics_map = TOKEN_METRICS.lock().unwrap();
        
        // Check if we have metrics for this token
        let metrics = match metrics_map.get(token_mint) {
            Some(m) => m,
            None => return Err(anyhow!("No metrics found for token {}", token_mint)),
        };
        
        // Calculate PNL
        let pnl_percentage = if metrics.entry_price > 0.0 {
            ((metrics.current_price - metrics.entry_price) / metrics.entry_price) * 100.0
        } else {
            0.0
        };
        
        // Calculate sell percentage based on PNL
        let sell_percentage = if pnl_percentage >= 200.0 {
            1.0 // Sell 100% if 200%+ profit
        } else if pnl_percentage >= 100.0 {
            0.8 // Sell 80% if 100%+ profit
        } else if pnl_percentage >= 50.0 {
            0.6 // Sell 60% if 50%+ profit
        } else if pnl_percentage >= 20.0 {
            0.5 // Sell 50% if 20%+ profit
        } else if pnl_percentage > 0.0 {
            0.4 // Sell 40% if any profit
        } else {
            0.9 // Sell 90% if at a loss
        };
        
        // Calculate amount to sell
        let amount_to_sell = metrics.amount_held * sell_percentage;
        
        Ok(amount_to_sell)
    }
    
    /// Execute a progressive sell strategy
    pub async fn progressive_sell(&self, token_mint: &str, parsed_data: &TradeInfoFromToken, protocol: SwapProtocol) -> Result<()> {
        self.logger.log(format!("Starting progressive sell for {}", token_mint).yellow().to_string());
        
        // Get optimal sell amount
        let total_amount = self.calculate_optimal_sell_amount(token_mint)?;
        
        // Calculate chunk size
        let chunk_size = total_amount / self.config.progressive_sell_chunks as f64;
        
        // Calculate dynamic slippage
        let slippage = self.calculate_dynamic_slippage(token_mint, chunk_size)?;
        
        self.logger.log(format!(
            "Progressive sell plan: total={:.2}, chunks={}, chunk_size={:.2}, slippage={:.2}%",
            total_amount, self.config.progressive_sell_chunks, chunk_size, (slippage as f64) / 100.0
        ));
        
        // Get recent blockhash for the trade info
        let recent_blockhash = match self.app_state.rpc_nonblocking_client.get_latest_blockhash().await {
            Ok(hash) => hash,
            Err(e) => return Err(anyhow!("Failed to get blockhash: {}", e)),
        };
        
        // Create a TradeInfoFromToken for the sell operation with real data from parsed_data
        let base_trade_info = TradeInfoFromToken {
            signature: "progressive_sell".to_string(),
            mint: token_mint.to_string(),
            dex_type: parsed_data.dex_type.clone(),
            is_buy: false,
            slot: parsed_data.slot,
            recent_blockhash,
            target: parsed_data.target.clone(),
            user: parsed_data.user.clone(),
            timestamp: match SystemTime::now().duration_since(UNIX_EPOCH) {
                Ok(n) => n.as_secs(),
                Err(_) => 0,
            },
            token_amount_f64: total_amount,
            
            // Copy real values from parsed_data
            sol_amount: parsed_data.sol_amount,
            token_amount: parsed_data.token_amount,
            virtual_sol_reserves: parsed_data.virtual_sol_reserves,
            virtual_token_reserves: parsed_data.virtual_token_reserves,
            real_sol_reserves: parsed_data.real_sol_reserves,
            real_token_reserves: parsed_data.real_token_reserves,
            bonding_curve: parsed_data.bonding_curve.clone(),
            volume_change: parsed_data.volume_change,
            bonding_curve_info: parsed_data.bonding_curve_info.clone(),
            pool_info: parsed_data.pool_info.clone(),
            amount: parsed_data.amount,
            max_sol_cost: parsed_data.max_sol_cost,
            min_sol_output: parsed_data.min_sol_output,
            pool: parsed_data.pool.clone(),
            base_amount_in: parsed_data.base_amount_in,
            min_quote_amount_out: parsed_data.min_quote_amount_out,
            user_base_token_reserves: parsed_data.user_base_token_reserves,
            user_quote_token_reserves: parsed_data.user_quote_token_reserves,
            pool_base_token_reserves: parsed_data.pool_base_token_reserves,
            pool_quote_token_reserves: parsed_data.pool_quote_token_reserves,
            quote_amount_out: parsed_data.quote_amount_out,
            lp_fee_basis_points: parsed_data.lp_fee_basis_points,
            lp_fee: parsed_data.lp_fee,
            protocol_fee_basis_points: parsed_data.protocol_fee_basis_points,
            protocol_fee: parsed_data.protocol_fee,
            quote_amount_out_without_lp_fee: parsed_data.quote_amount_out_without_lp_fee,
            user_quote_amount_out: parsed_data.user_quote_amount_out,
            user_base_token_account: parsed_data.user_base_token_account.clone(),
            user_quote_token_account: parsed_data.user_quote_token_account.clone(),
            protocol_fee_recipient: parsed_data.protocol_fee_recipient.clone(),
            protocol_fee_recipient_token_account: parsed_data.protocol_fee_recipient_token_account.clone(),
            coin_creator: parsed_data.coin_creator.clone(),
            coin_creator_fee_basis_points: parsed_data.coin_creator_fee_basis_points,
            coin_creator_fee: parsed_data.coin_creator_fee,
            base_amount_out: parsed_data.base_amount_out,
            max_quote_amount_in: parsed_data.max_quote_amount_in,
        };
        
        // Call execute_sell from copy_trading with progressive sell parameters
        match crate::engine::copy_trading::execute_sell(
            token_mint.to_string(),
            base_trade_info,
            self.app_state.clone(),
            self.swap_config.clone(),
            protocol.clone(),
            true, // Use progressive sell
            Some(self.config.progressive_sell_chunks),
            Some(self.config.progressive_sell_interval * 1000), // Convert seconds to milliseconds
        ).await {
            Ok(_) => {
                self.logger.log(format!("Progressive sell completed for {}", token_mint).green().to_string());
                Ok(())
            },
            Err(e) => {
                self.logger.log(format!("Progressive sell failed: {}", e).red().to_string());
                Err(anyhow!("Progressive sell failed: {}", e))
            }
        }
    }

    /// Update metrics after a sell operation
    fn update_metrics_after_sell(&self, token_mint: &str, amount_sold: f64) -> Result<()> {
        let mut metrics_map = TOKEN_METRICS.lock().unwrap();
        
        // Check if we have metrics for this token
        let metrics = match metrics_map.get_mut(token_mint) {
            Some(m) => m,
            None => return Err(anyhow!("No metrics found for token {}", token_mint)),
        };
        
        // Update amount held
        metrics.amount_held -= amount_sold;
        
        // If we sold everything, remove the metrics
        if metrics.amount_held <= 0.0 {
            metrics_map.remove(token_mint);
            self.logger.log(format!("Removed metrics for token {} after selling all", token_mint));
        } else {
            self.logger.log(format!("Updated metrics for token {}: amount_held={:.2}", token_mint, metrics.amount_held));
        }
        
        Ok(())
    }
    
    /// Send a transaction with priority fees
    async fn send_priority_transaction(
        &self,
        blockhash: Hash,
        keypair: &Keypair,
        instructions: Vec<Instruction>,
    ) -> Result<Signature> {
        self.logger.log("Sending transaction with priority fees".yellow().to_string());
        
        // Execute the transaction with priority
        let signatures = crate::core::tx::new_signed_and_send_nozomi(
            blockhash,
            keypair,
            instructions,
            &self.logger,
        ).await.map_err(|e| anyhow!("Transaction error: {}", e))?;
        
        if signatures.is_empty() {
            return Err(anyhow!("No transaction signature returned"));
        }
        
        let signature = Signature::from_str(&signatures[0])?;
        
        // Verify the transaction was successful
        self.logger.log("Verifying transaction...".yellow().to_string());
        
        // Wait for confirmation
        let mut retries = 5;
        while retries > 0 {
            match self.app_state.rpc_nonblocking_client.get_signature_status(&signature).await {
                Ok(Some(result)) => {
                    if result.is_ok() {
                        self.logger.log("Transaction confirmed".green().to_string());
                        return Ok(signature);
                    } else {
                        return Err(anyhow!("Transaction failed: {:?}", result));
                    }
                },
                Ok(None) => {
                    self.logger.log("Transaction not yet confirmed, waiting...".yellow().to_string());
                    sleep(Duration::from_millis(500)).await;
                    retries -= 1;
                },
                Err(e) => {
                    self.logger.log(format!("Error checking transaction status: {}", e).red().to_string());
                    sleep(Duration::from_millis(500)).await;
                    retries -= 1;
                }
            }
        }
        
        Err(anyhow!("Transaction verification timed out"))
    }
    
    /// Calculate the market cap of a token
    async fn calculate_market_cap(&self, token_mint: &str) -> Result<f64> {
        // Get token supply
        let token_supply = match self.app_state.rpc_nonblocking_client
            .get_token_supply(&Pubkey::from_str(token_mint)?)
            .await
        {
            Ok(supply) => supply.ui_amount.unwrap_or(0.0),
            Err(e) => return Err(anyhow!("Failed to get token supply: {}", e)),
        };
        
        // Get current price
        let price = self.get_current_price(token_mint).await?;
        
        // Calculate market cap
        let market_cap = token_supply * price;
        
        Ok(market_cap)
    }

    /// Calculate current price from trade info
    pub fn calculate_current_price(&self, trade_info: &TradeInfoFromToken) -> Option<f64> {
        match trade_info.dex_type {
            DexType::PumpFun => {
                // For Pump.fun: price = SOL amount / token amount
                trade_info.sol_amount.and_then(|sol| {
                    trade_info.token_amount.map(|tok| {
                        sol as f64 / tok as f64
                    })
                })
            },
            DexType::PumpSwap => {
                // For PumpSwap: price = quote reserves / base reserves
                trade_info.pool_quote_token_reserves.and_then(|quote| {
                    trade_info.pool_base_token_reserves.map(|base| {
                        quote as f64 / base as f64
                    })
                })
            },
            _ => None
        }
    }

    /// Calculate price impact for a trade
    pub fn calculate_price_impact(&self, trade_info: &TradeInfoFromToken) -> Option<f64> {
        match trade_info.dex_type {
            DexType::PumpFun => {
                // Price impact = (new virtual price - old virtual price) / old virtual price
                let old_price = trade_info.virtual_sol_reserves? as f64 / 
                               trade_info.virtual_token_reserves? as f64;
                let new_sol = (trade_info.virtual_sol_reserves? as i64 + 
                              trade_info.sol_amount? as i64) as u64;
                let new_tok = (trade_info.virtual_token_reserves? as i64 - 
                              trade_info.token_amount? as i64) as u64;
                
                if new_tok == 0 { return None; } // Avoid division by zero
                
                let new_price = new_sol as f64 / new_tok as f64;
                Some((new_price - old_price) / old_price)
            },
            DexType::PumpSwap => {
                // Similar calculation using pool reserves
                trade_info.pool_quote_token_reserves.and_then(|quote| {
                    trade_info.pool_base_token_reserves.and_then(|base| {
                        if base == 0 { return None; } // Avoid division by zero
                        
                        let old_price = quote as f64 / base as f64;
                        
                        // Calculate new reserves based on transaction type
                        let (new_quote, new_base) = if trade_info.is_buy {
                            // Buy: quote decreases, base increases
                            let quote_out = trade_info.quote_amount_out.unwrap_or(0);
                            let base_in = trade_info.base_amount_in.unwrap_or(0);
                            
                            ((quote as i64 - quote_out as i64) as u64, 
                             (base as i64 + base_in as i64) as u64)
                        } else {
                            // Sell: quote increases, base decreases
                            let quote_in = trade_info.quote_amount_out.unwrap_or(0); // Misleading name
                            let base_out = trade_info.base_amount_in.unwrap_or(0);   // Misleading name
                            
                            ((quote as i64 + quote_in as i64) as u64, 
                             (base as i64 - base_out as i64) as u64)
                        };
                        
                        if new_base == 0 { return None; } // Avoid division by zero
                        
                        let new_price = new_quote as f64 / new_base as f64;
                        Some((new_price - old_price) / old_price)
                    })
                })
            },
            _ => None
        }
    }
    
    /// Check liquidity conditions for selling
    pub fn check_liquidity_conditions(&self, trade_info: &TradeInfoFromToken) -> Option<String> {
        let current_liquidity = self.calculate_liquidity(trade_info)?;
        
        // Get or create metrics for this token
        let metrics_lock = TOKEN_METRICS.lock().expect("Failed to lock token metrics for liquidity check");
        let metrics = metrics_lock.get(&trade_info.mint)?;
        let entry_liquidity = metrics.liquidity_at_entry;
        
        // Only check if we have valid entry liquidity
        if entry_liquidity <= 0.0 {
            return None;
        }
        
        // Absolute liquidity threshold
        if current_liquidity < self.config.liquidity_monitor.min_absolute_liquidity {
            return Some(format!("Liquidity below absolute minimum: {} SOL", current_liquidity));
        }
        
        // Relative liquidity drop
        let liquidity_drop = (entry_liquidity - current_liquidity) / entry_liquidity;
        if liquidity_drop > self.config.liquidity_monitor.max_acceptable_drop {
            return Some(format!("Liquidity dropped {:.2}%", liquidity_drop * 100.0));
        }
        
        None
    }
    
    /// Calculate liquidity from trade info
    fn calculate_liquidity(&self, trade_info: &TradeInfoFromToken) -> Option<f64> {
        match trade_info.dex_type {
            DexType::PumpFun => {
                // For Pump.fun: liquidity = virtual SOL reserves in SOL units
                trade_info.virtual_sol_reserves.map(|v| v as f64 / 1_000_000_000.0)
            },
            DexType::PumpSwap => {
                // For PumpSwap: liquidity = min(base_reserves * price, quote_reserves) in SOL units
                let price = self.calculate_current_price(trade_info)?;
                let base_liquidity = trade_info.pool_base_token_reserves? as f64 * price;
                let quote_liquidity = trade_info.pool_quote_token_reserves? as f64 / 1_000_000_000.0;
                Some(base_liquidity.min(quote_liquidity))
            },
            _ => None
        }
    }
    
    /// Check volume conditions for selling
    pub fn check_volume_conditions(&self, trade_info: &TradeInfoFromToken) -> Option<String> {
        let current_volume = self.calculate_trade_volume(trade_info)?;
        
        // Get metrics for this token
        let mut metrics_lock = TOKEN_METRICS.lock().expect("Failed to lock token metrics for volume check");
        let metrics = metrics_lock.get_mut(&trade_info.mint)?;
        
        // Update volume history
        metrics.volume_history.push_back(current_volume);
        if metrics.volume_history.len() > self.config.volume_analysis.lookback_period {
            metrics.volume_history.pop_front();
        }
        
        // If we don't have enough data yet, return None
        if metrics.volume_history.len() < 5 {
            return None;
        }
        
        // Calculate volume moving average
        let volume_ma: f64 = metrics.volume_history.iter().sum::<f64>() / 
                           metrics.volume_history.len() as f64;
        
        // Check volume spike
        if current_volume > volume_ma * self.config.volume_analysis.spike_threshold {
            return Some(format!(
                "Volume spike detected: current {:.2} SOL > {:.2}x average {:.2} SOL", 
                current_volume, 
                self.config.volume_analysis.spike_threshold,
                volume_ma
            ));
        }
        
        // Check volume drop
        if current_volume < volume_ma * self.config.volume_analysis.drop_threshold {
            return Some(format!(
                "Volume drop detected: current {:.2} SOL < {:.2}x average {:.2} SOL",
                current_volume,
                self.config.volume_analysis.drop_threshold,
                volume_ma
            ));
        }
        
        None
    }
    
    /// Calculate trade volume from trade info
    fn calculate_trade_volume(&self, trade_info: &TradeInfoFromToken) -> Option<f64> {
        match trade_info.dex_type {
            DexType::PumpFun => {
                // Volume in SOL terms
                trade_info.sol_amount.map(|v| v as f64 / 1_000_000_000.0)
            },
            DexType::PumpSwap => {
                // Volume in quote token (SOL) terms
                trade_info.quote_amount_out.map(|v| v as f64 / 1_000_000_000.0)
            },
            _ => None
        }
    }
    
    /// Check price conditions for selling (take profit, trailing stop, EMA crossover)
    pub fn check_price_conditions(&self, trade_info: &TradeInfoFromToken) -> Option<String> {
        let current_price = self.calculate_current_price(trade_info)?;
        
        // Get metrics for this token
        let mut metrics_lock = TOKEN_METRICS.lock().expect("Failed to lock token metrics for price check");
        let metrics = metrics_lock.get_mut(&trade_info.mint)?;
        
        // Update price history
        metrics.price_history.push_back(current_price);
        if metrics.price_history.len() > 20 { // 20-period EMA
            metrics.price_history.pop_front();
        }
        
        // Update highest price
        if current_price > metrics.highest_price {
            metrics.highest_price = current_price;
        }
        
        // Update lowest price
        if metrics.lowest_price == 0.0 || current_price < metrics.lowest_price {
            metrics.lowest_price = current_price;
        }
        
        // 1. Profit taking
        let pnl = (current_price - metrics.entry_price) / metrics.entry_price;
        if pnl >= self.config.profit_taking.target_percentage {
            return Some(format!("Take profit at {:.2}%", pnl * 100.0));
        }
        
        // 2. Trailing stop
        let drawdown_from_high = (metrics.highest_price - current_price) / metrics.highest_price;
        if pnl > 0.0 && drawdown_from_high >= self.config.trailing_stop.activation_percentage {
            return Some(format!(
                "Trailing stop triggered ({:.2}% from high)", 
                drawdown_from_high * 100.0
            ));
        }
        
        // 3. EMA crossover (only if we have enough data)
        if metrics.price_history.len() >= 20 && self.check_ema_crossover(&metrics.price_history) {
            return Some("EMA crossover detected (9 EMA crossed below 20 EMA)".to_string());
        }
        
        None
    }
    
    /// Check time-based conditions for selling
    pub fn check_time_conditions(&self, trade_info: &TradeInfoFromToken) -> Option<String> {
        // Get current timestamp
        let current_timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
            Ok(n) => n.as_secs(),
            Err(_) => return None,
        };
        
        // Get metrics for this token
        let metrics_lock = TOKEN_METRICS.lock().expect("Failed to lock token metrics for time check");
        let metrics = metrics_lock.get(&trade_info.mint)?;
        
        // Calculate time held
        let time_held_seconds = if metrics.buy_timestamp > 0 {
            current_timestamp.saturating_sub(metrics.buy_timestamp)
        } else {
            metrics.time_held
        };
        
        // Check max hold time
        if time_held_seconds >= self.config.time_based.max_hold_time_secs {
            return Some(format!("Max hold time exceeded: {}s >= {}s", 
                time_held_seconds, self.config.time_based.max_hold_time_secs));
        }
        
        // Calculate PNL
        let pnl = (metrics.current_price - metrics.entry_price) / metrics.entry_price;
        
        // For profitable trades, check minimum hold time
        if pnl > 0.0 && time_held_seconds < self.config.time_based.min_profit_time_secs {
            return None; // Don't sell profitable trades too early
        }
        
        None
    }
    
    /// Check if this might be wash trading (self-trading, circular trades)
    pub fn check_wash_trading(&self, trade_info: &TradeInfoFromToken) -> Option<String> {
        // Check if same address is both pool creator and trader
        if let Some(pool) = &trade_info.pool {
            if trade_info.user == *pool {
                return Some("Possible wash trading (user == pool)".to_string());
            }
        }
        
        // Check if price action looks manipulated
        // This is a simplified approach - in reality you'd need more sophisticated analysis
        if let (Some(current_price), Some(virtual_sol), Some(virtual_token)) = (
            self.calculate_current_price(trade_info),
            trade_info.virtual_sol_reserves,
            trade_info.virtual_token_reserves
        ) {
            let expected_price = virtual_sol as f64 / virtual_token as f64;
            let price_diff = (current_price - expected_price).abs() / expected_price;
            
            if price_diff > 0.1 { // 10% difference
                return Some(format!("Possible price manipulation: {:.2}% difference", price_diff * 100.0));
            }
        }
        
        None
    }
    
    /// Check large holder actions
    pub fn check_large_holder_actions(&self, trade_info: &TradeInfoFromToken) -> Option<String> {
        // Check if creator is selling
        if let Some(creator) = &trade_info.coin_creator {
            if trade_info.user == *creator && !trade_info.is_buy {
                return Some("Token creator selling".to_string());
            }
        }
        
        // Check for large wallet movements
        let trade_size = self.calculate_trade_volume(trade_info)?;
        let liquidity = self.calculate_liquidity(trade_info)?;
        
        if trade_size > liquidity * 0.1 { // 10% of liquidity
            return Some(format!("Large trade size detected: {:.2} SOL ({:.2}% of liquidity)",
                trade_size, (trade_size / liquidity) * 100.0));
        }
        
        None
    }
    
    /// Calculate EMA for price history
    fn calculate_ema(&self, prices: &VecDeque<f64>, period: usize) -> Option<f64> {
        if prices.len() < period {
            return None;
        }
        
        let multiplier = 2.0 / (period as f64 + 1.0);
        let mut ema = prices[0];
        
        for price in prices.iter().skip(1) {
            ema = (price - ema) * multiplier + ema;
        }
        
        Some(ema)
    }
    
    /// Check if EMA crossover happened (9-period crosses below 20-period)
    fn check_ema_crossover(&self, prices: &VecDeque<f64>) -> bool {
        if prices.len() < 20 {
            return false;
        }
        
        // We need at least 2 data points to check for a crossover
        if prices.len() < 2 {
            return false;
        }
        
        // Calculate EMAs
        let ema9 = match self.calculate_ema(prices, 9) {
            Some(ema) => ema,
            None => return false,
        };
        
        let ema20 = match self.calculate_ema(prices, 20) {
            Some(ema) => ema,
            None => return false,
        };
        
        // Check for bearish crossover (9 EMA below 20 EMA)
        ema9 < ema20
    }
    
    /// Adjust strategy based on market conditions
    pub fn adjust_strategy_based_on_market(&mut self, market_condition: MarketCondition) {
        self.logger.log(format!("Adjusting strategy for market condition: {:?}", market_condition));
        
        match market_condition {
            MarketCondition::Bullish => {
                // Be more aggressive in taking profits
                self.config.profit_taking.target_percentage *= 1.2;
                self.config.trailing_stop.activation_percentage *= 1.2;
                self.logger.log("Adjusted for bullish market: increased profit targets".green().to_string());
            },
            MarketCondition::Bearish => {
                // Take profits earlier
                self.config.profit_taking.target_percentage *= 0.8;
                self.config.trailing_stop.activation_percentage *= 0.8;
                self.logger.log("Adjusted for bearish market: reduced profit targets".yellow().to_string());
            },
            MarketCondition::Volatile => {
                // Use tighter stops
                self.config.trailing_stop.trail_percentage *= 0.5;
                self.logger.log("Adjusted for volatile market: tightened stops".yellow().to_string());
            },
            MarketCondition::Stable => {
                // Let winners run longer
                self.config.profit_taking.target_percentage *= 1.5;
                self.logger.log("Adjusted for stable market: letting winners run longer".green().to_string());
            }
        }
    }
    
    /// Record trade execution for analytics
    pub async fn record_trade_execution(
        &self, 
        mint: &str, 
        reason: &str, 
        amount_sold: f64, 
        protocol: &str
    ) -> Result<()> {
        // Get current timestamp
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| anyhow!("Failed to get timestamp: {}", e))?
            .as_secs();
        
        // Get metrics for entry price
        let entry_price = {
            let metrics_map = TOKEN_METRICS.lock()
                .map_err(|e| anyhow!("Failed to lock token metrics: {}", e))?;
            
            metrics_map.get(mint)
                .map(|m| m.entry_price)
                .unwrap_or(0.0)
        };
        
        // Get current price
        let exit_price = match self.get_current_price(mint).await {
            Ok(price) => price,
            Err(_) => 0.0,
        };
        
        // Calculate PNL
        let pnl = if entry_price > 0.0 {
            ((exit_price - entry_price) / entry_price) * 100.0
        } else {
            0.0
        };
        
        // Create record
        let record = TradeExecutionRecord {
            mint: mint.to_string(),
            entry_price,
            exit_price,
            pnl,
            reason: reason.to_string(),
            timestamp,
            amount_sold,
            protocol: protocol.to_string(),
        };
        
        // Log record
        self.logger.log(format!(
            "Trade execution recorded: {} sold at {:.8} SOL (PNL: {:.2}%)",
            mint, exit_price, pnl
        ).green().to_string());
        
        // Add to history
        {
            let mut history = HISTORICAL_TRADES.lock()
                .expect("Failed to lock trade history for recording");
            
            history.push_back(record);
            
            // Keep history to a reasonable size
            if history.len() > 100 {
                history.pop_front();
            }
        }
        
        Ok(())
    }
    
    /// Monitor and sell based on trade info
    pub async fn monitor_and_sell(&self, trade_info: &TradeInfoFromToken) -> Result<bool> {
        self.logger.log(format!("Monitoring token: {}", trade_info.mint));
        
        // Update metrics with current price
        if let Some(price) = self.calculate_current_price(trade_info) {
            // Use expect instead of ? operator to avoid thread safety issues
            let mut metrics_map = TOKEN_METRICS.lock().expect("Failed to lock token metrics");
            if let Some(metrics) = metrics_map.get_mut(&trade_info.mint) {
                metrics.current_price = price;
                
                // Update highest price if current price is higher
                if price > metrics.highest_price {
                    metrics.highest_price = price;
                }
                
                // Update time held
                if let Some(timestamp) = Some(trade_info.timestamp) {
                    if metrics.buy_timestamp > 0 {
                        metrics.time_held = timestamp.saturating_sub(metrics.buy_timestamp);
                    }
                }
            }
        }
        
        // Check all selling conditions
        let sell_reasons: Vec<String> = vec![
            self.check_liquidity_conditions(trade_info),
            self.check_volume_conditions(trade_info),
            self.check_price_conditions(trade_info),
            self.check_time_conditions(trade_info),
            self.check_wash_trading(trade_info),
            self.check_large_holder_actions(trade_info),
        ]
        .into_iter()
        .flatten()
        .collect();
        
        // If any conditions are met, execute sell
        if !sell_reasons.is_empty() {
            let reason = sell_reasons.join(", ");
            self.logger.log(format!("Sell conditions met: {}", reason).green().to_string());
            
            // Determine protocol based on token
            let protocol = match self.determine_best_protocol_for_token(&trade_info.mint).await {
                Ok(p) => p,
                Err(e) => {
                    self.logger.log(format!("Failed to determine protocol: {}", e).red().to_string());
                    return Err(anyhow!("Failed to determine protocol: {}", e));
                }
            };
            
            // Execute progressive sell with the trade_info
            match self.progressive_sell(&trade_info.mint, trade_info, protocol).await {
                Ok(_) => {
                    self.logger.log(format!("Successfully sold token: {}", trade_info.mint).green().to_string());
                    
                    // Record the trade
                    if let Err(e) = self.record_trade_execution(
                        &trade_info.mint, 
                        &reason, 
                        0.0, // Amount will be filled in by execute_sell
                        &format!("{:?}", trade_info.dex_type)
                    ).await {
                        self.logger.log(format!("Failed to record trade: {}", e).red().to_string());
                    }
                    
                    return Ok(true);
                },
                Err(e) => {
                    self.logger.log(format!("Failed to sell token: {} - {}", trade_info.mint, e).red().to_string());
                    return Err(anyhow!("Failed to sell token: {}", e));
                }
            }
        }
        
        Ok(false)
    }
 
    
    /// Determine best protocol for selling a token
    async fn determine_best_protocol_for_token(&self, token_mint: &str) -> Result<SwapProtocol> {
        // Try PumpSwap first
        let pump_swap = PumpSwap::new(
            self.app_state.wallet.clone(),
            Some(self.app_state.rpc_client.clone()),
            Some(self.app_state.rpc_nonblocking_client.clone()),
        );
        
        match pump_swap.get_token_price(token_mint).await {
            Ok(_) => {
                self.logger.log(format!("Found token on PumpSwap: {}", token_mint).green().to_string());
                return Ok(SwapProtocol::PumpSwap);
            },
            Err(_) => {
                // Try PumpFun next
                let pump_fun = Pump::new(
                    self.app_state.rpc_nonblocking_client.clone(),
                    self.app_state.rpc_client.clone(),
                    self.app_state.wallet.clone(),
                );
                
                match pump_fun.get_token_price(token_mint).await {
                    Ok(_) => {
                        self.logger.log(format!("Found token on PumpFun: {}", token_mint).green().to_string());
                        return Ok(SwapProtocol::PumpFun);
                    },
                    Err(e) => {
                        return Err(anyhow!("Token not found on any supported DEX: {}", e));
                    }
                }
            }
        }
    }

    /// Convert TokenMetrics to a TradeInfoFromToken for analysis
    pub fn metrics_to_trade_info(&self, token_mint: &str, protocol: SwapProtocol) -> Option<TradeInfoFromToken> {
        let metrics_lock = TOKEN_METRICS.lock().expect("Failed to lock token metrics for conversion");
        let metrics = metrics_lock.get(token_mint)?;
        
        // Create timestamp
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .ok()?
            .as_secs();
        
        // The amount of tokens we own
        let token_amount = metrics.amount_held;
        
        // Create a reasonable default blockhash
        let recent_blockhash = anchor_client::solana_sdk::hash::Hash::default();
        
        // Create a DexType based on protocol
        let dex_type = match protocol {
            SwapProtocol::PumpSwap => DexType::PumpSwap,
            SwapProtocol::PumpFun => DexType::PumpFun,
            _ => DexType::Unknown,
        };
        
        // Create TradeInfoFromToken
        Some(TradeInfoFromToken {
            dex_type,
            mint: token_mint.to_string(),
            token_amount_f64: token_amount,
            timestamp,
            is_buy: false, // We're analyzing for sell
            
            // Standard required fields
            slot: 0,
            recent_blockhash,
            signature: "metrics_conversion".to_string(),
            target: "".to_string(),
            user: "".to_string(),
            
            // Empty optional fields
            sol_amount: None,
            token_amount: None,
            min_quote_amount_out: None,
            user_base_token_reserves: None,
            user_quote_token_reserves: None,
            pool_base_token_reserves: None,
            pool_quote_token_reserves: None,
            quote_amount_out: None,
            lp_fee_basis_points: None,
            lp_fee: None,
            protocol_fee_basis_points: None,
            protocol_fee: None,
            quote_amount_out_without_lp_fee: None,
            user_quote_amount_out: None,
            pool: None,
            user_base_token_account: None,
            user_quote_token_account: None,
            protocol_fee_recipient: None,
            protocol_fee_recipient_token_account: None,
            coin_creator: None,
            coin_creator_fee_basis_points: None,
            coin_creator_fee: None,
            virtual_sol_reserves: None,
            virtual_token_reserves: None,
            real_sol_reserves: None,
            real_token_reserves: None,
            bonding_curve: "".to_string(),
            volume_change: 0,
            bonding_curve_info: None,
            pool_info: None,
            amount: None,
            max_sol_cost: None,
            min_sol_output: None,
            base_amount_in: None,
            base_amount_out: None,
            max_quote_amount_in: None,
        })
    }

    /// Run a backtest of the selling strategy on historical data
    pub async fn backtest_strategy(
        &self,
        historical_data: &[TradeInfoFromToken],
        selling_config: Option<SellingConfig>,
    ) -> Vec<TradeExecutionRecord> {
        let logger = Logger::new("[SELLING-STRATEGY-BACKTEST] => ".cyan().to_string());
        logger.log("Starting backtest of selling strategy...".to_string());
        
        // Create a new engine with the provided config or default
        let config = selling_config.unwrap_or_else(|| self.config.clone());
        let backtest_engine = SellingEngine {
            app_state: self.app_state.clone(),
            swap_config: self.swap_config.clone(),
            config,
            logger: Logger::new("[BACKTEST] => ".cyan().to_string()),
        };
        
        // Clear global state for testing
        {
            let mut metrics = TOKEN_METRICS.lock().expect("Failed to lock token metrics");
            metrics.clear();
            
            let mut tracking = TOKEN_TRACKING.lock().expect("Failed to lock token tracking");
            tracking.clear();
            
            let mut history = HISTORICAL_TRADES.lock().expect("Failed to lock trade history");
            history.clear();
        }
        
        // Process all historical trades
        let mut tokens_bought: HashSet<String> = HashSet::new();
        
        for trade in historical_data {
            logger.log(format!("Processing trade: {} at timestamp {}", trade.mint, trade.timestamp));
            
            if trade.is_buy {
                // This is a buy - record it
                if !tokens_bought.contains(&trade.mint) {
                    // Calculate cost and amount
                    let token_amount = match trade.token_amount {
                        Some(amount) => amount as f64,
                        None => trade.token_amount_f64,
                    };
                    
                    let sol_amount = match trade.sol_amount {
                        Some(amount) => (amount as f64) / 1_000_000_000.0, // Convert from lamports
                        None => 0.0,
                    };
                    
                    if token_amount > 0.0 && sol_amount > 0.0 {
                        logger.log(format!("Recording buy: {} tokens for {} SOL", token_amount, sol_amount));
                        if let Err(e) = backtest_engine.record_buy(&trade.mint, token_amount, sol_amount) {
                            logger.log(format!("Error recording buy: {}", e).red().to_string());
                            continue; // Skip this trade if recording fails
                        }
                        tokens_bought.insert(trade.mint.clone());
                    }
                }
            } else {
                // This is a sell - evaluate selling condition
                if tokens_bought.contains(&trade.mint) {
                    match backtest_engine.monitor_and_sell(trade).await {
                        Ok(sold) => {
                            if sold {
                                logger.log(format!("Sold token: {}", trade.mint));
                                tokens_bought.remove(&trade.mint);
                            }
                        },
                        Err(e) => {
                            logger.log(format!("Error in backtest: {}", e).red().to_string());
                        }
                    }
                }
            }
        }
        
        // Get results
        let results: Vec<TradeExecutionRecord> = {
            let history: std::sync::MutexGuard<'_, VecDeque<TradeExecutionRecord>> = HISTORICAL_TRADES.lock().expect("Failed to lock trade history");
            history.clone().into_iter().collect()
        };
        
        // Log summary
        let mut total_profit = 0.0;
        let mut win_count = 0;
        let mut loss_count = 0;
        
        for record in &results {
            total_profit += record.pnl;
        
            // Count winning vs losing trades
            if record.pnl > 0.0 {
                win_count += 1;
            } else {
                loss_count += 1;
            }
        }
        
        logger.log(format!(
            "Backtest complete: {} trades, {} wins, {} losses, {:.2}% total return",
            results.len(),
            win_count,
            loss_count,
            total_profit * 100.0
        ));
        
        results
    }

    /// Generate backtest report from trade records
    pub fn generate_backtest_report(&self, records: &[TradeExecutionRecord]) -> String {
        let logger = Logger::new("[SELLING-STRATEGY-REPORT] => ".cyan().to_string());
        
        if records.is_empty() {
            return "No trades recorded in backtest.".to_string();
        }
        
        // Calculate statistics
        let mut total_pnl = 0.0;
        let mut winning_trades = 0;
        let mut losing_trades = 0;
        let mut avg_win = 0.0;
        let mut avg_loss = 0.0;
        let mut max_win = 0.0;
        let mut max_loss = 0.0;
        let mut hold_times = Vec::new();
        let mut reason_counts = HashMap::new();
        
        let mut prev_timestamp = records[0].timestamp;
        
        for record in records {
            total_pnl += record.pnl;
            
            // Count winning vs losing trades
            if record.pnl > 0.0 {
                winning_trades += 1;
                avg_win += record.pnl;
                if record.pnl > max_win {
                    max_win = record.pnl;
                }
            } else {
                losing_trades += 1;
                avg_loss += record.pnl;
                if record.pnl < max_loss {
                    max_loss = record.pnl;
                }
            }
            
            // Calculate hold time
            if record.timestamp > prev_timestamp {
                hold_times.push(record.timestamp - prev_timestamp);
            }
            prev_timestamp = record.timestamp;
            
            // Count reasons
            let reason = record.reason.clone();
            *reason_counts.entry(reason).or_insert(0) += 1;
        }
        
        // Calculate averages
        avg_win = if winning_trades > 0 { avg_win / winning_trades as f64 } else { 0.0 };
        avg_loss = if losing_trades > 0 { avg_loss / losing_trades as f64 } else { 0.0 };
        
        // Calculate average hold time
        let avg_hold_time = if !hold_times.is_empty() {
            hold_times.iter().sum::<u64>() as f64 / hold_times.len() as f64
        } else {
            0.0
        };
        
        // Build report
        let mut report = String::new();
        report.push_str(&format!("Backtest Report\n"));
        report.push_str(&format!("==============\n\n"));
        report.push_str(&format!("Total Trades: {}\n", records.len()));
        report.push_str(&format!("Winning Trades: {} ({:.1}%)\n", 
            winning_trades, (winning_trades as f64 / records.len() as f64) * 100.0));
        report.push_str(&format!("Losing Trades: {} ({:.1}%)\n", 
            losing_trades, (losing_trades as f64 / records.len() as f64) * 100.0));
        report.push_str(&format!("Total PnL: {:.2}%\n", total_pnl * 100.0));
        report.push_str(&format!("Average Win: {:.2}%\n", avg_win * 100.0));
        report.push_str(&format!("Average Loss: {:.2}%\n", avg_loss * 100.0));
        report.push_str(&format!("Max Win: {:.2}%\n", max_win * 100.0));
        report.push_str(&format!("Max Loss: {:.2}%\n", max_loss * 100.0));
        report.push_str(&format!("Average Hold Time: {:.2} seconds\n", avg_hold_time));
        
        // Report on exit reasons
        report.push_str(&format!("\nExit Reasons:\n"));
        for (reason, count) in reason_counts.iter() {
            report.push_str(&format!("  {}: {} ({:.1}%)\n", 
                reason, 
                count, 
                (*count as f64 / records.len() as f64) * 100.0
            ));
        }
        
        // Log statistics
        logger.log(format!("Generated backtest report: {} trades, {:.2}% total return", 
            records.len(), total_pnl * 100.0));
        
        report
    }

    /// Analyze recent trades to determine market condition for dynamic strategy adjustment
    pub async fn analyze_market_condition(&self, recent_trades: &[TradeInfoFromToken]) -> MarketCondition {
        if recent_trades.is_empty() {
            return MarketCondition::Stable; // Default to stable if no data
        }
        
        // Extract prices from trades
        let mut prices: Vec<f64> = Vec::with_capacity(recent_trades.len());
        let mut volumes: Vec<f64> = Vec::with_capacity(recent_trades.len());
        let mut timestamps: Vec<u64> = Vec::with_capacity(recent_trades.len());
        
        for trade in recent_trades {
            // Calculate price from trade info
            if let Some(price) = self.calculate_current_price(trade) {
                prices.push(price);
            }
            
            // Extract volume
            if let Some(volume) = self.calculate_trade_volume(trade) {
                volumes.push(volume);
            }
            
            // Extract timestamp
            timestamps.push(trade.timestamp);
        }
        
        // Sort by timestamp to ensure chronological order
        let mut price_time_pairs: Vec<(u64, f64)> = timestamps.iter()
            .zip(prices.iter())
            .map(|(t, p)| (*t, *p))
            .collect();
        price_time_pairs.sort_by_key(|(t, _)| *t);
        
        // Re-extract sorted prices
        let sorted_prices: Vec<f64> = price_time_pairs.iter()
            .map(|(_, p)| *p)
            .collect();
        
        // Calculate time periods between price points
        // Convert to u64 during the mapping process
        let _time_periods: Vec<u64> = if price_time_pairs.len() >= 2 {
            price_time_pairs.windows(2)
                .map(|w| w[1].0.saturating_sub(w[0].0)) // Using timestamp differences
                .collect()
        } else {
            vec![0] // Default if not enough data
        };
        
        // Price volatility (std deviation / mean)
        let price_volatility = if !sorted_prices.is_empty() {
            let mean_price = sorted_prices.iter().sum::<f64>() / sorted_prices.len() as f64;
            let variance = sorted_prices.iter()
                .map(|p| (p - mean_price).powi(2))
                .sum::<f64>() / sorted_prices.len() as f64;
            (variance.sqrt() / mean_price).abs()
        } else {
            0.0
        };
        
        // Volume volatility
        let volume_volatility = if !volumes.is_empty() {
            let mean_volume = volumes.iter().sum::<f64>() / volumes.len() as f64;
            let variance = volumes.iter()
                .map(|v| (v - mean_volume).powi(2))
                .sum::<f64>() / volumes.len() as f64;
            (variance.sqrt() / mean_volume).abs()
        } else {
            0.0
        };
        
        // Price trend (positive = up, negative = down)
        let price_trend = if sorted_prices.len() >= 2 {
            (sorted_prices[sorted_prices.len() - 1] - sorted_prices[0]) / sorted_prices[0]
        } else {
            0.0
        };
        
        // Log analysis results
        self.logger.log(format!(
            "Market analysis: Volatility: {:.2}%, Trend: {:.2}%, Volume vol: {:.2}%",
            price_volatility * 100.0, price_trend * 100.0, volume_volatility * 100.0
        ).blue().to_string());
        
        // Determine market condition based on analysis
        if price_volatility > 0.15 {
            // High volatility market
            if price_trend > 0.05 {
                self.logger.log("Market condition: Bullish with high volatility".green().to_string());
                MarketCondition::Bullish
            } else if price_trend < -0.05 {
                self.logger.log("Market condition: Bearish with high volatility".red().to_string());
                MarketCondition::Bearish
            } else {
                self.logger.log("Market condition: Volatile with no clear trend".yellow().to_string());
                MarketCondition::Volatile
            }
        } else {
            // Low volatility market
            if price_trend > 0.05 {
                self.logger.log("Market condition: Stable uptrend".green().to_string());
                MarketCondition::Bullish
            } else if price_trend < -0.05 {
                self.logger.log("Market condition: Stable downtrend".red().to_string());
                MarketCondition::Bearish
            } else {
                self.logger.log("Market condition: Stable sideways".blue().to_string());
                MarketCondition::Stable
            }
        }
    }

    /// Execute progressive sell for token
    pub async fn execute_progressive_sell(&self, token_mint: &str) -> Result<()> {
        // Determine protocol based on token
        let protocol = self.determine_best_protocol_for_token(token_mint).await?;
        
        // Create a minimal TradeInfoFromToken for the sell operation
        let recent_blockhash = match self.app_state.rpc_nonblocking_client.get_latest_blockhash().await {
            Ok(hash) => hash,
            Err(e) => return Err(anyhow!("Failed to get blockhash: {}", e)),
        };
        
        let minimal_trade_info = TradeInfoFromToken {
            signature: "execute_progressive_sell".to_string(),
            mint: token_mint.to_string(),
            dex_type: match protocol {
                SwapProtocol::PumpFun => DexType::PumpFun,
                SwapProtocol::PumpSwap => DexType::PumpSwap,
                _ => DexType::Unknown,
            },
            is_buy: false,
            slot: 0,
            recent_blockhash,
            target: String::new(),
            user: String::new(),
            timestamp: match SystemTime::now().duration_since(UNIX_EPOCH) {
                Ok(n) => n.as_secs(),
                Err(_) => 0,
            },
            token_amount_f64: 0.0, // Will be calculated in progressive_sell
            sol_amount: None,
            token_amount: None,
            virtual_sol_reserves: None,
            virtual_token_reserves: None,
            real_sol_reserves: None,
            real_token_reserves: None,
            bonding_curve: String::new(),
            volume_change: 0,
            bonding_curve_info: None,
            pool_info: None,
            amount: None,
            max_sol_cost: None,
            min_sol_output: None,
            pool: None,
            base_amount_in: None,
            min_quote_amount_out: None,
            user_base_token_reserves: None,
            user_quote_token_reserves: None,
            pool_base_token_reserves: None,
            pool_quote_token_reserves: None,
            quote_amount_out: None,
            lp_fee_basis_points: None,
            lp_fee: None,
            protocol_fee_basis_points: None,
            protocol_fee: None,
            quote_amount_out_without_lp_fee: None,
            user_quote_amount_out: None,
            user_base_token_account: None,
            user_quote_token_account: None,
            protocol_fee_recipient: None,
            protocol_fee_recipient_token_account: None,
            coin_creator: None,
            coin_creator_fee_basis_points: None,
            coin_creator_fee: None,
            base_amount_out: None,
            max_quote_amount_in: None,
        };
        
        // Execute progressive sell
        self.progressive_sell(token_mint, &minimal_trade_info, protocol).await
    }
} 

⚠️ **GitHub.com Fallback** ⚠️