#!/usr/bin/env python3
"""
OPTIMIZER BULL/BEAR - Optimise seulement BULL et BEAR

V10 Range est DÉJÀ OPTIMISÉE (+132% PnL)
On garde ses params FIXES et on optimise seulement BULL/BEAR

Cohérence:
- Détection RANGE: V10RangeDetector (même méthode partout)
- Params V10 Range: FIXES (optimaux trouvés)
- Params BULL/BEAR: À OPTIMISER
"""

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

from hybrid_strategy import HybridStrategy
from backtest_hybrid import HybridBacktest, get_btc_historical_data


# PARAMS V10 RANGE OPTIMAUX (FIXES - NE PAS TOUCHER)
V10_OPTIMAL_PARAMS = {
    'rsi_oversold': 35,
    'rsi_overbought': 78,
    'range_atr_threshold': 0.32,
    'min_confidence': 60,
    'sl_atr_multiplier': 1.64,
    'trailing_pct': 0.05,
    'leverage': 250
}


class BullBearOptimizer:
    """Optimise seulement BULL et BEAR, V10 Range fixe"""

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

        self._load_results()

    def _load_results(self):
        if Path(self.results_file).exists():
            with open(self.results_file, 'r') as f:
                self.results = json.load(f)
            for r in self.results:
                h = self._hash_config(r['config'])
                self.tested_hashes.add(h)
            print(f"📂 Chargé {len(self.results)} résultats")

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

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

    def _generate_random_config(self) -> dict:
        """Génère config aléatoire pour BULL et BEAR uniquement"""
        return {
            # Bull
            '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
            '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]),
        }

    def _mutate_config(self, base: dict) -> dict:
        """Mute config"""
        config = base.copy()
        rate = 0.3

        # Mutations BULL
        if random.random() < rate:
            config['bull.min_trend_strength'] = max(50, min(80,
                config['bull.min_trend_strength'] + random.randint(-10, 10)))
        if random.random() < 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() < rate:
            config['bull.leverage'] = random.choice([50, 75, 100, 125, 150, 175, 200])

        # Mutations BEAR
        if random.random() < rate:
            config['bear.min_trend_strength'] = max(50, min(80,
                config['bear.min_trend_strength'] + random.randint(-10, 10)))
        if random.random() < 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() < rate:
            config['bear.leverage'] = random.choice([50, 75, 100, 125, 150, 175, 200])

        return config

    def _test_config(self, config: dict) -> dict:
        """Teste config avec V10 Range fixe"""
        h = self._hash_config(config)
        if h in self.tested_hashes:
            return None
        self.tested_hashes.add(h)

        # Créer stratégie avec V10 Range FIXE
        strategy = HybridStrategy(leverage=100)

        # FIXER V10 Range avec params optimaux
        strategy.range_strategy.params['rsi_oversold'] = V10_OPTIMAL_PARAMS['rsi_oversold']
        strategy.range_strategy.params['rsi_overbought'] = V10_OPTIMAL_PARAMS['rsi_overbought']
        strategy.range_strategy.params['range_atr_threshold'] = V10_OPTIMAL_PARAMS['range_atr_threshold']
        strategy.range_strategy.params['min_confidence'] = V10_OPTIMAL_PARAMS['min_confidence']
        strategy.range_strategy.params['sl_atr_multiplier'] = V10_OPTIMAL_PARAMS['sl_atr_multiplier']
        strategy.range_strategy.params['trailing_pct'] = V10_OPTIMAL_PARAMS['trailing_pct']
        strategy.range_strategy.leverage = V10_OPTIMAL_PARAMS['leverage']

        # 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']

        # Backtest
        try:
            backtest = HybridBacktest(self.df, initial_capital=500.0)
            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'],
                'trades_by_strategy': result['trades_by_strategy'],
                'timestamp': datetime.now().isoformat()
            }
        except:
            return None

    def optimize(self, max_tests: int = 200):
        """Optimise BULL/BEAR avec V10 Range fixe"""
        print(f"🎯 Optimisation BULL/BEAR (V10 Range FIXE)")
        print(f"Objectif: {max_tests} tests")
        print()

        for i in range(max_tests - len(self.results)):
            start = time.time()

            # 50% exploration / 50% exploitation
            if random.random() < 0.5 or len(self.results) < 20:
                config = self._generate_random_config()
                mode = "EXPLORE"
            else:
                profitable = [r for r in self.results if r['pnl_pct'] > 0 and r['profit_factor'] > 1.0]
                if profitable:
                    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"
                else:
                    config = self._generate_random_config()
                    mode = "EXPLORE"

            result = self._test_config(config)
            elapsed = time.time() - start

            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"Trades {result['trades']} | "
                      f"{result['trades_by_strategy']} | "
                      f"{elapsed:.1f}s")

                if len(self.results) % 5 == 0:
                    self._save_results()
            else:
                print(f"[{len(self.results)}/{max_tests}] {mode} | Skip | {elapsed:.1f}s")

        self._save_results()
        self._print_results()

    def _print_results(self):
        print()
        print("=" * 80)
        print("📊 RÉSULTATS")
        print("=" * 80)

        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: {len(self.results)} | Profitables: {len(profitable)}")

        if profitable:
            profitable.sort(key=lambda x: x['pnl_pct'], reverse=True)
            print("\n🏆 TOP 3:")
            for i, r in enumerate(profitable[:3], 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"   Répartition: {r['trades_by_strategy']}")


def main():
    print("=" * 80)
    print("🚀 OPTIMISATION BULL/BEAR (V10 Range fixe)")
    print("=" * 80)
    print()
    print("V10 Range params (FIXES):")
    for k, v in V10_OPTIMAL_PARAMS.items():
        print(f"  {k}: {v}")
    print()

    # Charger candles 14 jours (5min) depuis le CSV
    print("📥 Chargement candles BTC 14 jours (5min)...")
    df = pd.read_csv('btc_14days.csv')
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    print(f"   {len(df)} candles chargées")
    print(f"   Période: {df.iloc[0]['timestamp']} → {df.iloc[-1]['timestamp']}")
    print()

    optimizer = BullBearOptimizer(df, results_file='bull_bear_14d_results.json')
    optimizer.optimize(max_tests=200)

    print()
    print("💾 Résultats: bull_bear_results.json")
    print("=" * 80)


if __name__ == '__main__':
    main()
