#!/usr/bin/env python3
"""
SMART OPTIMIZER HYBRID - Optimisation intelligente stratégie hybride

Comme smart_optimizer_dynamic.py mais pour les 3 stratégies BULL/BEAR/RANGE

Stratégie:
1. Exploration (50%): Random search dans l'espace des paramètres
2. Exploitation (50%): Mutation des meilleures configs
3. Auto-orientation vers setups profitables
4. Sauvegarde continue, évite doublons
5. Tests: 300-500+ configs
6. Objectif: PROFIT MAXIMUM avec adaptation marché
"""

import pandas as pd
import numpy as np
import json
import hashlib
import time
import random
from datetime import datetime
from pathlib import Path

from hybrid_strategy import HybridStrategy
from backtest_hybrid import HybridBacktest, get_btc_historical_data


class SmartHybridOptimizer:
    """Optimiseur intelligent pour stratégie hybride"""

    def __init__(self, df: pd.DataFrame, initial_capital: float = 500.0,
                 results_file: str = 'smart_hybrid_results.json'):
        self.df = df
        self.initial_capital = initial_capital
        self.results_file = results_file
        self.results = []
        self.tested_hashes = set()

        # Charger résultats existants
        self._load_results()

        # Stats
        self.total_tests = 0
        self.exploration_tests = 0
        self.exploitation_tests = 0

    def _load_results(self):
        """Charge résultats existants"""
        if Path(self.results_file).exists():
            with open(self.results_file, 'r') as f:
                self.results = json.load(f)

            for r in self.results:
                config_hash = self._hash_config(r['config'])
                self.tested_hashes.add(config_hash)

            print(f"📂 Chargé {len(self.results)} résultats existants")

    def _save_results(self):
        """Sauvegarde résultats"""
        with open(self.results_file, 'w') as f:
            json.dump(self.results, f, indent=2)

    def _hash_config(self, config: dict) -> str:
        """Hash une config pour détecter doublons"""
        rounded = {
            k: round(v, 2) if isinstance(v, float) else v
            for k, v in config.items()
        }
        config_str = json.dumps(rounded, sort_keys=True)
        return hashlib.md5(config_str.encode()).hexdigest()

    def _generate_random_config(self) -> dict:
        """Génère config aléatoire (EXPLORATION)"""
        config = {
            # Bull strategy
            'bull.min_trend_strength': random.randint(50, 80),
            'bull.rsi_pullback_max': random.randint(30, 50),
            'bull.rsi_breakout_min': random.randint(50, 70),
            'bull.volume_spike_threshold': round(random.uniform(1.2, 2.5), 2),
            'bull.sl_atr_multiplier': round(random.uniform(1.0, 3.0), 2),
            'bull.tp_atr_multiplier': round(random.uniform(2.0, 6.0), 2),
            'bull.trailing_pct': round(random.uniform(0.05, 0.20), 2),
            'bull.min_confidence': random.choice([60, 65, 70, 75]),
            'bull.leverage': random.choice([50, 75, 100, 125, 150, 175, 200]),

            # Bear strategy
            'bear.min_trend_strength': random.randint(50, 80),
            'bear.rsi_rally_min': random.randint(50, 70),
            'bear.rsi_overbought': random.randint(65, 80),
            'bear.volume_spike_threshold': round(random.uniform(1.2, 2.0), 2),
            'bear.sl_atr_multiplier': round(random.uniform(1.0, 2.5), 2),
            'bear.tp_atr_multiplier': round(random.uniform(2.0, 5.0), 2),
            'bear.trailing_pct': round(random.uniform(0.05, 0.15), 2),
            'bear.min_confidence': random.choice([60, 65, 70, 75]),
            'bear.leverage': random.choice([50, 75, 100, 125, 150, 175, 200]),

            # Range strategy (V10 optimisée)
            'range.rsi_oversold': random.randint(20, 40),
            'range.rsi_overbought': random.randint(60, 85),
            'range.range_atr_threshold': round(random.uniform(0.20, 0.50), 2),
            'range.min_confidence': random.choice([60, 65, 70, 75, 80]),
            'range.sl_atr_multiplier': round(random.uniform(0.5, 2.5), 2),
            'range.trailing_pct': round(random.uniform(0.03, 0.20), 2),
            'range.leverage': random.choice([100, 125, 150, 175, 200, 225, 250]),
        }

        # Valider cohérence RSI
        if config['range.rsi_overbought'] - config['range.rsi_oversold'] < 20:
            config['range.rsi_overbought'] = config['range.rsi_oversold'] + 30

        return config

    def _mutate_config(self, base_config: dict, mutation_rate: float = 0.3) -> dict:
        """Mute une config (EXPLOITATION)"""
        config = base_config.copy()

        # Muter chaque param avec probabilité mutation_rate
        if random.random() < mutation_rate:
            config['bull.min_trend_strength'] = max(50, min(80,
                config['bull.min_trend_strength'] + random.randint(-10, 10)))

        if random.random() < mutation_rate:
            config['bull.rsi_pullback_max'] = max(30, min(50,
                config['bull.rsi_pullback_max'] + random.randint(-5, 5)))

        if random.random() < mutation_rate:
            config['bull.sl_atr_multiplier'] = max(1.0, min(3.0,
                config['bull.sl_atr_multiplier'] + random.uniform(-0.3, 0.3)))

        if random.random() < mutation_rate:
            config['bull.tp_atr_multiplier'] = max(2.0, min(6.0,
                config['bull.tp_atr_multiplier'] + random.uniform(-0.5, 0.5)))

        if random.random() < mutation_rate:
            config['bull.leverage'] = random.choice([50, 75, 100, 125, 150, 175, 200])

        # Bear mutations
        if random.random() < mutation_rate:
            config['bear.min_trend_strength'] = max(50, min(80,
                config['bear.min_trend_strength'] + random.randint(-10, 10)))

        if random.random() < mutation_rate:
            config['bear.rsi_rally_min'] = max(50, min(70,
                config['bear.rsi_rally_min'] + random.randint(-5, 5)))

        if random.random() < mutation_rate:
            config['bear.sl_atr_multiplier'] = max(1.0, min(2.5,
                config['bear.sl_atr_multiplier'] + random.uniform(-0.3, 0.3)))

        if random.random() < mutation_rate:
            config['bear.leverage'] = random.choice([50, 75, 100, 125, 150, 175, 200])

        # Range mutations
        if random.random() < mutation_rate:
            config['range.rsi_oversold'] = max(20, min(40,
                config['range.rsi_oversold'] + random.randint(-5, 5)))

        if random.random() < mutation_rate:
            config['range.rsi_overbought'] = max(60, min(85,
                config['range.rsi_overbought'] + random.randint(-5, 5)))

        if random.random() < mutation_rate:
            config['range.range_atr_threshold'] = max(0.20, min(0.50,
                config['range.range_atr_threshold'] + random.uniform(-0.05, 0.05)))

        if random.random() < mutation_rate:
            config['range.leverage'] = random.choice([100, 125, 150, 175, 200, 225, 250])

        # Valider RSI range
        if config['range.rsi_overbought'] - config['range.rsi_oversold'] < 20:
            config['range.rsi_overbought'] = config['range.rsi_oversold'] + 30

        return config

    def _test_config(self, config: dict) -> dict:
        """Teste une configuration"""
        # Vérifier doublon
        config_hash = self._hash_config(config)
        if config_hash in self.tested_hashes:
            return None

        self.tested_hashes.add(config_hash)

        # Créer stratégie
        strategy = HybridStrategy(leverage=100)

        # Appliquer params bull
        strategy.bull_strategy.params['min_trend_strength'] = config['bull.min_trend_strength']
        strategy.bull_strategy.params['rsi_pullback_max'] = config['bull.rsi_pullback_max']
        strategy.bull_strategy.params['rsi_breakout_min'] = config['bull.rsi_breakout_min']
        strategy.bull_strategy.params['volume_spike_threshold'] = config['bull.volume_spike_threshold']
        strategy.bull_strategy.params['sl_atr_multiplier'] = config['bull.sl_atr_multiplier']
        strategy.bull_strategy.params['tp_atr_multiplier'] = config['bull.tp_atr_multiplier']
        strategy.bull_strategy.params['trailing_pct'] = config['bull.trailing_pct']
        strategy.bull_strategy.params['min_confidence'] = config['bull.min_confidence']
        strategy.bull_strategy.leverage = config['bull.leverage']

        # Appliquer params bear
        strategy.bear_strategy.params['min_trend_strength'] = config['bear.min_trend_strength']
        strategy.bear_strategy.params['rsi_rally_min'] = config['bear.rsi_rally_min']
        strategy.bear_strategy.params['rsi_overbought'] = config['bear.rsi_overbought']
        strategy.bear_strategy.params['volume_spike_threshold'] = config['bear.volume_spike_threshold']
        strategy.bear_strategy.params['sl_atr_multiplier'] = config['bear.sl_atr_multiplier']
        strategy.bear_strategy.params['tp_atr_multiplier'] = config['bear.tp_atr_multiplier']
        strategy.bear_strategy.params['trailing_pct'] = config['bear.trailing_pct']
        strategy.bear_strategy.params['min_confidence'] = config['bear.min_confidence']
        strategy.bear_strategy.leverage = config['bear.leverage']

        # Appliquer params range
        strategy.range_strategy.params['rsi_oversold'] = config['range.rsi_oversold']
        strategy.range_strategy.params['rsi_overbought'] = config['range.rsi_overbought']
        strategy.range_strategy.params['range_atr_threshold'] = config['range.range_atr_threshold']
        strategy.range_strategy.params['min_confidence'] = config['range.min_confidence']
        strategy.range_strategy.params['sl_atr_multiplier'] = config['range.sl_atr_multiplier']
        strategy.range_strategy.params['trailing_pct'] = config['range.trailing_pct']
        strategy.range_strategy.leverage = config['range.leverage']

        # Backtest
        try:
            backtest = HybridBacktest(self.df, initial_capital=self.initial_capital)
            result = backtest.run("Hybrid", strategy)

            if result['total_trades'] == 0:
                return None

            return {
                'config': config,
                'pnl_pct': result['total_pnl_pct'],
                'trades': result['total_trades'],
                'win_rate': result['win_rate'],
                'profit_factor': result['profit_factor'],
                'max_dd': result['max_drawdown'],
                'final_capital': result['final_capital'],
                'trades_by_strategy': result['trades_by_strategy'],
                'timestamp': datetime.now().isoformat()
            }
        except Exception as e:
            print(f"  ERREUR: {e}")
            return None

    def optimize(self, max_tests: int = 300):
        """Lance l'optimisation intelligente"""
        print(f"🎯 Objectif: {max_tests} tests")
        print(f"📊 Déjà effectués: {len(self.results)}")
        print()

        tests_to_do = max_tests - len(self.results)

        for i in range(tests_to_do):
            self.total_tests += 1
            start_time = time.time()

            # 50% exploration, 50% exploitation
            if random.random() < 0.5 or len(self.results) < 20:
                # EXPLORATION: Random
                config = self._generate_random_config()
                mode = "EXPLORE"
                self.exploration_tests += 1
            else:
                # EXPLOITATION: Muter les meilleures
                profitable = [r for r in self.results
                             if r['pnl_pct'] > 0 and r['profit_factor'] > 1.0]

                if profitable:
                    # Prendre une des 10 meilleures
                    profitable.sort(key=lambda x: x['pnl_pct'], reverse=True)
                    base = random.choice(profitable[:min(10, len(profitable))])
                    config = self._mutate_config(base['config'])
                    mode = "EXPLOIT"
                    self.exploitation_tests += 1
                else:
                    config = self._generate_random_config()
                    mode = "EXPLORE"
                    self.exploration_tests += 1

            # Tester
            result = self._test_config(config)

            elapsed = time.time() - start_time

            if result:
                self.results.append(result)
                print(f"[{len(self.results)}/{max_tests}] {mode} | "
                      f"PnL {result['pnl_pct']:+.1f}% | "
                      f"PF {result['profit_factor']:.2f} | "
                      f"WR {result['win_rate']:.0f}% | "
                      f"Trades {result['trades']} | "
                      f"{elapsed:.1f}s")

                # Sauvegarder tous les 5 tests
                if len(self.results) % 5 == 0:
                    self._save_results()
            else:
                print(f"[{len(self.results)}/{max_tests}] {mode} | Doublon/Aucun trade | {elapsed:.1f}s")

        # Sauvegarde finale
        self._save_results()

    def print_results(self):
        """Affiche les résultats"""
        print()
        print("=" * 80)
        print("📊 RÉSULTATS FINAUX")
        print("=" * 80)
        print()

        if not self.results:
            print("❌ Aucun résultat")
            return

        profitable = [r for r in self.results
                     if r['pnl_pct'] > 0 and r['profit_factor'] > 1.0]

        print(f"Tests total: {len(self.results)}")
        print(f"Profitables: {len(profitable)} ({len(profitable)/len(self.results)*100:.0f}%)")
        print(f"Exploration: {self.exploration_tests} | Exploitation: {self.exploitation_tests}")
        print()

        if profitable:
            # TOP 5 par PnL
            profitable.sort(key=lambda x: x['pnl_pct'], reverse=True)
            print("🏆 TOP 5 PAR PnL:")
            for i, r in enumerate(profitable[:5], 1):
                print(f"\n{i}. PnL: {r['pnl_pct']:+.1f}% | PF: {r['profit_factor']:.2f} | "
                      f"WR: {r['win_rate']:.0f}% | Trades: {r['trades']}")
                print(f"   Max DD: {r['max_dd']:.1f}%")
                print(f"   Répartition: {r['trades_by_strategy']}")

            # TOP 5 par PF
            print()
            profitable.sort(key=lambda x: x['profit_factor'], reverse=True)
            print("🏆 TOP 5 PAR PROFIT FACTOR:")
            for i, r in enumerate(profitable[:5], 1):
                print(f"\n{i}. PF: {r['profit_factor']:.2f} | PnL: {r['pnl_pct']:+.1f}% | "
                      f"WR: {r['win_rate']:.0f}% | Trades: {r['trades']}")
                print(f"   Max DD: {r['max_dd']:.1f}%")
                print(f"   Répartition: {r['trades_by_strategy']}")
        else:
            print("❌ Aucune config profitable")


def main():
    print("=" * 80)
    print("🚀 SMART OPTIMIZER HYBRID - Optimisation Intelligente")
    print("=" * 80)
    print(f"Démarrage: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print()

    # Charger données
    df = get_btc_historical_data(days=1, interval='1m')

    # Créer optimizer
    optimizer = SmartHybridOptimizer(
        df=df,
        initial_capital=500.0,
        results_file='smart_hybrid_results.json'
    )

    # Optimiser
    optimizer.optimize(max_tests=300)

    # Afficher résultats
    optimizer.print_results()

    print()
    print(f"Terminé: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("💾 Résultats: smart_hybrid_results.json")
    print("=" * 80)


if __name__ == '__main__':
    main()
