"""
AI TRADING OPTIMIZER - GPU Accelerated (RTX 5070)
==================================================
Script d'optimisation IA pour ameliorer les performances du trading.
Utilise CUDA via CuPy pour acceleration GPU massive.

Fonctionnalites:
1. Backtesting automatique sur donnees historiques
2. Optimisation des parametres par algorithmes genetiques
3. Exploration des strategies par apprentissage par renforcement
4. Analyse de correlation entre indicateurs
5. Generation de rapports de performance
6. [GPU] Calculs vectorisés sur CUDA (10-100x plus rapide)
7. [GPU] Backtesting parallèle de centaines de combinaisons
8. [GPU] Optimisation PSO (Particle Swarm Optimization)

Usage:
    python ai_optimizer.py --mode backtest
    python ai_optimizer.py --mode optimize
    python ai_optimizer.py --mode explore
    python ai_optimizer.py --mode full
"""

import os
import sys
import json
import time
import random
import asyncio
import aiohttp
import numpy as np
from datetime import datetime, timedelta
from collections import deque
from dataclasses import dataclass, asdict
from typing import List, Dict, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')

# ═══════════════════════════════════════════════════════════════════════════════
# GPU ACCELERATION (RTX 5070)
# ═══════════════════════════════════════════════════════════════════════════════

GPU_AVAILABLE = False
GPU_NAME = "CPU"
xp = np  # Par défaut, utiliser NumPy

try:
    import cupy as cp
    # Vérifier si GPU disponible
    if cp.cuda.runtime.getDeviceCount() > 0:
        GPU_AVAILABLE = True
        xp = cp  # Utiliser CuPy pour les calculs
        props = cp.cuda.runtime.getDeviceProperties(0)
        GPU_NAME = props['name'].decode() if isinstance(props['name'], bytes) else str(props['name'])
        print(f"[GPU] CUDA disponible: {GPU_NAME}")
        print(f"[GPU] Memoire GPU: {props['totalGlobalMem'] / 1024**3:.1f} GB")
except ImportError:
    print("[GPU] CuPy non installe - Utilisation CPU (pip install cupy-cuda12x)")
except Exception as e:
    print(f"[GPU] Erreur initialisation CUDA: {e}")

def to_gpu(arr):
    """Transfère un array vers le GPU si disponible"""
    if GPU_AVAILABLE and isinstance(arr, np.ndarray):
        return cp.asarray(arr)
    return arr

def to_cpu(arr):
    """Transfère un array vers le CPU"""
    if GPU_AVAILABLE and hasattr(arr, 'get'):
        return arr.get()
    return arr

# Fix encodage console Windows
try:
    if sys.platform == 'win32':
        import io
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
except:
    pass

# Configuration du script
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(SCRIPT_DIR)

# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════════

@dataclass
class TradingParams:
    """Paramètres de trading à optimiser - VERSION COMPLÈTE"""
    # Indicateurs techniques
    rsi_period: int = 14
    rsi_oversold: float = 30.0
    rsi_overbought: float = 70.0
    ema_short: int = 12
    ema_long: int = 26
    bb_period: int = 20
    bb_std: float = 2.0
    
    # Trend Following
    trend_strength_threshold: int = 40
    momentum_threshold: float = 1.5
    
    # Gestion du risque
    stop_loss: float = 1.0
    take_profit: float = 0.5
    
    # Trailing Stop
    enable_trailing_stop: bool = True
    trailing_stop_distance: float = 1.5
    trailing_stop_activation: float = 0.4
    
    # Positions
    max_order_size: int = 40
    max_open_positions: int = 20
    min_order_size: int = 10
    max_risk_per_trade: int = 20
    
    # Signaux
    required_signals: int = 2
    min_ai_score_for_buy: int = 60
    block_buy_on_bearish: bool = True
    min_buy_signals: int = 3
    min_sell_signals: int = 4
    
    # Breakout
    enable_breakout_detection: bool = True
    min_breakout_strength: float = 0.5
    breakout_signal_bonus: int = 3
    
    # Stratégies avancées
    enable_advanced_strategies: bool = True
    strategy_consensus_threshold: int = 60
    
    # Machine Learning Ensemble
    enable_ensemble_ml: bool = True
    ml_min_training_samples: int = 200
    ml_retrain_interval: int = 50
    ml_confidence_threshold: float = 0.65
    
    # Feature Engineering
    enable_advanced_features: bool = True
    
    # ═══════════════════════════════════════════════════════════════════════════
    # ROTATION INTELLIGENTE - Paramètres ajoutés le 28/12/2025
    # ═══════════════════════════════════════════════════════════════════════════
    enable_smart_rotation: bool = True
    rotation_min_cycle_end_score: int = 60
    rotation_min_opportunity_score: int = 70
    rotation_min_score_advantage: int = 20
    rotation_min_profit: float = -0.1
    rotation_min_hold_time: int = 20
    rotation_cooldown: int = 45
    rotation_max_per_hour: int = 2
    
    # ═══════════════════════════════════════════════════════════════════════════
    # EXPOSITION AU MARCHÉ - Paramètres ajoutés le 28/12/2025
    # ═══════════════════════════════════════════════════════════════════════════
    max_total_exposure_percent: int = 15
    max_exposure_per_crypto: int = 2
    max_trades_per_hour: int = 8
    enable_conservative_mode: bool = True
    btc_volatility_threshold: float = 3.0
    conservative_factor: float = 0.5
    
    def to_dict(self):
        return asdict(self)
    
    @classmethod
    def from_config(cls):
        """Charge les paramètres depuis config.py"""
        try:
            import config
            return cls(
                rsi_period=getattr(config, 'RSI_PERIOD', 14),
                rsi_oversold=getattr(config, 'RSI_OVERSOLD', 30.0),
                rsi_overbought=getattr(config, 'RSI_OVERBOUGHT', 70.0),
                ema_short=getattr(config, 'EMA_SHORT', 12),
                ema_long=getattr(config, 'EMA_LONG', 26),
                bb_period=getattr(config, 'BB_PERIOD', 20),
                bb_std=getattr(config, 'BB_STD', 2.0),
                trend_strength_threshold=getattr(config, 'TREND_STRENGTH_THRESHOLD', 40),
                momentum_threshold=getattr(config, 'MOMENTUM_THRESHOLD', 1.5),
                stop_loss=getattr(config, 'STOP_LOSS_PERCENT', 1.0),
                take_profit=getattr(config, 'TAKE_PROFIT_PERCENT', 0.5),
                enable_trailing_stop=getattr(config, 'ENABLE_TRAILING_STOP', True),
                trailing_stop_distance=getattr(config, 'TRAILING_STOP_DISTANCE', 1.5),
                trailing_stop_activation=getattr(config, 'TRAILING_STOP_ACTIVATION', 0.4),
                max_order_size=getattr(config, 'MAX_ORDER_SIZE', 40),
                max_open_positions=getattr(config, 'MAX_OPEN_POSITIONS', 20),
                min_order_size=getattr(config, 'MIN_ORDER_SIZE', 10),
                max_risk_per_trade=getattr(config, 'MAX_RISK_PER_TRADE', 20),
                required_signals=getattr(config, 'REQUIRED_SIGNALS', 2),
                min_ai_score_for_buy=getattr(config, 'MIN_AI_SCORE_FOR_BUY', 60),
                block_buy_on_bearish=getattr(config, 'BLOCK_BUY_ON_BEARISH', True),
                min_buy_signals=getattr(config, 'MIN_BUY_SIGNALS', 3),
                min_sell_signals=getattr(config, 'MIN_SELL_SIGNALS', 4),
                enable_breakout_detection=getattr(config, 'ENABLE_BREAKOUT_DETECTION', True),
                min_breakout_strength=getattr(config, 'MIN_BREAKOUT_STRENGTH', 0.5),
                breakout_signal_bonus=getattr(config, 'BREAKOUT_SIGNAL_BONUS', 3),
                enable_advanced_strategies=getattr(config, 'ENABLE_ADVANCED_STRATEGIES', True),
                strategy_consensus_threshold=getattr(config, 'STRATEGY_CONSENSUS_THRESHOLD', 60),
                enable_ensemble_ml=getattr(config, 'ENABLE_ENSEMBLE_ML', True),
                ml_min_training_samples=getattr(config, 'ML_MIN_TRAINING_SAMPLES', 200),
                ml_retrain_interval=getattr(config, 'ML_RETRAIN_INTERVAL', 50),
                ml_confidence_threshold=getattr(config, 'ML_CONFIDENCE_THRESHOLD', 0.65),
                enable_advanced_features=getattr(config, 'ENABLE_ADVANCED_FEATURES', True),
                # Rotation intelligente
                enable_smart_rotation=getattr(config, 'ENABLE_SMART_ROTATION', True),
                rotation_min_cycle_end_score=getattr(config, 'ROTATION_MIN_CYCLE_END_SCORE', 60),
                rotation_min_opportunity_score=getattr(config, 'ROTATION_MIN_OPPORTUNITY_SCORE', 70),
                rotation_min_score_advantage=getattr(config, 'ROTATION_MIN_SCORE_ADVANTAGE', 20),
                rotation_min_profit=getattr(config, 'ROTATION_MIN_PROFIT', -0.1),
                rotation_min_hold_time=getattr(config, 'ROTATION_MIN_HOLD_TIME', 20),
                rotation_cooldown=getattr(config, 'ROTATION_COOLDOWN', 45),
                rotation_max_per_hour=getattr(config, 'ROTATION_MAX_PER_HOUR', 2),
                # Exposition au marché
                max_total_exposure_percent=getattr(config, 'MAX_TOTAL_EXPOSURE_PERCENT', 15),
                max_exposure_per_crypto=getattr(config, 'MAX_EXPOSURE_PER_CRYPTO', 2),
                max_trades_per_hour=getattr(config, 'MAX_TRADES_PER_HOUR', 8),
                enable_conservative_mode=getattr(config, 'ENABLE_CONSERVATIVE_MODE', True),
                btc_volatility_threshold=getattr(config, 'BTC_VOLATILITY_THRESHOLD', 3.0),
                conservative_factor=getattr(config, 'CONSERVATIVE_FACTOR', 0.5)
            )
        except Exception as e:
            print(f"[WARNING] Impossible de charger config.py: {e}")
            return cls()
    
    @classmethod
    def random(cls):
        """Génère des paramètres aléatoires pour l'exploration"""
        return cls(
            rsi_period=random.randint(7, 21),
            rsi_oversold=random.uniform(20, 40),
            rsi_overbought=random.uniform(60, 80),
            ema_short=random.randint(5, 15),
            ema_long=random.randint(15, 50),
            bb_period=random.randint(10, 30),
            bb_std=random.uniform(1.5, 3.0),
            trend_strength_threshold=random.randint(30, 60),
            momentum_threshold=random.uniform(1.0, 3.0),
            stop_loss=random.uniform(0.5, 3.0),
            take_profit=random.uniform(0.3, 2.0),
            enable_trailing_stop=random.choice([True, False]),
            trailing_stop_distance=random.uniform(1.0, 3.0),
            trailing_stop_activation=random.uniform(0.2, 1.0),
            max_order_size=random.randint(20, 100),
            max_open_positions=random.randint(5, 30),
            min_order_size=10,
            max_risk_per_trade=random.randint(10, 30),
            required_signals=random.randint(1, 3),
            min_ai_score_for_buy=random.randint(50, 75),
            block_buy_on_bearish=random.choice([True, False]),
            min_buy_signals=random.randint(2, 5),
            min_sell_signals=random.randint(3, 5),
            enable_breakout_detection=True,
            min_breakout_strength=random.uniform(0.3, 1.0),
            breakout_signal_bonus=random.randint(1, 5),
            enable_advanced_strategies=True,
            strategy_consensus_threshold=random.randint(50, 80),
            enable_ensemble_ml=True,
            ml_min_training_samples=random.randint(100, 300),
            ml_retrain_interval=random.randint(30, 100),
            ml_confidence_threshold=random.uniform(0.5, 0.8),
            enable_advanced_features=True,
            # Rotation intelligente
            enable_smart_rotation=random.choice([True, False]),
            rotation_min_cycle_end_score=random.randint(40, 80),
            rotation_min_opportunity_score=random.randint(60, 85),
            rotation_min_score_advantage=random.randint(10, 35),
            rotation_min_profit=random.uniform(-0.5, 0.3),
            rotation_min_hold_time=random.randint(10, 45),
            rotation_cooldown=random.randint(20, 90),
            rotation_max_per_hour=random.randint(1, 6),
            # Exposition au marché
            max_total_exposure_percent=random.randint(10, 30),
            max_exposure_per_crypto=random.uniform(1.5, 4.0),
            max_trades_per_hour=random.randint(4, 15),
            enable_conservative_mode=random.choice([True, False]),
            btc_volatility_threshold=random.uniform(2.0, 5.0),
            conservative_factor=random.uniform(0.3, 0.8)
        )
    
    def mutate(self, mutation_rate: float = 0.2):
        """Mutation pour l'algorithme génétique"""
        params = self.to_dict()
        for key, value in params.items():
            if random.random() < mutation_rate:
                if isinstance(value, bool):
                    params[key] = not value
                elif isinstance(value, int):
                    params[key] = max(1, value + random.randint(-3, 3))
                elif isinstance(value, float):
                    params[key] = max(0.1, value * random.uniform(0.8, 1.2))
        return TradingParams(**params)


@dataclass
class TradeResult:
    """Résultat d'un trade simulé"""
    symbol: str
    entry_price: float
    exit_price: float
    entry_time: datetime
    exit_time: datetime
    pnl: float
    pnl_percent: float
    reason: str  # STOP_LOSS, TAKE_PROFIT, SIGNAL


@dataclass
class BacktestResult:
    """Résultat d'un backtest complet"""
    params: TradingParams
    total_trades: int
    winning_trades: int
    losing_trades: int
    total_pnl: float
    win_rate: float
    avg_win: float
    avg_loss: float
    profit_factor: float
    max_drawdown: float
    sharpe_ratio: float
    trades: List[TradeResult]
    
    def fitness_score(self) -> float:
        """Score de fitness pour l'optimisation (plus élevé = meilleur)"""
        if self.total_trades < 10:
            return -1000  # Pénaliser les stratégies qui tradent trop peu
        
        # Combinaison pondérée des métriques
        score = (
            self.total_pnl * 0.3 +
            self.win_rate * 2.0 +
            self.profit_factor * 10.0 +
            (100 - self.max_drawdown) * 0.5 +
            self.sharpe_ratio * 5.0 -
            (1 / max(self.total_trades, 1)) * 50  # Bonus pour plus de trades
        )
        return score


# ═══════════════════════════════════════════════════════════════════════════════
# INDICATEURS TECHNIQUES (GPU-ACCELERES)
# ═══════════════════════════════════════════════════════════════════════════════

class TechnicalIndicators:
    """Calcul des indicateurs techniques - Version CPU"""
    
    @staticmethod
    def sma(prices: List[float], period: int) -> Optional[float]:
        if len(prices) < period:
            return None
        return sum(prices[-period:]) / period
    
    @staticmethod
    def ema(prices: List[float], period: int) -> Optional[float]:
        if len(prices) < period:
            return None
        multiplier = 2 / (period + 1)
        ema = prices[0]
        for price in prices[1:]:
            ema = (price - ema) * multiplier + ema
        return ema
    
    @staticmethod
    def rsi(prices: List[float], period: int = 14) -> Optional[float]:
        if len(prices) < period + 1:
            return None
        
        deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
        gains = [d if d > 0 else 0 for d in deltas[-period:]]
        losses = [-d if d < 0 else 0 for d in deltas[-period:]]
        
        avg_gain = sum(gains) / period
        avg_loss = sum(losses) / period
        
        if avg_loss == 0:
            return 100
        
        rs = avg_gain / avg_loss
        return 100 - (100 / (1 + rs))
    
    @staticmethod
    def bollinger(prices: List[float], period: int = 20, std_dev: float = 2) -> Tuple[Optional[float], Optional[float], Optional[float]]:
        if len(prices) < period:
            return None, None, None
        
        slice_prices = prices[-period:]
        sma = sum(slice_prices) / period
        variance = sum((p - sma) ** 2 for p in slice_prices) / period
        std = variance ** 0.5
        
        return sma + std_dev * std, sma, sma - std_dev * std


class GPUIndicators:
    """Calcul des indicateurs techniques sur GPU (RTX 5060 Ti) - VECTORISE"""
    
    @staticmethod
    def sma_gpu(prices_gpu, period: int):
        """SMA vectorisée sur GPU - calcule pour TOUS les points"""
        n = len(prices_gpu)
        if n < period:
            return xp.full(n, xp.nan)
        
        # Utiliser cumsum pour O(n) au lieu de O(n*period)
        cumsum = xp.cumsum(prices_gpu)
        # Pas de xp.insert (non dispo CuPy), on utilise le padding manuel
        cumsum_padded = xp.zeros(n + 1)
        cumsum_padded[1:] = cumsum
        sma = (cumsum_padded[period:] - cumsum_padded[:-period]) / period
        
        # Padding au début avec NaN
        result = xp.full(n, xp.nan)
        result[period-1:] = sma
        return result
    
    @staticmethod
    def ema_gpu(prices_gpu, period: int):
        """EMA vectorisée sur GPU"""
        n = len(prices_gpu)
        if n < period:
            return xp.full(n, xp.nan)
        
        alpha = 2.0 / (period + 1)
        
        # Initialiser avec SMA pour les premiers points
        result = xp.zeros(n)
        result[period-1] = float(xp.mean(prices_gpu[:period]))
        
        # Calcul itératif (optimisé sur GPU)
        for i in range(period, n):
            result[i] = alpha * float(prices_gpu[i]) + (1 - alpha) * float(result[i-1])
        
        result[:period-1] = xp.nan
        return result
    
    @staticmethod
    def rsi_gpu(prices_gpu, period: int = 14):
        """RSI vectorisée sur GPU - calcule pour TOUS les points"""
        n = len(prices_gpu)
        if n < period + 1:
            return xp.full(n, xp.nan)
        
        # Calculer les deltas
        deltas = xp.diff(prices_gpu)
        
        # Séparer gains et pertes
        gains = xp.where(deltas > 0, deltas, 0.0)
        losses = xp.where(deltas < 0, -deltas, 0.0)
        
        # Moyenne mobile des gains/pertes
        avg_gains = xp.zeros(n - 1)
        avg_losses = xp.zeros(n - 1)
        
        # Premier calcul = SMA
        avg_gains[period-1] = float(xp.mean(gains[:period]))
        avg_losses[period-1] = float(xp.mean(losses[:period]))
        
        # Smoothed average
        for i in range(period, n - 1):
            avg_gains[i] = (float(avg_gains[i-1]) * (period - 1) + float(gains[i])) / period
            avg_losses[i] = (float(avg_losses[i-1]) * (period - 1) + float(losses[i])) / period
        
        # Calculer RS et RSI
        rs = xp.where(avg_losses > 0, avg_gains / avg_losses, 100.0)
        rsi = 100.0 - (100.0 / (1.0 + rs))
        
        # Résultat avec padding
        result = xp.full(n, xp.nan)
        result[period:] = rsi[period-1:]
        return result
    
    @staticmethod
    def bollinger_gpu(prices_gpu, period: int = 20, std_dev: float = 2.0):
        """Bollinger Bands vectorisées sur GPU"""
        n = len(prices_gpu)
        if n < period:
            return xp.full(n, xp.nan), xp.full(n, xp.nan), xp.full(n, xp.nan)
        
        # SMA comme bande centrale
        sma = GPUIndicators.sma_gpu(prices_gpu, period)
        
        # Calcul de l'écart-type mobile
        std = xp.full(n, xp.nan)
        for i in range(period - 1, n):
            window = prices_gpu[i - period + 1:i + 1]
            std[i] = float(xp.std(window))
        
        upper = sma + std_dev * std
        lower = sma - std_dev * std
        
        return upper, sma, lower
    
    @staticmethod
    def macd_gpu(prices_gpu, fast: int = 12, slow: int = 26, signal: int = 9):
        """MACD vectorisée sur GPU"""
        ema_fast = GPUIndicators.ema_gpu(prices_gpu, fast)
        ema_slow = GPUIndicators.ema_gpu(prices_gpu, slow)
        
        macd_line = ema_fast - ema_slow
        signal_line = GPUIndicators.ema_gpu(macd_line, signal)
        histogram = macd_line - signal_line
        
        return macd_line, signal_line, histogram


class GPUBacktester:
    """Backtester parallèle sur GPU - teste des centaines de combinaisons simultanément"""
    
    def __init__(self, prices_dict: Dict[str, np.ndarray]):
        """
        prices_dict: {symbol: np.array de prix close}
        """
        self.symbols = list(prices_dict.keys())
        self.n_symbols = len(self.symbols)
        
        # Transférer toutes les données sur GPU
        max_len = max(len(p) for p in prices_dict.values())
        self.max_len = max_len
        
        # Créer une matrice 2D: (n_symbols, max_len)
        self.prices_matrix = xp.zeros((self.n_symbols, max_len))
        self.valid_mask = xp.zeros((self.n_symbols, max_len), dtype=bool)
        
        for i, symbol in enumerate(self.symbols):
            prices = prices_dict[symbol]
            n = len(prices)
            self.prices_matrix[i, :n] = to_gpu(np.array(prices))
            self.valid_mask[i, :n] = True
        
        print(f"[GPU] Matrice de prix: {self.n_symbols} cryptos x {max_len} points")
        sys.stdout.flush()
    
    def compute_all_indicators(self, rsi_period=14, ema_short=9, ema_long=21, bb_period=20, bb_std=2.0):
        """Calcule tous les indicateurs pour toutes les cryptos en parallèle"""
        
        results = {
            'rsi': xp.zeros_like(self.prices_matrix),
            'ema_short': xp.zeros_like(self.prices_matrix),
            'ema_long': xp.zeros_like(self.prices_matrix),
            'bb_upper': xp.zeros_like(self.prices_matrix),
            'bb_middle': xp.zeros_like(self.prices_matrix),
            'bb_lower': xp.zeros_like(self.prices_matrix),
        }
        
        # Calculer pour chaque crypto (parallélisable sur GPU)
        for i in range(self.n_symbols):
            prices = self.prices_matrix[i]
            valid_len = int(xp.sum(self.valid_mask[i]))
            
            if valid_len > 0:
                valid_prices = prices[:valid_len]
                
                results['rsi'][i, :valid_len] = GPUIndicators.rsi_gpu(valid_prices, rsi_period)
                results['ema_short'][i, :valid_len] = GPUIndicators.ema_gpu(valid_prices, ema_short)
                results['ema_long'][i, :valid_len] = GPUIndicators.ema_gpu(valid_prices, ema_long)
                
                bb_u, bb_m, bb_l = GPUIndicators.bollinger_gpu(valid_prices, bb_period, bb_std)
                results['bb_upper'][i, :valid_len] = bb_u
                results['bb_middle'][i, :valid_len] = bb_m
                results['bb_lower'][i, :valid_len] = bb_l
        
        return results
    
    def backtest_params_batch(self, params_list: List[Dict], required_signals: int = 2) -> List[Dict]:
        """
        Teste un batch de paramètres en parallèle sur GPU
        
        params_list: Liste de dicts avec rsi_oversold, rsi_overbought, stop_loss, take_profit
        
        Retourne: Liste de résultats de backtest
        """
        results = []
        
        for params in params_list:
            rsi_oversold = params.get('rsi_oversold', 30)
            rsi_overbought = params.get('rsi_overbought', 70)
            stop_loss = params.get('stop_loss', 2.0)
            take_profit = params.get('take_profit', 4.0)
            
            # Calculer indicateurs
            indicators = self.compute_all_indicators(
                rsi_period=params.get('rsi_period', 14),
                ema_short=params.get('ema_short', 9),
                ema_long=params.get('ema_long', 21),
                bb_period=params.get('bb_period', 20),
                bb_std=params.get('bb_std', 2.0)
            )
            
            # Simuler le trading
            total_trades = 0
            winning_trades = 0
            total_pnl = 0.0
            
            for i in range(self.n_symbols):
                valid_len = int(to_cpu(xp.sum(self.valid_mask[i])))
                if valid_len < 50:
                    continue
                
                prices = to_cpu(self.prices_matrix[i, :valid_len])
                rsi = to_cpu(indicators['rsi'][i, :valid_len])
                ema_short = to_cpu(indicators['ema_short'][i, :valid_len])
                ema_long = to_cpu(indicators['ema_long'][i, :valid_len])
                bb_lower = to_cpu(indicators['bb_lower'][i, :valid_len])
                bb_upper = to_cpu(indicators['bb_upper'][i, :valid_len])
                
                in_position = False
                entry_price = 0.0
                
                for j in range(50, valid_len):
                    if np.isnan(rsi[j]) or np.isnan(ema_short[j]):
                        continue
                    
                    current_price = prices[j]
                    
                    if in_position:
                        # Vérifier sortie
                        pnl_pct = (current_price - entry_price) / entry_price * 100
                        
                        if pnl_pct <= -stop_loss or pnl_pct >= take_profit:
                            total_trades += 1
                            total_pnl += pnl_pct
                            if pnl_pct > 0:
                                winning_trades += 1
                            in_position = False
                    else:
                        # Vérifier entrée (signaux)
                        signals = 0
                        if rsi[j] < rsi_oversold:
                            signals += 1
                        if ema_short[j] > ema_long[j]:
                            signals += 1
                        if current_price < bb_lower[j] if not np.isnan(bb_lower[j]) else False:
                            signals += 1
                        
                        if signals >= required_signals:
                            in_position = True
                            entry_price = current_price
            
            # Calculer métriques
            win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
            avg_pnl = total_pnl / total_trades if total_trades > 0 else 0
            
            # Score de fitness
            fitness = 0
            if total_trades >= 10:
                fitness = total_pnl * 0.3 + win_rate * 2.0 + (total_trades * 0.1)
            
            results.append({
                'params': params,
                'total_trades': total_trades,
                'winning_trades': winning_trades,
                'total_pnl': total_pnl,
                'win_rate': win_rate,
                'avg_pnl': avg_pnl,
                'fitness': fitness
            })
        
        return results


class ParticleSwarmOptimizer:
    """Optimisation par essaim de particules (PSO) - GPU Acceleré"""
    
    def __init__(self, gpu_backtester: GPUBacktester, n_particles: int = 50):
        self.backtester = gpu_backtester
        self.n_particles = n_particles
        
        # Bornes des paramètres
        self.bounds = {
            'rsi_oversold': (20, 45),
            'rsi_overbought': (55, 80),
            'stop_loss': (1.0, 5.0),
            'take_profit': (2.0, 10.0),
            'ema_short': (5, 15),
            'ema_long': (15, 40),
            'bb_period': (10, 30),
            'bb_std': (1.5, 3.0),
        }
        
        self.param_names = list(self.bounds.keys())
        self.n_dims = len(self.param_names)
        
        # Initialiser les particules (sur GPU si disponible)
        self.positions = xp.zeros((n_particles, self.n_dims))
        self.velocities = xp.zeros((n_particles, self.n_dims))
        self.best_positions = xp.zeros((n_particles, self.n_dims))
        self.best_scores = xp.full(n_particles, -float('inf'))
        self.global_best_position = xp.zeros(self.n_dims)
        self.global_best_score = -float('inf')
        
        # Initialiser positions aléatoires
        for i, name in enumerate(self.param_names):
            low, high = self.bounds[name]
            self.positions[:, i] = xp.random.uniform(low, high, n_particles)
            self.velocities[:, i] = xp.random.uniform(-(high-low)/10, (high-low)/10, n_particles)
        
        self.best_positions = self.positions.copy()
    
    def position_to_params(self, position) -> Dict:
        """Convertit une position en dictionnaire de paramètres"""
        pos = to_cpu(position)
        params = {}
        for i, name in enumerate(self.param_names):
            value = pos[i]
            if name in ['ema_short', 'ema_long', 'bb_period']:
                params[name] = int(round(value))
            else:
                params[name] = float(value)
        return params
    
    def optimize(self, n_iterations: int = 30, w: float = 0.7, c1: float = 1.5, c2: float = 1.5) -> Dict:
        """
        Exécute l'optimisation PSO
        
        w: inertie
        c1: coefficient cognitif (attraction vers meilleur personnel)
        c2: coefficient social (attraction vers meilleur global)
        """
        print(f"[PSO] Demarrage optimisation PSO avec {self.n_particles} particules")
        print(f"[PSO] {n_iterations} iterations, {self.n_particles * n_iterations} evaluations totales")
        sys.stdout.flush()
        
        start_time = time.time()
        
        for iteration in range(n_iterations):
            # Évaluer toutes les particules
            params_list = [self.position_to_params(self.positions[i]) for i in range(self.n_particles)]
            results = self.backtester.backtest_params_batch(params_list)
            
            # Mettre à jour les meilleurs
            for i, result in enumerate(results):
                score = result['fitness']
                
                best_score_i = float(to_cpu(self.best_scores[i]))
                if score > best_score_i:
                    self.best_scores[i] = score
                    self.best_positions[i] = self.positions[i].copy()
                
                if score > self.global_best_score:
                    self.global_best_score = score
                    self.global_best_position = self.positions[i].copy()
                    best_params = result['params']
                    print(f"[PSO] Iteration {iteration+1}: Nouveau meilleur! Score={score:.2f}, WinRate={result['win_rate']:.1f}%, PnL={result['total_pnl']:.2f}%")
                    sys.stdout.flush()
            
            # Mettre à jour vélocités et positions
            r1 = xp.random.random((self.n_particles, self.n_dims))
            r2 = xp.random.random((self.n_particles, self.n_dims))
            
            cognitive = c1 * r1 * (self.best_positions - self.positions)
            social = c2 * r2 * (self.global_best_position - self.positions)
            
            self.velocities = w * self.velocities + cognitive + social
            self.positions = self.positions + self.velocities
            
            # Appliquer les bornes
            for i, name in enumerate(self.param_names):
                low, high = self.bounds[name]
                self.positions[:, i] = xp.clip(self.positions[:, i], low, high)
            
            # Progress
            if (iteration + 1) % 5 == 0:
                elapsed = time.time() - start_time
                print(f"[PSO] Progression: {iteration+1}/{n_iterations} ({elapsed:.1f}s)")
                sys.stdout.flush()
        
        elapsed = time.time() - start_time
        best_params = self.position_to_params(self.global_best_position)
        
        print(f"[PSO] Optimisation terminee en {elapsed:.1f}s")
        print(f"[PSO] Meilleur score: {self.global_best_score:.2f}")
        print(f"[PSO] Meilleurs parametres: {best_params}")
        sys.stdout.flush()
        
        return {
            'best_params': best_params,
            'best_score': float(self.global_best_score),
            'iterations': n_iterations,
            'particles': self.n_particles,
            'elapsed_time': elapsed
        }
    
    @staticmethod
    def macd(prices: List[float], fast: int = 12, slow: int = 26, signal: int = 9) -> Tuple[Optional[float], Optional[float], Optional[float]]:
        if len(prices) < slow + signal:
            return None, None, None
        
        ema_fast = TechnicalIndicators.ema(prices, fast)
        ema_slow = TechnicalIndicators.ema(prices, slow)
        
        if ema_fast is None or ema_slow is None:
            return None, None, None
        
        macd_line = ema_fast - ema_slow
        
        # Calculer la ligne de signal (EMA du MACD)
        # Pour simplifier, on retourne juste le MACD
        return macd_line, 0, macd_line
    
    @staticmethod
    def atr(highs: List[float], lows: List[float], closes: List[float], period: int = 14) -> Optional[float]:
        """Average True Range - mesure de volatilité"""
        if len(closes) < period + 1:
            return None
        
        true_ranges = []
        for i in range(1, len(closes)):
            high_low = highs[i] - lows[i]
            high_close = abs(highs[i] - closes[i-1])
            low_close = abs(lows[i] - closes[i-1])
            true_ranges.append(max(high_low, high_close, low_close))
        
        return sum(true_ranges[-period:]) / period


# ═══════════════════════════════════════════════════════════════════════════════
# DATA FETCHER (avec cache local prioritaire)
# ═══════════════════════════════════════════════════════════════════════════════

# Import du cache local
try:
    from crypto_data_fetcher import get_fetcher, CryptoDataFetcher
    CACHE_AVAILABLE = True
except ImportError:
    CACHE_AVAILABLE = False
    print("[WARN] Cache crypto non disponible, utilisation de Binance direct")

# Chemin du cache local
CACHE_DIR = os.path.join(SCRIPT_DIR, 'crypto_cache')
CACHE_FILE = os.path.join(CACHE_DIR, 'crypto_data.json')

# Dossier des données historiques (fetch_historical_data.py)
HISTORICAL_DATA_DIR = os.path.join(SCRIPT_DIR, 'historical_data')

class DataFetcher:
    """Recuperation des donnees historiques (base de données locale prioritaire)"""
    
    BASE_URL = "https://api.binance.com/api/v3"
    _cache_data = None
    _cache_loaded = False
    _historical_data = {}  # Cache mémoire des données historiques
    
    @staticmethod
    def load_historical_data(symbol: str, interval: str = "1h") -> Optional[List[Dict]]:
        """
        Charge les données depuis la base de données historical_data/
        Créée par fetch_historical_data.py
        """
        try:
            cache_key = f"{symbol}_{interval}"
            if cache_key in DataFetcher._historical_data:
                return DataFetcher._historical_data[cache_key]
            
            hist_file = os.path.join(HISTORICAL_DATA_DIR, f"{symbol}_historical.json")
            
            if os.path.exists(hist_file):
                with open(hist_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                intervals_data = data.get('intervals', {})
                
                if interval in intervals_data:
                    raw_klines = intervals_data[interval].get('klines', [])
                    
                    klines = []
                    for k in raw_klines:
                        klines.append({
                            "time": datetime.fromisoformat(k['datetime']) if isinstance(k.get('datetime'), str) else datetime.fromtimestamp(k['timestamp'] / 1000),
                            "open": float(k['open']),
                            "high": float(k['high']),
                            "low": float(k['low']),
                            "close": float(k['close']),
                            "volume": float(k['volume'])
                        })
                    
                    DataFetcher._historical_data[cache_key] = klines
                    print(f"[DB] {symbol} ({interval}): {len(klines)} klines depuis historical_data/")
                    sys.stdout.flush()
                    return klines
                    
            return None
            
        except Exception as e:
            print(f"[DB] Erreur lecture historical_data pour {symbol}: {e}")
            return None
    
    @staticmethod
    async def load_and_complete_historical_data(symbol: str, interval: str = "1h") -> Optional[List[Dict]]:
        """
        Charge les données historiques ET les complète avec les données récentes de Binance
        """
        try:
            cache_key = f"{symbol}_{interval}_complete"
            
            if cache_key in DataFetcher._historical_data:
                cached = DataFetcher._historical_data[cache_key]
                if cached and len(cached) > 0:
                    last_time = cached[-1]['time']
                    age_minutes = (datetime.now() - last_time).total_seconds() / 60
                    if age_minutes < 30:
                        return cached
            
            klines = DataFetcher.load_historical_data(symbol, interval)
            
            if not klines or len(klines) == 0:
                return None
            
            DataFetcher._historical_data[cache_key] = klines
            return klines
            
        except Exception as e:
            print(f"[DB] Erreur load_and_complete pour {symbol}: {e}")
            return DataFetcher.load_historical_data(symbol, interval)
    
    @staticmethod
    def load_local_cache() -> Optional[Dict]:
        """Charge les données depuis le cache local"""
        try:
            if os.path.exists(CACHE_FILE):
                with open(CACHE_FILE, 'r', encoding='utf-8') as f:
                    cache = json.load(f)
                    DataFetcher._cache_data = cache
                    DataFetcher._cache_loaded = True
                    symbols_count = len(cache.get('symbols', {}))
                    print(f"[CACHE] ✅ Cache local chargé: {symbols_count} cryptos")
                    return cache
        except Exception as e:
            print(f"[CACHE] ⚠️ Erreur lecture cache: {e}")
        return None
    
    @staticmethod
    def get_cached_indicators(symbol: str) -> Optional[Dict]:
        """Récupère les indicateurs précalculés depuis le cache"""
        if not DataFetcher._cache_loaded:
            DataFetcher.load_local_cache()
        
        if DataFetcher._cache_data:
            symbols = DataFetcher._cache_data.get('symbols', {})
            return symbols.get(symbol)
        return None
    
    @staticmethod
    async def fetch_klines(symbol: str, interval: str = "5m", limit: int = 1000, retries: int = 3) -> List[Dict]:
        """Recupere les donnees de bougies historiques avec retry"""
        url = f"{DataFetcher.BASE_URL}/klines"
        params = {
            "symbol": symbol,
            "interval": interval,
            "limit": limit
        }
        
        for attempt in range(retries):
            try:
                timeout = aiohttp.ClientTimeout(total=30)
                async with aiohttp.ClientSession(timeout=timeout) as session:
                    async with session.get(url, params=params) as response:
                        if response.status == 200:
                            data = await response.json()
                            return [
                                {
                                    "time": datetime.fromtimestamp(k[0] / 1000),
                                    "open": float(k[1]),
                                    "high": float(k[2]),
                                    "low": float(k[3]),
                                    "close": float(k[4]),
                                    "volume": float(k[5])
                                }
                                for k in data
                            ]
                        else:
                            print(f"[ERROR] API {symbol}: status {response.status}")
                            return []
            except asyncio.TimeoutError:
                print(f"[RETRY] {symbol} timeout, attempt {attempt + 1}/{retries}")
                await asyncio.sleep(1)
            except aiohttp.ClientConnectorError as e:
                print(f"[RETRY] {symbol} connection error, attempt {attempt + 1}/{retries}")
                await asyncio.sleep(2)
            except Exception as e:
                print(f"[ERROR] {symbol}: {type(e).__name__}")
                return []
        
        print(f"[FAILED] {symbol} after {retries} attempts")
        return []
    
    @staticmethod
    def save_klines_cache(data: Dict[str, List[Dict]], interval: str = "5m"):
        """Sauvegarde les klines dans le cache local pour réutilisation"""
        try:
            cache_file = os.path.join(CACHE_DIR, f'klines_cache_{interval}.json')
            os.makedirs(CACHE_DIR, exist_ok=True)
            
            # Convertir datetime en string pour JSON
            serializable_data = {}
            for symbol, klines in data.items():
                serializable_data[symbol] = [
                    {**k, 'time': k['time'].isoformat() if hasattr(k.get('time', ''), 'isoformat') else str(k.get('time', ''))}
                    for k in klines
                ]
            
            cache_content = {
                'data': serializable_data,
                'updated_at': datetime.now().isoformat(),
                'interval': interval,
                'count': len(serializable_data)
            }
            
            with open(cache_file, 'w', encoding='utf-8') as f:
                json.dump(cache_content, f)
            
            print(f"[CACHE] 💾 Klines sauvegardées: {len(serializable_data)} cryptos")
        except Exception as e:
            print(f"[CACHE] ⚠️ Erreur sauvegarde klines: {e}")
    
    @staticmethod
    def load_klines_cache(interval: str = "5m", max_age_minutes: int = 30) -> Optional[Dict[str, List[Dict]]]:
        """Charge les klines depuis le cache local si encore valides"""
        try:
            cache_file = os.path.join(CACHE_DIR, f'klines_cache_{interval}.json')
            
            if not os.path.exists(cache_file):
                return None
            
            with open(cache_file, 'r', encoding='utf-8') as f:
                cache_content = json.load(f)
            
            # Vérifier l'âge du cache
            updated_at = datetime.fromisoformat(cache_content.get('updated_at', '2000-01-01'))
            age = datetime.now() - updated_at
            
            if age.total_seconds() > max_age_minutes * 60:
                print(f"[CACHE] ⚠️ Cache klines expiré ({age.total_seconds()//60:.0f} min)")
                return None
            
            # Reconvertir les dates
            data = {}
            for symbol, klines in cache_content.get('data', {}).items():
                data[symbol] = [
                    {**k, 'time': datetime.fromisoformat(k['time']) if isinstance(k.get('time'), str) else k.get('time')}
                    for k in klines
                ]
            
            print(f"[CACHE] ✅ Klines chargées depuis cache: {len(data)} cryptos ({age.total_seconds()//60:.0f} min)")
            return data
            
        except Exception as e:
            print(f"[CACHE] ⚠️ Erreur lecture cache klines: {e}")
            return None
    
    @staticmethod
    async def fetch_multiple_symbols(symbols: List[str], interval: str = "5m", limit: int = 1000, use_cache: bool = True) -> Dict[str, List[Dict]]:
        """Recupere les donnees pour plusieurs symboles (base de données locale prioritaire)"""
        
        results = {}
        missing_symbols = []
        
        # ÉTAPE 0: Pour les intervalles 1h/4h/1d, utiliser historical_data/ en priorité
        if use_cache and interval in ['1h', '4h', '1d']:
            print(f"[DB] Chargement depuis historical_data/ (intervalle {interval})...")
            sys.stdout.flush()
            
            tasks = [DataFetcher.load_and_complete_historical_data(s, interval) for s in symbols]
            completed_data = await asyncio.gather(*tasks)
            
            for symbol, hist_data in zip(symbols, completed_data):
                if hist_data and len(hist_data) >= limit * 0.5:
                    results[symbol] = hist_data[-limit:] if len(hist_data) > limit else hist_data
                else:
                    missing_symbols.append(symbol)
            
            if results:
                total_klines = sum(len(k) for k in results.values())
                print(f"[DB] {len(results)}/{len(symbols)} cryptos chargees ({total_klines:,} klines)")
                sys.stdout.flush()
            
            if not missing_symbols:
                return results
            else:
                symbols = missing_symbols
                print(f"[DB] {len(missing_symbols)} cryptos manquantes, recherche en cache/API...")
                sys.stdout.flush()
        
        # ÉTAPE 1: Essayer le cache local
        if use_cache:
            cached = DataFetcher.load_klines_cache(interval, max_age_minutes=30)
            if cached:
                missing = [s for s in symbols if s not in cached or len(cached.get(s, [])) < limit * 0.8]
                
                if not missing:
                    print(f"[CACHE] Toutes les donnees trouvees dans le cache local!")
                    results.update({s: cached[s] for s in symbols if s in cached})
                    return results
                elif len(missing) < len(symbols) * 0.3:
                    print(f"[CACHE] {len(symbols) - len(missing)}/{len(symbols)} cryptos depuis cache")
                    results.update({s: cached[s] for s in symbols if s in cached and s not in missing})
                    symbols = missing
        
        # ÉTAPE 2: Télécharger depuis Binance
        batch_size = 5
        
        for i in range(0, len(symbols), batch_size):
            batch = symbols[i:i + batch_size]
            tasks = [DataFetcher.fetch_klines(s, interval, limit) for s in batch]
            batch_results = await asyncio.gather(*tasks)
            
            for symbol, data in zip(batch, batch_results):
                results[symbol] = data
            
            if i + batch_size < len(symbols):
                await asyncio.sleep(0.5)
        
        # ÉTAPE 3: Sauvegarder dans le cache
        if results:
            DataFetcher.save_klines_cache(results, interval)
        
        return results


# ═══════════════════════════════════════════════════════════════════════════════
# BACKTESTER
# ═══════════════════════════════════════════════════════════════════════════════

class Backtester:
    """Moteur de backtesting"""
    
    def __init__(self, data: Dict[str, List[Dict]], params: TradingParams):
        self.data = data
        self.params = params
        self.positions = {}
        self.trades: List[TradeResult] = []
        self.equity_curve = []
        self.initial_capital = 10000
        self.capital = self.initial_capital
        self.position_size = 100  # USDT par trade
    
    def generate_signals(self, symbol: str, prices: List[float], current_idx: int) -> str:
        """Génère les signaux d'achat/vente"""
        if current_idx < self.params.ema_long + 10:
            return "HOLD"
        
        price_slice = prices[:current_idx + 1]
        current_price = price_slice[-1]
        
        signals = []
        
        # RSI
        rsi = TechnicalIndicators.rsi(price_slice, self.params.rsi_period)
        if rsi is not None:
            if rsi < self.params.rsi_oversold:
                signals.append("BUY")
            elif rsi > self.params.rsi_overbought:
                signals.append("SELL")
        
        # EMA Cross
        ema_short = TechnicalIndicators.ema(price_slice, self.params.ema_short)
        ema_long = TechnicalIndicators.ema(price_slice, self.params.ema_long)
        if ema_short is not None and ema_long is not None:
            if ema_short > ema_long:
                signals.append("BUY")
            else:
                signals.append("SELL")
        
        # Bollinger Bands
        bb_upper, bb_mid, bb_lower = TechnicalIndicators.bollinger(
            price_slice, self.params.bb_period, self.params.bb_std
        )
        if bb_lower is not None and bb_upper is not None:
            if current_price < bb_lower:
                signals.append("BUY")
            elif current_price > bb_upper:
                signals.append("SELL")
        
        # Décision
        buy_count = signals.count("BUY")
        sell_count = signals.count("SELL")
        
        if buy_count >= self.params.required_signals:
            return "BUY"
        elif sell_count >= 2:
            return "SELL"
        
        return "HOLD"
    
    def check_exit_conditions(self, symbol: str, current_price: float, current_time: datetime) -> Optional[str]:
        """Vérifie les conditions de sortie (SL/TP)"""
        if symbol not in self.positions:
            return None
        
        pos = self.positions[symbol]
        entry_price = pos["entry_price"]
        
        # Stop Loss
        sl_price = entry_price * (1 - self.params.stop_loss / 100)
        if current_price <= sl_price:
            return "STOP_LOSS"
        
        # Take Profit
        tp_price = entry_price * (1 + self.params.take_profit / 100)
        if current_price >= tp_price:
            return "TAKE_PROFIT"
        
        return None
    
    def run(self) -> BacktestResult:
        """Exécute le backtest complet"""
        
        # Pour chaque symbole
        for symbol, candles in self.data.items():
            if len(candles) < 100:
                continue
            
            prices = [c["close"] for c in candles]
            
            for i in range(50, len(candles)):
                current_candle = candles[i]
                current_price = current_candle["close"]
                current_time = current_candle["time"]
                
                # Vérifier les sorties
                exit_reason = self.check_exit_conditions(symbol, current_price, current_time)
                if exit_reason:
                    self._close_position(symbol, current_price, current_time, exit_reason)
                
                # Générer signal
                signal = self.generate_signals(symbol, prices, i)
                
                # Exécuter signal
                if signal == "BUY" and symbol not in self.positions and len(self.positions) < 5:
                    self._open_position(symbol, current_price, current_time)
                elif signal == "SELL" and symbol in self.positions:
                    self._close_position(symbol, current_price, current_time, "SIGNAL")
                
                # Mise à jour equity curve
                self.equity_curve.append(self._calculate_equity(prices[:i+1]))
        
        return self._generate_result()
    
    def _open_position(self, symbol: str, price: float, time: datetime):
        """Ouvre une position"""
        quantity = self.position_size / price
        self.positions[symbol] = {
            "entry_price": price,
            "quantity": quantity,
            "entry_time": time
        }
    
    def _close_position(self, symbol: str, price: float, time: datetime, reason: str):
        """Ferme une position"""
        if symbol not in self.positions:
            return
        
        pos = self.positions[symbol]
        pnl = (price - pos["entry_price"]) * pos["quantity"]
        pnl_percent = ((price / pos["entry_price"]) - 1) * 100
        
        self.capital += pnl
        
        trade = TradeResult(
            symbol=symbol,
            entry_price=pos["entry_price"],
            exit_price=price,
            entry_time=pos["entry_time"],
            exit_time=time,
            pnl=pnl,
            pnl_percent=pnl_percent,
            reason=reason
        )
        self.trades.append(trade)
        
        del self.positions[symbol]
    
    def _calculate_equity(self, current_prices: Dict) -> float:
        """Calcule l'équité courante"""
        equity = self.capital
        # Ajouter la valeur des positions ouvertes (simplifié)
        return equity
    
    def _generate_result(self) -> BacktestResult:
        """Génère le résultat du backtest"""
        if not self.trades:
            return BacktestResult(
                params=self.params,
                total_trades=0,
                winning_trades=0,
                losing_trades=0,
                total_pnl=0,
                win_rate=0,
                avg_win=0,
                avg_loss=0,
                profit_factor=0,
                max_drawdown=0,
                sharpe_ratio=0,
                trades=[]
            )
        
        winning = [t for t in self.trades if t.pnl > 0]
        losing = [t for t in self.trades if t.pnl <= 0]
        
        total_pnl = sum(t.pnl for t in self.trades)
        win_rate = len(winning) / len(self.trades) * 100 if self.trades else 0
        avg_win = sum(t.pnl for t in winning) / len(winning) if winning else 0
        avg_loss = sum(t.pnl for t in losing) / len(losing) if losing else 0
        
        total_wins = sum(t.pnl for t in winning)
        total_losses = abs(sum(t.pnl for t in losing))
        profit_factor = total_wins / total_losses if total_losses > 0 else float('inf')
        
        # Max Drawdown
        max_drawdown = self._calculate_max_drawdown()
        
        # Sharpe Ratio (simplifié)
        returns = [t.pnl_percent for t in self.trades]
        sharpe_ratio = self._calculate_sharpe(returns)
        
        return BacktestResult(
            params=self.params,
            total_trades=len(self.trades),
            winning_trades=len(winning),
            losing_trades=len(losing),
            total_pnl=total_pnl,
            win_rate=win_rate,
            avg_win=avg_win,
            avg_loss=avg_loss,
            profit_factor=profit_factor,
            max_drawdown=max_drawdown,
            sharpe_ratio=sharpe_ratio,
            trades=self.trades
        )
    
    def _calculate_max_drawdown(self) -> float:
        """Calcule le drawdown maximum"""
        if not self.equity_curve:
            return 0
        
        peak = self.equity_curve[0]
        max_dd = 0
        
        for value in self.equity_curve:
            if value > peak:
                peak = value
            dd = (peak - value) / peak * 100 if peak > 0 else 0
            max_dd = max(max_dd, dd)
        
        return max_dd
    
    def _calculate_sharpe(self, returns: List[float], risk_free_rate: float = 0) -> float:
        """Calcule le ratio de Sharpe"""
        if len(returns) < 2:
            return 0
        
        avg_return = sum(returns) / len(returns)
        std_return = (sum((r - avg_return) ** 2 for r in returns) / len(returns)) ** 0.5
        
        if std_return == 0:
            return 0
        
        return (avg_return - risk_free_rate) / std_return


# ═══════════════════════════════════════════════════════════════════════════════
# ALGORITHME GÉNÉTIQUE
# ═══════════════════════════════════════════════════════════════════════════════

class GeneticOptimizer:
    """Optimisation par algorithme génétique"""
    
    def __init__(self, data: Dict[str, List[Dict]], population_size: int = 50, generations: int = 20):
        self.data = data
        self.population_size = population_size
        self.generations = generations
        self.population: List[TradingParams] = []
        self.best_params: Optional[TradingParams] = None
        self.best_score: float = float('-inf')
        self.history: List[Dict] = []
    
    def initialize_population(self):
        """Initialise la population avec des paramètres aléatoires"""
        self.population = [TradingParams.random() for _ in range(self.population_size)]
        
        # Ajouter les paramètres par défaut
        self.population[0] = TradingParams()
    
    def evaluate_fitness(self, params: TradingParams) -> float:
        """Évalue la fitness d'un ensemble de paramètres"""
        backtester = Backtester(self.data, params)
        result = backtester.run()
        return result.fitness_score()
    
    def select_parents(self, fitness_scores: List[Tuple[TradingParams, float]]) -> List[TradingParams]:
        """Sélection par tournoi"""
        parents = []
        tournament_size = 5
        
        for _ in range(self.population_size):
            tournament = random.sample(fitness_scores, min(tournament_size, len(fitness_scores)))
            winner = max(tournament, key=lambda x: x[1])
            parents.append(winner[0])
        
        return parents
    
    def crossover(self, parent1: TradingParams, parent2: TradingParams) -> TradingParams:
        """Croisement de deux parents"""
        p1_dict = parent1.to_dict()
        p2_dict = parent2.to_dict()
        child_dict = {}
        
        for key in p1_dict:
            child_dict[key] = p1_dict[key] if random.random() < 0.5 else p2_dict[key]
        
        return TradingParams(**child_dict)
    
    def evolve(self) -> TradingParams:
        """Exécute l'algorithme génétique"""
        print("\n🧬 OPTIMISATION GÉNÉTIQUE")
        print("=" * 60)
        
        self.initialize_population()
        
        for gen in range(self.generations):
            print(f"\n[GEN] Generation {gen + 1}/{self.generations}")
            
            # Évaluer la fitness
            fitness_scores = []
            for i, params in enumerate(self.population):
                score = self.evaluate_fitness(params)
                fitness_scores.append((params, score))
                
                if (i + 1) % 10 == 0:
                    print(f"   Évalué {i + 1}/{self.population_size}...")
            
            # Trier par fitness
            fitness_scores.sort(key=lambda x: x[1], reverse=True)
            
            # Sauvegarder le meilleur
            if fitness_scores[0][1] > self.best_score:
                self.best_score = fitness_scores[0][1]
                self.best_params = fitness_scores[0][0]
            
            # Stats de génération
            avg_fitness = sum(f[1] for f in fitness_scores) / len(fitness_scores)
            print(f"   [BEST] Meilleur score: {fitness_scores[0][1]:.2f}")
            print(f"   [AVG] Score moyen: {avg_fitness:.2f}")
            
            self.history.append({
                "generation": gen + 1,
                "best_score": fitness_scores[0][1],
                "avg_score": avg_fitness,
                "best_params": fitness_scores[0][0].to_dict()
            })
            
            # Sélection et reproduction
            if gen < self.generations - 1:
                parents = self.select_parents(fitness_scores)
                
                # Élitisme : garder les 10% meilleurs
                elite_size = max(2, self.population_size // 10)
                new_population = [f[0] for f in fitness_scores[:elite_size]]
                
                # Crossover et mutation
                while len(new_population) < self.population_size:
                    p1, p2 = random.sample(parents, 2)
                    child = self.crossover(p1, p2)
                    child = child.mutate(mutation_rate=0.15)
                    new_population.append(child)
                
                self.population = new_population
        
        print(f"\n[OK] Optimisation terminee !")
        print(f"   [BEST] Meilleur score: {self.best_score:.2f}")
        
        return self.best_params


# ═══════════════════════════════════════════════════════════════════════════════
# EXPLORATION PAR GRID SEARCH
# ═══════════════════════════════════════════════════════════════════════════════

class GridSearchOptimizer:
    """Exploration systématique par grille"""
    
    def __init__(self, data: Dict[str, List[Dict]]):
        self.data = data
        self.results: List[BacktestResult] = []
    
    def explore(self) -> List[BacktestResult]:
        """Explore une grille de paramètres"""
        print("\n🔍 EXPLORATION PAR GRILLE")
        print("=" * 60)
        
        # Grille de paramètres à explorer
        param_grid = {
            "rsi_oversold": [25, 30, 35, 40, 45],
            "rsi_overbought": [55, 60, 65, 70, 75],
            "stop_loss": [1.5, 2.0, 2.5, 3.0],
            "take_profit": [3.0, 4.0, 5.0, 6.0],
            "required_signals": [1, 2, 3]
        }
        
        # Générer toutes les combinaisons
        from itertools import product
        
        keys = list(param_grid.keys())
        values = list(param_grid.values())
        combinations = list(product(*values))
        
        print(f"[GRID] {len(combinations)} combinaisons a tester...")
        
        for i, combo in enumerate(combinations):
            params_dict = dict(zip(keys, combo))
            params = TradingParams(**params_dict)
            
            backtester = Backtester(self.data, params)
            result = backtester.run()
            self.results.append(result)
            
            if (i + 1) % 50 == 0:
                print(f"   Testé {i + 1}/{len(combinations)}...")
        
        # Trier par score
        self.results.sort(key=lambda x: x.fitness_score(), reverse=True)
        
        print(f"\n[OK] Exploration terminee !")
        print(f"\n[TOP] TOP 5 STRATEGIES:")
        for i, result in enumerate(self.results[:5]):
            print(f"\n   #{i+1} Score: {result.fitness_score():.2f}")
            print(f"      Trades: {result.total_trades} | Win Rate: {result.win_rate:.1f}%")
            print(f"      P&L: {result.total_pnl:.2f} | Profit Factor: {result.profit_factor:.2f}")
            print(f"      Params: RSI {result.params.rsi_oversold}/{result.params.rsi_overbought} | SL/TP {result.params.stop_loss}/{result.params.take_profit}")
        
        return self.results


# ═══════════════════════════════════════════════════════════════════════════════
# ANALYSEUR DE CORRÉLATION
# ═══════════════════════════════════════════════════════════════════════════════

class CorrelationAnalyzer:
    """Analyse les corrélations entre indicateurs et performance"""
    
    def __init__(self, results: List[BacktestResult]):
        self.results = results
    
    def analyze(self):
        """Analyse les correlations"""
        print("\n[CORR] ANALYSE DES CORRELATIONS")
        print("=" * 60)
        
        if len(self.results) < 10:
            print("[ERROR] Pas assez de resultats pour l'analyse")
            return
        
        # Extraire les données
        data = {
            "rsi_oversold": [r.params.rsi_oversold for r in self.results],
            "rsi_overbought": [r.params.rsi_overbought for r in self.results],
            "stop_loss": [r.params.stop_loss for r in self.results],
            "take_profit": [r.params.take_profit for r in self.results],
            "required_signals": [r.params.required_signals for r in self.results],
            "win_rate": [r.win_rate for r in self.results],
            "profit_factor": [r.profit_factor for r in self.results],
            "total_pnl": [r.total_pnl for r in self.results]
        }
        
        # Calculer les correlations avec le P&L
        print("\n[CORR] Correlations avec le P&L total:")
        pnl = data["total_pnl"]
        
        for param in ["rsi_oversold", "rsi_overbought", "stop_loss", "take_profit", "required_signals"]:
            corr = self._pearson_correlation(data[param], pnl)
            direction = "[+]" if corr > 0 else "[-]"
            strength = "FORTE" if abs(corr) > 0.5 else "MOYENNE" if abs(corr) > 0.3 else "FAIBLE"
            print(f"   {direction} {param}: {corr:+.3f} ({strength})")
        
        # Recommandations
        print("\n[TIP] RECOMMANDATIONS:")
        
        # Analyser les meilleurs résultats
        top_results = sorted(self.results, key=lambda x: x.total_pnl, reverse=True)[:10]
        
        avg_rsi_low = sum(r.params.rsi_oversold for r in top_results) / len(top_results)
        avg_rsi_high = sum(r.params.rsi_overbought for r in top_results) / len(top_results)
        avg_sl = sum(r.params.stop_loss for r in top_results) / len(top_results)
        avg_tp = sum(r.params.take_profit for r in top_results) / len(top_results)
        
        print(f"   [RSI] RSI optimal: {avg_rsi_low:.0f} / {avg_rsi_high:.0f}")
        print(f"   [SL] Stop Loss optimal: {avg_sl:.1f}%")
        print(f"   [TP] Take Profit optimal: {avg_tp:.1f}%")
    
    def _pearson_correlation(self, x: List[float], y: List[float]) -> float:
        """Calcule la corrélation de Pearson"""
        n = len(x)
        if n < 2:
            return 0
        
        mean_x = sum(x) / n
        mean_y = sum(y) / n
        
        numerator = sum((x[i] - mean_x) * (y[i] - mean_y) for i in range(n))
        denominator = (
            sum((xi - mean_x) ** 2 for xi in x) ** 0.5 *
            sum((yi - mean_y) ** 2 for yi in y) ** 0.5
        )
        
        return numerator / denominator if denominator > 0 else 0


# ═══════════════════════════════════════════════════════════════════════════════
# RAPPORT DE PERFORMANCE
# ═══════════════════════════════════════════════════════════════════════════════

class ReportGenerator:
    """Génère des rapports de performance"""
    
    @staticmethod
    def generate_json_report(result: BacktestResult, filename: str = "optimization_report.json"):
        """Génère un rapport JSON"""
        report = {
            "timestamp": datetime.now().isoformat(),
            "parameters": result.params.to_dict(),
            "performance": {
                "total_trades": result.total_trades,
                "winning_trades": result.winning_trades,
                "losing_trades": result.losing_trades,
                "win_rate": result.win_rate,
                "total_pnl": result.total_pnl,
                "profit_factor": result.profit_factor,
                "max_drawdown": result.max_drawdown,
                "sharpe_ratio": result.sharpe_ratio,
                "avg_win": result.avg_win,
                "avg_loss": result.avg_loss
            },
            "fitness_score": result.fitness_score()
        }
        
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(report, f, indent=2, ensure_ascii=False)
        
        print(f"[SAVED] Rapport sauvegarde: {filename}")
        return filename
    
    @staticmethod
    def generate_config_update(params: TradingParams, filename: str = "optimized_config.py"):
        """Génère un fichier de configuration optimisée COMPLÈTE"""
        content = f'''"""
Configuration Optimisée par IA - VERSION COMPLÈTE
==================================================
Généré le: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
Ce fichier contient TOUS les paramètres optimisés.
"""

# ═══════════════════════════════════════════════════════════════════════════════
# INDICATEURS TECHNIQUES OPTIMISÉS
# ═══════════════════════════════════════════════════════════════════════════════

RSI_PERIOD = {params.rsi_period}
RSI_OVERSOLD = {params.rsi_oversold:.1f}
RSI_OVERBOUGHT = {params.rsi_overbought:.1f}

EMA_SHORT = {params.ema_short}
EMA_LONG = {params.ema_long}

BB_PERIOD = {params.bb_period}
BB_STD = {params.bb_std:.1f}

# ═══════════════════════════════════════════════════════════════════════════════
# TREND FOLLOWING
# ═══════════════════════════════════════════════════════════════════════════════

TREND_STRENGTH_THRESHOLD = {params.trend_strength_threshold}
MOMENTUM_THRESHOLD = {params.momentum_threshold:.1f}

# ═══════════════════════════════════════════════════════════════════════════════
# GESTION DU RISQUE
# ═══════════════════════════════════════════════════════════════════════════════

STOP_LOSS_PERCENT = {params.stop_loss:.1f}
TAKE_PROFIT_PERCENT = {params.take_profit:.1f}

# ═══════════════════════════════════════════════════════════════════════════════
# TRAILING STOP
# ═══════════════════════════════════════════════════════════════════════════════

ENABLE_TRAILING_STOP = {params.enable_trailing_stop}
TRAILING_STOP_DISTANCE = {params.trailing_stop_distance:.1f}
TRAILING_STOP_ACTIVATION = {params.trailing_stop_activation:.1f}

# ═══════════════════════════════════════════════════════════════════════════════
# POSITIONS
# ═══════════════════════════════════════════════════════════════════════════════

MAX_ORDER_SIZE = {params.max_order_size}
MAX_OPEN_POSITIONS = {params.max_open_positions}
MIN_ORDER_SIZE = {params.min_order_size}
MAX_RISK_PER_TRADE = {params.max_risk_per_trade}

# ═══════════════════════════════════════════════════════════════════════════════
# SIGNAUX ET FILTRES
# ═══════════════════════════════════════════════════════════════════════════════

REQUIRED_SIGNALS = {params.required_signals}
MIN_AI_SCORE_FOR_BUY = {params.min_ai_score_for_buy}
BLOCK_BUY_ON_BEARISH = {params.block_buy_on_bearish}
MIN_BUY_SIGNALS = {params.min_buy_signals}
MIN_SELL_SIGNALS = {params.min_sell_signals}

# ═══════════════════════════════════════════════════════════════════════════════
# BREAKOUT DETECTION
# ═══════════════════════════════════════════════════════════════════════════════

ENABLE_BREAKOUT_DETECTION = {params.enable_breakout_detection}
MIN_BREAKOUT_STRENGTH = {params.min_breakout_strength:.1f}
BREAKOUT_SIGNAL_BONUS = {params.breakout_signal_bonus}

# ═══════════════════════════════════════════════════════════════════════════════
# STRATÉGIES AVANCÉES
# ═══════════════════════════════════════════════════════════════════════════════

ENABLE_ADVANCED_STRATEGIES = {params.enable_advanced_strategies}
STRATEGY_CONSENSUS_THRESHOLD = {params.strategy_consensus_threshold}

# ═══════════════════════════════════════════════════════════════════════════════
# MACHINE LEARNING ENSEMBLE
# ═══════════════════════════════════════════════════════════════════════════════

ENABLE_ENSEMBLE_ML = {params.enable_ensemble_ml}
ML_MIN_TRAINING_SAMPLES = {params.ml_min_training_samples}
ML_RETRAIN_INTERVAL = {params.ml_retrain_interval}
ML_CONFIDENCE_THRESHOLD = {params.ml_confidence_threshold:.2f}

# ═══════════════════════════════════════════════════════════════════════════════
# FEATURE ENGINEERING
# ═══════════════════════════════════════════════════════════════════════════════

ENABLE_ADVANCED_FEATURES = {params.enable_advanced_features}

# ═══════════════════════════════════════════════════════════════════════════════
# ROTATION INTELLIGENTE
# ═══════════════════════════════════════════════════════════════════════════════

ENABLE_SMART_ROTATION = {params.enable_smart_rotation}
ROTATION_MIN_CYCLE_END_SCORE = {params.rotation_min_cycle_end_score}
ROTATION_MIN_OPPORTUNITY_SCORE = {params.rotation_min_opportunity_score}
ROTATION_MIN_SCORE_ADVANTAGE = {params.rotation_min_score_advantage}
ROTATION_MIN_PROFIT = {params.rotation_min_profit:.1f}
ROTATION_MIN_HOLD_TIME = {params.rotation_min_hold_time}
ROTATION_COOLDOWN = {params.rotation_cooldown}
ROTATION_MAX_PER_HOUR = {params.rotation_max_per_hour}

# ═══════════════════════════════════════════════════════════════════════════════
# EXPOSITION AU MARCHÉ
# ═══════════════════════════════════════════════════════════════════════════════

MAX_TOTAL_EXPOSURE_PERCENT = {params.max_total_exposure_percent}
MAX_EXPOSURE_PER_CRYPTO = {params.max_exposure_per_crypto:.1f}
MAX_TRADES_PER_HOUR = {params.max_trades_per_hour}
ENABLE_CONSERVATIVE_MODE = {params.enable_conservative_mode}
BTC_VOLATILITY_THRESHOLD = {params.btc_volatility_threshold:.1f}
CONSERVATIVE_FACTOR = {params.conservative_factor:.1f}
'''
        
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(content)
        
        print(f"[SAVED] Configuration optimisee COMPLETE sauvegardee: {filename}")
        return filename
    
    @staticmethod
    def apply_to_config(params: TradingParams, config_file: str = "config.py"):
        """Applique automatiquement les paramètres optimisés dans config.py"""
        import re
        
        # Mapping des paramètres vers les noms dans config.py
        param_mapping = {
            'rsi_period': ('RSI_PERIOD', lambda x: str(x)),
            'rsi_oversold': ('RSI_OVERSOLD', lambda x: str(int(x)) if float(x).is_integer() else f"{x:.1f}"),
            'rsi_overbought': ('RSI_OVERBOUGHT', lambda x: str(int(x)) if float(x).is_integer() else f"{x:.1f}"),
            'ema_short': ('EMA_SHORT', lambda x: str(x)),
            'ema_long': ('EMA_LONG', lambda x: str(x)),
            'bb_period': ('BB_PERIOD', lambda x: str(x)),
            'bb_std': ('BB_STD', lambda x: f"{x:.1f}"),
            'trend_strength_threshold': ('TREND_STRENGTH_THRESHOLD', lambda x: str(x)),
            'momentum_threshold': ('MOMENTUM_THRESHOLD', lambda x: f"{x:.1f}"),
            'stop_loss': ('STOP_LOSS_PERCENT', lambda x: f"{x:.1f}"),
            'take_profit': ('TAKE_PROFIT_PERCENT', lambda x: f"{x:.1f}"),
            'enable_trailing_stop': ('ENABLE_TRAILING_STOP', lambda x: str(x)),
            'trailing_stop_distance': ('TRAILING_STOP_DISTANCE', lambda x: f"{x:.1f}"),
            'trailing_stop_activation': ('TRAILING_STOP_ACTIVATION', lambda x: f"{x:.1f}"),
            'max_order_size': ('MAX_ORDER_SIZE', lambda x: str(x)),
            'max_open_positions': ('MAX_OPEN_POSITIONS', lambda x: str(x)),
            'min_order_size': ('MIN_ORDER_SIZE', lambda x: str(x)),
            'max_risk_per_trade': ('MAX_RISK_PER_TRADE', lambda x: str(x)),
            'required_signals': ('REQUIRED_SIGNALS', lambda x: str(x)),
            'min_ai_score_for_buy': ('MIN_AI_SCORE_FOR_BUY', lambda x: str(x)),
            'block_buy_on_bearish': ('BLOCK_BUY_ON_BEARISH', lambda x: str(x)),
            'min_buy_signals': ('MIN_BUY_SIGNALS', lambda x: str(x)),
            'min_sell_signals': ('MIN_SELL_SIGNALS', lambda x: str(x)),
            'enable_breakout_detection': ('ENABLE_BREAKOUT_DETECTION', lambda x: str(x)),
            'min_breakout_strength': ('MIN_BREAKOUT_STRENGTH', lambda x: f"{x:.1f}"),
            'breakout_signal_bonus': ('BREAKOUT_SIGNAL_BONUS', lambda x: str(x)),
            'enable_advanced_strategies': ('ENABLE_ADVANCED_STRATEGIES', lambda x: str(x)),
            'strategy_consensus_threshold': ('STRATEGY_CONSENSUS_THRESHOLD', lambda x: str(x)),
            'enable_ensemble_ml': ('ENABLE_ENSEMBLE_ML', lambda x: str(x)),
            'ml_min_training_samples': ('ML_MIN_TRAINING_SAMPLES', lambda x: str(x)),
            'ml_retrain_interval': ('ML_RETRAIN_INTERVAL', lambda x: str(x)),
            'ml_confidence_threshold': ('ML_CONFIDENCE_THRESHOLD', lambda x: f"{x:.2f}"),
            'enable_advanced_features': ('ENABLE_ADVANCED_FEATURES', lambda x: str(x)),
            # Rotation intelligente
            'enable_smart_rotation': ('ENABLE_SMART_ROTATION', lambda x: str(x)),
            'rotation_min_cycle_end_score': ('ROTATION_MIN_CYCLE_END_SCORE', lambda x: str(x)),
            'rotation_min_opportunity_score': ('ROTATION_MIN_OPPORTUNITY_SCORE', lambda x: str(x)),
            'rotation_min_score_advantage': ('ROTATION_MIN_SCORE_ADVANTAGE', lambda x: str(x)),
            'rotation_min_profit': ('ROTATION_MIN_PROFIT', lambda x: f"{x:.1f}"),
            'rotation_min_hold_time': ('ROTATION_MIN_HOLD_TIME', lambda x: str(x)),
            'rotation_cooldown': ('ROTATION_COOLDOWN', lambda x: str(x)),
            'rotation_max_per_hour': ('ROTATION_MAX_PER_HOUR', lambda x: str(x)),
            # Exposition au marché
            'max_total_exposure_percent': ('MAX_TOTAL_EXPOSURE_PERCENT', lambda x: str(x)),
            'max_exposure_per_crypto': ('MAX_EXPOSURE_PER_CRYPTO', lambda x: f"{x:.1f}"),
            'max_trades_per_hour': ('MAX_TRADES_PER_HOUR', lambda x: str(x)),
            'enable_conservative_mode': ('ENABLE_CONSERVATIVE_MODE', lambda x: str(x)),
            'btc_volatility_threshold': ('BTC_VOLATILITY_THRESHOLD', lambda x: f"{x:.1f}"),
            'conservative_factor': ('CONSERVATIVE_FACTOR', lambda x: f"{x:.1f}"),
        }
        
        try:
            # Lire le fichier config.py
            with open(config_file, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # Créer une sauvegarde
            backup_file = config_file.replace('.py', f'_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}.py')
            with open(backup_file, 'w', encoding='utf-8') as f:
                f.write(content)
            print(f"[BACKUP] Sauvegarde creee: {backup_file}")
            
            # Appliquer les modifications
            params_dict = params.to_dict()
            updates_count = 0
            
            for param_key, (config_name, formatter) in param_mapping.items():
                if param_key in params_dict:
                    value = params_dict[param_key]
                    formatted_value = formatter(value)
                    
                    # Pattern pour trouver et remplacer la valeur
                    # Gère les cas: VAR = value, VAR = value  # comment
                    pattern = rf'^({config_name}\s*=\s*)([^#\n]+)(.*?)$'
                    
                    def replacer(match):
                        prefix = match.group(1)
                        comment = match.group(3) if match.group(3) else ''
                        return f"{prefix}{formatted_value}{comment}"
                    
                    new_content, count = re.subn(pattern, replacer, content, flags=re.MULTILINE)
                    if count > 0:
                        content = new_content
                        updates_count += 1
                        print(f"   [UPDATE] {config_name} = {formatted_value}")
            
            # Écrire les modifications
            with open(config_file, 'w', encoding='utf-8') as f:
                f.write(content)
            
            print(f"\n[SUCCESS] {updates_count} parametres mis a jour dans {config_file}")
            return True
            
        except Exception as e:
            print(f"[ERROR] Erreur lors de l'application: {e}")
            return False
    
    @staticmethod
    def save_optimization_results(result: BacktestResult, mode: str = "backtest"):
        """Sauvegarde les résultats COMPLETS dans optimization_results.json pour l'API"""
        output = {
            "timestamp": datetime.now().isoformat(),
            "mode": mode,
            "best_params": {
                # Indicateurs techniques
                "rsi_period": result.params.rsi_period,
                "rsi_oversold": result.params.rsi_oversold,
                "rsi_overbought": result.params.rsi_overbought,
                "ema_short": result.params.ema_short,
                "ema_long": result.params.ema_long,
                "bb_period": result.params.bb_period,
                "bb_std": result.params.bb_std,
                # Trend Following
                "trend_strength_threshold": result.params.trend_strength_threshold,
                "momentum_threshold": result.params.momentum_threshold,
                # Gestion du risque
                "stop_loss": result.params.stop_loss,
                "take_profit": result.params.take_profit,
                # Trailing Stop
                "enable_trailing_stop": result.params.enable_trailing_stop,
                "trailing_stop_distance": result.params.trailing_stop_distance,
                "trailing_stop_activation": result.params.trailing_stop_activation,
                # Positions
                "max_order_size": result.params.max_order_size,
                "max_open_positions": result.params.max_open_positions,
                "min_order_size": result.params.min_order_size,
                "max_risk_per_trade": result.params.max_risk_per_trade,
                # Signaux
                "required_signals": result.params.required_signals,
                "min_ai_score_for_buy": result.params.min_ai_score_for_buy,
                "block_buy_on_bearish": result.params.block_buy_on_bearish,
                "min_buy_signals": result.params.min_buy_signals,
                "min_sell_signals": result.params.min_sell_signals,
                # Breakout
                "enable_breakout_detection": result.params.enable_breakout_detection,
                "min_breakout_strength": result.params.min_breakout_strength,
                "breakout_signal_bonus": result.params.breakout_signal_bonus,
                # Stratégies avancées
                "enable_advanced_strategies": result.params.enable_advanced_strategies,
                "strategy_consensus_threshold": result.params.strategy_consensus_threshold,
                # Machine Learning
                "enable_ensemble_ml": result.params.enable_ensemble_ml,
                "ml_min_training_samples": result.params.ml_min_training_samples,
                "ml_retrain_interval": result.params.ml_retrain_interval,
                "ml_confidence_threshold": result.params.ml_confidence_threshold,
                # Feature Engineering
                "enable_advanced_features": result.params.enable_advanced_features,
                # Rotation intelligente
                "enable_smart_rotation": result.params.enable_smart_rotation,
                "rotation_min_cycle_end_score": result.params.rotation_min_cycle_end_score,
                "rotation_min_opportunity_score": result.params.rotation_min_opportunity_score,
                "rotation_min_score_advantage": result.params.rotation_min_score_advantage,
                "rotation_min_profit": result.params.rotation_min_profit,
                "rotation_min_hold_time": result.params.rotation_min_hold_time,
                "rotation_cooldown": result.params.rotation_cooldown,
                "rotation_max_per_hour": result.params.rotation_max_per_hour,
                # Exposition au marché
                "max_total_exposure_percent": result.params.max_total_exposure_percent,
                "max_exposure_per_crypto": result.params.max_exposure_per_crypto,
                "max_trades_per_hour": result.params.max_trades_per_hour,
                "enable_conservative_mode": result.params.enable_conservative_mode,
                "btc_volatility_threshold": result.params.btc_volatility_threshold,
                "conservative_factor": result.params.conservative_factor
            },
            "metrics": {
                "total_trades": result.total_trades,
                "winning_trades": result.winning_trades,
                "losing_trades": result.losing_trades,
                "win_rate": round(result.win_rate, 2),
                "total_pnl": round(result.total_pnl, 2),
                "profit_factor": round(result.profit_factor, 2),
                "max_drawdown": round(result.max_drawdown, 2),
                "sharpe_ratio": round(result.sharpe_ratio, 2)
            },
            "fitness_score": round(result.fitness_score(), 4)
        }
        
        with open("optimization_results.json", 'w', encoding='utf-8') as f:
            json.dump(output, f, indent=2, ensure_ascii=False)
        
        print(f"[SAVED] Resultats COMPLETS sauvegardes: optimization_results.json")
        return output


# ═══════════════════════════════════════════════════════════════════════════════
# LISTE COMPLÈTE DES CRYPTOS POUR OPTIMISATION GPU
# ═══════════════════════════════════════════════════════════════════════════════

# Top 10 par capitalisation
TOP_10_SYMBOLS = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "XRPUSDT", "SOLUSDT", 
                  "ADAUSDT", "DOGEUSDT", "AVAXUSDT", "DOTUSDT", "LINKUSDT"]

# Top 25 cryptos
TOP_25_SYMBOLS = TOP_10_SYMBOLS + [
    "POLUSDT", "ATOMUSDT", "LTCUSDT", "ETCUSDT", "XLMUSDT",
    "NEARUSDT", "APTUSDT", "ARBUSDT", "OPUSDT", "INJUSDT",
    "UNIUSDT", "AAVEUSDT", "FILUSDT", "TIAUSDT", "ICPUSDT"
]

# Liste complète pour calculs massifs GPU (60+ cryptos)
ALL_SYMBOLS = TOP_25_SYMBOLS + [
    # Layer 1
    "SUIUSDT", "SEIUSDT", "FTMUSDT", "ALGOUSDT", "EGLDUSDT",
    # Layer 2 & DeFi
    "MKRUSDT", "GRTUSDT", "RUNEUSDT", "LDOUSDT", "CRVUSDT",
    # Gaming & Metaverse
    "SANDUSDT", "MANAUSDT", "AXSUSDT", "GALAUSDT", "APEUSDT",
    "ENJUSDT", "FLOWUSDT", "CHZUSDT", "IMXUSDT",
    # Altcoins majeurs
    "KAVAUSDT", "ROSEUSDT", "ZILUSDT", "QNTUSDT", "HBARUSDT",
    "VETUSDT", "XTZUSDT", "EOSUSDT", "THETAUSDT", "IOTAUSDT",
    # Meme & Trending
    "SHIBUSDT", "PEPEUSDT", "FLOKIUSDT", "BONKUSDT", "WIFUSDT"
]


# ═══════════════════════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════════════════════

async def main():
    """Point d'entree principal"""
    
    print("""
+======================================================================+
|          AI TRADING OPTIMIZER - v3.0 GPU ACCELERATED                 |
|          Optimisation des strategies de trading par IA               |
+======================================================================+
|  Modes disponibles:                                                  |
|    1. backtest   - Test des parametres actuels                       |
|    2. grid       - Exploration par grille                            |
|    3. genetic    - Optimisation genetique                            |
|    4. pso        - [GPU] Particle Swarm Optimization (rapide!)       |
|    5. gpu_full   - [GPU] Analyse complete PSO + grid + genetic       |
|    6. full       - Analyse complete (grid + genetic + correlation)   |
|                                                                      |
|  Options:                                                            |
|    --symbols top10|top25|all|BTC,ETH,SOL  (defaut: top10)            |
|    --candles 1000-10000                   (defaut: 2000)             |
|    --interval 1h|4h|1d|5m                 (defaut: 1h)               |
|    --apply                                 Applique auto dans config.py |
+======================================================================+
    """)
    
    # Parsing des arguments
    mode = "full"
    symbols_preset = "top10"
    candle_limit = 2000
    interval = "1h"  # Par défaut 1h (disponible dans historical_data/)
    auto_apply = False  # Par défaut, ne pas appliquer automatiquement
    
    args = sys.argv[1:]
    i = 0
    while i < len(args):
        if args[i] == "--mode" and i + 1 < len(args):
            mode = args[i + 1]
            i += 2
        elif args[i] == "--symbols" and i + 1 < len(args):
            symbols_preset = args[i + 1]
            i += 2
        elif args[i] == "--candles" and i + 1 < len(args):
            candle_limit = int(args[i + 1])
            i += 2
        elif args[i] == "--interval" and i + 1 < len(args):
            interval = args[i + 1]
            i += 2
        elif args[i] == "--apply":
            auto_apply = True
            i += 1
        else:
            i += 1
    
    print(f"[MODE] Mode selectionne: {mode.upper()}")
    print(f"[INTERVAL] Intervalle: {interval}")
    if auto_apply:
        print(f"[AUTO-APPLY] Les optimisations seront appliquees automatiquement dans config.py")
    sys.stdout.flush()
    
    # Sélection des symboles
    if symbols_preset == "top10":
        symbols = TOP_10_SYMBOLS
    elif symbols_preset == "top25":
        symbols = TOP_25_SYMBOLS
    elif symbols_preset == "all":
        symbols = ALL_SYMBOLS
    elif "," in symbols_preset:
        # Liste personnalisée: BTC,ETH,SOL
        symbols = [s.strip().upper() + "USDT" if not s.strip().upper().endswith("USDT") else s.strip().upper() 
                   for s in symbols_preset.split(",")]
    else:
        symbols = TOP_10_SYMBOLS
    
    print(f"[SYMBOLS] {len(symbols)} cryptos selectionnees")
    print(f"[DATA] Bougies par crypto: {candle_limit}")
    sys.stdout.flush()
    
    # ÉTAPE 1: Essayer de charger depuis le cache local
    print(f"\n[CACHE] Vérification du cache local...")
    cache_data = DataFetcher.load_local_cache()
    use_cache_indicators = False
    
    if cache_data and cache_data.get('symbols'):
        cached_symbols = list(cache_data['symbols'].keys())
        matching = [s for s in symbols if s in cached_symbols]
        print(f"   [OK] {len(matching)}/{len(symbols)} cryptos trouvées dans le cache")
        
        if len(matching) >= len(symbols) * 0.8:  # Au moins 80% des symboles en cache
            use_cache_indicators = True
            print(f"   [✓] Utilisation des indicateurs précalculés du cache")
        else:
            print(f"   [!] Cache incomplet, téléchargement Binance nécessaire")
    else:
        print(f"   [!] Pas de cache disponible")
    
    # ÉTAPE 2: Récupérer les données OHLCV depuis historical_data/
    print(f"\n[FETCH] Recuperation des donnees historiques (intervalle: {interval})...")
    sys.stdout.flush()
    if len(symbols) <= 15:
        print(f"   Symboles: {', '.join([s.replace('USDT', '') for s in symbols])}")
    else:
        print(f"   {len(symbols)} cryptos (Top: {', '.join([s.replace('USDT', '') for s in symbols[:10]])}...)")
    sys.stdout.flush()
    
    # Récupérer les données avec l'intervalle et le nombre de bougies spécifiés
    start_time = time.time()
    data = await DataFetcher.fetch_multiple_symbols(symbols, interval=interval, limit=candle_limit)
    fetch_time = time.time() - start_time
    
    total_candles = sum(len(d) for d in data.values())
    print(f"   [OK] {total_candles:,} bougies chargees en {fetch_time:.1f}s")
    sys.stdout.flush()
    
    # ÉTAPE 3: Enrichir avec les indicateurs du cache si disponibles
    if use_cache_indicators and cache_data:
        print(f"\n[CACHE] Enrichissement avec indicateurs précalculés...")
        enriched_count = 0
        for symbol in symbols:
            cached = DataFetcher.get_cached_indicators(symbol)
            if cached and 'indicators' in cached:
                # Stocker les indicateurs précalculés pour utilisation rapide
                if symbol in data and data[symbol]:
                    # Ajouter les indicateurs au dernier point de données
                    indicators = cached['indicators']
                    data[symbol][-1]['cached_rsi'] = indicators.get('rsi', 50)
                    data[symbol][-1]['cached_ema_short'] = indicators.get('ema_short', 0)
                    data[symbol][-1]['cached_ema_long'] = indicators.get('ema_long', 0)
                    data[symbol][-1]['cached_bb'] = indicators.get('bollinger', {})
                    enriched_count += 1
        print(f"   [OK] {enriched_count} cryptos enrichies avec indicateurs cache")
    
    print(f"   [GPU] Pret pour calculs GPU massifs")
    
    if mode == "backtest":
        # Test simple avec les parametres par defaut
        print("\n[TEST] BACKTEST AVEC PARAMETRES ACTUELS")
        params = TradingParams()
        backtester = Backtester(data, params)
        result = backtester.run()
        
        print(f"\n[RESULTS] RESULTATS:")
        print(f"   Trades: {result.total_trades}")
        print(f"   Win Rate: {result.win_rate:.1f}%")
        print(f"   P&L Total: {result.total_pnl:.2f} EUR")
        print(f"   Profit Factor: {result.profit_factor:.2f}")
        print(f"   Max Drawdown: {result.max_drawdown:.1f}%")
        print(f"   Sharpe Ratio: {result.sharpe_ratio:.2f}")
        
        ReportGenerator.generate_json_report(result)
        ReportGenerator.save_optimization_results(result, "backtest")
    
    elif mode == "grid":
        # Exploration par grille
        optimizer = GridSearchOptimizer(data)
        results = optimizer.explore()
        
        if results:
            ReportGenerator.generate_json_report(results[0], "grid_best_result.json")
            ReportGenerator.generate_config_update(results[0].params)
            ReportGenerator.save_optimization_results(results[0], "grid")
            
            # Application automatique si demandée
            if auto_apply:
                print("\n[AUTO-APPLY] Application automatique dans config.py...")
                ReportGenerator.apply_to_config(results[0].params)
    
    elif mode == "genetic":
        # Optimisation génétique
        optimizer = GeneticOptimizer(data, population_size=30, generations=15)
        best_params = optimizer.evolve()
        
        if best_params:
            # Backtest final
            backtester = Backtester(data, best_params)
            result = backtester.run()
            
            print(f"\n[RESULTS] RESULTATS FINAUX:")
            print(f"   Trades: {result.total_trades}")
            print(f"   Win Rate: {result.win_rate:.1f}%")
            print(f"   P&L Total: {result.total_pnl:.2f} EUR")
            
            ReportGenerator.generate_json_report(result, "genetic_best_result.json")
            ReportGenerator.generate_config_update(best_params)
            ReportGenerator.save_optimization_results(result, "genetic")
            
            # Application automatique si demandée
            if auto_apply:
                print("\n[AUTO-APPLY] Application automatique dans config.py...")
                ReportGenerator.apply_to_config(best_params)
    
    elif mode == "full":
        # Analyse complete
        print("\n" + "=" * 70)
        print("[RUNNING] ANALYSE COMPLETE EN COURS...")
        print("=" * 70)
        
        # 1. Grid Search
        grid_optimizer = GridSearchOptimizer(data)
        grid_results = grid_optimizer.explore()
        
        # 2. Genetic Optimization
        genetic_optimizer = GeneticOptimizer(data, population_size=30, generations=10)
        best_genetic = genetic_optimizer.evolve()
        
        # 3. Analyse des corrélations
        analyzer = CorrelationAnalyzer(grid_results)
        analyzer.analyze()
        
        # 4. Comparer les meilleurs resultats
        print("\n" + "=" * 70)
        print("[TROPHY] SYNTHESE FINALE")
        print("=" * 70)
        
        # Backtest du meilleur génétique
        backtester = Backtester(data, best_genetic)
        genetic_result = backtester.run()
        
        print("\n[COMPARE] Comparaison:")
        print(f"\n   Grid Search (meilleur):")
        print(f"      Score: {grid_results[0].fitness_score():.2f}")
        print(f"      P&L: {grid_results[0].total_pnl:.2f} EUR | Win Rate: {grid_results[0].win_rate:.1f}%")
        
        print(f"\n   Algorithme Genetique:")
        print(f"      Score: {genetic_result.fitness_score():.2f}")
        print(f"      P&L: {genetic_result.total_pnl:.2f} EUR | Win Rate: {genetic_result.win_rate:.1f}%")
        
        # Choisir le meilleur
        if genetic_result.fitness_score() > grid_results[0].fitness_score():
            best_result = genetic_result
            best_params = best_genetic
            print("\n   [WINNER] Vainqueur: Algorithme Genetique")
        else:
            best_result = grid_results[0]
            best_params = grid_results[0].params
            print("\n   [WINNER] Vainqueur: Grid Search")
        
        # Sauvegarder
        ReportGenerator.generate_json_report(best_result, "ai_optimization_report.json")
        ReportGenerator.generate_config_update(best_params, "ai_optimized_config.py")
        ReportGenerator.save_optimization_results(best_result, "full")
        
        # Application automatique si demandée
        if auto_apply:
            print("\n[AUTO-APPLY] Application automatique dans config.py...")
            ReportGenerator.apply_to_config(best_params)
        
        print("\n" + "=" * 70)
        print("[SUCCESS] OPTIMISATION TERMINEE !")
        print("=" * 70)
        print("\n[FILES] Fichiers generes:")
        print("   - ai_optimization_report.json (rapport complet)")
        print("   - ai_optimized_config.py (nouvelle configuration)")
        print("   - optimization_results.json (pour l'API)")
        if auto_apply:
            print("   - config.py (MIS A JOUR AUTOMATIQUEMENT)")
            print("\n[APPLIED] Les optimisations ont ete appliquees dans config.py")
        else:
            print("\n[TIP] Pour appliquer les optimisations automatiquement, utilisez --apply")
            print("   ou copiez les parametres de ai_optimized_config.py dans config.py")
    
    elif mode == "pso":
        # Optimisation PSO GPU-accelere
        print("\n" + "=" * 70)
        print("[PSO] OPTIMISATION PSO GPU-ACCELEREE")
        print("=" * 70)
        
        if GPU_AVAILABLE:
            print(f"[GPU] Utilisation de {GPU_NAME}")
        else:
            print("[CPU] Mode CPU (installez cupy-cuda12x pour GPU)")
        
        # Preparer les donnees pour GPU
        prices_dict = {}
        for symbol, candles in data.items():
            if len(candles) >= 50:
                prices = [c['close'] for c in candles]
                prices_dict[symbol] = np.array(prices)
        
        print(f"[PSO] {len(prices_dict)} cryptos chargees pour optimisation")
        sys.stdout.flush()
        
        # Creer le backtester GPU et l'optimiseur PSO
        gpu_backtester = GPUBacktester(prices_dict)
        pso_optimizer = ParticleSwarmOptimizer(gpu_backtester, n_particles=50)
        
        # Lancer l'optimisation
        pso_result = pso_optimizer.optimize(n_iterations=30)
        
        best_params = pso_result['best_params']
        
        # Creer TradingParams depuis les resultats
        optimized_params = TradingParams(
            rsi_period=14,
            rsi_oversold=best_params.get('rsi_oversold', 30),
            rsi_overbought=best_params.get('rsi_overbought', 70),
            ema_short=best_params.get('ema_short', 9),
            ema_long=best_params.get('ema_long', 21),
            bb_period=best_params.get('bb_period', 20),
            bb_std=best_params.get('bb_std', 2.0),
            stop_loss=best_params.get('stop_loss', 2.0),
            take_profit=best_params.get('take_profit', 4.0),
            required_signals=2
        )
        
        # Backtest final avec les meilleurs parametres
        backtester = Backtester(data, optimized_params)
        result = backtester.run()
        
        print(f"\n[RESULTS] RESULTATS FINAUX PSO:")
        print(f"   Trades: {result.total_trades}")
        print(f"   Win Rate: {result.win_rate:.1f}%")
        print(f"   P&L Total: {result.total_pnl:.2f} EUR")
        print(f"   Temps d'optimisation: {pso_result['elapsed_time']:.1f}s")
        print(f"   Evaluations: {pso_result['particles']} x {pso_result['iterations']} = {pso_result['particles'] * pso_result['iterations']}")
        sys.stdout.flush()
        
        ReportGenerator.generate_json_report(result, "pso_best_result.json")
        ReportGenerator.generate_config_update(optimized_params, "pso_optimized_config.py")
        ReportGenerator.save_optimization_results(result, "pso")
        
        # Application automatique si demandée
        if auto_apply:
            print("\n[AUTO-APPLY] Application automatique dans config.py...")
            ReportGenerator.apply_to_config(optimized_params)
    
    elif mode == "gpu_full":
        # Analyse complete avec GPU
        print("\n" + "=" * 70)
        print("[GPU] ANALYSE COMPLETE GPU-ACCELEREE")
        print("=" * 70)
        
        if GPU_AVAILABLE:
            print(f"[GPU] Utilisation de {GPU_NAME}")
        else:
            print("[CPU] Mode CPU (GPU non disponible)")
        
        # Preparer les donnees
        prices_dict = {}
        for symbol, candles in data.items():
            if len(candles) >= 50:
                prices = [c['close'] for c in candles]
                prices_dict[symbol] = np.array(prices)
        
        print(f"[GPU] {len(prices_dict)} cryptos chargees")
        sys.stdout.flush()
        
        # 1. PSO Optimization
        print("\n[1/3] Optimisation PSO...")
        gpu_backtester = GPUBacktester(prices_dict)
        pso_optimizer = ParticleSwarmOptimizer(gpu_backtester, n_particles=50)
        pso_result = pso_optimizer.optimize(n_iterations=30)
        
        # 2. Grid Search rapide (comparaison)
        print("\n[2/3] Grid Search (comparaison)...")
        grid_optimizer = GridSearchOptimizer(data)
        grid_results = grid_optimizer.explore()
        
        # 3. Genetic Algorithm
        print("\n[3/3] Algorithme Genetique...")
        genetic_optimizer = GeneticOptimizer(data, population_size=20, generations=10)
        best_genetic = genetic_optimizer.evolve()
        genetic_backtester = Backtester(data, best_genetic)
        genetic_result = genetic_backtester.run()
        
        # Comparer
        print("\n" + "=" * 70)
        print("[COMPARE] COMPARAISON DES METHODES")
        print("=" * 70)
        
        pso_params = TradingParams(
            rsi_oversold=pso_result['best_params'].get('rsi_oversold', 30),
            rsi_overbought=pso_result['best_params'].get('rsi_overbought', 70),
            ema_short=pso_result['best_params'].get('ema_short', 9),
            ema_long=pso_result['best_params'].get('ema_long', 21),
            bb_period=pso_result['best_params'].get('bb_period', 20),
            bb_std=pso_result['best_params'].get('bb_std', 2.0),
            stop_loss=pso_result['best_params'].get('stop_loss', 2.0),
            take_profit=pso_result['best_params'].get('take_profit', 4.0),
        )
        pso_backtester = Backtester(data, pso_params)
        pso_final_result = pso_backtester.run()
        
        print(f"\n   PSO GPU ({pso_result['elapsed_time']:.1f}s):")
        print(f"      Score: {pso_final_result.fitness_score():.2f}")
        print(f"      P&L: {pso_final_result.total_pnl:.2f} EUR | Win Rate: {pso_final_result.win_rate:.1f}%")
        
        print(f"\n   Grid Search:")
        print(f"      Score: {grid_results[0].fitness_score():.2f}")
        print(f"      P&L: {grid_results[0].total_pnl:.2f} EUR | Win Rate: {grid_results[0].win_rate:.1f}%")
        
        print(f"\n   Algorithme Genetique:")
        print(f"      Score: {genetic_result.fitness_score():.2f}")
        print(f"      P&L: {genetic_result.total_pnl:.2f} EUR | Win Rate: {genetic_result.win_rate:.1f}%")
        
        # Determiner le vainqueur
        results_compare = [
            ("PSO GPU", pso_final_result.fitness_score(), pso_params, pso_final_result),
            ("Grid Search", grid_results[0].fitness_score(), grid_results[0].params, grid_results[0]),
            ("Genetic", genetic_result.fitness_score(), best_genetic, genetic_result),
        ]
        
        winner = max(results_compare, key=lambda x: x[1])
        print(f"\n   [TROPHY] Vainqueur: {winner[0]} (Score: {winner[1]:.2f})")
        
        ReportGenerator.generate_json_report(winner[3], "gpu_full_best_result.json")
        ReportGenerator.generate_config_update(winner[2], "gpu_optimized_config.py")
        ReportGenerator.save_optimization_results(winner[3], "gpu_full")
        
        # Application automatique si demandée
        if auto_apply:
            print("\n[AUTO-APPLY] Application automatique dans config.py...")
            ReportGenerator.apply_to_config(winner[2])
        
        print("\n" + "=" * 70)
        print("[SUCCESS] OPTIMISATION GPU TERMINEE !")
        print("=" * 70)
        if auto_apply:
            print("[APPLIED] Les optimisations ont ete appliquees dans config.py")
        sys.stdout.flush()
    
    else:
        print(f"[ERROR] Mode inconnu: {mode}")
        print("   Modes valides: backtest, grid, genetic, pso, gpu_full, full")


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