#!/usr/bin/env python3
"""
Quantum Beast - Raydium MEV Bot
-------------------------------
High-performance MEV bot for Solana/Raydium that combines:
1. Real-time instruction monitoring (0x03/0x09 instruction detection)
2. Advanced pool selection and security verification
3. High-speed trade execution via Rust trading core
4. Automatic profit taking and balance monitoring
"""

import os
import sys
import json
import asyncio
import signal
import time
import base64
import base58
import websockets
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Set, Tuple
import threading
import queue
import traceback

# Import custom modules
from config_loader import config, load_env_file
from solana_logger import logger

# Import Raydium trading components (Rust bindings)
from solana_rust_bot import (
    RaydiumTradingCore, 
    TradingConfig, 
    TokenPairSetting,
    SwapParams,
    TransactionStatus
)

# Import optional ML components
try:
    import torch
    import numpy as np
    HAVE_ML_MODULES = True
except ImportError:
    HAVE_ML_MODULES = False

# Constants
LAMPORTS_PER_SOL = 1_000_000_000
DEFAULT_CONFIG_DIR = Path("config")
DEFAULT_TARGET_LIST_PATH = DEFAULT_CONFIG_DIR / "target_pools.json"
DEFAULT_KEYPAIR_PATH = Path(config.get("keypair_path", r"C:\solana_rust_bot\keypair.bin"))

# Globals for inter-thread communication
opportunity_queue = queue.Queue(maxsize=100)
command_queue = queue.Queue(maxsize=10)
active_trades = set()
bot_running = threading.Event()
balance_updates = {
    "SOL": 0.0,
    "WSOL": 0.0,
    "last_update": 0
}
stats = {
    "start_time": time.time(),
    "total_logs": 0,
    "logs_with_ray": 0,
    "instruction_counts": {},
    "opportunities": 0,
    "executions": 0,
    "trades_successful": 0,
    "trades_failed": 0,
    "total_profit": 0.0,
    "last_profit_time": None,
    "errors": 0,
    "unique_pools_seen": set(),
}


class QuantumMEVBot:
    """High-performance Raydium MEV bot with GPU acceleration support"""
    
    def __init__(self):
        """Initialize the MEV bot"""
        logger.system("Initializing Quantum MEV Bot...")
        
        # Initialize empty maps if not already done
        self.target_pools = {}
        self.pool_to_token = {}
        
        # System initialization
        self.initialize_system()
        
        # Initialize configuration
        self.initialize_config()
        
        # Set core configuration options
        # IMPORTANT: Initialize auto_add_pools BEFORE initializing target pools
        self.auto_add_pools = config.get_bool("auto_add_pools", True)
        
        # Set correct Raydium instruction type for swap
        self.RAYDIUM_SWAP_INSTRUCTION = 0x09  # Raydium V4 uses 0x09 for swaps
        
        # Initialize trading core (Rust)
        self.initialize_trading_core()
        
        # Initialize target pools
        self.initialize_target_pools()
        
        # Core state tracking
        self.last_execution_time = 0
        self.last_trade_signature = None
        self.active_trades = threading.Semaphore(config.get_int("max_active_mevs", 1))
        self.seen_txs = set()
        self.pool_security_cache = {}
        self.current_position = None
        
        logger.system(f"Bot initialized with {len(self.target_pools)} target pools")
        if self.auto_add_pools:
            logger.system("Auto-add pools mode is ENABLED - New pools will be automatically added")
        else:
            logger.system("Auto-add pools mode is DISABLED - Only trading on pre-configured pools")
    
    def initialize_system(self):
        """Initialize system settings and CUDA if available"""
        # Create required directories
        os.makedirs("logs", exist_ok=True)
        os.makedirs("config", exist_ok=True)
        
        # Set up GPU if enabled and available
        if HAVE_ML_MODULES and config.get_bool("use_gpu", False):
            self.setup_gpu()
        else:
            logger.info("GPU acceleration disabled")
            self.has_gpu = False
    
    def setup_gpu(self):
        """Configure GPU acceleration if available"""
        try:
            # Configure CUDA settings
            if torch.cuda.is_available():
                # Get GPU settings from config
                gpu_memory_fraction = config.get_float("gpu_memory_fraction", 0.95)
                allow_tf32 = config.get_bool("allow_tf32", True)
                cuda_benchmark = config.get_bool("cuda_benchmark", True)
                cuda_deterministic = config.get_bool("cuda_deterministic", False)
                
                # Apply settings
                torch.backends.cuda.matmul.allow_tf32 = allow_tf32
                torch.backends.cudnn.benchmark = cuda_benchmark
                torch.backends.cudnn.deterministic = cuda_deterministic
                
                # Configure PyTorch settings
                if config.get_bool("use_amp", True):
                    torch.set_float32_matmul_precision('high')
                
                # Log GPU info
                gpu_name = torch.cuda.get_device_name(0)
                gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
                logger.system(f"GPU acceleration enabled: {gpu_name} ({gpu_memory:.2f} GB)")
                
                # Configure NumPy
                np.set_printoptions(precision=6, suppress=True)
                threads = config.get_int("threads", 48)
                np.set_num_threads(threads)
                logger.system(f"NumPy configured with {threads} threads")
                
                self.has_gpu = True
            else:
                logger.warning("CUDA requested but no GPU available, using CPU")
                self.has_gpu = False
                
        except Exception as e:
            logger.warning(f"GPU setup error: {e}")
            self.has_gpu = False
    
    def initialize_config(self):
        """Initialize configuration and parameters"""
        # Log key configuration values
        logger.info(f"RPC URL: {config.get('rpc_url')}")
        logger.info(f"WebSocket URL: {config.get('ws_url')}")
        logger.info(f"Public Key: {config.get('solana_public_key')}")
        
        # Create config directory if needed
        DEFAULT_CONFIG_DIR.mkdir(exist_ok=True, parents=True)
        
        # Trading parameters
        self.front_run_amount = config.get_float("front_run_amount", 0.3)
        self.min_victim_size = config.get_float("sandwich_min_victim_size", 5.0)
        self.max_victim_size = config.get_float("sandwich_max_victim_size", 320.0)
        self.min_profit = config.get_float("min_profit", 0.002)
        self.min_trade_interval = config.get_float("min_trade_interval", 0.05)
        self.execution_min_interval = config.get_float("execution_min_interval", 1.0)
        self.slippage_bps = config.get_int("slippage_bps", 1000)
        self.max_retries = config.get_int("max_retries", 3)
        
        # Log important settings
        logger.info(f"Minimum victim size: {self.min_victim_size} SOL")
        logger.info(f"Maximum victim size: {self.max_victim_size} SOL")
        logger.info(f"Front-run amount: {self.front_run_amount} SOL")
        logger.info(f"Minimum profit: {self.min_profit} SOL")
    
    def initialize_trading_core(self):
        """Initialize the Rust trading core"""
        try:
            # Get keypair path
            keypair_path = config.get("keypair_path")
            if not keypair_path:
                logger.error("No keypair path specified")
                sys.exit(1)
                
            # Create trading core instance
            self.trading_core = RaydiumTradingCore(
                rpc_url=config.get("rpc_url"),
                keypair_path=keypair_path
            )
            
            # Get public key for verification
            public_key = self.trading_core.get_public_key()
            logger.system(f"Trading core initialized with public key: {public_key}")
            
            # Verify public key matches config
            expected_public_key = config.get("solana_public_key")
            if expected_public_key and expected_public_key != public_key:
                logger.warning(f"Public key mismatch: {public_key} (actual) vs {expected_public_key} (config)")
            
            # Get balances (async)
            asyncio.run(self.check_balances())
            
        except Exception as e:
            logger.critical(f"Failed to initialize trading core: {e}")
            sys.exit(1)
    
    async def check_balances(self):
        """Check SOL and token balances"""
        try:
            # Get balances
            balances = await self.trading_core.get_balances()
            sol_balance, token_balances = balances
            
            # Log SOL balance
            logger.info(f"SOL balance: {sol_balance:.6f} SOL")
            
            # Update global balance tracking
            balance_updates["SOL"] = sol_balance
            balance_updates["last_update"] = time.time()
            
            # Check if WSOL balance exists
            wsol_address = config.get("wsol_address")
            if wsol_address in token_balances:
                wsol_balance = token_balances[wsol_address]
                logger.info(f"WSOL balance: {wsol_balance:.6f} SOL")
                balance_updates["WSOL"] = wsol_balance
            else:
                logger.warning("No WSOL balance found")
            
            # Check minimum balance requirements
            min_sol_balance = config.get_float("min_sol_balance", 0.03)
            min_wsol_balance = config.get_float("min_wsol_balance", 0.02)
            
            if sol_balance < min_sol_balance:
                logger.warning(f"SOL balance ({sol_balance:.6f}) below minimum ({min_sol_balance:.6f})")
            
            if wsol_address in token_balances and token_balances[wsol_address] < min_wsol_balance:
                logger.warning(f"WSOL balance ({token_balances[wsol_address]:.6f}) below minimum ({min_wsol_balance:.6f})")
            
        except Exception as e:
            logger.error(f"Failed to check balances: {e}")
    
    def initialize_target_pools(self):
        """Load and initialize target pools"""
        # Check if target list file exists
        if not DEFAULT_TARGET_LIST_PATH.exists():
            # Create empty target list
            logger.warning(f"Target list not found, creating empty list at {DEFAULT_TARGET_LIST_PATH}")
            with open(DEFAULT_TARGET_LIST_PATH, 'w') as f:
                json.dump({}, f, indent=2)
        
        # Load target pools from file
        try:
            with open(DEFAULT_TARGET_LIST_PATH, 'r') as f:
                self.target_pools = json.load(f)
            
            # Create lookup maps
            if self.target_pools:
                self.pool_to_token = {
                    pool_data["pool"]: token_mint 
                    for token_mint, pool_data in self.target_pools.items()
                    if "pool" in pool_data  # Add safety check
                }
                
                # Log target pool details for debugging
                logger.info(f"Target pools loaded: {len(self.target_pools)}")
                for token_mint, pool_data in self.target_pools.items():
                    logger.info(f"  - {token_mint[:8]}... : {pool_data.get('pool', 'No pool')[:8]}... (score: {pool_data.get('score', 0)})")
            else:
                logger.warning("Target pools file is empty. No pools will be monitored.")
                
                # Add demo pool for testing if auto_add is enabled
                if self.auto_add_pools and config.get_bool("add_demo_pool", False):
                    logger.info("Adding demo pool for testing purposes")
                    # Popular token to demonstrate functionality
                    demo_pool = "CK1Df6JE4sEMCTJA7nfSXYHBFLRUWGV6rX9QNyHvuPeU" 
                    demo_token = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" # USDC
                    self.add_target_pool(demo_token, demo_pool, auto_detected=False)
            
            # Count high-score targets (score >= 80)
            high_score_targets = len([t for t in self.target_pools.values() if t.get("score", 0) >= 80])
            
            logger.system(f"Loaded {len(self.target_pools)} target pools ({high_score_targets} high-score)")
            
        except Exception as e:
            logger.error(f"Failed to load target pools: {e}")
            # Ensure we have empty containers even after exception
            self.target_pools = {}
            self.pool_to_token = {}
    
    def add_target_pool(self, token_mint: str, pool_id: str, auto_detected: bool = False) -> bool:
        """Add a new target pool to the monitoring list
        
        Args:
            token_mint: The token mint address
            pool_id: The Raydium pool ID
            auto_detected: Whether this pool was automatically detected
            
        Returns:
            bool: True if added successfully, False otherwise
        """
        try:
            if token_mint in self.target_pools:
                logger.info(f"Pool for token {token_mint[:8]}... already exists")
                return False
                
            # Create a new pool entry
            pool_data = {
                "pool": pool_id,
                "score": 50 if auto_detected else 80,
                "mint_revoked": False,  # Conservative default
                "lp_locked": False,     # Conservative default
                "exit_liquidity": 15.0,  # Moderate default
                "auto_detected": auto_detected,
                "detection_time": time.time(),
                
                # Default AMM accounts (will need verification)
                "amm_authority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
                "amm_open_orders": "",
                "amm_target": "",
                "pool_token_account": "",
                "pool_pc_account": "",
                "serum_market": "",
                "serum_bids": "",
                "serum_asks": "",
                "serum_event_queue": "",
                "serum_coin_vault": "",
                "serum_pc_vault": "",
                "serum_vault_signer": ""
            }
            
            # Add to internal maps
            self.target_pools[token_mint] = pool_data
            self.pool_to_token[pool_id] = token_mint
            
            # Save to file (optional - could be slow if done too frequently)
            if not auto_detected or len(self.target_pools) % 10 == 0:  # Save every 10 auto-detections
                try:
                    with open(DEFAULT_TARGET_LIST_PATH, 'w') as f:
                        json.dump(self.target_pools, f, indent=2)
                    logger.info(f"Target pools saved to {DEFAULT_TARGET_LIST_PATH}")
                except Exception as e:
                    logger.error(f"Error saving target pools: {e}")
            
            logger.info(f"Added new target pool: {token_mint[:8]}... ({pool_id[:8]}...)")
            return True
            
        except Exception as e:
            logger.error(f"Error adding target pool: {e}")
            return False
    
    async def run(self):
        """Run the MEV bot"""
        logger.system("Starting Quantum MEV Bot...")
        bot_running.set()
        
        # Start worker threads
        self.start_worker_threads()
        
        # Start balance monitoring
        balance_task = asyncio.create_task(self.monitor_balances())
        
        # Start monitoring WebSocket
        try:
            await self.monitor_mempool()
        except KeyboardInterrupt:
            logger.system("Keyboard interrupt detected, shutting down...")
        except Exception as e:
            logger.critical(f"Error in main loop: {e}", extra={"error": str(e)})
        finally:
            # Signal threads to stop
            bot_running.clear()
            command_queue.put(("shutdown", None))
            
            # Cancel balance monitoring
            balance_task.cancel()
            try:
                await balance_task
            except asyncio.CancelledError:
                pass
                
            logger.system("Bot shutdown complete")
    
    def start_worker_threads(self):
        """Start worker threads for trade execution and monitoring"""
        # Start execution thread
        self.execution_thread = threading.Thread(
            target=self.execution_worker, 
            name="execution_worker", 
            daemon=True
        )
        self.execution_thread.start()
        
        # Start stats thread
        self.stats_thread = threading.Thread(
            target=self.stats_worker, 
            name="stats_worker", 
            daemon=True
        )
        self.stats_thread.start()
        
        logger.system("Worker threads started")
    
    async def monitor_balances(self):
        """Monitor SOL and WSOL balances via WebSocket subscription"""
        retry_delay = 1
        max_retry_delay = 30
        
        while bot_running.is_set():
            try:
                # Connect to WebSocket with API key in headers
                async with websockets.connect(
                    config.get("ws_url"),
                    extra_headers={"api-key": config.get("api_key")},
                    ping_interval=20,
                    ping_timeout=30,
                    close_timeout=60
                ) as ws:
                    logger.info("Connected to balance monitoring WebSocket")
                    
                    # Accounts to monitor
                    accounts = {
                        "SOL": config.get("solana_public_key"),
                        "WSOL": config.get("wsol_token_account"),
                    }
                    
                    # Subscribe to each account
                    subscriptions = {}
                    for acc_type, address in accounts.items():
                        if not address:
                            continue
                            
                        sub_id = len(subscriptions) + 1
                        await ws.send(
                            json.dumps({
                                "jsonrpc": "2.0",
                                "id": sub_id,
                                "method": "accountSubscribe",
                                "params": [
                                    address,
                                    {"encoding": "base64", "commitment": "confirmed"}
                                ]
                            })
                        )
                        
                        # Store mapping of subscription IDs to account types
                        response = await ws.recv()
                        data = json.loads(response)
                        if "result" in data:
                            subscriptions[data["result"]] = acc_type
                    
                    # Reset retry delay on successful connection
                    retry_delay = 1
                    
                    # Process balance updates
                    while bot_running.is_set():
                        try:
                            msg = await asyncio.wait_for(ws.recv(), timeout=30)
                            data = json.loads(msg)
                            
                            # Handle account notifications
                            if "method" in data and data["method"] == "accountNotification":
                                sub_id = data["params"]["subscription"]
                                acc_type = subscriptions.get(sub_id)
                                
                                if acc_type:
                                    # Extract lamports for SOL account
                                    if acc_type == "SOL":
                                        lamports = data["params"]["result"]["value"]["lamports"]
                                        balance = lamports / LAMPORTS_PER_SOL
                                        balance_updates["SOL"] = balance
                                        balance_updates["last_update"] = time.time()
                                        logger.info(f"SOL Balance updated: {balance:.6f} SOL")
                                    
                                    # Parse token account data for WSOL
                                    elif acc_type == "WSOL":
                                        # Decode base64 data from the account
                                        encoded_data = data["params"]["result"]["value"]["data"][0]
                                        decoded_data = base64.b64decode(encoded_data)
                                        
                                        # SPL Token account layout:
                                        # - mint: Pubkey (32 bytes)
                                        # - owner: Pubkey (32 bytes)
                                        # - amount: u64 (8 bytes) - at offset 64
                                        # - delegate: Option<Pubkey>
                                        # - ...other fields
                                        
                                        # Extract token amount at offset 64, as u64 little-endian
                                        if len(decoded_data) >= 72:  # Make sure we have enough data
                                            amount = int.from_bytes(decoded_data[64:72], byteorder="little")
                                            wsol_balance = amount / (10 ** 9)  # WSOL uses 9 decimals
                                            balance_updates["WSOL"] = wsol_balance
                                            balance_updates["last_update"] = time.time()
                                            logger.info(f"WSOL Balance updated: {wsol_balance:.6f} SOL")
                        
                        except asyncio.TimeoutError:
                            # Send ping to keep connection alive
                            await ws.ping()
                        except websockets.ConnectionClosed:
                            logger.warning("Balance monitoring connection closed")
                            break
                        
            except Exception as e:
                logger.error(f"Balance monitoring error: {e}")
                
                if not bot_running.is_set():
                    break
                    
                # Retry with backoff
                logger.info(f"Reconnecting balance monitor in {retry_delay}s...")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)
    
    async def monitor_mempool(self):
        """Monitor mempool for Raydium transactions"""
        retry_delay = 1
        max_retry_delay = 30
        
        # Get WebSocket URL
        ws_url = config.get("helius_ws_url") or config.get("ws_url")
        raydium_program_id = config.get("raydium_program_id", "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
        
        # Option to auto-add pools
        if self.auto_add_pools:
            logger.system("Auto-add pools mode is ENABLED - New pools will be automatically added to target list")
        else:
            logger.system("Auto-add pools mode is DISABLED - Only trading on pre-configured pools")
        
        logger.system(f"Monitoring mempool for Raydium transactions at: {ws_url}")
        
        # Add tracking for unique pools seen
        unique_pools_seen = set()
        
        while bot_running.is_set():
            try:
                # Connect to WebSocket
                async with websockets.connect(
                    ws_url,
                    extra_headers={"api-key": config.get("api_key")},
                    ping_interval=20,
                    ping_timeout=30,
                    close_timeout=60
                ) as ws:
                    logger.info("Connected to mempool WebSocket")
                    
                    # Subscribe to Raydium program logs
                    subscription_msg = json.dumps({
                        "jsonrpc": "2.0",
                        "id": 1,
                        "method": "logsSubscribe",
                        "params": [
                            {"mentions": [raydium_program_id]},
                            {"commitment": config.get("commitment_level", "processed")}
                        ]
                    })
                    
                    await ws.send(subscription_msg)
                    
                    # Check subscription confirmation
                    response = await ws.recv()
                    confirmation = json.loads(response)
                    
                    if "result" in confirmation:
                        subscription_id = confirmation["result"]
                        logger.system(f"Subscription successful (ID: {subscription_id})")
                    else:
                        logger.error(f"Subscription failed: {confirmation}")
                        raise Exception("Failed to subscribe to logs")
                    
                    # Reset retry delay on successful connection
                    retry_delay = 1
                    
                    # Process WebSocket messages
                    while bot_running.is_set():
                        try:
                            # Receive message with timeout
                            msg = await asyncio.wait_for(ws.recv(), timeout=30)
                            
                            # Process the message
                            await self.process_mempool_message(msg, unique_pools_seen)
                            
                        except asyncio.TimeoutError:
                            # Send a ping to keep connection alive
                            await ws.ping()
                        except websockets.ConnectionClosed:
                            logger.warning("WebSocket connection closed")
                            break
            
            except Exception as e:
                logger.error(f"WebSocket error: {e}")
                
                # Check if bot is still running
                if not bot_running.is_set():
                    break
                
                # Exponential backoff for reconnection
                logger.warning(f"Reconnecting in {retry_delay} seconds...")
                await asyncio.sleep(retry_delay)
                retry_delay = min(retry_delay * 2, max_retry_delay)
                
        # Log statistics about unique pools seen
        stats["unique_pools_seen"] = unique_pools_seen
        logger.system(f"Unique pools seen during this session: {len(unique_pools_seen)}")
    
    async def process_mempool_message(self, message: str, unique_pools_seen: set = None):
        """Process a mempool message"""
        try:
            # Parse the message
            data = json.loads(message)
            
            # Update stats
            stats["total_logs"] += 1
            
            # Check if valid notification with logs
            if "params" in data and "result" in data["params"]:
                result = data["params"]["result"]
                
                # Extract logs and signature
                if "value" in result:
                    value = result["value"]
                    logs = value.get("logs", [])
                    signature = value.get("signature", "unknown")
                    
                    # Skip if no logs or already seen
                    if not logs or signature in self.seen_txs:
                        return
                    
                    # Add to seen transactions
                    self.seen_txs.add(signature)
                    if len(self.seen_txs) > 5000:  # Limit cache size
                        self.seen_txs = set(list(self.seen_txs)[-2500:])
                    
                    # Check for ray_log entries
                    for log in logs:
                        if "ray_log:" in log:
                            stats["logs_with_ray"] += 1
                            
                            # Decode and process the ray_log
                            decoded = self.decode_ray_log(log)
                            if decoded:
                                # Add signature to decoded data
                                decoded["signature"] = signature
                                
                                # Attempt to extract token info even if not immediately actioning
                                # This helps with discovery mode
                                try:
                                    if unique_pools_seen is not None:
                                        token_info = self.extract_token_info(logs)
                                        if token_info:
                                            token_mint, pool_id = token_info
                                            # Track unique pools seen
                                            unique_pools_seen.add(pool_id)
                                            
                                            # If in auto-add mode and this is a substantial trade, consider adding it
                                            amount_sol = decoded.get("value_1_sol", 0)
                                            if (self.auto_add_pools and 
                                                amount_sol >= self.min_victim_size and 
                                                amount_sol <= self.max_victim_size and
                                                pool_id not in self.pool_to_token and 
                                                token_mint not in self.target_pools):
                                                
                                                # Log pool discovery
                                                logger.info(f"Discovered new pool: {pool_id[:8]}... for token {token_mint[:8]}... ({amount_sol:.2f} SOL)")
                                                
                                                # Auto-add to target pools
                                                self.add_target_pool(token_mint, pool_id, auto_detected=True)
                                except Exception as e:
                                    logger.error(f"Error in pool discovery: {e}")
                                
                                # Process the decoded instruction
                                await self.process_instruction(decoded, signature, logs)
        
        except json.JSONDecodeError:
            # Ignore invalid JSON
            pass
        except Exception as e:
            logger.error(f"Error processing mempool message: {e}")
            stats["errors"] += 1
    
    def decode_ray_log(self, log_line: str) -> Optional[Dict]:
        """Decode Raydium ray_log data from log line"""
        try:
            if "ray_log:" not in log_line:
                return None
            
            # Extract the base64 encoded part after ray_log:
            ray_log_part = log_line.split("ray_log:")[1].strip()
            raw_data = base64.b64decode(ray_log_part)
            
            if len(raw_data) < 9:  # Need at least 9 bytes for valid instruction
                return None
            
            # Get instruction type (first byte)
            instr_type = raw_data[0]
            instr_hex = f"0x{instr_type:02x}"
            
            # Update instruction type counts
            stats["instruction_counts"][instr_hex] = stats["instruction_counts"].get(instr_hex, 0) + 1
            
            # We care about 0x03, 0x06, and 0x09 instructions
            if instr_type in (0x03, 0x06, 0x09):
                # Parse the instruction data
                return self.parse_instruction(raw_data, instr_type)
            
            return None
            
        except Exception as e:
            logger.error(f"Error decoding ray_log: {e}")
            return None
    
    def parse_instruction(self, raw_data: bytes, instr_type: int) -> Optional[Dict]:
        """Parse Raydium instruction data"""
        try:
            # Basic data structure for instructions
            data = {
                "instruction_type": f"0x{instr_type:02x}",
                "timestamp": datetime.now().strftime('%H:%M:%S.%f')[:-3],
                "raw_data": raw_data
            }
            
            # Extract amount bytes (position 1-9)
            if len(raw_data) >= 9:
                amount_bytes = raw_data[1:9]
                amount_lamports = int.from_bytes(amount_bytes, byteorder="little")
                data["amount_lamports"] = amount_lamports
                data["amount_sol"] = amount_lamports / LAMPORTS_PER_SOL
            
            # For 0x03 instruction, extract additional fields if available
            if instr_type == 0x03 and len(raw_data) >= 33:
                # Extract up to 4 uint64 values (we need vals 1-4 for 0x03)
                for i in range(4):
                    start_pos = 1 + (i * 8)
                    end_pos = start_pos + 8
                    
                    if len(raw_data) >= end_pos:
                        val = int.from_bytes(raw_data[start_pos:end_pos], byteorder="little")
                        data[f"value_{i+1}"] = val
                        data[f"value_{i+1}_sol"] = val / LAMPORTS_PER_SOL
            
            # For 0x09 instruction, extract amount and minimum out
            elif instr_type == 0x09 and len(raw_data) >= 17:
                # First 8 bytes are the input amount
                # Next 8 bytes are the minimum expected output
                min_out_bytes = raw_data[9:17]
                min_out_lamports = int.from_bytes(min_out_bytes, byteorder="little")
                data["min_out_lamports"] = min_out_lamports
                data["min_out_sol"] = min_out_lamports / LAMPORTS_PER_SOL
                
                # Also extract values like 0x03 for consistency
                for i in range(2):  # Extract first 2 values
                    start_pos = 1 + (i * 8)
                    end_pos = start_pos + 8
                    
                    if len(raw_data) >= end_pos:
                        val = int.from_bytes(raw_data[start_pos:end_pos], byteorder="little")
                        data[f"value_{i+1}"] = val
                        data[f"value_{i+1}_sol"] = val / LAMPORTS_PER_SOL
            
            return data
                
        except Exception as e:
            logger.error(f"Error parsing instruction: {e}")
            return None
    
    async def process_instruction(self, decoded: Dict, signature: str, logs: List[str]):
        """Process a decoded instruction"""
        try:
            # Extract the amount
            amount_sol = decoded.get("amount_sol", 0)
            instr_type = decoded.get("instruction_type", "unknown")
            
            # Check if we're currently in a position and need to exit
            if self.current_position is not None:
                # If we see a victim trade execute, sell our position
                if instr_type in ("0x03", "0x06", "0x09") and signature != self.last_trade_signature:
                    logger.info(f"Detected potential victim trade execution: {signature}")
                    # Queue up position exit
                    command_queue.put(("exit_position", self.current_position))
                    return
            
            # Log the instruction for debugging
            logger.debug(f"Detected {instr_type} instruction: {amount_sol:.6f} SOL (tx: {signature[:8]}...)")
            
            # Check if this is a trade we're interested in
            if self.is_actionable_trade(decoded, signature, logs):
                # Extract token mint and pool from logs
                token_info = self.extract_token_info(logs)
                if not token_info:
                    # Add more diagnostic logging
                    logger.debug(f"Could not extract token info from tx: {signature[:8]}...")
                    return
                
                token_mint, pool_id = token_info
                
                # Check if this is a target pool
                if pool_id not in self.pool_to_token and token_mint not in self.target_pools:
                    # Add debug logging to help diagnose why no opportunities are being found
                    logger.debug(f"Found potential pool {pool_id[:8]}... for {token_mint[:8]}... but not in target list")
                    
                    # Auto-add this pool if enabled
                    if self.auto_add_pools:
                        logger.info(f"Auto-adding new pool {pool_id[:8]}... for token {token_mint[:8]}... to target list")
                        self.add_target_pool(token_mint, pool_id, auto_detected=True)
                    else:
                        return
                
                # Create opportunity
                opportunity = {
                    "decoded": decoded,
                    "signature": signature,
                    "token_mint": token_mint,
                    "pool_id": pool_id,
                    "amount_sol": amount_sol,
                    "timestamp": time.time(),
                    "instruction_type": instr_type,
                    "logs": logs  # Include logs for account extraction
                }
                
                # Add to opportunity queue
                stats["opportunities"] += 1
                opportunity_queue.put(opportunity)
                logger.info(f"Found opportunity: {token_mint} {amount_sol:.4f} SOL (instruction: {instr_type})")
                
        except Exception as e:
            logger.error(f"Error processing instruction: {e}")
    
    def is_actionable_trade(self, decoded: Dict, signature: str, logs: List[str]) -> bool:
        """Check if a trade is actionable (size within range, etc.)"""
        # Check if trade is within size limits
        amount_sol = decoded.get("amount_sol", 0)
        if amount_sol < self.min_victim_size or amount_sol > self.max_victim_size:
            # Add debug logging for size filtering
            if amount_sol >= 1.0:  # Only log for substantial amounts to avoid spam
                logger.debug(f"Trade size filter: {amount_sol:.4f} SOL not in range [{self.min_victim_size}-{self.max_victim_size}] SOL")
            return False
        
        # Check trading interval
        current_time = time.time()
        if current_time - self.last_execution_time < self.min_trade_interval:
            logger.debug(f"Trade interval filter: Last trade was {current_time - self.last_execution_time:.3f}s ago")
            return False
        
        # Check if we are at max active trades
        if len(active_trades) >= config.get_int("max_active_mevs", 1):
            logger.debug(f"Max active trades filter: {len(active_trades)} active trades")
            return False
        
        # Check if we're already in a position
        if self.current_position is not None:
            logger.debug("Position filter: Already in a position")
            return False
            
        # Check WSOL balance
        wsol_balance = balance_updates.get("WSOL", 0)
        if wsol_balance < self.front_run_amount + 0.01:  # Add buffer for fees
            logger.warning(f"Insufficient WSOL balance: {wsol_balance:.6f} < {self.front_run_amount + 0.01:.6f}")
            return False
        
        # Additional checks based on instruction type
        instr_type = decoded.get("instruction_type", "unknown")
        
        # For 0x03 instructions, check additional fields
        if instr_type == "0x03":
            # Check if instruction has the expected fields
            if not all(f"value_{i}" in decoded for i in range(1, 5)):
                logger.debug(f"Instruction format filter: Missing required fields for 0x03 instruction")
                return False
        
        # Log that this trade has passed all filters
        logger.debug(f"Trade passed all actionable filters: {amount_sol:.4f} SOL (tx: {signature[:8]}...)")
        return True
    
    def extract_token_info(self, logs: List[str]) -> Optional[Tuple[str, str]]:
        """Extract token mint and pool ID from logs"""
        try:
            # Ensure pool_to_token exists
            if not hasattr(self, 'pool_to_token'):
                logger.warning("pool_to_token attribute not initialized, creating empty dict")
                self.pool_to_token = {}
                
            # Look for token mint and pool ID in logs
            token_mint = None
            pool_id = None
            
            # First look for direct pool references
            for log in logs:
                for target_pool in self.pool_to_token.keys():
                    if target_pool in log:
                        pool_id = target_pool
                        token_mint = self.pool_to_token[pool_id]
                        return token_mint, pool_id
            
            # Pull out all potential Solana addresses from logs
            potential_addresses = []
            raydium_program = config.get("raydium_program_id", "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
            wsol_mint = config.get("wsol_address", "So11111111111111111111111111111111111111112")
            
            for log in logs:
                words = log.split()
                for word in words:
                    # Check for Solana address format (base58, 32-44 chars)
                    if (len(word) >= 32 and len(word) <= 44 and 
                        word.isalnum() and 
                        word != raydium_program and 
                        word != wsol_mint):
                        potential_addresses.append(word)
            
            # Look for specific token-related patterns in logs
            for log in logs:
                # Pattern 1: Directly mentions token mint
                if "Token mint" in log or "token mint" in log:
                    parts = log.split()
                    for i, part in enumerate(parts):
                        if (part.lower() in ("mint", "token") and 
                            i < len(parts) - 1 and 
                            len(parts[i+1]) >= 32 and len(parts[i+1]) <= 44):
                            token_mint = parts[i+1]
            
                # Pattern 2: Look for Raydium pools
                if "raydium" in log.lower() or "amm" in log.lower():
                    for addr in potential_addresses:
                        # Prioritize addresses that could be pools
                        if "pool" in log.lower() and addr in log:
                            pool_id = addr
                        # Any address in a Raydium context could be a pool
                        elif pool_id is None:
                            pool_id = addr
            
            # If we still don't have a pool or token, make educated guesses
            if pool_id is None and len(potential_addresses) >= 2:
                # First address might be the pool, second might be token
                # This is a heuristic that works quite often with Raydium logs
                pool_id = potential_addresses[0]
                if token_mint is None and len(potential_addresses) > 1:
                    # Use second unique address as token mint
                    for addr in potential_addresses:
                        if addr != pool_id and addr != wsol_mint:
                            token_mint = addr
                            break
            
            # If we have a pool but no token, generate a temporary token mint
            # This allows auto-discovery to work even with partial information
            if pool_id and token_mint is None and self.auto_add_pools:
                # Generate a pseudo-token mint based on the pool ID
                # This will be replaced later when we get better information
                token_mint = f"TEMP_{pool_id[:16]}"
                logger.debug(f"Generated temporary token mint for pool {pool_id[:8]}...")
            
            # If we found both pool and token mint, return them
            if pool_id and token_mint:
                logger.debug(f"Extracted token info: {token_mint[:8]}... (pool: {pool_id[:8]}...)")
                return token_mint, pool_id
            
            # No match found, log details to help debugging
            if len(potential_addresses) > 0:
                logger.debug(f"Could not extract token info. Found {len(potential_addresses)} potential addresses.")
            return None
                
        except Exception as e:
            logger.error(f"Error extracting token info: {e}")
            logger.error(traceback.format_exc())
            return None
    
    async def get_transaction_accounts(self, signature: str, max_retries: int = 3) -> Optional[List[str]]:
        """Get transaction accounts from a transaction signature"""
        retry_delay = 0.2
        
        # Log transaction fetch attempt
        logger.debug(f"Fetching accounts for transaction: {signature[:12]}...")
        
        for attempt in range(max_retries):
            try:
                # Construct the RPC request
                request = {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "getTransaction",
                    "params": [
                        signature,
                        {"encoding": "json", "maxSupportedTransactionVersion": 0}
                    ]
                }
                
                # Send the request using WebSockets
                async with websockets.connect(
                    config.get("rpc_url"),
                    extra_headers={"api-key": config.get("api_key")},
                    ping_interval=20,
                    ping_timeout=30,
                    close_timeout=10
                ) as ws:
                    await ws.send(json.dumps(request))
                    response = await ws.recv()
                    data = json.loads(response)
                    
                    if "result" in data and data["result"]:
                        # Transaction found
                        transaction = data["result"]
                        
                        # Extract all account keys
                        account_keys = transaction["transaction"]["message"]["accountKeys"]
                        
                        # Find Raydium instructions
                        raydium_program_id = config.get("raydium_program_id", "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
                        raydium_accounts = []
                        raydium_program_index = None
                        
                        # First identify the Raydium program index
                        for i, key in enumerate(account_keys):
                            if key == raydium_program_id:
                                raydium_program_index = i
                                break
                        
                        # Find instructions that use the Raydium program
                        for instruction in transaction["transaction"]["message"]["instructions"]:
                            # Check both by programId and by program index
                            if (instruction["programId"] == raydium_program_id or 
                                (raydium_program_index is not None and 
                                 "programIdIndex" in instruction and 
                                 instruction["programIdIndex"] == raydium_program_index)):
                                
                                # This is a Raydium instruction
                                # Get accounts involved in this instruction
                                if "accounts" in instruction:
                                    # Direct account indices
                                    for account_idx in instruction["accounts"]:
                                        account = account_keys[account_idx]
                                        if account not in raydium_accounts:
                                            raydium_accounts.append(account)
                                            
                                # Also check for any data that might contain account references
                                if "data" in instruction and instruction["data"]:
                                    # Try to decode hex data and see if it contains a 0x09 instruction
                                    try:
                                        data_bytes = bytes.fromhex(instruction["data"])
                                        if len(data_bytes) > 0 and data_bytes[0] == 0x09:
                                            logger.debug(f"Found 0x09 instruction in data: {data_bytes.hex()[:20]}...")
                                    except:
                                        pass
                                
                        # Return Raydium accounts if found
                        if raydium_accounts:
                            logger.debug(f"Found {len(raydium_accounts)} Raydium accounts in transaction")
                            # Ensure we include the Raydium program itself
                            if raydium_program_id not in raydium_accounts:
                                raydium_accounts.insert(0, raydium_program_id)
                            return raydium_accounts
                                
                        # If we got here and no specific Raydium instruction found,
                        # return all accounts with Raydium program first if present
                        if account_keys:
                            if raydium_program_id in account_keys:
                                # Move Raydium program to front
                                keys = [raydium_program_id]
                                for k in account_keys:
                                    if k != raydium_program_id:
                                        keys.append(k)
                                return keys
                            return account_keys
                            
                        return None
                        
                    # Transaction not found, retry with backoff
                    logger.warning(f"Transaction {signature} not found (attempt {attempt + 1}/{max_retries})")
                    if attempt < max_retries - 1:
                        backoff = retry_delay * (2 ** attempt)  # Exponential backoff
                        await asyncio.sleep(backoff)
                    
            except Exception as e:
                logger.error(f"Error fetching transaction {signature}: {e}")
                if attempt < max_retries - 1:
                    backoff = retry_delay * (2 ** attempt)
                    await asyncio.sleep(backoff)
                else:
                    logger.error(traceback.format_exc())
        
        # If we got here, all retries failed
        logger.warning(f"Failed to fetch transaction {signature} after {max_retries} attempts")
        return None
    
    def execution_worker(self):
        """Worker thread for executing trades"""
        logger.system("Execution worker started")
        
        while bot_running.is_set():
            try:
                # Check command queue first for high-priority commands
                try:
                    command, args = command_queue.get_nowait()
                    if command == "shutdown":
                        logger.system("Execution worker received shutdown command")
                        break
                    elif command == "exit_position" and args is not None:
                        # Exit current position
                        asyncio.run(self.exit_position(args))
                    command_queue.task_done()
                except queue.Empty:
                    pass
                
                # Don't process new opportunities if in a position
                if self.current_position is not None:
                    time.sleep(0.01)  # Short sleep to avoid CPU spinning
                    continue
                    
                # Check if we have any opportunities to process
                try:
                    opportunity = opportunity_queue.get(block=False)
                    
                    # Check if this opportunity is still valid
                    current_time = time.time()
                    opportunity_age = current_time - opportunity.get("timestamp", current_time)
                    
                    # Skip stale opportunities (older than 2 seconds)
                    if opportunity_age > 2.0:
                        logger.info(f"Skipping stale opportunity ({opportunity_age:.2f}s old)")
                        opportunity_queue.task_done()
                        continue
                    
                    # Execute trade with atomic entry
                    logger.info(f"Processing opportunity for {opportunity['token_mint']}")
                    asyncio.run(self.execute_trade(opportunity))
                    
                    # Mark as processed
                    opportunity_queue.task_done()
                    
                except queue.Empty:
                    # No opportunities available, wait briefly
                    time.sleep(0.01)
                    
            except Exception as e:
                logger.error(f"Error in execution worker: {e}")
                stats["errors"] += 1
                time.sleep(0.1)  # Add delay on error
        
        logger.system("Execution worker stopped")
    
    async def execute_trade(self, opportunity: Dict):
        """Execute a trade based on an opportunity"""
        # Extract key information
        token_mint = opportunity["token_mint"]
        amount_sol = opportunity["amount_sol"]
        signature = opportunity["signature"]
        pool_id = opportunity["pool_id"]
        instr_type = opportunity["instruction_type"]
        logs = opportunity.get("logs", [])
        
        logger.info(f"Executing trade for {token_mint} (pool: {pool_id[:8]}... {amount_sol:.4f} SOL)")
        
        try:
            # Add to active trades
            active_trades.add(signature)
            
            # Update stats
            stats["executions"] += 1
            self.last_execution_time = time.time()
            self.last_trade_signature = signature
            
            # Only attempt to execute if trading is enabled
            if not config.get_bool("trading", True):
                logger.info(f"Trading disabled, simulating trade for {token_mint}")
                time.sleep(0.2)  # Simulate execution time
                active_trades.remove(signature)
                return
            
            # Handle temporary token mints specially
            if token_mint.startswith("TEMP_"):
                logger.info(f"Working with temporary token mint: {token_mint}")
                # For temporary tokens, we need to extract more information from logs
                for log in logs:
                    # Look for any mention of a token mint address
                    if "mint" in log.lower() or "token" in log.lower():
                        parts = log.split()
                        for part in parts:
                            if len(part) >= 32 and len(part) <= 44 and part.isalnum():
                                # This could be the real token mint
                                potential_mint = part
                                if potential_mint != pool_id and not potential_mint.startswith("TEMP_"):
                                    token_mint = potential_mint
                                    logger.info(f"Found real token mint: {token_mint[:8]}...")
                                    break
            
            # Verify pool security
            security_result = await self.verify_pool_security(pool_id, token_mint)
            if not security_result["is_valid"]:
                logger.warning(f"Pool security verification failed for {token_mint}: {security_result}")
                active_trades.remove(signature)
                return
                
            # Use the WSOL balance from balance_updates
            wsol_balance = balance_updates.get("WSOL", 0)
            if wsol_balance < self.front_run_amount + 0.01:  # Add buffer for fees
                logger.warning(f"Insufficient WSOL balance: {wsol_balance:.6f} SOL")
                active_trades.remove(signature)
                return
                
            # Ensure token ATA exists
            token_ata = await self.trading_core.ensure_ata(token_mint)
            if not token_ata:
                logger.error(f"Failed to ensure token ATA for {token_mint}")
                active_trades.remove(signature)
                return
                
            # Get token balance before trade (to verify the difference after)
            balances_before = await self.trading_core.get_balances()
            _, token_balances_before = balances_before
            token_balance_before = token_balances_before.get(token_mint, 0)
                
            # Get transaction accounts for route mirroring
            accounts = await self.get_transaction_accounts(signature, max_retries=self.max_retries)
            
            if not accounts or len(accounts) < 10:
                logger.warning(f"Could not get transaction accounts for {signature}, using default accounts")
                # We'll try to extract accounts from logs
                
                # Extract potential accounts from logs
                potential_accounts = []
                raydium_program_id = config.get("raydium_program_id", "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")
                wsol_mint = config.get("wsol_address", "So11111111111111111111111111111111111111112")
                
                # Add known required accounts
                if raydium_program_id not in potential_accounts:
                    potential_accounts.append(raydium_program_id)
                
                # Extract accounts from logs
                for log in logs:
                    words = log.split()
                    for word in words:
                        if len(word) >= 32 and len(word) <= 44 and word.isalnum():
                            if word not in potential_accounts:
                                potential_accounts.append(word)
                
                # If we found enough accounts, use them
                if len(potential_accounts) >= 10:
                    accounts = potential_accounts
                    logger.info(f"Extracted {len(accounts)} accounts from logs")
            
            # Create instruction data - IMPORTANT: Use 0x09 for Raydium V4 swaps
            # Convert original instruction data to our front-run amount
            decoded_data = opportunity["decoded"]["raw_data"]
            
            # Modify instruction data:
            # 1. Change instruction type to 0x09 (Raydium V4 swap)
            # 2. Replace amount with our front-run amount
            
            # Create a copy of the instruction data
            new_instruction_data = bytearray(decoded_data)
            
            # Set instruction type to 0x09 (Raydium V4 swap)
            new_instruction_data[0] = self.RAYDIUM_SWAP_INSTRUCTION  # 0x09
            
            # Replace amount with our front-run amount
            front_run_lamports = int(self.front_run_amount * LAMPORTS_PER_SOL)
            amount_bytes = front_run_lamports.to_bytes(8, byteorder="little")
            
            # Copy the new amount bytes into the instruction data
            for i in range(8):
                if 1 + i < len(new_instruction_data):
                    new_instruction_data[1 + i] = amount_bytes[i]
            
            # Ensure we have at least 9 bytes (instruction type + amount)
            if len(new_instruction_data) < 9:
                # Extend to 9 bytes if needed
                new_instruction_data.extend(b'\x00' * (9 - len(new_instruction_data)))
            
            # For 0x09 instructions, we also need min out amount (bytes 9-17)
            # Calculate min out with slippage
            min_out_amount = int(front_run_lamports * (1 - (self.slippage_bps / 10000)))
            min_out_bytes = min_out_amount.to_bytes(8, byteorder="little")
            
            # If original was 0x03, add min_out bytes
            if instr_type == "0x03" and len(new_instruction_data) < 17:
                new_instruction_data.extend(min_out_bytes)
            # If original was 0x09, replace min_out bytes
            elif instr_type == "0x09" and len(new_instruction_data) >= 17:
                for i in range(8):
                    if 9 + i < len(new_instruction_data):
                        new_instruction_data[9 + i] = min_out_bytes[i]
            
            logger.debug(f"Original instruction: {instr_type}, New instruction: 0x{self.RAYDIUM_SWAP_INSTRUCTION:02x}")
            logger.debug(f"Front-run amount: {self.front_run_amount} SOL ({front_run_lamports} lamports)")
            
            # Log the instruction data for debugging
            logger.debug(f"Instruction data: {bytes(new_instruction_data).hex()}")
            
            # Execute the swap with dynamic accounts
            status = None  # Initialize status
            
            # Try several approaches in sequence until one works
            if accounts and len(accounts) >= 10:
                logger.info(f"Attempting with mirrored accounts ({len(accounts)} accounts)")
                try:
                    # First try with all accounts exactly as they are
                    status = await self.trading_core.execute_with_accounts(token_mint, bytes(new_instruction_data), accounts)
                    
                    if not status.success and len(accounts) > 10:
                        # If that failed, try with just the key accounts (first 10)
                        logger.info("First attempt failed, trying with first 10 accounts")
                        status = await self.trading_core.execute_with_accounts(token_mint, bytes(new_instruction_data), accounts[:10])
                except Exception as e:
                    logger.error(f"Error with mirrored accounts: {e}")
                    # Continue to next approach
            
            # If previous attempts failed or no accounts available, try custom swap
            if status is None or not status.success:
                try:
                    logger.info("Attempting custom swap")
                    status = await self.trading_core.execute_custom_swap(token_mint, bytes(new_instruction_data))
                except Exception as e:
                    logger.error(f"Error with custom swap: {e}")
                    # Continue to next approach
            
            # If all attempts failed, try one last time with a SwapParams object
            if status is None or not status.success:
                try:
                    logger.info("Attempting with SwapParams")
                    # Create swap parameters
                    swap_params = SwapParams(
                        token_mint,
                        self.front_run_amount,
                        True,  # is_buy
                        self.slippage_bps,
                        50000,  # compute_limit
                        950000  # priority_fee
                    )
                    
                    # Execute the swap with swap_params
                    status = await self.trading_core.execute_swap(swap_params)
                except Exception as e:
                    logger.error(f"Error with SwapParams: {e}")
                    
            # If we still don't have a status or it failed, abort
            if status is None or not status.success:
                logger.error(f"All trade attempts failed: {status.error if status else 'No status'}")
                active_trades.remove(signature)
                stats["trades_failed"] += 1
                return
                
            # Trade successful - get new balances to see exactly how many tokens we received
            await asyncio.sleep(0.1)  # Small delay to ensure transaction is processed
            balances_after = await self.trading_core.get_balances()
            _, token_balances_after = balances_after
            
            token_balance_after = token_balances_after.get(token_mint, 0)
            
            # Calculate exact tokens received
            tokens_received = token_balance_after - token_balance_before
            
            logger.trade(f"Trade successful: {status.signature}", extra={
                "token": token_mint,
                "amount": self.front_run_amount,
                "tokens_received": tokens_received,
                "signature": status.signature
            })
                
            # Store current position for exit with exact token amount
            self.current_position = {
                "token_mint": token_mint,
                "pool_id": pool_id,
                "entry_signature": status.signature,
                "entry_time": time.time(),
                "entry_amount": self.front_run_amount,
                "entry_token_amount": tokens_received,
                "security_result": security_result,
                "victim_signature": signature,
                "victim_amount": amount_sol,
                "accounts": accounts,  # Store accounts for exit
                "instruction_data": bytes(new_instruction_data).hex()  # Store instruction data for debugging
            }
            
            logger.info(f"Position opened, monitoring for victim execution: {signature[:8]}...")
            
        except Exception as e:
            logger.error(f"Error executing trade: {e}")
            logger.error(traceback.format_exc())
            stats["errors"] += 1
            stats["trades_failed"] += 1
        finally:
            # Remove from active trades
            if signature in active_trades:
                active_trades.remove(signature)

    async def exit_position(self, position: Dict):
        """Exit a position (sell exact tokens back to SOL)"""
        if not position:
            return
            
        token_mint = position["token_mint"]
        pool_id = position["pool_id"]
        entry_time = position["entry_time"]
        entry_signature = position["entry_signature"]
        entry_token_amount = position.get("entry_token_amount", 0)
        accounts = position.get("accounts", None)
        
        # Calculate time in position
        time_in_position = time.time() - entry_time
        
        logger.info(f"Exiting position for {token_mint} (time in position: {time_in_position:.2f}s)")
        
        try:
            # Avoid rapid selling if too soon (RPC rate limit protection)
            if time_in_position < config.get_float("min_trade_interval", 0.05):
                wait_time = config.get_float("min_trade_interval", 0.05) - time_in_position
                logger.info(f"Waiting {wait_time:.2f}s before exiting position (RPC rate limit)")
                await asyncio.sleep(wait_time)
            
            # Get current token balance to verify
            balances = await self.trading_core.get_balances()
            _, token_balances = balances
            
            token_balance = token_balances.get(token_mint, 0)
            wsol_balance_before = token_balances.get(config.get("wsol_address"), 0)
            
            if token_balance <= 0:
                logger.warning(f"No token balance to sell for {token_mint}")
                self.current_position = None
                return
                
            if token_balance < entry_token_amount:
                logger.warning(f"Token balance ({token_balance}) is less than entry amount ({entry_token_amount})")
                # Use available balance instead
                entry_token_amount = token_balance
                
            # Sell exactly the amount of tokens we bought
            sell_amount = entry_token_amount
            
            logger.info(f"Selling {sell_amount} tokens of {token_mint}")
            
            # Create instruction data for sell
            # For a 0x09 instruction, bytes:
            # 0: instruction type (0x09)
            # 1-8: token amount to sell (little endian)
            # 9-16: minimum SOL to receive (little endian)
            
            # Get token decimals - most SPL tokens use 9 decimals
            token_decimals = 9
            
            # Convert token amount to raw amount
            token_amount_raw = int(sell_amount * (10 ** token_decimals))
            
            # Create instruction data with 0x09 instruction type
            instruction_data = bytearray()
            instruction_data.append(self.RAYDIUM_SWAP_INSTRUCTION)  # 0x09
            instruction_data.extend(token_amount_raw.to_bytes(8, byteorder="little"))
            
            # Calculate minimum out amount with slippage (use higher slippage for exit)
            exit_slippage_bps = 1500  # 15% slippage for exits
            min_out_amount = int(token_amount_raw * (1 - (exit_slippage_bps / 10000)))
            instruction_data.extend(min_out_amount.to_bytes(8, byteorder="little"))
            
            # Get all accounts needed for the swap
            security_result = position["security_result"]
            
            # Execute the swap
            sell_successful = False
            retries = 0
            max_retries = config.get_int("max_retries", 3)
            
            while not sell_successful and retries < max_retries:
                try:
                    # Try to execute with accounts if available
                    if accounts and len(accounts) >= 10:
                        # Reverse accounts for sell (token → SOL)
                        # For a sell, we need to swap some account positions
                        
                        logger.info(f"Using mirrored accounts for exit ({len(accounts)} accounts)")
                        status = await self.trading_core.execute_with_accounts(token_mint, bytes(instruction_data), accounts)
                    else:
                        # Use custom swap without specific accounts
                        logger.info(f"Using custom swap for exit")
                        status = await self.trading_core.execute_custom_swap(token_mint, bytes(instruction_data))
                    
                    if status.success:
                        sell_successful = True
                        
                        # Get balances after sell
                        await asyncio.sleep(0.1)  # Small delay to ensure transaction is processed
                        balances_after = await self.trading_core.get_balances()
                        _, token_balances_after = balances_after
                        
                        wsol_balance_after = token_balances_after.get(config.get("wsol_address"), 0)
                        token_balance_after = token_balances_after.get(token_mint, 0)
                        
                        # Calculate actual profit
                        wsol_received = wsol_balance_after - wsol_balance_before
                        tokens_sold = token_balance - token_balance_after
                        
                        net_profit = wsol_received - position["entry_amount"]
                        
                        # Log success
                        logger.trade(f"Exit successful: {status.signature}", extra={
                            "token": token_mint,
                            "tokens_sold": tokens_sold,
                            "wsol_received": wsol_received,
                            "profit": net_profit,
                            "signature": status.signature
                        })
                        
                        # Log profit
                        if net_profit > 0:
                            logger.profit(f"Profit: {net_profit:.6f} SOL", extra={
                                "profit": net_profit,
                                "token": token_mint
                            })
                            stats["total_profit"] += net_profit
                            stats["last_profit_time"] = time.time()
                            stats["trades_successful"] += 1
                        else:
                            logger.warning(f"Loss: {net_profit:.6f} SOL")
                            stats["trades_failed"] += 1
                            
                    else:
                        retries += 1
                        logger.warning(f"Sell attempt {retries} failed: {status.error}")
                        await asyncio.sleep(0.1 * retries)  # Increasing backoff
                
                except Exception as e:
                    logger.error(f"Error in sell attempt {retries}: {e}")
                    retries += 1
                    await asyncio.sleep(0.1 * retries)
            
            if not sell_successful:
                logger.error(f"Failed to exit position after {max_retries} attempts")
            
        except Exception as e:
            logger.error(f"Error exiting position: {e}")
            logger.error(traceback.format_exc())
            stats["errors"] += 1
        finally:
            # Clear position regardless to prevent getting stuck
            self.current_position = None

    async def verify_pool_security(self, pool_id: str, token_mint: str) -> Dict:
        """Verify pool security by checking mint authority and LP lock status"""
        try:
            # Check cache first
            if pool_id in self.pool_security_cache:
                cache_time, security_result = self.pool_security_cache[pool_id]
                cache_age = time.time() - cache_time
                
                # Use cache if still valid
                if cache_age < config.get_int("security_cache_seconds", 600):
                    return security_result
            
            # Default security result
            security_result = {
                "is_valid": False,
                "mint_revoked": False,
                "lp_locked": False,
                "exit_liquidity": 0.0,
                "has_sufficient_exit_liquidity": False,
                
                # Default account values for AMM
                "amm_authority": "5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1",
                "amm_open_orders": "",
                "amm_target": "",
                "pool_token_account": "",
                "pool_pc_account": "",
                "serum_market": "",
                "serum_bids": "",
                "serum_asks": "",
                "serum_event_queue": "",
                "serum_coin_vault": "",
                "serum_pc_vault": "",
                "serum_vault_signer": "",
            }
            
            # Get info from target pools if available
            if token_mint in self.target_pools:
                pool_data = self.target_pools[token_mint]
                
                # Fill in security info
                security_result["mint_revoked"] = pool_data.get("mint_revoked", False)
                security_result["lp_locked"] = pool_data.get("lp_locked", False)
                security_result["exit_liquidity"] = pool_data.get("exit_liquidity", 0.0)
                
                # Fill in account info
                security_result["amm_authority"] = pool_data.get("amm_authority", security_result["amm_authority"])
                security_result["amm_open_orders"] = pool_data.get("amm_open_orders", "")
                security_result["amm_target"] = pool_data.get("amm_target", "")
                security_result["pool_token_account"] = pool_data.get("pool_token_account", "")
                security_result["pool_pc_account"] = pool_data.get("pool_pc_account", "")
                security_result["serum_market"] = pool_data.get("serum_market", "")
                security_result["serum_bids"] = pool_data.get("serum_bids", "")
                security_result["serum_asks"] = pool_data.get("serum_asks", "")
                security_result["serum_event_queue"] = pool_data.get("serum_event_queue", "")
                security_result["serum_coin_vault"] = pool_data.get("serum_coin_vault", "")
                security_result["serum_pc_vault"] = pool_data.get("serum_pc_vault", "")
                security_result["serum_vault_signer"] = pool_data.get("serum_vault_signer", "")
            else:
                # For unknown tokens, be more cautious
                logger.warning(f"Token {token_mint} not in target list, using default security values")
            
            # Check verification settings
            verify_mint = config.get_bool("verify_mint_authority", True)
            verify_lp_lock = config.get_bool("verify_liquidity_lock", True)
            min_exit_liq = config.get_float("min_exit_liquidity_sol", 10.0)
            
            # Determine if exit liquidity is sufficient
            security_result["has_sufficient_exit_liquidity"] = security_result["exit_liquidity"] >= min_exit_liq
            
            # If auto discovery mode, be more lenient with security checks
            if self.auto_add_pools:
                # In auto-discovery mode, always consider pools valid to enable trading
                logger.info(f"Auto-discovery mode: Considering pool {pool_id[:8]}... valid for trading")
                security_result["is_valid"] = True
            else:
                # Determine overall validity
                is_valid = True
                
                if verify_mint and not security_result["mint_revoked"]:
                    logger.warning(f"Mint authority not revoked for {token_mint}")
                    is_valid = False
                    
                if verify_lp_lock and not security_result["lp_locked"]:
                    logger.warning(f"LP tokens not locked for {token_mint}")
                    is_valid = False
                    
                if not security_result["has_sufficient_exit_liquidity"]:
                    logger.warning(f"Insufficient exit liquidity for {token_mint}: {security_result['exit_liquidity']:.2f} SOL")
                    is_valid = False
                    
                security_result["is_valid"] = is_valid
            
            # Cache the result
            self.pool_security_cache[pool_id] = (time.time(), security_result)
            
            return security_result
            
        except Exception as e:
            logger.error(f"Error verifying pool security: {e}")
            return {"is_valid": False, "error": str(e)}

    async def get_swap_accounts(self, token_mint: str, pool_id: str, security_result: Dict, is_sell: bool = False) -> Optional[List[str]]:
        """Get all accounts required for a Raydium swap in correct order"""
        try:
            # Get core program IDs
            token_program_id = config.get("token_program_id", "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
            serum_program_id = config.get("serum_program_id", "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin")
            
            # Get user accounts
            user_public_key = config.get("solana_public_key")
            wsol_token_account = config.get("wsol_token_account")
            
            # Get token ATA
            token_ata = await self.trading_core.ensure_ata(token_mint)
            
            # For buys: WSOL is source, token is destination
            # For sells: token is source, WSOL is destination
            source_account = token_ata if is_sell else wsol_token_account
            destination_account = wsol_token_account if is_sell else token_ata
            
            # Build the accounts list in correct order for Raydium swap (18 accounts)
            accounts = [
                token_program_id,                    # 1. Token Program
                pool_id,                             # 2. AMM account (pool)
                security_result["amm_authority"],    # 3. AMM Authority
                security_result["amm_open_orders"],  # 4. AMM Open Orders
                security_result["amm_target"],       # 5. AMM Target Orders
                security_result["pool_token_account"],# 6. Pool Coin Token Account
                security_result["pool_pc_account"],  # 7. Pool PC Token Account
                serum_program_id,                    # 8. Serum Program
                security_result["serum_market"],     # 9. Serum Market
                security_result["serum_bids"],       # 10. Serum Bids
                security_result["serum_asks"],       # 11. Serum Asks
                security_result["serum_event_queue"],# 12. Serum Event Queue
                security_result["serum_coin_vault"], # 13. Serum Coin Vault
                security_result["serum_pc_vault"],   # 14. Serum PC Vault
                security_result["serum_vault_signer"],# 15. Serum Vault Signer
                source_account,                      # 16. User Source Token Account
                destination_account,                 # 17. User Destination Token Account
                user_public_key,                     # 18. User wallet (signer)
            ]
            
            # Verify all accounts are present
            for i, account in enumerate(accounts):
                if not account:
                    logger.error(f"Missing account at position {i+1}/18")
                    return None
            
            return accounts
            
        except Exception as e:
            logger.error(f"Error building swap accounts: {e}")
            return None

    def stats_worker(self):
        """Worker thread for printing stats"""
        logger.system("Stats worker started")
        last_stats_time = time.time()
        
        while bot_running.is_set():
            try:
                # Show stats every 30 seconds or if significant change
                current_time = time.time()
                if (current_time - last_stats_time >= 30 or 
                    stats["trades_successful"] % 5 == 0 and stats["trades_successful"] > 0):
                    self.show_stats()
                    last_stats_time = current_time
                
                # Check command queue
                try:
                    command, args = command_queue.get_nowait()
                    if command == "shutdown":
                        logger.system("Stats worker received shutdown command")
                        break
                    command_queue.task_done()
                except queue.Empty:
                    pass
                
                # Sleep to avoid high CPU usage
                time.sleep(1)
                
            except Exception as e:
                logger.error(f"Error in stats worker: {e}")
                stats["errors"] += 1
        
        # Show final stats
        self.show_stats()
        logger.system("Stats worker stopped")

    def show_stats(self):
        """Show statistics"""
        # Calculate runtime
        runtime = time.time() - stats["start_time"]
        hours, remainder = divmod(runtime, 3600)
        minutes, seconds = divmod(remainder, 60)
        
        # Calculate trading stats
        success_rate = 0
        if stats["executions"] > 0:
            success_rate = (stats["trades_successful"] / stats["executions"]) * 100
        
        # Format output
        logger.system("=" * 80)
        logger.system(f"QUANTUM MEV BOT STATISTICS | Runtime: {int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}")
        logger.system(f"Total logs processed: {stats['total_logs']:,}")
        logger.system(f"Raydium logs found: {stats['logs_with_ray']:,}")
        logger.system(f"Opportunities detected: {stats['opportunities']:,}")
        logger.system(f"Trades executed: {stats['executions']:,}")
        logger.system(f"Successful trades: {stats['trades_successful']:,} ({success_rate:.1f}%)")
        logger.system(f"Total profit: {stats['total_profit']:.6f} SOL")
        
        # Count actionable pools
        actionable_pools = 0
        target_count = len(self.target_pools)
        if target_count == 0:
            logger.system("WARNING: No target pools configured. Bot will not find opportunities.")
            logger.system("Create target_pools.json in the config directory or enable auto_add_pools.")
        else:
            actionable_pools = len([p for p in self.target_pools.values() if p.get("score", 0) >= 50 and p.get("enabled", True)])
            logger.system(f"Target pools: {target_count} configured, {actionable_pools} actionable")
            
        # Show unique pools seen
        unique_pools = len(stats.get("unique_pools_seen", set()))
        logger.system(f"Unique pools seen: {unique_pools}")
        
        # Show balances
        logger.system(f"SOL Balance: {balance_updates['SOL']:.6f} SOL")
        logger.system(f"WSOL Balance: {balance_updates['WSOL']:.6f} SOL")
        
        # Show current position if any
        if self.current_position is not None:
            token_mint = self.current_position["token_mint"]
            entry_time = datetime.fromtimestamp(self.current_position["entry_time"]).strftime('%H:%M:%S')
            time_in_position = time.time() - self.current_position["entry_time"]
            entry_token_amount = self.current_position.get("entry_token_amount", 0)
            logger.system(f"Current position: {token_mint} ({entry_token_amount} tokens, entered at {entry_time}, {time_in_position:.1f}s ago)")
        
        logger.system("=" * 80)


# Main function to run the bot
def main():
    """Main function to run the bot"""
    # Initialize the bot
    bot = QuantumMEVBot()
    
    # Set up signal handlers
    def signal_handler(sig, frame):
        logger.system("Shutdown signal received, stopping bot...")
        bot_running.clear()
        command_queue.put(("shutdown", None))
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # Run the bot
    try:
        asyncio.run(bot.run())
    except KeyboardInterrupt:
        logger.system("Keyboard interrupt detected, shutting down...")
    except Exception as e:
        logger.critical(f"Fatal error: {e}")
    finally:
        logger.system("Bot shutdown complete")


if __name__ == "__main__":
    # Add Windows-specific event loop policy to support asyncio
    if sys.platform == "win32":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    
    main()    how do we fix this -21 16:28:46.537 | DEBUG    | Trade size filter: 1.0523 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.537 | DEBUG    | Detected 0x03 instruction: 1.100624 SOL (tx: 2hrVHjXx...)
2025-03-21 16:28:46.537 | DEBUG    | Trade size filter: 1.1006 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.537 | DEBUG    | Detected 0x03 instruction: 0.733089 SOL (tx: 5gGa9SMa...)
2025-03-21 16:28:46.538 | DEBUG    | Detected 0x03 instruction: 0.103707 SOL (tx: 5LSnVx9f...)
2025-03-21 16:28:46.538 | DEBUG    | Detected 0x03 instruction: 1.326513 SOL (tx: 1SQhd5bF...)
2025-03-21 16:28:46.538 | DEBUG    | Trade size filter: 1.3265 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.538 | DEBUG    | Detected 0x03 instruction: 0.037864 SOL (tx: 37BChFL8...)
2025-03-21 16:28:46.538 | DEBUG    | Detected 0x03 instruction: 10.280859 SOL (tx: 5bnFmyCq...)
2025-03-21 16:28:46.539 | DEBUG    | Trade interval filter: Last trade was 0.003s ago
2025-03-21 16:28:46.539 | DEBUG    | Detected 0x03 instruction: 0.000001 SOL (tx: 5pt1JShA...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.279349 SOL (tx: 5F6b2etS...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: 56irwg3f...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: 5193uosA...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: bRgs4WHN...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.079580 SOL (tx: ixqsx767...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: 5AZj9f1V...)
2025-03-21 16:28:46.540 | DEBUG    | Detected 0x03 instruction: 0.001546 SOL (tx: 4jKADd2B...)
2025-03-21 16:28:46.541 | DEBUG    | Detected 0x03 instruction: 0.247034 SOL (tx: 4gVK474R...)
2025-03-21 16:28:46.541 | DEBUG    | Detected 0x03 instruction: 0.899100 SOL (tx: AjdmBF87...)
2025-03-21 16:28:46.542 | DEBUG    | Detected 0x03 instruction: 1.000000 SOL (tx: 5imE5kzk...)
2025-03-21 16:28:46.542 | DEBUG    | Trade size filter: 1.0000 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.542 | DEBUG    | Detected 0x03 instruction: 5.000000 SOL (tx: MoxxraKy...)
2025-03-21 16:28:46.542 | DEBUG    | Trade interval filter: Last trade was 0.007s ago
2025-03-21 16:28:46.542 | DEBUG    | Detected 0x03 instruction: 5.000000 SOL (tx: 4Gmwyqhb...)
2025-03-21 16:28:46.542 | DEBUG    | Trade interval filter: Last trade was 0.007s ago
2025-03-21 16:28:46.542 | DEBUG    | Detected 0x03 instruction: 10.000000 SOL (tx: orqWjPXy...)
2025-03-21 16:28:46.542 | DEBUG    | Trade interval filter: Last trade was 0.007s ago
2025-03-21 16:28:46.544 | DEBUG    | Detected 0x03 instruction: 5.000000 SOL (tx: 2ur4iLKv...)
2025-03-21 16:28:46.544 | DEBUG    | Trade interval filter: Last trade was 0.008s ago
2025-03-21 16:28:46.544 | DEBUG    | Detected 0x03 instruction: 9.113800 SOL (tx: 3kxQdzxT...)
2025-03-21 16:28:46.544 | DEBUG    | Trade interval filter: Last trade was 0.008s ago
2025-03-21 16:28:46.544 | DEBUG    | Detected 0x03 instruction: 6.200000 SOL (tx: 3RRZVo7v...)
2025-03-21 16:28:46.544 | DEBUG    | Trade interval filter: Last trade was 0.008s ago
2025-03-21 16:28:46.968 | DEBUG    | Detected 0x03 instruction: 95.272137 SOL (tx: 5X48zkq6...)
2025-03-21 16:28:46.968 | DEBUG    | Max active trades filter: 1 active trades
2025-03-21 16:28:46.968 | DEBUG    | Detected 0x03 instruction: 0.000001 SOL (tx: 3zyvXxWP...)
2025-03-21 16:28:46.975 | DEBUG    | Detected 0x03 instruction: 4.628823 SOL (tx: Z61xMNDo...)
2025-03-21 16:28:46.975 | DEBUG    | Trade size filter: 4.6288 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.976 | DEBUG    | Detected 0x03 instruction: 42.409276 SOL (tx: 2JFQj3Yg...)
2025-03-21 16:28:46.976 | DEBUG    | Max active trades filter: 1 active trades
2025-03-21 16:28:46.976 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: mMry9ZpU...)
2025-03-21 16:28:46.976 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: 3HofHhP9...)
2025-03-21 16:28:46.977 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: 2rvTMF5d...)
2025-03-21 16:28:46.977 | DEBUG    | Detected 0x03 instruction: 0.079580 SOL (tx: 3WQRXSVr...)
2025-03-21 16:28:46.977 | DEBUG    | Detected 0x03 instruction: 0.000001 SOL (tx: 3Ugqm745...)
2025-03-21 16:28:46.977 | DEBUG    | Detected 0x03 instruction: 0.252389 SOL (tx: 4WdHW5ec...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.003000 SOL (tx: Urcq1X5Y...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.320000 SOL (tx: 2iyEVpHZ...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.000001 SOL (tx: 3wtw47f5...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.248169 SOL (tx: 3coZjbyD...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.250000 SOL (tx: 4gjYP1SD...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.320000 SOL (tx: 59FwmuhX...)
2025-03-21 16:28:46.978 | DEBUG    | Detected 0x03 instruction: 0.320000 SOL (tx: ignW1JL8...)
2025-03-21 16:28:46.979 | DEBUG    | Detected 0x03 instruction: 0.320000 SOL (tx: 26MRgxaS...)
2025-03-21 16:28:46.979 | DEBUG    | Extracted token info: Tokenkeg... (pool: 6t83LoAj...)
2025-03-21 16:28:46.979 | DEBUG    | Detected 0x03 instruction: 0.241656 SOL (tx: WmY45DTr...)
2025-03-21 16:28:46.979 | DEBUG    | Extracted token info: Tokenkeg... (pool: 6t83LoAj...)
2025-03-21 16:28:46.979 | DEBUG    | Detected 0x03 instruction: 0.241656 SOL (tx: 3D33jcXX...)
2025-03-21 16:28:46.979 | DEBUG    | Extracted token info: Tokenkeg... (pool: 6t83LoAj...)
2025-03-21 16:28:46.979 | DEBUG    | Detected 0x03 instruction: 0.241656 SOL (tx: 4DqtK8Q4...)
2025-03-21 16:28:46.980 | DEBUG    | Detected 0x03 instruction: 1.413597 SOL (tx: jBmzjHUK...)
2025-03-21 16:28:46.980 | DEBUG    | Trade size filter: 1.4136 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.980 | DEBUG    | Detected 0x03 instruction: 0.251778 SOL (tx: 5QvHNRxy...)
2025-03-21 16:28:46.980 | DEBUG    | Extracted token info: Tokenkeg... (pool: 6t83LoAj...)
2025-03-21 16:28:46.980 | DEBUG    | Detected 0x03 instruction: 0.241656 SOL (tx: 4Zp3NMvk...)
2025-03-21 16:28:46.980 | DEBUG    | Detected 0x03 instruction: 9.210736 SOL (tx: g2gd6zSE...)
2025-03-21 16:28:46.980 | DEBUG    | Max active trades filter: 1 active trades
2025-03-21 16:28:46.981 | DEBUG    | Detected 0x03 instruction: 0.251320 SOL (tx: 4c6hmibj...)
2025-03-21 16:28:46.981 | DEBUG    | Detected 0x03 instruction: 0.789334 SOL (tx: 2aKEz2Sj...)
2025-03-21 16:28:46.981 | DEBUG    | Detected 0x03 instruction: 0.250000 SOL (tx: KJeTxL4n...)
2025-03-21 16:28:46.981 | DEBUG    | Detected 0x03 instruction: 0.146522 SOL (tx: ASiLZD9u...)
2025-03-21 16:28:46.981 | DEBUG    | Detected 0x03 instruction: 3854.430069 SOL (tx: 5Puj3Gfo...)
2025-03-21 16:28:46.981 | ERROR    | Error executing trade: Failed to create ATA: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: incorrect program id for instruction [9 log messages]
2025-03-21 16:28:46.981 | DEBUG    | Trade size filter: 3854.4301 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:46.982 | ERROR    | Traceback (most recent call last):
  File "C:\solana_rust_bot\src\python\bestbestbestmain.py", line 1225, in execute_trade
    token_ata = await self.trading_core.ensure_ata(token_mint)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: Failed to create ATA: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: incorrect program id for instruction [9 log messages]

2025-03-21 16:28:46.982 | DEBUG    | Detected 0x03 instruction: 0.000762 SOL (tx: 3H7MZSTA...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.000010 SOL (tx: 2DBBgVPX...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.000020 SOL (tx: 4t4u6kZC...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.251778 SOL (tx: 5AfbH7HN...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.020000 SOL (tx: 2cFADqHp...)
2025-03-21 16:28:46.983 | DEBUG    | Extracted token info: Tokenkeg... (pool: 3HXfeBuc...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.306000 SOL (tx: 3NrWk31p...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.320000 SOL (tx: 3PyQUt8W...)
2025-03-21 16:28:46.983 | DEBUG    | Detected 0x03 instruction: 0.243193 SOL (tx: 5Ek3Z35B...)
2025-03-21 16:28:46.984 | DEBUG    | Detected 0x03 instruction: 0.250041 SOL (tx: 4ZH6Rucw...)
2025-03-21 16:28:46.997 | DEBUG    | Detected 0x03 instruction: 0.815767 SOL (tx: 5LAec9du...)
2025-03-21 16:28:47.000 | DEBUG    | Detected 0x03 instruction: 1999.043350 SOL (tx: 47NP8eBP...)
2025-03-21 16:28:47.000 | DEBUG    | Trade size filter: 1999.0434 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.000 | DEBUG    | Detected 0x03 instruction: 2.182674 SOL (tx: 3VDuqQ5y...)
2025-03-21 16:28:47.000 | DEBUG    | Trade size filter: 2.1827 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.001 | DEBUG    | Detected 0x03 instruction: 0.000020 SOL (tx: 4ocQzJMB...)
2025-03-21 16:28:47.001 | DEBUG    | Detected 0x03 instruction: 19.328236 SOL (tx: 5PvprAEq...)
2025-03-21 16:28:47.001 | DEBUG    | Trade passed all actionable filters: 19.3282 SOL (tx: 5PvprAEq...)
2025-03-21 16:28:47.001 | INFO     | Found opportunity: ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL 19.3282 SOL (instruction: 0x03)
2025-03-21 16:28:47.001 | DEBUG    | Detected 0x03 instruction: 0.000004 SOL (tx: 45Mn2WkX...)
2025-03-21 16:28:47.002 | DEBUG    | Detected 0x03 instruction: 0.243193 SOL (tx: 24c7nAQ6...)
2025-03-21 16:28:47.002 | DEBUG    | Detected 0x03 instruction: 0.000003 SOL (tx: 3KHbQ8Ng...)
2025-03-21 16:28:47.002 | DEBUG    | Detected 0x03 instruction: 0.600000 SOL (tx: 3Lm9PK9m...)
2025-03-21 16:28:47.002 | DEBUG    | Detected 0x03 instruction: 0.000003 SOL (tx: 3E5PewhG...)
2025-03-21 16:28:47.002 | DEBUG    | Detected 0x03 instruction: 0.923788 SOL (tx: 3niGBsQU...)
2025-03-21 16:28:47.003 | INFO     | Processing opportunity for ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL
2025-03-21 16:28:47.003 | DEBUG    | Detected 0x03 instruction: 0.040325 SOL (tx: 5wafdhYH...)
2025-03-21 16:28:47.003 | DEBUG    | Detected 0x03 instruction: 0.708000 SOL (tx: 3KNWoD8s...)
2025-03-21 16:28:47.003 | INFO     | Executing trade for ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL (pool: ComputeB... 19.3282 SOL)
2025-03-21 16:28:47.003 | DEBUG    | Detected 0x03 instruction: 0.000038 SOL (tx: 2YaANKo5...)
2025-03-21 16:28:47.004 | DEBUG    | Detected 0x03 instruction: 0.036119 SOL (tx: 51G5JCjx...)
2025-03-21 16:28:47.004 | DEBUG    | Detected 0x03 instruction: 0.103946 SOL (tx: wBfdpyLm...)
2025-03-21 16:28:47.004 | DEBUG    | Detected 0x03 instruction: 1210.783936 SOL (tx: aLNrUei2...)
2025-03-21 16:28:47.004 | DEBUG    | Trade size filter: 1210.7839 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.004 | DEBUG    | Detected 0x03 instruction: 0.040000 SOL (tx: 4DPuW3Bw...)
2025-03-21 16:28:47.004 | DEBUG    | Detected 0x03 instruction: 0.053026 SOL (tx: 4eYV1KKY...)
2025-03-21 16:28:47.004 | DEBUG    | Detected 0x03 instruction: 0.250103 SOL (tx: 2R1exbmE...)
2025-03-21 16:28:47.005 | DEBUG    | Detected 0x03 instruction: 0.605200 SOL (tx: 34WAUWy1...)
2025-03-21 16:28:47.005 | DEBUG    | Detected 0x03 instruction: 0.000053 SOL (tx: 2iSzqr6h...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.039732 SOL (tx: 5HFtZXqr...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.331898 SOL (tx: 2ZJPtMZ9...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 1.068500 SOL (tx: 2XPiuLAn...)
2025-03-21 16:28:47.006 | DEBUG    | Trade size filter: 1.0685 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.000037 SOL (tx: 4qYLMdfZ...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.119400 SOL (tx: 622CJGDr...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.377326 SOL (tx: 582yJLRK...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.000044 SOL (tx: 67SdEdxR...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.010027 SOL (tx: 3GAcY4dj...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.111965 SOL (tx: 62iAbaFn...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.004400 SOL (tx: R3ffk6EX...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.002200 SOL (tx: 3EcQRdQM...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.251778 SOL (tx: 65g99HEm...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.002000 SOL (tx: 44KzZ5sB...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.007500 SOL (tx: 2vNEUVfQ...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.000010 SOL (tx: 3uRVjPaG...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.011504 SOL (tx: 4XuTrExU...)
2025-03-21 16:28:47.006 | DEBUG    | Detected 0x03 instruction: 0.221823 SOL (tx: NUUXoAxG...)
2025-03-21 16:28:47.009 | DEBUG    | Detected 0x03 instruction: 4.080101 SOL (tx: RMoCfQLJ...)
2025-03-21 16:28:47.009 | DEBUG    | Trade size filter: 4.0801 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.009 | DEBUG    | Detected 0x03 instruction: 0.243193 SOL (tx: 2fahcEnf...)
2025-03-21 16:28:47.010 | DEBUG    | Detected 0x03 instruction: 0.257560 SOL (tx: 2z2zu5m5...)
2025-03-21 16:28:47.010 | DEBUG    | Extracted token info: Tokenkeg... (pool: 6t83LoAj...)
2025-03-21 16:28:47.010 | DEBUG    | Detected 0x03 instruction: 0.241656 SOL (tx: 5T2MW3ux...)
2025-03-21 16:28:47.010 | DEBUG    | Detected 0x03 instruction: 5.000000 SOL (tx: p4mXqc2p...)
2025-03-21 16:28:47.010 | DEBUG    | Trade interval filter: Last trade was 0.007s ago
2025-03-21 16:28:47.010 | DEBUG    | Detected 0x03 instruction: 573.700851 SOL (tx: 5hdF2JR6...)
2025-03-21 16:28:47.010 | DEBUG    | Trade size filter: 573.7009 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.011 | DEBUG    | Detected 0x03 instruction: 0.000001 SOL (tx: 7Ckh9N9R...)
2025-03-21 16:28:47.011 | DEBUG    | Detected 0x03 instruction: 0.257560 SOL (tx: iWRXwbFC...)
2025-03-21 16:28:47.011 | DEBUG    | Detected 0x03 instruction: 0.241656 SOL (tx: 2Aqh7QVt...)
2025-03-21 16:28:47.011 | DEBUG    | Detected 0x03 instruction: 0.251778 SOL (tx: oc3ohyiw...)
2025-03-21 16:28:47.011 | DEBUG    | Detected 0x03 instruction: 2.772629 SOL (tx: 4QzKWFpK...)
2025-03-21 16:28:47.011 | DEBUG    | Trade size filter: 2.7726 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.012 | DEBUG    | Detected 0x03 instruction: 2.772629 SOL (tx: vKNFhj43...)
2025-03-21 16:28:47.012 | DEBUG    | Trade size filter: 2.7726 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.012 | DEBUG    | Detected 0x03 instruction: 1.150775 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.012 | DEBUG    | Trade size filter: 1.1508 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.012 | DEBUG    | Detected 0x03 instruction: 1.161511 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.012 | DEBUG    | Trade size filter: 1.1615 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.012 | DEBUG    | Detected 0x03 instruction: 1.146499 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.012 | DEBUG    | Trade size filter: 1.1465 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.012 | DEBUG    | Detected 0x03 instruction: 1.159380 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.012 | DEBUG    | Trade size filter: 1.1594 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.013 | DEBUG    | Detected 0x03 instruction: 1.150886 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.013 | DEBUG    | Trade size filter: 1.1509 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.013 | DEBUG    | Detected 0x03 instruction: 1.149951 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.013 | DEBUG    | Trade size filter: 1.1500 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.013 | DEBUG    | Detected 0x03 instruction: 1.152700 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.013 | DEBUG    | Trade size filter: 1.1527 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.013 | DEBUG    | Detected 0x03 instruction: 1.155588 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.013 | DEBUG    | Trade size filter: 1.1556 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.013 | DEBUG    | Detected 0x03 instruction: 1.140912 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.013 | DEBUG    | Trade size filter: 1.1409 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 1.150432 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.014 | DEBUG    | Trade size filter: 1.1504 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 1.146884 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.014 | DEBUG    | Trade size filter: 1.1469 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 1.157943 SOL (tx: 4waoyu5G...)
2025-03-21 16:28:47.014 | DEBUG    | Trade size filter: 1.1579 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 0.041971 SOL (tx: 4PeQXbDz...)
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 453.988179 SOL (tx: uTd4p2s3...)
2025-03-21 16:28:47.014 | DEBUG    | Trade size filter: 453.9882 SOL not in range [5.0-320.0] SOL
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 0.222330 SOL (tx: 666hWhhB...)
2025-03-21 16:28:47.014 | DEBUG    | Detected 0x03 instruction: 0.222330 SOL (tx: 4CAQcfjE...)
2025-03-21 16:28:47.417 | SYSTEM   | Shutdown signal received, stopping bot...
2025-03-21 16:28:47.417 | DEBUG    | Detected 0x03 instruction: 305.124072 SOL (tx: 5ajusduE...)
2025-03-21 16:28:47.417 | DEBUG    | Max active trades filter: 1 active trades
2025-03-21 16:28:47.435 | ERROR    | Error executing trade: Failed to create ATA: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: incorrect program id for instruction [9 log messages]
2025-03-21 16:28:47.436 | ERROR    | Traceback (most recent call last):
  File "C:\solana_rust_bot\src\python\bestbestbestmain.py", line 1225, in execute_trade
    token_ata = await self.trading_core.ensure_ata(token_mint)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: Failed to create ATA: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: incorrect program id for instruction [9 log messages]

2025-03-21 16:28:47.436 | SYSTEM   | Execution worker stopped
2025-03-21 16:28:47.771 | SYSTEM   | ================================================================================
2025-03-21 16:28:47.771 | SYSTEM   | QUANTUM MEV BOT STATISTICS | Runtime: 00:00:03
2025-03-21 16:28:47.771 | SYSTEM   | Total logs processed: 411
2025-03-21 16:28:47.771 | SYSTEM   | Raydium logs found: 219
2025-03-21 16:28:47.771 | SYSTEM   | Opportunities detected: 3
2025-03-21 16:28:47.771 | SYSTEM   | Trades executed: 3
2025-03-21 16:28:47.771 | SYSTEM   | Successful trades: 0 (0.0%)
2025-03-21 16:28:47.771 | SYSTEM   | Total profit: 0.000000 SOL
2025-03-21 16:28:47.771 | SYSTEM   | Target pools: 2 configured, 2 actionable
2025-03-21 16:28:47.771 | SYSTEM   | Unique pools seen: 0
2025-03-21 16:28:47.771 | SYSTEM   | SOL Balance: 0.051922 SOL
2025-03-21 16:28:47.771 | SYSTEM   | WSOL Balance: 0.350000 SOL
2025-03-21 16:28:47.778 | SYSTEM   | ================================================================================
2025-03-21 16:28:47.778 | SYSTEM   | Stats worker stopped
2025-03-21 16:29:05.388 | SYSTEM   | Shutdown signal received, stopping bot...
2025-03-21 16:30:47.451 | SYSTEM   | Unique pools seen during this session: 5
2025-03-21 16:30:47.451 | SYSTEM   | Bot shutdown complete
2025-03-21 16:30:47.451 | SYSTEM   | Bot shutdown complete