(quantum_beast) PS C:\solana_rust_bot> python "C:\solana_rust_bot\src\python\test_solana_trading.py"
2025-03-06 04:27:40 | INFO | Loaded 29 settings from environment variables
2025-03-06 04:27:40 | INFO | Configuration loaded: 34 settings
2025-03-06 04:27:40 | INFO     | Logger initialized: trade_engine, level: 20
2025-03-06 04:27:40 | INFO     | Log file: logs\trade_engine_20250306_042740.log
2025-03-06 04:27:41 | INFO     | Starting Solana trading engine test
2025-03-06 04:27:41 | INFO     | Initializing test trading engine
2025-03-06 04:27:41 | INFO     | CUDA setup complete: NVIDIA GeForce RTX 4070 Ti
2025-03-06 04:27:41 | INFO     | Found keypair at: keypair.bin
2025-03-06 04:27:41 | ERROR    | Failed to initialize Rust trading core: module 'solana_rust_bot' has no attribute 'TradingCore'
2025-03-06 04:27:41 | INFO     | Using mock Rust core instead
2025-03-06 04:27:41 | INFO     | Loading ML model from models/trade_model.pt
2025-03-06 04:27:41 | INFO | Model loaded from models/trade_model.pt
2025-03-06 04:27:41 | INFO     | ML model loaded and tested successfully
2025-03-06 04:27:41 | INFO     | Starting test with 10 simulated trades
2025-03-06 04:27:41 | INFO     | Updated ML signals: 6 signals
2025-03-06 04:27:41 | INFO     | Test trade 1/10 - Size: 7.1100 SOL
2025-03-06 04:27:41 | TRADE    | TRADE | Simulating trade with amount: 7.1100 SOL
2025-03-06 04:27:41 | INFO     | ML evaluation - Confidence: 2.3597, Execute: True
2025-03-06 04:27:41 | TRADE    | TRADE | Executing front-run: 0.35 SOL → Token
2025-03-06 04:27:41 | INFO     | Mock trade executed: MockTx17412640618741
2025-03-06 04:27:42 | TRADE    | TRADE | Executing back-run: Token → SOL
2025-03-06 04:27:42 | INFO     | Mock trade executed: MockTx17412640623832
2025-03-06 04:27:42 | PROFIT   | PROFIT |
Trade #1 completed successfully:
Amount: 7.1100 SOL
Front TX: MockTx17412640618741
Back TX: MockTx17412640623832
Profit: 0.002800 SOL
Execution Time: 508.887 ms
ML Confidence: 2.3597
Total Profit: 0.002800 SOL

2025-03-06 04:27:43 | INFO     | Test trade 2/10 - Size: 10.7924 SOL
2025-03-06 04:27:43 | TRADE    | TRADE | Simulating trade with amount: 10.7924 SOL
2025-03-06 04:27:43 | INFO     | ML evaluation - Confidence: 1.2414, Execute: True
2025-03-06 04:27:43 | TRADE    | TRADE | Executing front-run: 0.35 SOL → Token
2025-03-06 04:27:43 | INFO     | Mock trade executed: MockTx17412640633883
2025-03-06 04:27:43 | TRADE    | TRADE | Executing back-run: Token → SOL
2025-03-06 04:27:43 | INFO     | Mock trade executed: MockTx17412640638954
2025-03-06 04:27:43 | PROFIT   | PROFIT |
Trade #2 completed successfully:
Amount: 10.7924 SOL
Front TX: MockTx17412640633883
Back TX: MockTx17412640638954
Profit: 0.003500 SOL
Execution Time: 506.557 ms
ML Confidence: 1.2414
Total Profit: 0.006300 SOL

2025-03-06 04:27:44 | INFO     | Test trade 3/10 - Size: 6.0823 SOL
2025-03-06 04:27:44 | TRADE    | TRADE | Simulating trade with amount: 6.0823 SOL
2025-03-06 04:27:44 | INFO     | ML evaluation - Confidence: 2.7610, Execute: True
2025-03-06 04:27:44 | TRADE    | TRADE | Executing front-run: 0.35 SOL → Token
2025-03-06 04:27:44 | INFO     | Mock trade executed: MockTx17412640649035
2025-03-06 04:27:45 | TRADE    | TRADE | Executing back-run: Token → SOL
2025-03-06 04:27:45 | INFO     | Mock trade executed: MockTx17412640654176
2025-03-06 04:27:45 | PROFIT   | PROFIT |
Trade #3 completed successfully:
Amount: 6.0823 SOL
Front TX: MockTx17412640649035
Back TX: MockTx17412640654176
Profit: 0.002800 SOL
Execution Time: 514.218 ms
ML Confidence: 2.7610
Total Profit: 0.009100 SOL

2025-03-06 04:27:46 | INFO     | Test trade 4/10 - Size: 32.6542 SOL
2025-03-06 04:27:46 | TRADE    | TRADE | Simulating trade with amount: 32.6542 SOL
2025-03-06 04:27:46 | INFO     | ML evaluation - Confidence: -3.8852, Execute: False
forrtl: error (200): program aborting due to control-C event
Image              PC                Routine            Line        Source
libifcoremd.dll    00007FF97B2DDF54  Unknown               Unknown  Unknown
KERNELBASE.dll     00007FFB35373427  Unknown               Unknown  Unknown
KERNEL32.DLL       00007FFB365B259D  Unknown               Unknown  Unknown
ntdll.dll          00007FFB37D4AF38  Unknown               Unknown  Unknown
(quantum_beast) PS C:\solana_rust_bot> use log::{debug};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3_log;

use base58::FromBase58;
use serde::{Deserialize, Serialize};

use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
    commitment_config::CommitmentConfig,
    compute_budget::ComputeBudgetInstruction,
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use spl_associated_token_account::{
    get_associated_token_address, instruction::create_associated_token_account_idempotent,
};
use solana_sdk::program_pack::Pack;

use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};

// Constants
const COMPUTE_UNITS: u32 = 50_000;
const PRIORITY_FEE: u64 = 750_000;
const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
const DEFAULT_RPC_RATE_LIMIT: u64 = 5; // 5 RPC calls per second
const MAX_RETRY_COUNT: u8 = 3;

// Rate limiter for RPC calls
struct RateLimiter {
    call_count: AtomicU64,
    last_reset: AtomicU64,
    limit_per_second: Arc<AtomicU64>,
}

impl RateLimiter {
    fn new(limit_per_second: u64) -> Self {
        Self {
            call_count: AtomicU64::new(0),
            last_reset: AtomicU64::new(
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_secs(),
            ),
            limit_per_second: Arc::new(AtomicU64::new(limit_per_second)),
        }
    }

    fn check_and_increment(&self) -> bool {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        let last_reset = self.last_reset.load(Ordering::Relaxed);

        if now > last_reset {
            // Reset counter if we're in a new second
            self.call_count.store(0, Ordering::Relaxed);
            self.last_reset.store(now, Ordering::Relaxed);
        }

        let count = self.call_count.fetch_add(1, Ordering::Relaxed);
        let limit = self.limit_per_second.load(Ordering::Relaxed);

        count < limit
    }

    fn set_limit(&self, limit: u64) {
        self.limit_per_second.store(limit, Ordering::Relaxed);
    }
}

// Define TokenPairSetting
#[pyclass]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenPairSetting {
    #[pyo3(get, set)]
    pub token_mint: String,
    #[pyo3(get, set)]
    pub amm_id: String,
    #[pyo3(get, set)]
    pub amm_authority: String,
    #[pyo3(get, set)]
    pub amm_open_orders: String,
    #[pyo3(get, set)]
    pub amm_target: String,
    #[pyo3(get, set)]
    pub pool_token_account: String,
    #[pyo3(get, set)]
    pub pool_pc_account: String,
    #[pyo3(get, set)]
    pub min_liquidity_pct: f64,
    #[pyo3(get, set)]
    pub min_exit_liquidity_sol: f64,
    #[pyo3(get, set)]
    pub auth_revoked: bool,
    #[pyo3(get, set)]
    pub buyer_seller_ratio: f64,
    #[pyo3(get, set)]
    pub min_volume_5m: f64,
    #[pyo3(get, set)]
    pub enabled: bool,
}

#[pymethods]
impl TokenPairSetting {
    #[new]
    fn new(
        token_mint: String,
        amm_id: String,
        amm_authority: String,
        amm_open_orders: String,
        amm_target: String,
        pool_token_account: String,
        pool_pc_account: String,
        min_liquidity_pct: Option<f64>,
        min_exit_liquidity_sol: Option<f64>,
        auth_revoked: Option<bool>,
        buyer_seller_ratio: Option<f64>,
        min_volume_5m: Option<f64>,
        enabled: Option<bool>,
    ) -> Self {
        Self {
            token_mint,
            amm_id,
            amm_authority,
            amm_open_orders,
            amm_target,
            pool_token_account,
            pool_pc_account,
            min_liquidity_pct: min_liquidity_pct.unwrap_or(95.0),
            min_exit_liquidity_sol: min_exit_liquidity_sol.unwrap_or(5.0),
            auth_revoked: auth_revoked.unwrap_or(true),
            buyer_seller_ratio: buyer_seller_ratio.unwrap_or(1.0),
            min_volume_5m: min_volume_5m.unwrap_or(10.0),
            enabled: enabled.unwrap_or(true),
        }
    }
}

// Dynamic configuration structure
#[pyclass]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TradingConfig {
    #[pyo3(get, set)]
    pub rpc_url: String,
    #[pyo3(get, set)]
    pub ws_url: String,
    #[pyo3(get, set)]
    pub api_key: String,

    #[pyo3(get, set)]
    pub sol_mint: String,
    #[pyo3(get, set)]
    pub rpc_rate_limit: u64,

    #[pyo3(get, set)]
    pub token_mint: Option<String>,
    #[pyo3(get, set)]
    pub amm_program: Option<String>,

    #[pyo3(get, set)]
    pub compute_limit: u32,
    #[pyo3(get, set)]
    pub priority_fee: u64,
    #[pyo3(get, set)]
    pub skip_preflight: bool,
    #[pyo3(get, set)]
    pub max_retries: u8,

    #[pyo3(get, set)]
    pub front_run_amount: f64,
    #[pyo3(get, set)]
    pub min_victim_size: f64,
    #[pyo3(get, set)]
    pub max_victim_size: f64,
    #[pyo3(get, set)]
    pub min_profit: f64,
    #[pyo3(get, set)]
    pub slippage_bps: u16,

    #[pyo3(get, set)]
    pub min_sol_balance: f64,
    #[pyo3(get, set)]
    pub min_wsol_balance: f64,

    #[pyo3(get, set)]
    pub amm_id: Option<String>,
    #[pyo3(get, set)]
    pub market_amm_authority: Option<String>,
    #[pyo3(get, set)]
    pub market_amm_open_orders: Option<String>,
    #[pyo3(get, set)]
    pub market_amm_target: Option<String>,
    #[pyo3(get, set)]
    pub market_pool_token_account: Option<String>,
    #[pyo3(get, set)]
    pub market_pool_pc_token_account: Option<String>,

    #[pyo3(get, set)]
    pub raydium_program_id: String,
    #[pyo3(get, set)]
    pub token_program_id: String,
    #[pyo3(get, set)]
    pub system_program_id: String,
    #[pyo3(get, set)]
    pub associated_token_program_id: String,

    #[pyo3(get)]
    pub token_pair_settings: HashMap<String, TokenPairSetting>,
}

#[pymethods]
impl TradingConfig {
    #[new]
    fn new(
        rpc_url: Option<String>,
        ws_url: Option<String>,
        api_key: Option<String>,
        sol_mint: Option<String>,
        rpc_rate_limit: Option<u64>,
        compute_limit: Option<u32>,
        priority_fee: Option<u64>,
        skip_preflight: Option<bool>,
        max_retries: Option<u8>,
        front_run_amount: Option<f64>,
        min_victim_size: Option<f64>,
        max_victim_size: Option<f64>,
        min_profit: Option<f64>,
        slippage_bps: Option<u16>,
        min_sol_balance: Option<f64>,
        min_wsol_balance: Option<f64>,
        raydium_program_id: Option<String>,
        token_program_id: Option<String>,
        system_program_id: Option<String>,
        associated_token_program_id: Option<String>,
        token_mint: Option<String>,
        amm_program: Option<String>,
        amm_id: Option<String>,
        market_amm_authority: Option<String>,
        market_amm_open_orders: Option<String>,
        market_amm_target: Option<String>,
        market_pool_token_account: Option<String>,
        market_pool_pc_token_account: Option<String>,
    ) -> Self {
        // Use a closure to avoid .clone()
        let get_or_default = |key| {
            env::var(key).unwrap_or_else(|_| "".to_string())
        };

        // Fixed SOL_MINT handling with proper fallback
        let sol_mint_value = sol_mint.unwrap_or_else(|| {
            let env_val = get_or_default("SOL_MINT");
            if env_val.is_empty() {
                "So11111111111111111111111111111111111111112".to_string()
            } else {
                env_val
            }
        });
        
        // Clone amm_program before it's moved
        let amm_program_clone = amm_program.clone();

        Self {
            rpc_url: rpc_url.unwrap_or_else(|| {
                env::var("RPC_URL").unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string())
            }),
            ws_url: ws_url.unwrap_or_else(|| {
                env::var("WS_URL").unwrap_or_else(|_| "wss://api.mainnet-beta.solana.com".to_string())
            }),
            api_key: api_key.unwrap_or_else(|| get_or_default("API_KEY")),
            sol_mint: sol_mint_value,
            rpc_rate_limit: rpc_rate_limit.unwrap_or_else(|| {
                env::var("RPC_RATE_LIMIT")
                    .map(|v| v.parse().unwrap_or(DEFAULT_RPC_RATE_LIMIT))
                    .unwrap_or(DEFAULT_RPC_RATE_LIMIT)
            }),
            // Store additional fields
            token_mint,
            amm_program,
            compute_limit: compute_limit.unwrap_or_else(|| {
                env::var("COMPUTE_LIMIT").map(|v| v.parse().unwrap_or(COMPUTE_UNITS)).unwrap_or(COMPUTE_UNITS)
            }),
            priority_fee: priority_fee.unwrap_or_else(|| {
                env::var("PRIORITY_FEE").map(|v| v.parse().unwrap_or(PRIORITY_FEE)).unwrap_or(PRIORITY_FEE)
            }),
            skip_preflight: skip_preflight.unwrap_or_else(|| {
                env::var("SKIP_PREFLIGHT").map(|v| v.parse().unwrap_or(true)).unwrap_or(true)
            }),
            max_retries: max_retries.unwrap_or_else(|| {
                env::var("MAX_RETRIES").map(|v| v.parse().unwrap_or(MAX_RETRY_COUNT)).unwrap_or(MAX_RETRY_COUNT)
            }),
            front_run_amount: front_run_amount.unwrap_or_else(|| {
                env::var("FRONT_RUN_AMOUNT").map(|v| v.parse().unwrap_or(0.35)).unwrap_or(0.35)
            }),
            min_victim_size: min_victim_size.unwrap_or_else(|| {
                env::var("MIN_VICTIM_SIZE").map(|v| v.parse().unwrap_or(5.0)).unwrap_or(5.0)
            }),
            max_victim_size: max_victim_size.unwrap_or_else(|| {
                env::var("MAX_VICTIM_SIZE").map(|v| v.parse().unwrap_or(320.0)).unwrap_or(320.0)
            }),
            min_profit: min_profit.unwrap_or_else(|| {
                env::var("MIN_PROFIT").map(|v| v.parse().unwrap_or(0.002)).unwrap_or(0.002)
            }),
            slippage_bps: slippage_bps.unwrap_or_else(|| {
                env::var("SLIPPAGE_BPS").map(|v| v.parse().unwrap_or(1000)).unwrap_or(1000)
            }),
            min_sol_balance: min_sol_balance.unwrap_or_else(|| {
                env::var("MIN_SOL_BALANCE").map(|v| v.parse().unwrap_or(0.03)).unwrap_or(0.03)
            }),
            min_wsol_balance: min_wsol_balance.unwrap_or_else(|| {
                env::var("MIN_WSOL_BALANCE").map(|v| v.parse().unwrap_or(0.02)).unwrap_or(0.02)
            }),
            // Store additional market parameters
            amm_id,
            market_amm_authority,
            market_amm_open_orders,
            market_amm_target,
            market_pool_token_account,
            market_pool_pc_token_account,
            raydium_program_id: raydium_program_id.unwrap_or_else(|| {
                amm_program_clone.unwrap_or_else(|| get_or_default("RAYDIUM_PROGRAM_ID"))
            }),
            token_program_id: token_program_id.unwrap_or_else(|| get_or_default("TOKEN_PROGRAM_ID")),
            system_program_id: system_program_id.unwrap_or_else(|| get_or_default("SYSTEM_PROGRAM_ID")),
            associated_token_program_id: associated_token_program_id.unwrap_or_else(|| get_or_default("ASSOCIATED_TOKEN_PROGRAM_ID")),
            token_pair_settings: HashMap::new(),
        }
    }

    // Add a token pair setting
    fn add_token_pair_setting(&mut self, token_pair: TokenPairSetting) -> PyResult<()> {
        self.token_pair_settings
            .insert(token_pair.token_mint.clone(), token_pair);
        Ok(())
    }

    // Remove a token pair setting
    fn remove_token_pair_setting(&mut self, token_mint: &str) -> PyResult<bool> {
        Ok(self.token_pair_settings.remove(token_mint).is_some())
    }

    // Get a token pair setting
    fn get_token_pair_setting(&self, token_mint: &str) -> PyResult<Option<TokenPairSetting>> {
        Ok(self.token_pair_settings.get(token_mint).cloned())
    }

    // Get all active token mints
    fn get_active_token_mints(&self) -> PyResult<Vec<String>> {
        Ok(self.token_pair_settings
            .iter()
            .filter(|(_, v)| v.enabled)
            .map(|(k, _)| k.clone())
            .collect())
    }

    // Save config to file
    fn save_to_file(&self, path: &str) -> PyResult<()> {
        let json = serde_json::to_string_pretty(&self)
            .map_err(|e| PyValueError::new_err(format!("Failed to serialize config: {}", e)))?;

        fs::write(path, json)
            .map_err(|e| PyValueError::new_err(format!("Failed to write config file: {}", e)))?;

        Ok(())
    }

    // Load config from file
    #[staticmethod]
    fn load_from_file(path: &str) -> PyResult<Self> {
        let json = fs::read_to_string(path)
            .map_err(|e| PyValueError::new_err(format!("Failed to read config file: {}", e)))?;

        let config: Self = serde_json::from_str(&json)
            .map_err(|e| PyValueError::new_err(format!("Failed to parse config file: {}", e)))?;

        Ok(config)
    }

    // Load from environment variables
    #[staticmethod]
    fn from_env() -> PyResult<Self> {
        Ok(Self::new(
            None, None, None, None, None, None, None, None,
            None, None, None, None, None, None, None, None,
            None, None, None, None, None, None, None, None,
            None, None, None, None
        ))
    }
}

// Structure to hold swap parameters
#[pyclass]
#[derive(Clone)]
pub struct SwapParams {
    #[pyo3(get, set)]
    pub token_mint: String,
    #[pyo3(get, set)]
    pub amount_in: f64,
    #[pyo3(get, set)]
    pub is_buy: bool,
    #[pyo3(get, set)]
    pub slippage_bps: u16,
    #[pyo3(get, set)]
    pub compute_limit: u32,
    #[pyo3(get, set)]
    pub priority_fee: u64,
}

#[pymethods]
impl SwapParams {
    #[new]
    fn new(
        token_mint: String,
        amount_in: f64,
        is_buy: bool,
        slippage_bps: Option<u16>,
        compute_limit: Option<u32>,
        priority_fee: Option<u64>,
    ) -> Self {
        Self {
            token_mint,
            amount_in,
            is_buy,
            slippage_bps: slippage_bps.unwrap_or(1000),
            compute_limit: compute_limit.unwrap_or(COMPUTE_UNITS),
            priority_fee: priority_fee.unwrap_or(PRIORITY_FEE),
        }
    }
}

// Structure for transaction status tracking
#[pyclass]
#[derive(Clone)]
pub struct TransactionStatus {
    #[pyo3(get)]
    pub signature: String,
    #[pyo3(get)]
    pub success: bool,
    #[pyo3(get)]
    pub error: Option<String>,
    #[pyo3(get)]
    pub timestamp: u64,
    #[pyo3(get)]
    pub simulation_only: bool,
}

#[pymethods]
impl TransactionStatus {
    #[new]
    fn new(
        signature: String,
        success: bool,
        error: Option<String>,
        timestamp: Option<u64>,
        simulation_only: Option<bool>,
    ) -> Self {
        Self {
            signature,
            success,
            error,
            timestamp: timestamp.unwrap_or_else(|| {
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_secs()
            }),
            simulation_only: simulation_only.unwrap_or(false),
        }
    }
}

// Main trading core class
#[pyclass]
pub struct RaydiumTradingCore {
    client: Arc<RpcClient>,
    keypair: Arc<Keypair>,
    config: Arc<RwLock<TradingConfig>>,
    balances: Arc<RwLock<(f64, HashMap<String, f64>)>>, // (SOL, token balances by mint)
    rate_limiter: Arc<RateLimiter>,
    pending_transaction: Arc<AtomicBool>, // Flag to indicate pending transaction
    last_trade_time: Arc<AtomicU64>,      // Store the last trade time
    public_key: String,
}

#[pymethods]
impl RaydiumTradingCore {
    #[new]
    fn new(
        rpc_url: Option<String>,
        private_key: Option<String>,
        keypair_path: Option<String>,
        config: Option<TradingConfig>,
    ) -> PyResult<Self> {
        let config_obj = config.unwrap_or_else(|| {
            TradingConfig::new(
                rpc_url.clone(),
                None, None, None, None, None, None, None,
                None, None, None, None, None, None,
                None, None, None, None, None, None,
                None, None, None, None, None, None,
                None, None
            )
        });

        let client = Arc::new(RpcClient::new_with_commitment(
            rpc_url.unwrap_or_else(|| config_obj.rpc_url.clone()),
            CommitmentConfig::confirmed(),
        ));

        // Load keypair from either a provided string or file
        let keypair = if let Some(pk) = private_key {
            let bytes = pk
                .from_base58()
                .map_err(|e| PyValueError::new_err(format!("Invalid private key: {:?}", e)))?;

            Keypair::from_bytes(&bytes)
                .map_err(|e| PyValueError::new_err(format!("Invalid keypair: {}", e)))?
        } else if let Some(path) = keypair_path {
            if !Path::new(&path).exists() {
                return Err(PyValueError::new_err(format!(
                    "Keypair file not found: {}",
                    path
                )));
            }

            let bytes = fs::read(&path).map_err(|e| {
                PyValueError::new_err(format!("Failed to read keypair file: {}", e))
            })?;

            Keypair::from_bytes(&bytes)
                .map_err(|e| PyValueError::new_err(format!("Invalid keypair: {}", e)))?
        } else {
            let private_key = env::var("SOLANA_PRIVATE_KEY").map_err(|_| {
                PyValueError::new_err("SOLANA_PRIVATE_KEY not found in environment")
            })?;

            let secret_key_bytes = private_key.from_base58().map_err(|e| {
                PyValueError::new_err(format!("Invalid private key encoding: {:?}", e))
            })?;

            Keypair::from_bytes(&secret_key_bytes)
                .map_err(|e| PyValueError::new_err(format!("Invalid keypair: {}", e)))?
        };

        let rate_limiter = Arc::new(RateLimiter::new(config_obj.rpc_rate_limit));
        let public_key = keypair.pubkey().to_string();

        Ok(Self {
            client,
            keypair: Arc::new(keypair),
            config: Arc::new(RwLock::new(config_obj)),
            balances: Arc::new(RwLock::new((0.0, HashMap::new()))),
            rate_limiter,
            pending_transaction: Arc::new(AtomicBool::new(false)),
            last_trade_time: Arc::new(AtomicU64::new(0)),
            public_key,
        })
    }

    // Get the public key for logging/display
    fn get_public_key(&self) -> String {
        self.public_key.clone()
    }

    // Function to execute a swap on Raydium
    fn execute_swap<'py>(&self, py: Python<'py>, params: SwapParams) -> PyResult<&'py PyAny> {
        let client = self.client.clone();
        let keypair = self.keypair.clone();
        let config_arc = self.config.clone();
        let _rate_limiter = self.rate_limiter.clone();
        let pending_transaction = self.pending_transaction.clone();

        // Check if a transaction is already pending
        if pending_transaction.load(Ordering::Relaxed) {
            return Err(PyValueError::new_err("A transaction is already in progress"));
        }

        let params_clone = params.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            // Set pending transaction flag
            pending_transaction.store(true, Ordering::Relaxed);

            // Get config values
            let config = match config_arc.read() {
                Ok(config) => config.clone(),
                Err(_) => {
                    pending_transaction.store(false, Ordering::Relaxed);
                    return Err(PyValueError::new_err("Failed to read config"));
                }
            };

            // Retrieve token pair settings
            let token_pair = match config.token_pair_settings.get(&params_clone.token_mint) {
                Some(settings) => settings.clone(),
                None => {
                    if Some(params_clone.token_mint.clone()) == config.token_mint {
                        // Handle synthetic token pair logic
                        let all_present = config.amm_id.is_some() &&
                                          config.market_amm_authority.is_some() &&
                                          config.market_amm_open_orders.is_some() &&
                                          config.market_amm_target.is_some() &&
                                          config.market_pool_token_account.is_some() &&
                                          config.market_pool_pc_token_account.is_some();
                        
                        if all_present {
                            // Return synthetic settings
                            TokenPairSetting {
                                token_mint: params_clone.token_mint.clone(),
                                amm_id: config.amm_id.clone().unwrap(),
                                amm_authority: config.market_amm_authority.as_ref().unwrap().clone(),
                                amm_open_orders: config.market_amm_open_orders.as_ref().unwrap().clone(),
                                amm_target: config.market_amm_target.as_ref().unwrap().clone(),
                                pool_token_account: config.market_pool_token_account.as_ref().unwrap().clone(),
                                pool_pc_account: config.market_pool_pc_token_account.as_ref().unwrap().clone(),
                                min_liquidity_pct: 95.0,
                                min_exit_liquidity_sol: 5.0,
                                auth_revoked: true,
                                buyer_seller_ratio: 1.0,
                                min_volume_5m: 10.0,
                                enabled: true,
                            }
                        } else {
                            pending_transaction.store(false, Ordering::Relaxed);
                            return Err(PyValueError::new_err(format!(
                                "Token pair settings not found for {}",
                                params_clone.token_mint
                            )));
                        }
                    } else {
                        pending_transaction.store(false, Ordering::Relaxed);
                        return Err(PyValueError::new_err(format!(
                            "Token pair settings not found for {}",
                            params_clone.token_mint
                        )));
                    }
                }
            };

            // Generate, sign, and send a transaction to perform the swap
            let _token_mint_pubkey = match Pubkey::from_str(&params_clone.token_mint) {
                Ok(pubkey) => pubkey,
                Err(e) => {
                    pending_transaction.store(false, Ordering::Relaxed);
                    return Err(PyValueError::new_err(format!("Invalid token mint: {}", e)));
                }
            };

            // Create and send transaction
            let result = execute_swap_internal(
                &client,
                &keypair,
                &token_pair,
                &params_clone,
                params_clone.is_buy
            ).await;

            // Reset pending transaction flag
            pending_transaction.store(false, Ordering::Relaxed);

            match result {
                Ok(signature) => {
                    let status = TransactionStatus::new(
                        signature, 
                        true, 
                        None, 
                        Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()),
                        Some(false)
                    );
                    Ok(status)
                },
                Err(e) => {
                    let status = TransactionStatus::new(
                        "failed".to_string(), 
                        false, 
                        Some(e.to_string()), 
                        Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()),
                        Some(false)
                    );
                    Ok(status)
                }
            }
        })
    }

    // Get balances for SOL and SPL tokens
    fn get_balances<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> {
        let client = self.client.clone();
        let keypair = self.keypair.clone();
        let config_arc = self.config.clone();
        let balances_arc = self.balances.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let pubkey = keypair.pubkey();
            
            // Get SOL balance
            let sol_balance = client.get_balance(&pubkey)
                .await
                .map_err(|e| PyValueError::new_err(format!("Failed to get SOL balance: {}", e)))?;
            
            let sol_balance_f64 = sol_balance as f64 / LAMPORTS_PER_SOL as f64;
            
            // Get config
            let config = match config_arc.read() {
                Ok(config) => config.clone(),
                Err(_) => return Err(PyValueError::new_err("Failed to read config")),
            };
            
            // Get token balances
            let mut token_balances = HashMap::new();
            
            // Add WSOL balance
            let wsol_token_account = get_associated_token_address(&pubkey, &Pubkey::from_str(&config.sol_mint).unwrap());
            
            // Check if token account exists
            let wsol_account = client.get_account(&wsol_token_account).await;
            
            if let Ok(account) = wsol_account {
                // Parse token account data
                if let Ok(token_account) = spl_token::state::Account::unpack_from_slice(&account.data) {
                    let wsol_balance = token_account.amount as f64 / LAMPORTS_PER_SOL as f64;
                    token_balances.insert(config.sol_mint.clone(), wsol_balance);
                }
            } else {
                // Account doesn't exist, balance is 0
                token_balances.insert(config.sol_mint.clone(), 0.0);
            }
            
            // Get token balances for all active tokens
            for token_mint in config.get_active_token_mints()? {
                if token_mint == config.sol_mint {
                    continue; // Already added WSOL above
                }
                
                let token_account = get_associated_token_address(
                    &pubkey, 
                    &Pubkey::from_str(&token_mint).map_err(|e| PyValueError::new_err(format!("Invalid token mint: {}", e)))?
                );
                
                // Check if token account exists
                let account = client.get_account(&token_account).await;
                
                if let Ok(account) = account {
                    // Parse token account data
                    if let Ok(token_account) = spl_token::state::Account::unpack_from_slice(&account.data) {
                        // We would need token decimals to convert properly, but using 9 as default
                        let token_balance = token_account.amount as f64 / LAMPORTS_PER_SOL as f64;
                        token_balances.insert(token_mint, token_balance);
                    }
                } else {
                    // Account doesn't exist, balance is 0
                    token_balances.insert(token_mint, 0.0);
                }
            }
            
            // Update cached balances
            if let Ok(mut balances) = balances_arc.write() {
                *balances = (sol_balance_f64, token_balances.clone());
            }
            
            Ok((sol_balance_f64, token_balances))
        })
    }

    // Create or get associated token account for a token mint
    fn ensure_ata<'py>(&self, py: Python<'py>, token_mint: String) -> PyResult<&'py PyAny> {
        let client = self.client.clone();
        let keypair = self.keypair.clone();

        pyo3_asyncio::tokio::future_into_py(py, async move {
            let pubkey = keypair.pubkey();
            
            // Parse token mint
            let token_mint_pubkey = Pubkey::from_str(&token_mint)
                .map_err(|e| PyValueError::new_err(format!("Invalid token mint: {}", e)))?;
            
            // Get associated token account address
            let ata = get_associated_token_address(&pubkey, &token_mint_pubkey);
            
            // Check if account exists
            let account = client.get_account(&ata).await;
            
            if account.is_err() {
                // Create ATA instruction
                let ix = create_associated_token_account_idempotent(
                    &pubkey,     // Payer
                    &pubkey,     // Owner
                    &token_mint_pubkey, // Mint
                    &spl_token::id(), // Token program
                );
                
                // Create transaction
                let mut transaction = Transaction::new_with_payer(&[ix], Some(&pubkey));

                // Get recent blockhash
                let blockhash = client.get_latest_blockhash()
                    .await
                    .map_err(|e| PyValueError::new_err(format!("Failed to get blockhash: {}", e)))?;
                
                // Sign transaction
                transaction.sign(&[&*keypair], blockhash);
                
                // Send transaction
                let signature = client.send_transaction(&transaction)
                    .await
                    .map_err(|e| PyValueError::new_err(format!("Failed to create ATA: {}", e)))?;
                
                debug!("Created ATA for {}: {}", token_mint, signature);
            }
            
            Ok(ata.to_string())
        })
    }

    // Update ML signal (placeholder)
    fn update_ml_signal(&self, key: String, value: f64) -> PyResult<()> {
        // This is a placeholder for ML signal update functionality
        debug!("Updated ML signal: {} = {}", key, value);
        Ok(())
    }
}

// Function to create and send a Raydium swap transaction
async fn execute_swap_internal(
    client: &RpcClient, 
    keypair: &Arc<Keypair>, 
    token_pair: &TokenPairSetting,
    params: &SwapParams,
    is_buy: bool
) -> Result<String, PyErr> {
    // Code to create and send a Raydium swap transaction would go here
    // This is a placeholder implementation
    
    // Create transaction with correct payer format
    let mut transaction = Transaction::new_with_payer(&[], Some(&keypair.pubkey()));
    
    // Add compute budget instructions - we'll create new instructions
    let compute_limit_ix = ComputeBudgetInstruction::set_compute_unit_limit(params.compute_limit);
    let priority_fee_ix = ComputeBudgetInstruction::set_compute_unit_price(params.priority_fee);
    
    // Create a vector of instructions and use with_payer
    let instructions = vec![compute_limit_ix, priority_fee_ix];
    let mut transaction = Transaction::new_with_payer(&instructions, Some(&keypair.pubkey()));
    
    // Add swap instruction - this would be a real Raydium swap instruction
    // Placeholder for now 
    
    // Sign and send transaction
    let blockhash = client.get_latest_blockhash()
        .await
        .map_err(|e| PyValueError::new_err(format!("Failed to get blockhash: {}", e)))?;
    
    transaction.sign(&[&*keypair], blockhash);
    
    // Send transaction
    let signature = client.send_transaction(&transaction)
        .await
        .map_err(|e| PyValueError::new_err(format!("Failed to send transaction: {}", e)))?;
    
    Ok(signature.to_string())
}

// Module configuration
#[pymodule]
fn solana_rust_bot(_py: Python, m: &PyModule) -> PyResult<()> {
    pyo3_log::init();

    m.add_class::<TradingConfig>()?;
    m.add_class::<TokenPairSetting>()?;
    m.add_class::<SwapParams>()?;
    m.add_class::<TransactionStatus>()?;
    m.add_class::<RaydiumTradingCore>()?;

    Ok(())
}import asyncio
import time
import base64
import json
import os
from pathlib import Path
import torch
import numpy as np
from typing import Dict, Any, Optional

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

# Try to import the Rust module
try:
    import solana_rust_bot
    RUST_AVAILABLE = True
except ImportError:
    logger.warning("solana_rust_bot module not available. Running in simulation mode only.")
    RUST_AVAILABLE = False

class MockRustCore:
    """Mock implementation of Rust trading core for testing"""
    
    def __init__(self):
        """Initialize mock trading core"""
        self.public_key = "MockPublicKey123456789"
        self.sol_balance = 10.0
        self.wsol_balance = 5.0
        self.trades_executed = 0
        self.ml_signals = {}
    
    def get_public_key(self) -> str:
        """Get public key"""
        return self.public_key
    
    def get_balances(self) -> tuple:
        """Get SOL and wSOL balances"""
        return self.sol_balance, self.wsol_balance
    
    def update_ml_signal(self, key: str, value: float) -> None:
        """Update ML signal"""
        self.ml_signals[key] = value
        logger.debug(f"Updated ML signal: {key} = {value}")
    
    def execute_trade(self, **kwargs) -> Optional[str]:
        """Execute mock trade"""
        # Simulate success with 90% probability
        if np.random.random() < 0.9:
            self.trades_executed += 1
            # Simulate a transaction hash
            tx_hash = f"MockTx{int(time.time() * 1000)}{self.trades_executed}"
            
            # Update balances
            if kwargs.get('is_buy', True):
                # Buying token, reduce SOL
                self.sol_balance -= kwargs.get('amount_in', 0.35)
            else:
                # Selling token, increase SOL with a small profit
                self.sol_balance += kwargs.get('amount_in', 0.35) * 1.01
            
            logger.info(f"Mock trade executed: {tx_hash}")
            return tx_hash
        else:
            logger.error("Mock trade failed")
            return None


class TestTradingEngine:
    """Test harness for trading engine"""
    
    def __init__(self):
        """Initialize test harness"""
        logger.info("Initializing test trading engine")
        
        # Setup CUDA if available
        self.setup_cuda()
        
        # Initialize Rust core or mock
        if RUST_AVAILABLE:
            self.setup_rust_core()
        else:
            logger.info("Using mock Rust core")
            self.rust_core = MockRustCore()
        
        # Load ML model if available
        self.setup_ml_model()
        
        # Initialize statistics
        self.stats = {
            "start_time": time.time(),
            "trades_processed": 0,
            "trades_executed": 0,
            "successful_trades": 0,
            "total_profit": 0.0,
            "execution_times": []
        }
    
    def setup_cuda(self):
        """Setup CUDA for PyTorch"""
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        
        if torch.cuda.is_available():
            # Configure CUDA settings from config
            torch.backends.cuda.matmul.allow_tf32 = config.get("allow_tf32", True)
            torch.backends.cudnn.benchmark = config.get("cuda_benchmark", True)
            
            # Create high-priority CUDA stream for processing
            priority = config.get("stream_priority", -1)
            self.stream = torch.cuda.Stream(device=self.device, priority=priority)
            
            # GPU memory fraction
            memory_fraction = config.get("gpu_memory_fraction", 0.95)
            torch.cuda.set_per_process_memory_fraction(memory_fraction)
            
            logger.info(f"CUDA setup complete: {torch.cuda.get_device_name(0)}")
        else:
            logger.warning("CUDA not available, using CPU")
    
    def setup_rust_core(self):
        """Setup Rust trading core"""
        try:
            # Create config for Rust
            rust_config = solana_rust_bot.TradingConfig(
                rpc_url=config.get("rpc_url"),
                ws_url=config.get("ws_url"),
                api_key=config.get("api_key"),
                sol_mint=config.get("sol_mint"),
                token_mint=config.get("token_mint"),
                amm_program=config.get("raydium_program"),
                compute_limit=config.get("compute_limit"),
                priority_fee=config.get("priority_fee"),
                skip_preflight=config.get("skip_preflight"),
                front_run_amount=config.get("front_run_amount"),
                min_victim_size=config.get("min_victim_size"),
                max_victim_size=config.get("max_victim_size"),
                min_profit=config.get("min_profit"),
                slippage_bps=config.get("slippage_bps"),
                amm_id=config.get("amm_id"),
                market_amm_authority=config.get("market_amm_authority"),
                market_amm_open_orders=config.get("market_amm_open_orders"),
                market_amm_target=config.get("market_amm_target"),
                market_pool_token_account=config.get("market_pool_token_account"),
                market_pool_pc_token_account=config.get("market_pool_pc_token_account")
            )
            
            # Find keypair file
            keypair_paths = [
                Path("keypair.bin"),
                Path("id.json"),
                Path("..") / "id.json",
                Path("C:") / "solana_rust_bot" / "keypair.bin",
                Path("C:") / "solana_rust_bot" / "id.json"
            ]
            
            keypair_path = None
            for path in keypair_paths:
                if path.exists():
                    keypair_path = str(path)
                    logger.info(f"Found keypair at: {keypair_path}")
                    break
            
            if keypair_path:
                # Initialize Rust core with keypair
                self.rust_core = solana_rust_bot.TradingCore(rust_config, keypair_path)
                logger.info(f"Rust trading core initialized with public key: {self.rust_core.get_public_key()}")
                
                # Get initial balances
                sol, wsol = self.rust_core.get_balances()
                logger.info(f"Initial balances - SOL: {sol:.6f}, WSOL: {wsol:.6f}")
            else:
                logger.warning("No keypair found, using mock Rust core")
                self.rust_core = MockRustCore()
                
        except Exception as e:
            logger.error(f"Failed to initialize Rust trading core: {e}")
            logger.info("Using mock Rust core instead")
            self.rust_core = MockRustCore()
    
    def setup_ml_model(self):
        """Setup ML model for trade evaluation"""
        model_path = config.get("model_path", "models/trade_model.pt")
        
        try:
            if Path(model_path).exists():
                logger.info(f"Loading ML model from {model_path}")
                self.model = ml_model.load_model(model_path)
                self.model_loaded = True
                
                # Test model with dummy input
                with torch.cuda.stream(self.stream):
                    dummy_input = torch.randn(1, 32, device=self.device)
                    with torch.no_grad():
                        _ = self.model(dummy_input)
                        logger.info("ML model loaded and tested successfully")
            else:
                logger.warning(f"ML model not found at {model_path}")
                self.model_loaded = False
                self.model = None
                
        except Exception as e:
            logger.error(f"Failed to load ML model: {e}")
            self.model_loaded = False
            self.model = None
    
    def update_ml_signals(self, signals: Dict[str, float]):
        """Update ML signals in Rust core"""
        for key, value in signals.items():
            self.rust_core.update_ml_signal(key, value)
        
        logger.info(f"Updated ML signals: {len(signals)} signals")
    
    def decode_ray_log(self, log_str: str) -> Optional[bytes]:
        """Decode Raydium log data from string"""
        try:
            if "ray_log:" not in log_str:
                return None
            
            b64_data = log_str.split("ray_log:")[1].strip()
            return base64.b64decode(b64_data)
        except Exception as e:
            logger.error(f"Error decoding ray log: {e}")
            return None
    
    def validate_trade(self, raw_data: bytes) -> Optional[Dict[str, Any]]:
        """Validate trade data from raw bytes"""
        try:
            # Basic validation
            if len(raw_data) < 9:
                return None
            
            # Extract instruction type and amount
            instruction = raw_data[0]
            amount = int.from_bytes(raw_data[1:9], byteorder="little") / 1e9
            
            # Validate instruction type (hardcoded buy instructions as in paste-3.txt)
            buy_instructions = {0x03, 0x09}
            is_buy = instruction in buy_instructions
            
            # Skip if not a buy
            if not is_buy:
                return None
            
            # Validate amount is within our range
            min_size = config.get("min_victim_size", 5.0)
            max_size = config.get("max_victim_size", 320.0)
            
            if not (min_size <= amount <= max_size):
                return None
            
            # Return validated trade info
            return {
                "instruction": instruction,
                "amount": amount,
                "is_buy": is_buy,
                "raw_data": raw_data,
                "timestamp": time.time()
            }
        
        except Exception as e:
            logger.error(f"Trade validation error: {e}")
            return None
    
    async def simulate_trade(self, amount: float) -> None:
        """Simulate a trade with the given victim amount"""
        try:
            # Generate synthetic trade data
            instruction = 0x03  # Buy instruction
            amount_bytes = int(amount * 1e9).to_bytes(8, byteorder="little")
            raw_data = bytes([instruction]) + amount_bytes + b"\x00" * 20  # Padding
            
            logger.trade(f"Simulating trade with amount: {amount:.4f} SOL")
            
            # Validate trade
            trade_info = self.validate_trade(raw_data)
            if not trade_info:
                logger.warning(f"Trade validation failed for amount {amount}")
                return
            
            self.stats["trades_processed"] += 1
            
            # Evaluate with ML model if available
            execute_trade = True
            ml_confidence = 0.0
            
            if self.model_loaded:
                features = self.prepare_features(amount, raw_data)
                
                with torch.cuda.stream(self.stream):
                    with torch.no_grad():
                        result = self.model(features)
                        ml_confidence = result.item()
                
                # Threshold for execution
                threshold = config.get("ml_confidence_threshold", 0.7)
                execute_trade = ml_confidence >= threshold
                
                logger.info(f"ML evaluation - Confidence: {ml_confidence:.4f}, Execute: {execute_trade}")
            
            # Execute trade if approved
            if execute_trade:
                self.stats["trades_executed"] += 1
                
                # Track execution time
                start_time = time.time()
                
                # Execute front-run (buy)
                token_mint = config.get("token_mint")
                sol_mint = config.get("sol_mint")
                front_amount = config.get("front_run_amount", 0.35)
                
                logger.trade(f"Executing front-run: {front_amount} SOL → Token")
                front_tx = self.rust_core.execute_trade(
                    token_mint=token_mint,
                    sol_mint=sol_mint,
                    amount_in=front_amount,
                    is_buy=True,
                    slippage_bps=config.get("slippage_bps", 800),
                    priority_fee=config.get("priority_fee", 750000)
                )
                
                if not front_tx:
                    logger.error("Front-run transaction failed")
                    return
                
                # Wait for victim transaction
                await asyncio.sleep(0.5)
                
                # Execute back-run (sell)
                logger.trade("Executing back-run: Token → SOL")
                back_tx = self.rust_core.execute_trade(
                    token_mint=token_mint,
                    sol_mint=sol_mint,
                    amount_in=0,  # 0 means "use all available tokens"
                    is_buy=False,
                    slippage_bps=config.get("slippage_bps", 800),
                    priority_fee=config.get("priority_fee", 750000)
                )
                
                if not back_tx:
                    logger.error("Back-run transaction failed")
                    return
                
                # Calculate execution time
                execution_time = (time.time() - start_time) * 1000  # in ms
                self.stats["execution_times"].append(execution_time)
                
                # Simulate profit based on victim size
                if amount >= 200:
                    profit_rate = 0.018  # 1.8% for large trades
                elif amount >= 100:
                    profit_rate = 0.015  # 1.5% for medium-large trades
                elif amount >= 50:
                    profit_rate = 0.012  # 1.2% for medium trades
                elif amount >= 10:
                    profit_rate = 0.010  # 1.0% for small-medium trades
                else:
                    profit_rate = 0.008  # 0.8% for small trades
                
                # Calculate profit
                profit = front_amount * profit_rate
                
                # Update statistics
                self.stats["successful_trades"] += 1
                self.stats["total_profit"] += profit
                
                # Log trade details
                logger.profit(f"""
Trade #{self.stats["successful_trades"]} completed successfully:
Amount: {amount:.4f} SOL
Front TX: {front_tx}
Back TX: {back_tx}
Profit: {profit:.6f} SOL
Execution Time: {execution_time:.3f} ms
ML Confidence: {ml_confidence:.4f}
Total Profit: {self.stats["total_profit"]:.6f} SOL
""")
        
        except Exception as e:
            logger.error(f"Error simulating trade: {e}")
    
    def prepare_features(self, amount: float, raw_data: bytes) -> torch.Tensor:
        """Prepare features for ML model"""
        # Base features
        base_features = [
            amount,  # Victim size
            config.get("front_run_amount", 0.35),  # Our position size
            time.time() % 86400 / 86400,  # Time of day normalized
        ]
        
        # Add bytes from raw data
        data_features = [b / 255.0 for b in raw_data[:20]]
        
        # Pad to expected size (32 features)
        padded_features = base_features + data_features
        while len(padded_features) < 32:
            padded_features.append(0.0)
        
        # Convert to tensor and add batch dimension
        return torch.tensor([padded_features], dtype=torch.float32, device=self.device)
    
    def print_stats(self):
        """Print current statistics"""
        runtime = time.time() - self.stats["start_time"]
        runtime_minutes = runtime / 60
        
        avg_execution = 0
        if self.stats["execution_times"]:
            avg_execution = sum(self.stats["execution_times"]) / len(self.stats["execution_times"])
        
        success_rate = 0
        if self.stats["trades_executed"] > 0:
            success_rate = (self.stats["successful_trades"] / self.stats["trades_executed"]) * 100
        
        profit_per_minute = 0
        if runtime_minutes > 0:
            profit_per_minute = self.stats["total_profit"] / runtime_minutes
        
        logger.stats(f"""
==== TEST STATISTICS ====
Runtime: {runtime_minutes:.2f} minutes
Trades Processed: {self.stats["trades_processed"]}
Trades Executed: {self.stats["trades_executed"]}
Successful Trades: {self.stats["successful_trades"]}
Success Rate: {success_rate:.2f}%
Total Profit: {self.stats["total_profit"]:.6f} SOL
Avg Execution Time: {avg_execution:.3f} ms
Profit per Minute: {profit_per_minute:.6f} SOL/min
========================
""")
    
    async def run_test(self, num_trades: int = 10, interval: float = 1.0):
        """Run test with simulated trades"""
        logger.info(f"Starting test with {num_trades} simulated trades")
        
        # Update ML signals
        self.update_ml_signals({
            "market_volatility": 0.35,
            "execution_confidence": 0.85,
            "price_direction": 0.2,
            "trade_profitability": 0.75,
            "risk_score": 0.3,
            "custom_model_score": 0.92
        })
        
        # Generate random trade sizes
        min_size = config.get("min_victim_size", 5.0)
        max_size = config.get("max_victim_size", 320.0)
        
        # Create a realistic distribution - more small trades, fewer large ones
        trade_sizes = []
        for _ in range(num_trades):
            # Use a log-normal distribution centered around smaller trades
            size = min_size + np.random.lognormal(2, 1)
            size = min(size, max_size)
            trade_sizes.append(size)
        
        # Run simulations
        for i, size in enumerate(trade_sizes):
            logger.info(f"Test trade {i+1}/{num_trades} - Size: {size:.4f} SOL")
            await self.simulate_trade(size)
            
            # Wait between trades
            if i < num_trades - 1:
                await asyncio.sleep(interval)
        
        # Print final stats
        self.print_stats()


async def main():
    """Main function to run tests"""
    logger.info("Starting Solana trading engine test")
    
    # Initialize test engine
    engine = TestTradingEngine()
    
    try:
        # Run test simulation
        await engine.run_test(num_trades=10, interval=1.0)
        
    except KeyboardInterrupt:
        logger.info("Test interrupted by user")
    
    except Exception as e:
        logger.error(f"Test error: {e}")
    
    finally:
        # Print final stats
        engine.print_stats()


if __name__ == "__main__":
    asyncio.run(main())