#!/usr/bin/env python3
"""
St. Jude Charity Trading Bot for Solana
A high-performance trading bot that donates 100% of profits to St. Jude Children's Research Hospital.

This bot uses GPU acceleration, machine learning, and sophisticated trading strategies
to monitor and trade on the Solana blockchain using Raydium swaps.
"""

import sys
sys.path.append('C:\\solana_rust_bot')
import threading
import queue
import json
import base64
import struct
import time
import os
import signal
import random
import numpy as np
import torch
import websockets
import traceback
import asyncio
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Any, Union
from datetime import datetime, timedelta
from collections import deque
from concurrent.futures import ThreadPoolExecutor

# Try to set console to UTF-8 mode - silently handle errors
try:
    if os.name == 'nt':  # Only on Windows
        os.system('chcp 65001 >NUL 2>&1')  # Redirect output to suppress errors
except Exception:
    pass  # Ignore if command fails

# Import our custom modules
from solana_logger import logger
from config_loader import config

# Import Rust trading core
import solana_rust_bot
from solana_rust_bot import TradingCore, DynamicConfig, TradeParams

# Configure logging to prevent duplication
logger.logger.handlers = logger.logger.handlers[:1]  # Keep only the first handler

# Try to import optional libraries
try:
    import psutil  # For memory monitoring
    HAS_PSUTIL = True
except ImportError:
    HAS_PSUTIL = False

try:
    from rich.console import Console
    from rich.table import Table
    HAS_RICH = True
    console = Console()
except ImportError:
    HAS_RICH = False

# Create a global thread pool executor for synchronous operations
thread_pool = ThreadPoolExecutor(max_workers=4)

# Message queue for communication between threads
message_queue = queue.Queue()

# Global event loop for async operations in sync context
EVENT_LOOP = None

# Balance cache to prevent excessive RPC calls
BALANCE_CACHE = {
    "sol": 0.0,
    "wsol": 0.0,
    "last_update": 0,
    "update_interval": 60,  # Update every 60 seconds (configurable)
    "pending_transaction": False
}

# RPC rate limiter
class RPCRateLimiter:
    def __init__(self, max_calls=5, period=1.0):
        self.max_calls = max_calls
        self.period = period
        self.calls = []
        self.lock = threading.Lock()
        
    def acquire(self):
        with self.lock:
            now = time.time()
            # Remove calls older than the period
            self.calls = [t for t in self.calls if now - t < self.period]
            # If we've reached the limit, wait
            if len(self.calls) >= self.max_calls:
                wait_time = self.period - (now - self.calls[0])
                if wait_time > 0:
                    time.sleep(wait_time)
                    now = time.time()  # Update now after waiting
                # Clean up again after waiting
                self.calls = [t for t in self.calls if now - t < self.period]
            # Add this call
            self.calls.append(now)

# Global rate limiter
RPC_RATE_LIMITER = RPCRateLimiter(max_calls=4, period=1.0)  # Use 4 to be safe


class TradePerformanceTracker:
    """Track and analyze trade performance metrics"""
    
    def __init__(self, max_history=1000):
        self.trades = deque(maxlen=max_history)
        self.hourly_stats = {hour: {"count": 0, "profit": 0.0, "win_rate": 0.0} for hour in range(24)}
        self.size_brackets = {
            "small": {"range": (0, 10), "count": 0, "profit": 0.0, "win_rate": 0.0},
            "medium": {"range": (10, 50), "count": 0, "profit": 0.0, "win_rate": 0.0},
            "large": {"range": (50, 150), "count": 0, "profit": 0.0, "win_rate": 0.0},
            "xlarge": {"range": (150, float('inf')), "count": 0, "profit": 0.0, "win_rate": 0.0}
        }
        self.consecutive_wins = 0
        self.consecutive_losses = 0
        self.best_streak = 0
        self.worst_streak = 0
        self.best_trade = {"profit": 0.0, "details": None}
        self.worst_trade = {"profit": 0.0, "details": None}
    
    def add_trade(self, trade_info, profit, execution_time_ms):
        """Add a completed trade to the tracker"""
        timestamp = trade_info.get("timestamp", time.time())
        dt = datetime.fromtimestamp(timestamp)
        hour = dt.hour
        amount = trade_info["amount"]
        
        # Create trade record
        trade = {
            "timestamp": timestamp,
            "datetime": dt,
            "hour": hour,
            "amount": amount,
            "profit": profit,
            "is_win": profit > 0,
            "execution_time_ms": execution_time_ms
        }
        
        # Update best/worst trade
        if profit > self.best_trade["profit"]:
            self.best_trade = {"profit": profit, "details": trade}
        if profit < self.worst_trade["profit"]:
            self.worst_trade = {"profit": profit, "details": trade}
        
        # Update streak information
        if profit > 0:
            self.consecutive_wins += 1
            self.consecutive_losses = 0
            if self.consecutive_wins > self.best_streak:
                self.best_streak = self.consecutive_wins
        elif profit < 0:
            self.consecutive_losses += 1
            self.consecutive_wins = 0
            if self.consecutive_losses > self.worst_streak:
                self.worst_streak = self.consecutive_losses
        
        # Add to history
        self.trades.append(trade)
        
        # Update hourly stats
        self.hourly_stats[hour]["count"] += 1
        self.hourly_stats[hour]["profit"] += profit
        win_count = sum(1 for t in self.trades if t["hour"] == hour and t["is_win"])
        if self.hourly_stats[hour]["count"] > 0:
            self.hourly_stats[hour]["win_rate"] = win_count / self.hourly_stats[hour]["count"]
        
        # Update size bracket stats
        for name, bracket in self.size_brackets.items():
            min_val, max_val = bracket["range"]
            if min_val <= amount < max_val:
                bracket["count"] += 1
                bracket["profit"] += profit
                win_count = sum(1 for t in self.trades 
                                if bracket["range"][0] <= t["amount"] < bracket["range"][1] and t["is_win"])
                if bracket["count"] > 0:
                    bracket["win_rate"] = win_count / bracket["count"]
                break
    
    def get_best_hours(self, top_n=3):
        """Get the best performing hours based on profit"""
        sorted_hours = sorted(
            [(hour, stats["profit"]) for hour, stats in self.hourly_stats.items() if stats["count"] > 0],
            key=lambda x: x[1],
            reverse=True
        )
        return sorted_hours[:top_n]
    
    def get_best_size_bracket(self):
        """Get the best performing size bracket based on win rate"""
        sorted_brackets = sorted(
            [(name, bracket["win_rate"]) for name, bracket in self.size_brackets.items() if bracket["count"] > 0],
            key=lambda x: x[1],
            reverse=True
        )
        return sorted_brackets[0] if sorted_brackets else None
    
    def get_summary(self):
        """Get a summary of trade performance metrics"""
        total_trades = len(self.trades)
        if total_trades == 0:
            return "No trades recorded yet."
        
        total_profit = sum(trade["profit"] for trade in self.trades)
        win_count = sum(1 for trade in self.trades if trade["is_win"])
        win_rate = win_count / total_trades if total_trades > 0 else 0
        avg_execution_time = sum(trade["execution_time_ms"] for trade in self.trades) / total_trades
        
        best_hours = self.get_best_hours(3)
        best_bracket = self.get_best_size_bracket()
        
        return f"""
📊 St. Jude Charity Trade Performance Summary:
=========================
Total Trades: {total_trades}
Total Charity Contribution: {total_profit:.6f} SOL
Win Rate: {win_rate:.2%}
Avg Execution Time: {avg_execution_time:.2f} ms

Best Trading Hours:
{', '.join([f"{hour}:00-{hour+1}:00 ({profit:.6f} SOL)" for hour, profit in best_hours]) if best_hours else "Not enough data"}

Best Size Bracket:
{f"{best_bracket[0]} trades ({best_bracket[1]:.2%} win rate)" if best_bracket else "Not enough data"}

Streaks:
Current Win Streak: {self.consecutive_wins}
Best Win Streak: {self.best_streak}
Current Loss Streak: {self.consecutive_losses}
Worst Loss Streak: {self.worst_streak}

Best Trade: {self.best_trade["profit"]:.6f} SOL
Worst Trade: {self.worst_trade["profit"]:.6f} SOL
"""


class ErrorTracker:
    """Track and analyze errors for better diagnostics"""
    
    def __init__(self, max_errors=100):
        self.errors = deque(maxlen=max_errors)
        self.error_counts = {}
        self.error_first_seen = {}
        self.error_last_seen = {}
        self.resolved_errors = set()
    
    def track_error(self, error_type, message, details=None):
        """Track an error with context"""
        timestamp = time.time()
        error_id = f"{error_type}:{message}"
        
        error_record = {
            "id": error_id,
            "timestamp": timestamp,
            "datetime": datetime.fromtimestamp(timestamp),
            "type": error_type,
            "message": message,
            "details": details,
            "traceback": traceback.format_exc()
        }
        
        self.errors.append(error_record)
        
        # Update error tracking
        if error_type not in self.error_counts:
            self.error_counts[error_type] = 1
            self.error_first_seen[error_type] = timestamp
        else:
            self.error_counts[error_type] += 1
        
        self.error_last_seen[error_type] = timestamp
        
        # Remove from resolved if it happens again
        if error_id in self.resolved_errors:
            self.resolved_errors.remove(error_id)
        
        return error_record
    
    def mark_resolved(self, error_id):
        """Mark an error as resolved"""
        self.resolved_errors.add(error_id)
    
    def get_most_common_errors(self, top_n=3):
        """Get the most commonly occurring errors"""
        sorted_errors = sorted(
            self.error_counts.items(),
            key=lambda x: x[1],
            reverse=True
        )
        return sorted_errors[:top_n]
    
    def get_recent_errors(self, limit=5):
        """Get the most recent errors"""
        return list(self.errors)[-limit:]
    
    def get_active_errors(self):
        """Get errors that are still occurring (not resolved)"""
        active_errors = {}
        for error_type, count in self.error_counts.items():
            # Check if any error of this type is unresolved
            type_has_unresolved = any(
                err["id"] not in self.resolved_errors 
                for err in self.errors 
                if err["type"] == error_type
            )
            
            if type_has_unresolved:
                active_errors[error_type] = {
                    "count": count,
                    "first_seen": datetime.fromtimestamp(self.error_first_seen[error_type]),
                    "last_seen": datetime.fromtimestamp(self.error_last_seen[error_type])
                }
                
        return active_errors
    
    def get_summary(self):
        """Get a summary of error statistics"""
        total_errors = len(self.errors)
        if total_errors == 0:
            return "No errors recorded."
        
        most_common = self.get_most_common_errors(3)
        recent_errors = self.get_recent_errors(3)
        active_errors = self.get_active_errors()
        
        return f"""
⚠️ Error Summary:
=============
Total Errors: {total_errors}
Active Error Types: {len(active_errors)}

Most Common Errors:
{chr(10).join([f"- {error_type}: {count} occurrences" for error_type, count in most_common]) if most_common else "None recorded"}

Recent Errors:
{chr(10).join([f"- {error['datetime'].strftime('%Y-%m-%d %H:%M:%S')} - {error['type']}: {error['message']}" for error in recent_errors]) if recent_errors else "None recorded"}
"""


class MarketVolumeAnalyzer:
    """Analyze trading volume and opportunities"""
    
    def __init__(self, window_size=300):  # 5-minute window
        self.window_size = window_size
        self.volume_window = deque(maxlen=window_size)
        self.opportunity_window = deque(maxlen=window_size)
        self.last_update = time.time()
        self.significant_moves = deque(maxlen=100)  # Track significant market moves
        self.volume_by_hour = {hour: 0.0 for hour in range(24)}
        self.last_hour = datetime.now().hour
    
    def update(self, trade_amount=None, is_opportunity=False):
        """Update the analyzer with new data"""
        current_time = time.time()
        current_hour = datetime.now().hour
        
        # Check for hour change to reset hourly stats
        if current_hour != self.last_hour:
            self.volume_by_hour[current_hour] = 0.0
            self.last_hour = current_hour
        
        # Fill in missing seconds with zeros
        seconds_since_update = int(current_time - self.last_update)
        if seconds_since_update > 1:
            for _ in range(min(seconds_since_update - 1, self.window_size)):
                self.volume_window.append(0.0)
                self.opportunity_window.append(False)
        
        # Add current data
        amount = trade_amount if trade_amount else 0.0
        self.volume_window.append(amount)
        self.opportunity_window.append(is_opportunity)
        
        # Update hourly volume
        self.volume_by_hour[current_hour] += amount
        
        # Check for significant moves
        if amount > 100:  # Threshold for significant trades
            self.significant_moves.append({
                "timestamp": current_time,
                "datetime": datetime.fromtimestamp(current_time),
                "amount": amount,
                "is_opportunity": is_opportunity
            })
        
        self.last_update = current_time
    
    def get_total_volume(self):
        """Get total volume in the current window"""
        return sum(self.volume_window)
    
    def get_opportunity_count(self):
        """Get number of opportunities in the current window"""
        return sum(1 for o in self.opportunity_window if o)
    
    def get_opportunity_ratio(self):
        """Get ratio of opportunities to total observations"""
        if not self.opportunity_window:
            return 0
        return self.get_opportunity_count() / len(self.opportunity_window)
    
    def get_volume_trend(self, segments=5):
        """Get the trend of volume over the window, divided into segments"""
        if not self.volume_window or len(self.volume_window) < segments * 2:
            return []
        
        segment_size = len(self.volume_window) // segments
        trend = []
        
        for i in range(segments):
            start = i * segment_size
            end = (i + 1) * segment_size
            segment_volume = sum(list(self.volume_window)[start:end])
            trend.append(segment_volume)
        
        return trend
    
    def get_active_hours(self, top_n=3):
        """Get the most active hours by volume"""
        sorted_hours = sorted(
            [(hour, volume) for hour, volume in self.volume_by_hour.items() if volume > 0],
            key=lambda x: x[1],
            reverse=True
        )
        return sorted_hours[:top_n]
    
    def get_summary(self):
        """Get a summary of volume and opportunity metrics"""
        if not self.volume_window:
            return "No volume data recorded."
        
        total_volume = self.get_total_volume()
        opportunity_count = self.get_opportunity_count()
        opportunity_ratio = self.get_opportunity_ratio()
        volume_trend = self.get_volume_trend()
        active_hours = self.get_active_hours(3)
        
        trend_direction = "increasing" if volume_trend and volume_trend[-1] > volume_trend[0] else "decreasing"
        
        return f"""
📈 Market Activity (Last {len(self.volume_window)} seconds):
===========================================
Total Volume: {total_volume:.2f} SOL
Opportunity Count: {opportunity_count}
Opportunity Ratio: {opportunity_ratio:.2%}
Volume Trend: {trend_direction}

Most Active Hours:
{', '.join([f"{hour}:00-{hour+1}:00 ({volume:.2f} SOL)" for hour, volume in active_hours]) if active_hours else "Not enough data"}

Recent Significant Moves:
{len(self.significant_moves)} large trades in recent history
"""


class RiskManager:
    """Advanced risk management for trading operations"""
    
    def __init__(self):
        self.daily_profit = 0.0
        self.daily_loss = 0.0
        self.max_daily_profit = config.get("MAX_DAILY_PROFIT", float('inf'))
        self.max_daily_loss = config.get("MAX_DAILY_LOSS", -1.0)  # Default -1 SOL
        self.max_position_size = config.get("MAX_POSITION_SIZE", 1.0)  # Default 1 SOL
        self.min_balance_threshold = config.get("MIN_BALANCE_THRESHOLD", 0.1)  # Default 0.1 SOL
        self.current_day = datetime.now().day
        self.position_sizing_factor = 1.0  # Dynamic adjustment factor
        self.consecutive_losses = 0
        self.cooldown_until = 0  # Timestamp for cooldown period
    
    def update_daily_stats(self, profit):
        """Update daily profit/loss tracking"""
        # Reset on new day
        current_day = datetime.now().day
        if current_day != self.current_day:
            self.daily_profit = 0.0
            self.daily_loss = 0.0
            self.current_day = current_day
            self.position_sizing_factor = 1.0  # Reset factor on new day
        
        # Update counters
        if profit > 0:
            self.daily_profit += profit
            self.consecutive_losses = 0
        elif profit < 0:
            self.daily_loss += abs(profit)
            self.consecutive_losses += 1
            
            # Reduce position size after consecutive losses
            if self.consecutive_losses >= 2:
                self.position_sizing_factor = max(0.5, self.position_sizing_factor * 0.8)
    
    def check_daily_limits(self) -> Tuple[bool, str]:
        """Check if daily profit/loss limits have been reached"""
        if self.daily_profit >= self.max_daily_profit:
            return False, f"Daily profit target reached: {self.daily_profit:.6f} SOL"
        
        if self.daily_loss >= abs(self.max_daily_loss):
            return False, f"Daily loss limit reached: {self.daily_loss:.6f} SOL"
        
        return True, "Within limits"
    
    def calculate_position_size(self, base_size, trade_info=None) -> float:
        """Calculate optimal position size based on risk parameters"""
        # Start with base size multiplied by position sizing factor
        size = base_size * self.position_sizing_factor
        
        # Apply maximum position size limit
        size = min(size, self.max_position_size)
        
        # If in cooldown due to losses, reduce size further
        if time.time() < self.cooldown_until:
            cooldown_factor = 0.5
            size *= cooldown_factor
            
        # Round to 5 decimal places
        return round(size, 5)
    
    def check_balance(self, current_balance) -> Tuple[bool, str]:
        """Check if balance is sufficient for trading"""
        if current_balance < self.min_balance_threshold:
            return False, f"Balance too low: {current_balance:.6f} SOL < {self.min_balance_threshold:.6f} SOL"
        
        return True, "Sufficient balance"
    
    def apply_cooldown(self, duration_minutes=30):
        """Apply a trading cooldown period after severe losses"""
        self.cooldown_until = time.time() + (duration_minutes * 60)
    
    def get_status(self) -> Dict[str, Any]:
        """Get current risk management status"""
        return {
            "daily_profit": self.daily_profit,
            "daily_loss": self.daily_loss,
            "position_sizing_factor": self.position_sizing_factor,
            "consecutive_losses": self.consecutive_losses,
            "in_cooldown": time.time() < self.cooldown_until,
            "cooldown_remaining": max(0, self.cooldown_until - time.time())
        }


# Raydium instruction builder for correct data format
class RaydiumInstructionBuilder:
    """
    Builder for Raydium swap instruction data with proper formatting
    """
    
    @staticmethod
    def build_swap_instruction(amount_in: float, min_amount_out: float, instruction_type: int = 9) -> bytes:
        """
        Build swap instruction data with proper binary format
        
        Args:
            amount_in: Amount to swap in (in SOL)
            min_amount_out: Minimum amount to receive (in SOL)
            instruction_type: Raydium instruction type (9=swap base in, 11=swap base out)
            
        Returns:
            Properly formatted instruction data
        """
        # Convert to lamports (integer)
        amount_in_lamports = int(amount_in * 1_000_000_000) if amount_in > 0 else 0
        min_amount_out_lamports = int(min_amount_out * 1_000_000_000) if min_amount_out > 0 else 0
        
        # Pack instruction data using struct
        # Format: instruction_type(u8) + amount_in(u64) + min_amount_out(u64)
        instruction_data = struct.pack('<BQQ', 
                                      instruction_type, 
                                      amount_in_lamports, 
                                      min_amount_out_lamports)
        
        logger.debug(f"Instruction data: type={instruction_type}, " 
                    f"amount_in={amount_in_lamports} lamports, "
                    f"min_out={min_amount_out_lamports} lamports")
        
        return instruction_data
    
    @staticmethod
    def calculate_min_amount_out(amount_in: float, slippage_bps: int = 100) -> float:
        """
        Calculate minimum amount out based on slippage
        
        Args:
            amount_in: Amount to swap in
            slippage_bps: Slippage in basis points (100 = 1%)
            
        Returns:
            Minimum amount out
        """
        slippage_factor = slippage_bps / 10000.0
        return amount_in * (1.0 - slippage_factor)


def run_async_in_thread(coro, timeout=10):
    """
    Helper function to run an async function in the global event loop with backoff retry.
    This is needed to call async Rust functions from synchronous code.
    """
    global EVENT_LOOP
    if EVENT_LOOP is None or EVENT_LOOP.is_closed():
        # Create a new event loop if none exists or if it's closed
        EVENT_LOOP = asyncio.new_event_loop()
    
    max_retries = 3
    retry_delay = 1.0
    
    # Apply rate limiting
    RPC_RATE_LIMITER.acquire()
    
    for attempt in range(max_retries):
        try:
            # Create a future to get the result
            future = asyncio.run_coroutine_threadsafe(coro, EVENT_LOOP)
            
            # Wait for the result with timeout and return it
            return future.result(timeout=timeout)
        except Exception as e:
            if "429 Too Many Requests" in str(e):
                # Rate limiting - use exponential backoff
                wait_time = retry_delay * (2 ** attempt)
                logger.debug(f"Rate limited. Waiting {wait_time:.1f}s before retry.")
                time.sleep(wait_time)
                continue
            elif attempt < max_retries - 1:
                # Other error, but we still have retries left
                logger.debug(f"Error in async call: {e}. Retrying...")
                time.sleep(retry_delay)
                continue
            else:
                # Out of retries
                raise


class CharityTradingEngine:
    """
    St. Jude Charity Trading Engine for Solana
    
    Key features:
    - GPU-accelerated trading for maximum performance
    - Thread-based architecture to avoid asyncio event loop issues
    - Uses PyTorch for ML-based decision making (if available)
    - Uses Rust for high-performance Solana interactions
    - Monitors transactions via WebSocket
    - Optimized for production use with minimal RPC calls
    - Features sophisticated risk management and analytics
    - 100% of profits donated to St. Jude Children's Research Hospital
    """
    
    def __init__(self):
        """Initialize the trading engine"""
        logger.info("Initializing St. Jude Charity Trading Engine")
        
        # Initialize advanced analytics and tracking
        self.trade_tracker = TradePerformanceTracker()
        self.error_tracker = ErrorTracker()
        self.volume_analyzer = MarketVolumeAnalyzer()
        self.risk_manager = RiskManager()
        
        # Initialize ML signals dictionary for fallback
        self.ml_signals = {
            "market_volatility": 0.35,
            "execution_confidence": 0.85,
            "price_direction": 0.2,
            "trade_profitability": 0.75,
            "risk_score": 0.3,
        }
        
        # Initialize state variables
        self.is_running = False
        self.last_trade_time = 0
        self.opportunities_found = 0
        self.trades_executed = 0
        self.successful_trades = 0
        self.total_profit = 0.0
        self.start_time = time.time()
        
        # Performance tracking
        self.last_memory_check = time.time()
        self.heartbeat_time = time.time()
        
        # Trading statistics
        self.stats = {
            "opportunities": 0,
            "filtered_too_small": 0,
            "filtered_too_large": 0,
            "filtered_by_ml": 0,
            "filtered_by_time": 0,
            "viable": 0,
            "attempted": 0,
            "completed": 0,
            "profit": 0.0,
            "execution_times": [],
            "balance_history": [],
            "start_balance": 0.0,
            "current_balance": 0.0,
        }
        
        # Thread management
        self.threads = []
        self.event_loop_thread = None
        
        # Initialize CUDA for ML model
        self.setup_cuda()
        
        # Start event loop thread first
        self.start_event_loop_thread()
        
        # Initialize the Rust trading core
        self.setup_trading_core()
        
        # Initialize WSOL if needed
        self.initialize_wsol()
        
        # Set up handling for graceful shutdown
        signal.signal(signal.SIGINT, self.handle_shutdown)
        signal.signal(signal.SIGTERM, self.handle_shutdown)
        
        # Load ML model
        self.model = self.load_ml_model()
        
        # Log initialization success
        logger.info("Charitable Trading Engine initialized successfully!")
    
    def start_event_loop_thread(self):
        """Start a thread that runs the event loop for async operations"""
        def run_event_loop():
            """Function to run in thread"""
            global EVENT_LOOP
            try:
                # Create new event loop for this thread
                EVENT_LOOP = asyncio.new_event_loop()
                asyncio.set_event_loop(EVENT_LOOP)
                logger.info("Event loop thread started")
                
                # Run the event loop forever
                EVENT_LOOP.run_forever()
            except Exception as e:
                logger.error(f"Event loop thread error: {e}")
                logger.error(traceback.format_exc())
            finally:
                if EVENT_LOOP and not EVENT_LOOP.is_closed():
                    EVENT_LOOP.close()
                logger.info("Event loop thread terminated")
        
        # Create and start the thread
        self.event_loop_thread = threading.Thread(target=run_event_loop, daemon=True)
        self.event_loop_thread.start()
        
        # Give it a moment to initialize
        time.sleep(0.5)
    
    def setup_cuda(self):
        """Configure CUDA for optimal performance"""
        if not torch.cuda.is_available():
            logger.warning("CUDA not available! Using CPU for inference.")
            self.device = torch.device("cpu")
            return
        
        # Set device
        self.device = torch.device("cuda")
        
        # Enable TF32 acceleration if available (much faster on RTX cards)
        if torch.cuda.get_device_capability()[0] >= 8:  # Ampere or newer
            torch.backends.cuda.matmul.allow_tf32 = config.get("ALLOW_TF32", True)
            torch.backends.cudnn.allow_tf32 = config.get("ALLOW_TF32", True)
            logger.info("TF32 acceleration enabled for tensor operations")
        
        # Set memory fraction
        memory_fraction = config.get("GPU_MEMORY_FRACTION", 0.95)
        torch.cuda.set_per_process_memory_fraction(memory_fraction)
        
        # Set stream priority (-1 is highest)
        stream_priority = config.get("STREAM_PRIORITY", -3)
        self.stream = torch.cuda.Stream(priority=stream_priority)
        
        # Enable cuDNN benchmark mode for optimized kernels
        torch.backends.cudnn.benchmark = config.get("CUDA_BENCHMARK", True)
        
        # Set deterministic mode if needed (slower but more reproducible)
        torch.backends.cudnn.deterministic = config.get("CUDA_DETERMINISTIC", False)
        
        # CPU thread settings
        threads = config.get("THREADS", 48)
        torch.set_num_threads(threads)
        os.environ["MKL_NUM_THREADS"] = str(threads)
        os.environ["OMP_NUM_THREADS"] = str(threads)
        
        # Empty cache
        torch.cuda.empty_cache()
        
        # Log GPU information
        logger.info(f"Using GPU: {torch.cuda.get_device_name(0)}")
        logger.info(f"CUDA Version: {torch.version.cuda}")
        logger.info(f"Allocated Memory: {torch.cuda.memory_allocated(0) / 1024**2:.2f} MB")
        logger.info(f"Reserved Memory: {torch.cuda.memory_reserved(0) / 1024**2:.2f} MB")
        
        # Warm up GPU (optional)
        self.warmup_gpu()
    
    def warmup_gpu(self):
        """Warm up GPU to ensure consistent performance"""
        logger.info("Warming up GPU...")
        tensor_size = config.get("TENSOR_SIZE", 176)
        warmup_cycles = config.get("WARMUP_CYCLES", 2500)
        
        with torch.cuda.stream(self.stream):
            # Create tensors for warmup
            a = torch.randn(tensor_size, tensor_size, device=self.device)
            b = torch.randn(tensor_size, tensor_size, device=self.device)
            
            # Perform matrix multiplications to warm up the GPU
            start_time = time.time()
            for _ in range(warmup_cycles):
                _ = torch.matmul(a, b)
            
            # Ensure all operations are completed
            torch.cuda.synchronize()
            
            elapsed = time.time() - start_time
            logger.info(f"GPU warmup completed in {elapsed:.2f} seconds, {warmup_cycles / elapsed:.2f} ops/s")
    
    def setup_trading_core(self):
        """Set up Rust trading core with PyO3 bindings"""
        try:
            # Create configuration for Rust
            rust_config = DynamicConfig(
                rpc_url=config.get("RPC_URL"),
                ws_url=config.get("WS_URL"),
                api_key=config.get("API_KEY"),
                sol_mint=config.get("SOL_MINT"),
                token_mint=config.get("TOKEN_MINT"),
                amm_program=config.get("RAYDIUM_PROGRAM_ID"),
                compute_limit=config.get("COMPUTE_LIMIT"),
                priority_fee=config.get("PRIORITY_FEE"),
                skip_preflight=config.get("SKIP_PREFLIGHT", True),
                front_run_amount=config.get("FRONT_RUN_AMOUNT"),
                min_victim_size=config.get("MIN_VICTIM_SIZE"),
                max_victim_size=config.get("MAX_VICTIM_SIZE"),
                min_profit=config.get("MIN_PROFIT"),
                slippage_bps=config.get("SLIPPAGE_BPS"),
                amm_id=config.get("AMM_ID"),
                market_amm_authority=config.get("MARKET_AMM_AUTHORITY"),
                market_amm_open_orders=config.get("MARKET_AMM_OPEN_ORDERS"),
                market_amm_target=config.get("MARKET_AMM_TARGET"),
                market_pool_token_account=config.get("MARKET_POOL_TOKEN_ACCOUNT"),
                market_pool_pc_token_account=config.get("MARKET_POOL_PC_TOKEN_ACCOUNT")
            )
            
            # Find keypair path - try multiple common locations
            keypair_paths = TradingCore.get_keypair_paths()
            keypair_path = None
            
            for path in keypair_paths:
                if os.path.exists(path):
                    keypair_path = path
                    logger.info(f"Using keypair from: {path}")
                    break
            
            if not keypair_path:
                # Check if environment variables are set
                if os.environ.get("SOLANA_PRIVATE_KEY") and os.environ.get("SOLANA_PUBLIC_KEY"):
                    logger.info("Using keypair from environment variables")
                    keypair_path = keypair_paths[0]  # Use first path as placeholder
                else:
                    raise ValueError("No keypair file found. Please place keypair.bin or id.json in the current directory")
            
            # Initialize Rust trading core with correct parameters
            self.trading_core = TradingCore(
                config.get("RPC_URL"),  # rpc_url
                None,                   # private_key (None since we're using path)
                keypair_path,           # keypair_path
                rust_config             # config
            )
            
            logger.info(f"Rust trading core initialized with keypair from {keypair_path}")
            
            # Update our initial ML signals dictionary
            self.update_ml_signals()
            
            # Initialize balance history with current balance
            self.update_and_log_balances(force_update=True)
            
        except Exception as e:
            error_details = self.error_tracker.track_error("TradingCoreInitError", str(e))
            logger.error(f"Failed to initialize Rust trading core: {e}")
            raise
    
    def initialize_wsol(self):
        """Initialize WSOL account if needed and wrap some SOL"""
        try:
            # Check if we have any WSOL
            sol_balance, wsol_balance = self._get_cached_balances(force_update=True)
            
            if wsol_balance > 0:
                logger.info(f"WSOL already initialized with {wsol_balance:.6f} WSOL")
                return
            
            logger.info("Initializing WSOL account and wrapping some SOL...")
            
            # Create WSOL ATA if it doesn't exist
            self.ensure_token_ata(config.get("SOL_MINT"))
            
            # Wrap a small amount of SOL for gas
            wrap_amount = min(sol_balance * 0.2, 0.1)  # 20% of SOL or max 0.1 SOL
            
            if wrap_amount > 0.01:  # Only wrap if amount is meaningful
                async def wrap_sol_async():
                    future = self.trading_core.execute_trade(
                        token_mint=config.get("SOL_MINT"),
                        sol_mint=config.get("SOL_MINT"),
                        amount_in=wrap_amount,
                        is_buy=True,
                        slippage_bps=config.get("SLIPPAGE_BPS", 100)
                    )
                    return await future
                
                # Execute wrap SOL transaction
                tx_hash = run_async_in_thread(wrap_sol_async())
                logger.info(f"Wrapped {wrap_amount:.6f} SOL to WSOL. TX: {tx_hash}")
                
                # Update balances
                self.update_and_log_balances(force_update=True)
            else:
                logger.warning(f"Not enough SOL to wrap ({sol_balance:.6f} SOL available)")
        
        except Exception as e:
            self.error_tracker.track_error("WSOLInitError", str(e))
            logger.error(f"Failed to initialize WSOL: {e}")
    
    def ensure_token_ata(self, token_mint):
        """Ensure Associated Token Account exists for a specific token"""
        try:
            async def create_ata_async():
                # Use the trading core's ensure_ata_exists method if available
                if hasattr(self.trading_core, "ensure_ata_exists"):
                    future = self.trading_core.ensure_ata_exists(token_mint)
                    return await future
                else:
                    # Otherwise create it through execute_trade with 0 amount
                    future = self.trading_core.execute_trade(
                        token_mint=token_mint,
                        sol_mint=config.get("SOL_MINT"),
                        amount_in=0,
                        is_buy=True,
                        slippage_bps=0
                    )
                    return await future
            
            result = run_async_in_thread(create_ata_async())
            logger.info(f"Ensured ATA exists for token {token_mint}")
            return True
        
        except Exception as e:
            self.error_tracker.track_error("ATACreationError", str(e))
            logger.error(f"Failed to create ATA for {token_mint}: {e}")
            return False
    
    # Memory and performance optimization
    def optimize_memory(self):
        """Perform memory optimization to prevent leaks during long runs"""
        # Clear PyTorch cache
        if torch.cuda.is_available():
            torch.cuda.empty_cache()
            
        # Suggest garbage collection
        import gc
        gc.collect()
        
        # Log memory usage
        if torch.cuda.is_available():
            allocated = torch.cuda.memory_allocated() / 1024**2
            reserved = torch.cuda.memory_reserved() / 1024**2
            logger.debug(f"GPU Memory - Allocated: {allocated:.2f} MB, Reserved: {reserved:.2f} MB")
        
        # CPU memory (if psutil is available)
        if HAS_PSUTIL:
            process = psutil.Process()
            memory_info = process.memory_info()
            logger.debug(f"CPU Memory - RSS: {memory_info.rss / 1024**2:.2f} MB, VMS: {memory_info.vms / 1024**2:.2f} MB")
        
        self.last_memory_check = time.time()
    
    def update_ml_signal(self, key: str, value: float) -> None:
        """Update ML signal (Python implementation)"""
        self.ml_signals[key] = value
    
    def get_ml_signal(self, key: str, default_value: float = 0.0) -> float:
        """Get ML signal value (Python implementation)"""
        return self.ml_signals.get(key, default_value)
    
    def update_ml_signals(self, custom_signals: Optional[Dict[str, float]] = None):
        """Update ML signals using Python dictionary (fallback for missing Rust method)"""
        # Default signals
        signals = {
            "market_volatility": 0.35,
            "execution_confidence": 0.85,
            "price_direction": 0.2,
            "trade_profitability": 0.75,
            "risk_score": 0.3,
        }
        
        # Update with custom signals if provided
        if custom_signals:
            signals.update(custom_signals)
        
        # Update each signal in our Python dictionary
        for key, value in signals.items():
            self.update_ml_signal(key, value)
        
        logger.debug(f"Updated ML signals: {signals}")
    
    def _get_cached_balances(self, force_update=False):
        """
        Get SOL and WSOL balances from cache or update from blockchain
        This prevents excessive RPC calls
        """
        global BALANCE_CACHE
        
        # Check if we need to update the cache
        current_time = time.time()
        cache_age = current_time - BALANCE_CACHE["last_update"]
        
        if (force_update or 
            cache_age > BALANCE_CACHE["update_interval"] or
            BALANCE_CACHE["sol"] == 0.0):
            
            try:
                # Define async function to get balances
                async def get_balances_async():
                    future = self.trading_core.get_balances()
                    return await future
                
                # Run in the event loop thread and wait for result
                sol_balance, wsol_balance = run_async_in_thread(get_balances_async())
                
                # Update cache
                BALANCE_CACHE["sol"] = sol_balance
                BALANCE_CACHE["wsol"] = wsol_balance
                BALANCE_CACHE["last_update"] = current_time
                
                return sol_balance, wsol_balance
                
            except Exception as e:
                if not force_update:  # Only log error if not forced update
                    self.error_tracker.track_error("BalanceError", str(e))
                    logger.error(f"Error getting balances: {e}")
                return BALANCE_CACHE["sol"], BALANCE_CACHE["wsol"]
        else:
            # Return cached values
            return BALANCE_CACHE["sol"], BALANCE_CACHE["wsol"]
    
    def update_and_log_balances(self, force_update=False) -> Tuple[float, float]:
        """Update and log balances, return (sol_balance, wsol_balance)"""
        try:
            # Get balances (from cache unless forced)
            sol_balance, wsol_balance = self._get_cached_balances(force_update)
            
            # Update stats
            if self.stats["start_balance"] == 0.0:
                self.stats["start_balance"] = sol_balance
            
            self.stats["current_balance"] = sol_balance
            
            # Log balance only if forced or significant change
            if force_update or len(self.stats["balance_history"]) == 0:
                # Log balance
                self.stats["balance_history"].append({
                    "timestamp": time.time(),
                    "sol": sol_balance,
                    "wsol": wsol_balance,
                    "total": sol_balance + wsol_balance
                })
                
                # Calculate lifetime profit
                lifetime_profit = sol_balance - self.stats["start_balance"]
                
                logger.info(f"Balances - SOL: {sol_balance:.6f}, WSOL: {wsol_balance:.6f}")
                logger.info(f"Lifetime profit: {lifetime_profit:.6f} SOL")
            
            return sol_balance, wsol_balance
            
        except Exception as e:
            self.error_tracker.track_error("BalanceUpdateError", str(e))
            logger.error(f"Failed to update balances: {e}")
            return 0.0, 0.0
    
    def decode_ray_log(self, log_str: str) -> Optional[bytes]:
        """Decode Raydium log data from base64"""
        try:
            if "ray_log:" not in log_str:
                return None
    
    def validate_trade(self, raw_data: bytes) -> Optional[Dict[str, Any]]:
        """Validate and extract trade information from raw Raydium log data"""
        try:
            # Basic validation: minimum length
            if len(raw_data) < 9:
                return None
            
            # Extract instruction and amount (first 9 bytes)
            instruction = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], byteorder="little") / 1e9
            
            # Check if it's a buy (1, 3, 9 are buy instructions in Raydium)
            is_buy = instruction in (1, 3, 9)
            
            # Skip if not a buy
            if not is_buy:
                return None
            
            # Additional validation
            min_size = config.get("MIN_VICTIM_SIZE", 5.0)
            max_size = config.get("MAX_VICTIM_SIZE", 320.0)
            
            if amount < min_size:
                self.stats["filtered_too_small"] += 1
                return None
            
            if amount > max_size:
                self.stats["filtered_too_large"] += 1
                return None
            
            # Return validated trade info
            return {
                "instruction": instruction,
                "amount": amount,
                "is_buy": is_buy,
                "raw_data": raw_data,
                "timestamp": time.time()
            }
        
        except Exception as e:
            self.error_tracker.track_error("TradeValidationError", str(e))
            logger.error(f"Error validating trade: {e}")
            return None
    
    def load_ml_model(self) -> Optional[torch.nn.Module]:
        """Load ML model for trade evaluation"""
        model_path = config.get("MODEL_PATH", "models/trade_model.pt")
        
        if not os.path.exists(model_path):
            logger.warning(f"ML model not found at {model_path}. Running in fallback mode.")
            return None
        
        try:
            logger.info(f"Loading ML model from {model_path}")
            
            # Load model
            with torch.cuda.stream(self.stream):
                model = torch.jit.load(model_path, map_location=self.device)
                model.eval()
                
                # Test with dummy input
                with torch.no_grad():
                    dummy_input = torch.randn(1, 32, device=self.device)
                    _ = model(dummy_input)
            
            logger.info("ML model loaded and tested successfully")
            return model
        
        except Exception as e:
            self.error_tracker.track_error("ModelLoadError", str(e))
            logger.error(f"Failed to load ML model: {e}")
            return None
    
    def prepare_ml_features(self, trade_info: Dict[str, Any]) -> torch.Tensor:
        """Prepare features for ML model input"""
        # Extract amount from trade info
        amount = trade_info["amount"]
        raw_data = trade_info["raw_data"]
        
        # Create base features
        base_features = [
            amount,  # Victim size
            config.get("FRONT_RUN_AMOUNT", 0.35),  # Our position size
            time.time() % 86400 / 86400,  # Time of day normalized to [0,1]
        ]
        
        # Get ML signals from our Python dictionary
        signals = [
            self.get_ml_signal("market_volatility", 0.5),
            self.get_ml_signal("execution_confidence", 0.85),
            self.get_ml_signal("price_direction", 0.0),
            self.get_ml_signal("trade_profitability", 0.75),
            self.get_ml_signal("risk_score", 0.3),
        ]
        
        # Add normalized bytes from raw data (first 20 bytes)
        data_features = [b / 255.0 for b in raw_data[:min(20, len(raw_data))]]
        
        # Combine all features
        features = base_features + signals + data_features
        
        # Pad or truncate to expected size (32)
        if len(features) < 32:
            features.extend([0.0] * (32 - len(features)))
        else:
            features = features[:32]
        
        # Convert to tensor with batch dimension
        return torch.tensor([features], dtype=torch.float32, device=self.device)
    
    def predict_trade_success(self, features: torch.Tensor) -> float:
        """Predict trade success probability using ML model"""
        if self.model is None:
            # Fallback logic without ML model
            return 0.85  # Default confidence
        
        try:
            with torch.cuda.stream(self.stream):
                with torch.no_grad():
                    # Use mixed precision if supported
                    if config.get("USE_AMP", True) and torch.cuda.is_available():
                        with torch.amp.autocast(device_type=self.device.type):
                            output = self.model(features)
                    else:
                        output = self.model(features)
                    
                    # Get prediction (probability)
                    confidence = output.item()
                    return confidence
        
        except Exception as e:
            self.error_tracker.track_error("ModelInferenceError", str(e))
            logger.error(f"Error during model inference: {e}")
            return 0.0  # Zero confidence on error
    
    def execute_with_retry(self, operation_name, func, max_retries=3, retry_delay=0.5):
        """Execute a function with retry logic"""
        retries = 0
        last_error = None
        
        while retries < max_retries:
            try:
                result = func()
                if retries > 0:
                    logger.info(f"{operation_name} succeeded after {retries} retries")
                return result
            except Exception as e:
                retries += 1
                last_error = e
                
                # Special handling for rate limiting
                if "429 Too Many Requests" in str(e):
                    wait_time = retry_delay * (2 ** retries)  # Exponential backoff
                    logger.warning(f"{operation_name} rate limited. Waiting {wait_time:.1f}s before retry {retries}/{max_retries}")
                else:
                    logger.warning(f"{operation_name} failed (attempt {retries}/{max_retries}): {e}")
                
                if retries < max_retries:
                    # Exponential backoff
                    time.sleep(retry_delay * (2 ** (retries - 1)))
        
        # Log the final failure
        self.error_tracker.track_error(f"{operation_name}Error", str(last_error))
        logger.error(f"{operation_name} failed after {max_retries} attempts: {last_error}")
        raise last_error
    
    def estimate_profit(self, amount: float) -> float:
        """Estimate potential profit based on victim trade size"""
        # Profit rates based on victim size
        if amount >= 200:
            profit_rate = 0.018  # 1.8% for large trades
        elif amount >= 100:
            profit_rate = 0.015  # 1.5% for medium-large trades
        elif amount >= 50:
            profit_rate = 0.012  # 1.2% for medium trades
        elif amount >= 10:
            profit_rate = 0.010  # 1.0% for small-medium trades
        else:
            profit_rate = 0.008  # 0.8% for small trades
        
        # Our position size
        position_size = config.get("FRONT_RUN_AMOUNT", 0.35)
        
        # Calculate estimated gross profit
        gross_profit = position_size * profit_rate
        
        # Subtract gas costs
        gas_cost = config.get("PRIORITY_FEE", 750000) / 1e9 * 2  # Both ways
        net_profit = gross_profit - gas_cost
        
        return net_profit
    
    def score_trade_opportunity(self, trade_info: Dict[str, Any]) -> float:
        """Calculate a comprehensive score for a trade opportunity"""
        # Get basic values
        amount = trade_info["amount"]
        estimated_profit = self.estimate_profit(amount)
        
        # Base score on estimated profit
        base_score = estimated_profit * 100  # Scale up for easier math
        
        # Adjust for risk factors
        
        # 1. Size risk - extremely large trades might have more slippage/impact
        size_risk = 1.0
        if amount > 150:
            # Progressive discount for very large trades
            size_risk = 1.0 - min(0.3, (amount - 150) / 500)
        
        # 2. Time of day - some hours might be more profitable (market dependent)
        hour = datetime.now().hour
        time_factor = 1.0
        # Example: 0.8-1.2 multiplier based on historical profitability by hour
        hour_multipliers = {
            0: 0.9, 1: 0.85, 2: 0.8, 3: 0.8, 4: 0.85, 5: 0.9,
            6: 0.95, 7: 1.0, 8: 1.05, 9: 1.1, 10: 1.15, 11: 1.2,
            12: 1.15, 13: 1.1, 14: 1.05, 15: 1.0, 16: 1.05, 17: 1.1,
            18: 1.15, 19: 1.1, 20: 1.05, 21: 1.0, 22: 0.95, 23: 0.9
        }
        time_factor = hour_multipliers.get(hour, 1.0)
        
        # 3. ML confidence
        features = self.prepare_ml_features(trade_info)
        ml_confidence = self.predict_trade_success(features)
        
        # 4. Market volatility (using your ML signal)
        market_volatility = self.get_ml_signal("market_volatility", 0.5)
        volatility_factor = 1.0 + (market_volatility - 0.5)  # 0.5-1.5 range
        
        # 5. Recent trade performance
        recent_success_factor = 1.0
        if len(self.stats["execution_times"]) > 0:
            # If we've had successful trades, factor in recent performance
            recent_profit = self.get_ml_signal("last_profit", 0.0)
            if recent_profit > 0:
                recent_success_factor = 1.1  # Boost if last trade was profitable
            elif recent_profit < 0:
                recent_success_factor = 0.9  # Reduce if last trade lost money
        
        # Calculate final score with all factors
        final_score = (
            base_score * 
            size_risk * 
            time_factor * 
            ml_confidence * 
            volatility_factor * 
            recent_success_factor
        )
        
        logger.debug(f"""
Trade Score Calculation:
- Base Score (from profit): {base_score:.2f}
- Size Risk Factor: {size_risk:.2f}
- Time of Day Factor: {time_factor:.2f}
- ML Confidence: {ml_confidence:.2f}
- Volatility Factor: {volatility_factor:.2f}
- Recent Success Factor: {recent_success_factor:.2f}
Final Score: {final_score:.2f}
""")
        
        return final_score
    
    def process_trade_opportunity(self, trade_info: Dict[str, Any]):
        """Process and execute a trade opportunity if profitable"""
        amount = trade_info["amount"]
        raw_data = trade_info["raw_data"]
        
        # Update volume analyzer
        self.volume_analyzer.update(amount, True)
        
        logger.info(f"Evaluating trade opportunity: {amount:.4f} SOL")
        
        # Track statistics
        self.stats["opportunities"] += 1
        
        # Check risk management limits
        can_trade, risk_message = self.risk_manager.check_daily_limits()
        if not can_trade:
            logger.info(f"Skipping: Risk limit reached - {risk_message}")
            return
        
        # Check if we have a pending transaction
        global BALANCE_CACHE
        if BALANCE_CACHE["pending_transaction"]:
            logger.debug(f"Skipping: Transaction already in progress")
            return
        
        # Check balance is sufficient - USE CACHED VALUE to avoid RPC calls
        sol_balance, wsol_balance = self._get_cached_balances()
        
        if sol_balance < 0.01:  # Very low balance threshold for quick check
            logger.debug(f"Skipping: SOL balance too low ({sol_balance:.6f})")
            return
        
        # More thorough balance check with risk manager
        can_trade, balance_message = self.risk_manager.check_balance(sol_balance)
        if not can_trade:
            logger.info(f"Skipping: {balance_message}")
            return
        
        # Use the enhanced scoring system
        score = self.score_trade_opportunity(trade_info)
        min_score_threshold = config.get("MIN_SCORE_THRESHOLD", 0.5)
        
        if score < min_score_threshold:
            logger.debug(f"Skipping: Score ({score:.2f}) below threshold ({min_score_threshold:.2f})")
            return
        
        # Estimate potential profit
        estimated_profit = self.estimate_profit(amount)
        min_profit = config.get("MIN_PROFIT", 0.0009)
        
        if estimated_profit < min_profit:
            logger.debug(f"Skipping: Estimated profit ({estimated_profit:.6f} SOL) below threshold ({min_profit:.6f} SOL)")
            return
        
        # Check if we're in cooldown period
        cooldown_ms = config.get("COOLDOWN_AFTER_TRADE_MS", 200)
        elapsed_since_last_trade = (time.time() - self.last_trade_time) * 1000
        
        if elapsed_since_last_trade < cooldown_ms:
            logger.debug(f"Skipping: In cooldown period ({elapsed_since_last_trade:.0f}ms < {cooldown_ms}ms)")
            self.stats["filtered_by_time"] += 1
            return
        
        # Prepare features for ML prediction
        features = self.prepare_ml_features(trade_info)
        
        # Get ML prediction
        confidence = self.predict_trade_success(features)
        confidence_threshold = config.get("ML_CONFIDENCE_THRESHOLD", 0.7)
        
        logger.info(f"ML evaluation: Confidence={confidence:.4f}, Threshold={confidence_threshold:.2f}")
        
        if confidence < confidence_threshold:
            logger.info(f"Skipping: ML confidence too low ({confidence:.4f} < {confidence_threshold:.2f})")
            self.stats["filtered_by_ml"] += 1
            return
        
        # Mark as viable opportunity
        self.stats["viable"] += 1
        
        # Execute the trade
        try:
            # Set pending transaction flag
            BALANCE_CACHE["pending_transaction"] = True
            
            # Execute trade
            self.execute_trade(trade_info, confidence)
            
            # Reset pending flag
            BALANCE_CACHE["pending_transaction"] = False
            
        except Exception as e:
            # Reset pending flag on error
            BALANCE_CACHE["pending_transaction"] = False
            self.error_tracker.track_error("TradeExecutionError", str(e))
            logger.error(f"Error in process_trade_opportunity: {e}")
            # Prevent unhandled exceptions from crashing the process
            pass
    
    def execute_trade(self, trade_info: Dict[str, Any], confidence: float):
        """Execute front-run and back-run trades"""
        # Start timing
        start_time = time.time()
        
        # Update attempt counter
        self.stats["attempted"] += 1
        
        try:
            # Get balance before trade using cached value to avoid RPC call
            sol_before, _ = self._get_cached_balances()
            
            # Extract configuration
            token_mint = config.get("TOKEN_MINT")
            sol_mint = config.get("SOL_MINT")
            
            # Calculate position size using risk manager
            base_front_run_amount = config.get("FRONT_RUN_AMOUNT", 0.35)
            front_run_amount = self.risk_manager.calculate_position_size(base_front_run_amount, trade_info)
            
            slippage_bps = config.get("SLIPPAGE_BPS", 800)
            
            # Make sure we have ATAs for both SOL and token
            self.ensure_token_ata(sol_mint)
            self.ensure_token_ata(token_mint)
            
            # Calculate minimum output amount based on slippage for the buy instruction
            min_amount_out_buy = RaydiumInstructionBuilder.calculate_min_amount_out(
                front_run_amount, slippage_bps
            )
            
            # Build proper instruction data for the buy
            buy_instruction_data = RaydiumInstructionBuilder.build_swap_instruction(
                amount_in=front_run_amount,
                min_amount_out=min_amount_out_buy,
                instruction_type=9  # SWAP_BASE_IN for buying
            )
            
            # Execute front-run (buy)
            logger.info(f"Executing front-run: {front_run_amount} SOL → Token")
            
            # Define the async function to execute trade
            async def execute_buy_trade_async():
                # Build trade parameters
                trade_params = TradeParams(
                    accounts=[
                        config.get("RAYDIUM_PROGRAM_ID"),
                        config.get("MARKET_AMM_AUTHORITY"),
                        config.get("MARKET_AMM_OPEN_ORDERS"),
                        config.get("MARKET_AMM_TARGET"),
                        config.get("MARKET_POOL_TOKEN_ACCOUNT"),
                        config.get("MARKET_POOL_PC_TOKEN_ACCOUNT")
                    ],
                    amount_in=int(front_run_amount * 1_000_000_000),  # Convert to lamports
                    is_buy=True,
                    token_a_mint=token_mint,
                    token_b_mint=sol_mint,
                    sell_all=False,
                    victim_data=list(buy_instruction_data),
                    slippage_bps=slippage_bps,
                    priority_fee=config.get("PRIORITY_FEE")
                )
                
                # Execute through trading core
                future = self.trading_core.execute_trade_py(trade_params)
                return await future
            
            # Execute using retry mechanism
            def execute_buy_trade():
                return run_async_in_thread(execute_buy_trade_async())
            
            front_tx = self.execute_with_retry("Front-run trade", execute_buy_trade)
            logger.info(f"Front-run transaction sent: {front_tx}")
            
            # Wait for victim transaction
            time.sleep(config.get("VICTIM_WAIT_TIME", 0.5))
            
            # Build proper instruction data for the sell
            # For selling, we calculate a reasonable minimum amount out based on expected profit
            # We'll use 0.001 SOL as a very minimal amount to prevent errors, but the Rust implementation 
            # will actually use the account balance and apply slippage properly
            min_amount_out_sell = 0.001  # Minimum SOL expected back
            
            sell_instruction_data = RaydiumInstructionBuilder.build_swap_instruction(
                amount_in=0.0,  # 0 for "sell all"
                min_amount_out=min_amount_out_sell,
                instruction_type=11  # SWAP_BASE_OUT for selling
            )
            
            # Execute back-run (sell)
            logger.info("Executing back-run: Token → SOL")
            
            # Define the async function to execute sell trade
            async def execute_sell_trade_async():
                # Build trade parameters
                trade_params = TradeParams(
                    accounts=[
                        config.get("RAYDIUM_PROGRAM_ID"),
                        config.get("MARKET_AMM_AUTHORITY"),
                        config.get("MARKET_AMM_OPEN_ORDERS"),
                        config.get("MARKET_AMM_TARGET"),
                        config.get("MARKET_POOL_TOKEN_ACCOUNT"),
                        config.get("MARKET_POOL_PC_TOKEN_ACCOUNT")
                    ],
                    amount_in=0,  # 0 means "sell all tokens"
                    is_buy=False,
                    token_a_mint=token_mint,
                    token_b_mint=sol_mint,
                    sell_all=True,
                    victim_data=list(sell_instruction_data),
                    slippage_bps=slippage_bps + 200,  # Add extra slippage for sell
                    priority_fee=config.get("PRIORITY_FEE")
                )
                
                # Execute through trading core
                future = self.trading_core.execute_trade_py(trade_params)
                return await future
            
            # Define a lambda function for selling
            def execute_sell_trade():
                return run_async_in_thread(execute_sell_trade_async())
            
            # Execute using retry mechanism
            back_tx = self.execute_with_retry("Back-run trade", execute_sell_trade)
            logger.info(f"Back-run transaction sent: {back_tx}")
            
            # Wait for transactions to settle
            time.sleep(0.5)
            
            # Force update of balance cache
            sol_after, _ = self._get_cached_balances(force_update=True)
            profit = sol_after - sol_before
            
            # Record execution time
            execution_time = (time.time() - start_time) * 1000  # in milliseconds
            self.stats["execution_times"].append(execution_time)
            
            # Update statistics
            self.stats["completed"] += 1
            self.stats["profit"] += profit
            self.last_trade_time = time.time()
            
            # Update risk manager
            self.risk_manager.update_daily_stats(profit)
            
            # Update trade tracker
            self.trade_tracker.add_trade(trade_info, profit, execution_time)
            
            # Update ML signals with result
            self.update_ml_signals({
                "last_profit": profit,
                "execution_time": execution_time / 1000.0,
                "trade_count": self.stats["completed"]
            })
            
            # Log detailed results
            logger.info(f"""
St. Jude Trade #{self.stats["completed"]} Results:
Victim Size: {trade_info["amount"]:.4f} SOL
Front TX: {front_tx}
Back TX: {back_tx}
Profit: {profit:.6f} SOL
ML Confidence: {confidence:.4f}
Execution Time: {execution_time:.2f}ms
Total Profit: {self.stats["profit"]:.6f} SOL
Lifetime Charity Balance: {self.stats["current_balance"]:.6f} SOL
""")
            
            # Periodic stats update
            if self.stats["completed"] % 5 == 0:
                self.print_stats()
            
            return True
        
        except Exception as e:
            self.error_tracker.track_error("TradeExecutionError", str(e))
            logger.error(f"Error executing trade: {e}")
            return False
            
    def websocket_thread(self):
        """Thread for WebSocket connection and message handling"""
        # Create asyncio loop for this thread
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
        # Define the async function to run the WebSocket
        async def run_websocket():
            # Get WebSocket URL and API key
            ws_url = config.get('WS_URL')
            api_key = config.get('API_KEY')
            raydium_program_id = config.get("RAYDIUM_PROGRAM_ID")
            
            # Check if the URL already contains the API key
            if "api-key=" not in ws_url and "?" not in ws_url:
                # Use Bearer token authentication (more secure)
                headers = {
                    "Authorization": f"Bearer {api_key}"
                }
            else:
                # API key is already in URL or will be added below
                headers = {}
                
            # If URL doesn't have API key and we're not using headers, add it to URL
            if "api-key=" not in ws_url and not headers:
                ws_url = f"{ws_url}?api-key={api_key}"
                
            while self.is_running:
                try:
                    logger.info(f"Connecting to WebSocket: {ws_url.split('?')[0]}")  # Log URL without API key
                    
                    # Connect with appropriate authentication
                    async with websockets.connect(
                        ws_url,
                        ping_interval=30,
                        ping_timeout=10,
                        close_timeout=10,
                        extra_headers=headers if headers else None
                    ) as ws:
                        # Subscribe to Raydium program logs
                        await ws.send(json.dumps({
                            "jsonrpc": "2.0",
                            "id": 1,
                            "method": "logsSubscribe",
                            "params": [
                                {"mentions": [raydium_program_id]},
                                {"commitment": "processed"}
                            ]
                        }))
                        
                        # Try to get public key for account subscription
                        try:
                            # Check if the method exists
                            if hasattr(self.trading_core, "get_public_key"):
                                public_key = self.trading_core.get_public_key()
                                
                                # Subscribe to our wallet account updates if we have the public key
                                if public_key:
                                    await ws.send(json.dumps({
                                        "jsonrpc": "2.0",
                                        "id": 2,
                                        "method": "accountSubscribe",
                                        "params": [
                                            public_key,
                                            {"encoding": "jsonParsed", "commitment": "processed"}
                                        ]
                                    }))
                                    logger.info(f"Subscribed to account updates for: {public_key}")
                        except Exception as e:
                            # If account subscription fails, just log it and continue
                            # This shouldn't stop the main Raydium subscription
                            logger.warning(f"Failed to subscribe to account updates: {e}")
                        
                        logger.info("WebSocket connected, listening for Raydium transactions")
                        
                        # Process incoming messages
                        while self.is_running:
                            try:
                                # Set a timeout to periodically check is_running
                                message = await asyncio.wait_for(ws.recv(), timeout=1.0)
                                
                                # Parse response
                                data = json.loads(message)
                                
                                # Handle subscription confirmations
                                if "method" not in data and "id" in data:
                                    if data.get("id") == 1:
                                        # Raydium subscription confirmation
                                        subscription_id = data.get("result")
                                        logger.info(f"Raydium subscription confirmed: {subscription_id}")
                                        continue
                                    elif data.get("id") == 2:
                                        # Account subscription confirmation
                                        subscription_id = data.get("result")
                                        logger.info(f"Account subscription confirmed: {subscription_id}")
                                        continue
                                
                                # Process transaction logs
                                if "params" in data and "result" in data["params"] and "subscription" in data["params"]:
                                    result = data["params"]["result"]
                                    
                                    # Handle Raydium logs
                                    if "value" in result and "logs" in result["value"]:
                                        logs = result["value"]["logs"]
                                        
                                        for log in logs:
                                            if "ray_log:" in log:
                                                # Send log to trade processing thread via queue
                                                message_queue.put(("process_log", log))
                                    
                                    # Handle account update (balance change)
                                    elif "value" in result and result.get("value", {}).get("lamports") is not None:
                                        # Update our balance cache from WebSocket notification
                                        lamports = result["value"]["lamports"]
                                        sol_balance = lamports / 1_000_000_000  # Convert to SOL
                                        
                                        # Update cache if there's a significant change
                                        if abs(sol_balance - BALANCE_CACHE["sol"]) > 0.0001:
                                            BALANCE_CACHE["sol"] = sol_balance
                                            BALANCE_CACHE["last_update"] = time.time()
                                            
                                            logger.debug(f"Balance updated from WebSocket: {sol_balance:.6f} SOL")
                            
                            except asyncio.TimeoutError:
                                # This is expected due to our timeout, just continue
                                continue
                            except Exception as e:
                                logger.error(f"Error processing WebSocket message: {e}")
                        
                        # If we get here, is_running became False
                        logger.info("WebSocket loop terminated due to shutdown signal")
                        break
                
                except Exception as e:
                    logger.error(f"WebSocket connection error: {e}")
                    if self.is_running:
                        logger.info("Reconnecting in 5 seconds...")
                        await asyncio.sleep(5)
                    else:
                        break
            
            logger.info("WebSocket thread shutting down")
        
        # Run the async function in the loop
        try:
            loop.run_until_complete(run_websocket())
        except Exception as e:
            logger.error(f"Unhandled exception in WebSocket thread: {e}")
            logger.error(traceback.format_exc())
        finally:
            loop.close()
            logger.info("WebSocket thread loop closed")
    
    def balance_monitor_thread(self):
        """Thread for monitoring balances"""
        logger.info("Balance monitor thread started")
        
        while self.is_running:
            try:
                # Update and log balances (once per minute)
                self.update_and_log_balances()
                
                # Sleep until next update
                time.sleep(60)  # Update every minute
            
            except Exception as e:
                logger.error(f"Error monitoring balances: {e}")
                time.sleep(5)
        
        logger.info("Balance monitor thread shutting down")
    
    def system_health_thread(self):
        """Thread for monitoring system health"""
        logger.info("System health monitor thread started")
        
        while self.is_running:
            try:
                # Clear CUDA cache periodically
                if torch.cuda.is_available() and time.time() % 300 < 1:
                    torch.cuda.empty_cache()
                    logger.debug("CUDA memory cache cleared")
                
                # Memory optimization
                if time.time() - self.last_memory_check > 300:  # Every 5 minutes
                    self.optimize_memory()
                
                # Sleep for a while
                time.sleep(30)
            
            except Exception as e:
                logger.error(f"Error in system health check: {e}")
                time.sleep(5)
        
        logger.info("System health thread shutting down")
    
    def trade_processor_thread(self):
        """Thread for processing trades from queue"""
        logger.info("Trade processor thread started")
        
        while self.is_running:
            try:
                # Get message from queue with timeout
                try:
                    message_type, data = message_queue.get(timeout=1.0)
                except queue.Empty:
                    # This is expected, just continue
                    continue
                
                # Process message based on type
                if message_type == "process_log":
                    # Decode and process Raydium log
                    raw_data = self.decode_ray_log(data)
                    if raw_data:
                        trade_info = self.validate_trade(raw_data)
                        if trade_info:
                            self.process_trade_opportunity(trade_info)
                
                # Mark task as done
                message_queue.task_done()
            
            except Exception as e:
                logger.error(f"Error processing trade: {e}")
                time.sleep(1)
        
        logger.info("Trade processor thread shutting down")
    
    def print_stats(self):
        """Print current trading statistics"""
        runtime = time.time() - self.start_time
        runtime_hours = runtime / 3600
        
        # Calculate averages
        avg_execution_time = 0
        if self.stats["execution_times"]:
            avg_execution_time = sum(self.stats["execution_times"]) / len(self.stats["execution_times"])
        
        profit_per_hour = 0
        if runtime_hours > 0:
            profit_per_hour = self.stats["profit"] / runtime_hours
        
        success_rate = 0
        if self.stats["attempted"] > 0:
            success_rate = (self.stats["completed"] / self.stats["attempted"]) * 100
        
        # Calculate ROI
        initial_balance = self.stats["start_balance"]
        current_balance = self.stats["current_balance"]
        roi_percent = 0
        if initial_balance > 0:
            roi_percent = ((current_balance - initial_balance) / initial_balance) * 100
        
        # Get risk management status
        risk_status = self.risk_manager.get_status()
        
        # Print using Rich if available
        if HAS_RICH:
            # Main statistics table
            table = Table(title="St. Jude Charity Trading Performance", style="blue")
            table.add_column("Metric", style="cyan")
            table.add_column("Value", style="green")
            
            table.add_row("Runtime", f"{runtime_hours:.2f} hours")
            table.add_row("Opportunities", f"{self.stats['opportunities']}")
            table.add_row("Viable Opportunities", f"{self.stats['viable']}")
            table.add_row("Attempted Trades", f"{self.stats['attempted']}")
            table.add_row("Completed Trades", f"{self.stats['completed']}")
            table.add_row("Success Rate", f"{success_rate:.1f}%")
            table.add_row("Avg Execution Time", f"{avg_execution_time:.2f}ms")
            table.add_row("Total Profit", f"{self.stats['profit']:.6f} SOL")
            table.add_row("Profit per Hour", f"{profit_per_hour:.6f} SOL/hr")
            table.add_row("Starting Balance", f"{initial_balance:.6f} SOL")
            table.add_row("Current Charity Balance", f"{current_balance:.6f} SOL")
            table.add_row("ROI", f"{roi_percent:.2f}%")
            
            console.print(table)
            
            # Risk management table
            risk_table = Table(title="Risk Management", style="blue")
            risk_table.add_column("Metric", style="cyan")
            risk_table.add_column("Value", style="yellow")
            
            risk_table.add_row("Daily Profit", f"{risk_status['daily_profit']:.6f} SOL")
            risk_table.add_row("Daily Loss", f"{risk_status['daily_loss']:.6f} SOL")
            risk_table.add_row("Position Sizing", f"{risk_status['position_sizing_factor']:.2f}x")
            risk_table.add_row("Consecutive Losses", f"{risk_status['consecutive_losses']}")
            risk_table.add_row("In Cooldown", f"{'Yes' if risk_status['in_cooldown'] else 'No'}")
            
            console.print(risk_table)
            
            # Print trade tracker summary
            console.print(self.trade_tracker.get_summary())
            
            # Print error tracker summary
            console.print(self.error_tracker.get_summary())
            
            # Print volume analyzer summary
            console.print(self.volume_analyzer.get_summary())
            
        else:
            # Simple text output
            logger.info(f"""
================= ST. JUDE CHARITY TRADING STATISTICS =================
Runtime: {runtime_hours:.2f} hours
Opportunities Found: {self.stats["opportunities"]}
Viable Opportunities: {self.stats["viable"]}
Attempted Trades: {self.stats["attempted"]}
Completed Trades: {self.stats["completed"]}
Success Rate: {success_rate:.1f}%
Avg Execution Time: {avg_execution_time:.2f}ms

FILTERING STATS:
Too Small: {self.stats["filtered_too_small"]}
Too Large: {self.stats["filtered_too_large"]}
ML Filtered: {self.stats["filtered_by_ml"]}
Time Filtered: {self.stats["filtered_by_time"]}

PROFIT METRICS:
Total Profit: {self.stats['profit']:.6f} SOL
Profit per Hour: {profit_per_hour:.6f} SOL/hr
Starting Balance: {initial_balance:.6f} SOL
Current Charity Balance: {current_balance:.6f} SOL
ROI: {roi_percent:.2f}%

RISK MANAGEMENT:
Daily Profit: {risk_status['daily_profit']:.6f} SOL
Daily Loss: {risk_status['daily_loss']:.6f} SOL
Position Sizing: {risk_status['position_sizing_factor']:.2f}x
Consecutive Losses: {risk_status['consecutive_losses']}
In Cooldown: {'Yes' if risk_status['in_cooldown'] else 'No'}
======================================================================
""")
            
            # Print tracker summaries (only when requested specifically)
            if runtime_hours > 1 and self.stats["completed"] > 0:
                logger.info(self.trade_tracker.get_summary())
                logger.info(self.error_tracker.get_summary())
                logger.info(self.volume_analyzer.get_summary())
    
    def handle_shutdown(self, signum, frame):
        """Handle graceful shutdown on signal"""
        logger.info("Shutdown signal received, stopping engine...")
        self.is_running = False
    
    def start(self):
        """Start the trading engine"""
        if self.is_running:
            logger.warning("Trading engine is already running")
            return
        
        self.is_running = True
        logger.info("Starting St. Jude Charity Trading Engine")
        
        # Print a welcoming message with donation intent
        print("""
╔══════════════════════════════════════════════════════════════╗
║                                                              ║
║  St. Jude Charity Trading Bot                                ║
║  ───────────────────────────────                            ║
║                                                              ║
║  100% of profits from this bot will support:                 ║
║  • St. Jude Children's Research Hospital                     ║
║  • Creating a better future for all children                 ║
║                                                              ║
║  May your trades be profitable and your impact meaningful!   ║
║                                                              ║
╚══════════════════════════════════════════════════════════════╝
""")
        
        # Start all threads
        self.threads = []
        
        # WebSocket thread for monitoring transactions
        websocket_thread = threading.Thread(target=self.websocket_thread, daemon=True)
        websocket_thread.start()
        self.threads.append(websocket_thread)
        
        # Balance monitor thread
        balance_thread = threading.Thread(target=self.balance_monitor_thread, daemon=True)
        balance_thread.start()
        self.threads.append(balance_thread)
        
        # System health monitor thread
        health_thread = threading.Thread(target=self.system_health_thread, daemon=True)
        health_thread.start()
        self.threads.append(health_thread)
        
        # Trade processor thread
        processor_thread = threading.Thread(target=self.trade_processor_thread, daemon=True)
        processor_thread.start()
        self.threads.append(processor_thread)
        
        logger.info(f"All threads started ({len(self.threads)} total)")
        
        # Wait for threads to complete (they will exit when is_running becomes False)
        try:
            # Keep main thread alive until interrupted
            while self.is_running:
                time.sleep(1)
        except KeyboardInterrupt:
            logger.info("Keyboard interrupt received, shutting down...")
            self.is_running = False
        finally:
            # Wait for threads to terminate
            for thread in self.threads:
                thread.join(timeout=2.0)
            
            # Stop the event loop
            global EVENT_LOOP
            if EVENT_LOOP and not EVENT_LOOP.is_closed():
                EVENT_LOOP.call_soon_threadsafe(EVENT_LOOP.stop)
                
            # Wait for event loop thread to terminate
            if self.event_loop_thread and self.event_loop_thread.is_alive():
                self.event_loop_thread.join(timeout=2.0)
            
            # Final stats
            self.print_stats()
            logger.info("St. Jude Charity Trading Engine stopped")


# Main entry point
if __name__ == "__main__":
    try:
        # Initialize the trading engine
        engine = CharityTradingEngine()
        
        # Start the engine
        engine.start()
    except Exception as e:
        logger.critical(f"Fatal error: {e}")
        logger.critical(traceback.format_exc())
        sys.exit(1)
            
            b64_data = log_str.split("ray_log:")[1].strip()
            raw_data = base64.b64decode(b64_data)
            return raw_data
        
        except Exception as e:
            self.error_tracker.track_error("LogDecodeError", str(e))
            logger.error(f"Error decoding ray log: {e}")
            return None can u fix this script for me please ?  its giving me a syntax error   File "C:\solana_rust_bot\src\python\main.py", line 1101
    def validate_trade(self, raw_data: bytes) -> Optional[Dict[str, Any]]:
SyntaxError: expected 'except' or 'finally' block
(quantum_beast) PS C:\solana_rust_bot>