#!/usr/bin/env python3
"""
Optimisation Stratégie Hybride pour BNB/USD
Grid search sur les 3 sous-stratégies (BULL/BEAR/RANGE) sur 10 jours de données BNB
"""

import pandas as pd
import numpy as np
import requests
import json
import itertools
from typing import Dict, List
from datetime import datetime

from hybrid_strategy import HybridStrategy


class BNBHybridOptimizer:
    """Optimiseur pour stratégie hybride sur BNB"""

    def __init__(self, df: pd.DataFrame, initial_capital: float = 500.0):
        self.df = df
        self.initial_capital = initial_capital

    def _calculate_pnl(self, position: Dict, current_price: float) -> float:
        """Calcule PnL% avec leverage"""
        entry = position['entry_price']
        leverage = position['leverage']

        if position['side'] == 'LONG':
            price_change_pct = ((current_price - entry) / entry) * 100
        else:  # SHORT
            price_change_pct = ((entry - current_price) / entry) * 100

        return price_change_pct * leverage

    def backtest(self, strategy: HybridStrategy) -> Dict:
        """Execute backtest sur la stratégie"""
        capital = self.initial_capital
        trades = []
        open_position = None

        # Parcourir données
        for i in range(100, len(self.df)):
            window = self.df.iloc[:i+1].copy()

            # 1. Gérer position ouverte
            if open_position:
                current_price = window['close'].iloc[-1]
                pnl_pct = self._calculate_pnl(open_position, current_price)

                # Check SL/TP
                hit_sl = (
                    (open_position['side'] == 'LONG' and current_price <= open_position['stop_loss']) or
                    (open_position['side'] == 'SHORT' and current_price >= open_position['stop_loss'])
                )

                hit_tp = (
                    (open_position['side'] == 'LONG' and current_price >= open_position['take_profit']) or
                    (open_position['side'] == 'SHORT' and current_price <= open_position['take_profit'])
                )

                if hit_sl or hit_tp:
                    # Fermer position
                    exit_reason = 'TP' if hit_tp else 'SL'
                    exit_price = open_position['take_profit'] if hit_tp else open_position['stop_loss']

                    # Position size: 20% du capital
                    position_size = capital * 0.20
                    pnl_usd = position_size * (pnl_pct / 100)
                    capital += pnl_usd

                    trades.append({
                        'pnl_pct': pnl_pct,
                        'pnl_usd': pnl_usd,
                        'exit_reason': exit_reason,
                        'strategy_used': open_position.get('strategy_used', 'UNKNOWN')
                    })

                    open_position = None

            # 2. Chercher nouveau signal
            if not open_position:
                signal = strategy.analyze('BNB', window)

                if signal:
                    open_position = {
                        'side': signal.side,
                        'entry_price': signal.entry_price,
                        'stop_loss': signal.stop_loss,
                        'take_profit': signal.take_profit,
                        'leverage': signal.leverage,
                        'strategy_used': signal.strategy_used
                    }

        # Résultats
        if len(trades) == 0:
            return {
                'total_trades': 0,
                'total_pnl_pct': 0,
                'profit_factor': 0,
                'win_rate': 0,
                'final_capital': self.initial_capital
            }

        winning_trades = [t for t in trades if t['pnl_pct'] > 0]
        losing_trades = [t for t in trades if t['pnl_pct'] <= 0]

        total_win = sum(t['pnl_usd'] for t in winning_trades) if winning_trades else 0
        total_loss = abs(sum(t['pnl_usd'] for t in losing_trades)) if losing_trades else 0

        profit_factor = (total_win / total_loss) if total_loss > 0 else (10.0 if total_win > 0 else 0)

        total_pnl = capital - self.initial_capital
        total_pnl_pct = (total_pnl / self.initial_capital) * 100

        # Count par stratégie
        bull_trades = len([t for t in trades if t['strategy_used'] == 'BULL'])
        bear_trades = len([t for t in trades if t['strategy_used'] == 'BEAR'])
        range_trades = len([t for t in trades if t['strategy_used'] == 'RANGE'])

        return {
            'total_trades': len(trades),
            'total_pnl_pct': total_pnl_pct,
            'profit_factor': profit_factor,
            'win_rate': (len(winning_trades) / len(trades)) * 100,
            'final_capital': capital,
            'bull_trades': bull_trades,
            'bear_trades': bear_trades,
            'range_trades': range_trades
        }

    def optimize(self, param_grid: Dict) -> List[Dict]:
        """
        Grid search sur les paramètres

        Args:
            param_grid: Dict avec listes de valeurs à tester

        Returns:
            Liste des résultats triés par PnL
        """
        results = []

        # Générer toutes les combinaisons
        param_names = list(param_grid.keys())
        param_values = list(param_grid.values())

        total_tests = 1
        for values in param_values:
            total_tests *= len(values)

        print(f"🔬 Grid search: {total_tests} combinaisons à tester")
        print(f"📊 Paramètres: {param_names}")
        print()

        test_count = 0

        for combination in itertools.product(*param_values):
            test_count += 1

            # Créer dict de params
            params = dict(zip(param_names, combination))

            # Créer stratégie avec ces params
            strategy = HybridStrategy(leverage=200)

            # Appliquer params RANGE
            if 'range.rsi_oversold' in params:
                strategy.range_strategy.params['rsi_oversold'] = params['range.rsi_oversold']
            if 'range.rsi_overbought' in params:
                strategy.range_strategy.params['rsi_overbought'] = params['range.rsi_overbought']
            if 'range.range_atr_threshold' in params:
                strategy.range_strategy.params['range_atr_threshold'] = params['range.range_atr_threshold']
            if 'range.leverage' in params:
                strategy.range_strategy.leverage = params['range.leverage']

            # Appliquer params BULL
            if 'bull.min_trend_strength' in params:
                strategy.bull_strategy.min_trend_strength = params['bull.min_trend_strength']
            if 'bull.sl_atr_multiplier' in params:
                strategy.bull_strategy.sl_atr_multiplier = params['bull.sl_atr_multiplier']
            if 'bull.tp_atr_multiplier' in params:
                strategy.bull_strategy.tp_atr_multiplier = params['bull.tp_atr_multiplier']
            if 'bull.trailing_pct' in params:
                strategy.bull_strategy.trailing_pct = params['bull.trailing_pct']
            if 'bull.leverage' in params:
                strategy.bull_strategy.leverage = params['bull.leverage']

            # Appliquer params BEAR
            if 'bear.min_trend_strength' in params:
                strategy.bear_strategy.min_trend_strength = params['bear.min_trend_strength']
            if 'bear.sl_atr_multiplier' in params:
                strategy.bear_strategy.sl_atr_multiplier = params['bear.sl_atr_multiplier']
            if 'bear.tp_atr_multiplier' in params:
                strategy.bear_strategy.tp_atr_multiplier = params['bear.tp_atr_multiplier']
            if 'bear.trailing_pct' in params:
                strategy.bear_strategy.trailing_pct = params['bear.trailing_pct']
            if 'bear.leverage' in params:
                strategy.bear_strategy.leverage = params['bear.leverage']

            # Backtest
            result = self.backtest(strategy)

            # Ajouter params au résultat
            result['params'] = params
            results.append(result)

            # Log progress
            if test_count % 10 == 0 or test_count == total_tests:
                profitable = [r for r in results if r['total_pnl_pct'] > 0 and r['profit_factor'] > 1.0]
                print(f"  [{test_count}/{total_tests}] Tests: {len(results)} | Profitables: {len(profitable)} ({len(profitable)/len(results)*100:.1f}%)")

                if len(profitable) > 0:
                    best = sorted(profitable, key=lambda x: x['total_pnl_pct'], reverse=True)[0]
                    print(f"    💰 Meilleur: PnL {best['total_pnl_pct']:+.1f}% | PF {best['profit_factor']:.2f} | WR {best['win_rate']:.0f}% | Trades {best['total_trades']} (BULL:{best['bull_trades']}, BEAR:{best['bear_trades']}, RANGE:{best['range_trades']})")

        # Trier par PnL décroissant
        results.sort(key=lambda x: x['total_pnl_pct'], reverse=True)

        return results


def get_bnb_historical_data(days: int = 10, interval: str = '5m') -> pd.DataFrame:
    """Récupère données historiques BNB/USDT"""
    print(f"📊 Récupération données BNB ({days} jours, {interval})...")

    # Calcul nombre de bougies
    if interval == '1m':
        candles_per_day = 1440
    elif interval == '5m':
        candles_per_day = 288
    elif interval == '15m':
        candles_per_day = 96
    else:
        candles_per_day = 288

    limit = min(1000, days * candles_per_day)

    url = "https://api.binance.com/api/v3/klines"
    params = {
        'symbol': 'BNBUSDT',
        'interval': interval,
        'limit': limit
    }

    response = requests.get(url, params=params, timeout=10)
    response.raise_for_status()

    data = response.json()

    df = pd.DataFrame(data, columns=[
        'timestamp', 'open', 'high', 'low', 'close', 'volume',
        'close_time', 'quote_volume', 'trades', 'taker_buy_base',
        'taker_buy_quote', 'ignore'
    ])

    # Convertir types
    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
    for col in ['open', 'high', 'low', 'close', 'volume']:
        df[col] = df[col].astype(float)

    print(f"   ✅ {len(df)} bougies chargées")
    print(f"   📅 {df['timestamp'].iloc[0]} → {df['timestamp'].iloc[-1]}")
    print(f"   💰 Prix: ${df['close'].iloc[0]:.2f} → ${df['close'].iloc[-1]:.2f}")

    # Stats de mouvement
    price_change = ((df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0]) * 100
    print(f"   📈 Variation: {price_change:+.2f}%")

    return df


def main():
    print("=" * 80)
    print("🎯 OPTIMISATION STRATÉGIE HYBRIDE POUR BNB")
    print("=" * 80)
    print()

    # Charger données BNB 10 jours (5min = ~2880 bougies, limité à 1000 par Binance)
    df = get_bnb_historical_data(days=10, interval='5m')

    print()
    print("=" * 80)
    print("🔬 CONFIGURATION GRID SEARCH")
    print("=" * 80)

    # Définir grid de paramètres à optimiser
    # Grid réduit mais efficace (~2000 combinaisons, ~15min)
    param_grid = {
        # RANGE Strategy (V10) - 2x2x2x2 = 16 combinaisons
        'range.rsi_oversold': [30, 40],
        'range.rsi_overbought': [70, 80],
        'range.range_atr_threshold': [0.30, 0.40],
        'range.leverage': [200, 300],

        # BULL Strategy - 2x2x2x2x2 = 32 combinaisons
        'bull.min_trend_strength': [55, 65],
        'bull.sl_atr_multiplier': [1.5, 2.0],
        'bull.tp_atr_multiplier': [2.0, 3.0],
        'bull.trailing_pct': [0.10, 0.20],
        'bull.leverage': [150, 200],

        # BEAR Strategy - 2x2x2x2x2 = 32 combinaisons
        'bear.min_trend_strength': [55, 65],
        'bear.sl_atr_multiplier': [1.5, 2.0],
        'bear.tp_atr_multiplier': [2.5, 3.5],
        'bear.trailing_pct': [0.08, 0.12],
        'bear.leverage': [150, 200],
    }
    # Total: 16 * 32 * 32 = 16,384 combinaisons (~15-20 minutes)

    # Calculer nombre total de tests
    total_combinations = 1
    for param, values in param_grid.items():
        total_combinations *= len(values)
        print(f"  {param}: {len(values)} valeurs")

    print(f"\n🔢 Total combinaisons: {total_combinations:,}")
    print(f"⏱️  Temps estimé: ~{total_combinations * 0.5 / 60:.0f} minutes")
    print()

    # Optimiser
    optimizer = BNBHybridOptimizer(df, initial_capital=500.0)

    print("=" * 80)
    print("🚀 DÉBUT OPTIMISATION")
    print("=" * 80)
    print()

    results = optimizer.optimize(param_grid)

    # Filtrer résultats profitables
    profitable = [r for r in results if r['total_pnl_pct'] > 0 and r['profit_factor'] > 1.0 and r['total_trades'] >= 3]

    print()
    print("=" * 80)
    print("📊 RÉSULTATS OPTIMISATION")
    print("=" * 80)
    print(f"\nTotal tests: {len(results)}")
    print(f"Profitables (PnL>0, PF>1, Trades≥3): {len(profitable)} ({len(profitable)/len(results)*100:.1f}%)")

    if len(profitable) > 0:
        print("\n🏆 TOP 5 CONFIGURATIONS:")
        print("-" * 80)

        for i, result in enumerate(profitable[:5], 1):
            print(f"\n#{i} - PnL: {result['total_pnl_pct']:+.2f}% | PF: {result['profit_factor']:.2f} | WR: {result['win_rate']:.0f}%")
            print(f"    Trades: {result['total_trades']} (BULL:{result['bull_trades']}, BEAR:{result['bear_trades']}, RANGE:{result['range_trades']})")
            print(f"    Capital: ${result['initial_capital']:.0f} → ${result['final_capital']:.0f}")

            # Afficher params clés
            params = result['params']
            print(f"    RANGE: RSI {params['range.rsi_oversold']}/{params['range.rsi_overbought']}, ATR {params['range.range_atr_threshold']}, Lev {params['range.leverage']}x")
            print(f"    BULL: Trend {params['bull.min_trend_strength']}, SL {params['bull.sl_atr_multiplier']}x, TP {params['bull.tp_atr_multiplier']}x, Trail {params['bull.trailing_pct']:.0%}, Lev {params['bull.leverage']}x")
            print(f"    BEAR: Trend {params['bear.min_trend_strength']}, SL {params['bear.sl_atr_multiplier']}x, TP {params['bear.tp_atr_multiplier']}x, Trail {params['bear.trailing_pct']:.0%}, Lev {params['bear.leverage']}x")
    else:
        print("\n⚠️  Aucune configuration profitable trouvée avec critères (PnL>0, PF>1, Trades≥3)")
        print("    Affichage des meilleures configurations disponibles:")

        for i, result in enumerate(results[:5], 1):
            print(f"\n#{i} - PnL: {result['total_pnl_pct']:+.2f}% | Trades: {result['total_trades']}")

    # Sauvegarder résultats
    output_file = 'bnb_hybrid_optimization_results.json'
    with open(output_file, 'w') as f:
        json.dump({
            'optimization_date': datetime.now().isoformat(),
            'asset': 'BNBUSDT',
            'period': f"{df['timestamp'].iloc[0]} to {df['timestamp'].iloc[-1]}",
            'total_tests': len(results),
            'profitable_configs': len(profitable),
            'top_results': profitable[:10] if profitable else results[:10],
            'all_results': results
        }, f, indent=2, default=str)

    print(f"\n💾 Résultats complets sauvegardés: {output_file}")

    # Créer fichier params optimaux
    if len(profitable) > 0:
        best = profitable[0]
        optimal_params = {
            'version': '1.0_BNB',
            'optimization_date': datetime.now().isoformat(),
            'optimization_period': f"{df['timestamp'].iloc[0]} to {df['timestamp'].iloc[-1]}",
            'performance': {
                'total_pnl_pct': best['total_pnl_pct'],
                'profit_factor': best['profit_factor'],
                'win_rate': best['win_rate'],
                'total_trades': best['total_trades']
            },
            'range_strategy': {
                'params': {
                    'rsi_oversold': best['params']['range.rsi_oversold'],
                    'rsi_overbought': best['params']['range.rsi_overbought'],
                    'range_atr_threshold': best['params']['range.range_atr_threshold'],
                    'leverage': best['params']['range.leverage']
                }
            },
            'bull_strategy': {
                'params': {
                    'min_trend_strength': best['params']['bull.min_trend_strength'],
                    'sl_atr_multiplier': best['params']['bull.sl_atr_multiplier'],
                    'tp_atr_multiplier': best['params']['bull.tp_atr_multiplier'],
                    'trailing_pct': best['params']['bull.trailing_pct'],
                    'leverage': best['params']['bull.leverage']
                }
            },
            'bear_strategy': {
                'params': {
                    'min_trend_strength': best['params']['bear.min_trend_strength'],
                    'sl_atr_multiplier': best['params']['bear.sl_atr_multiplier'],
                    'tp_atr_multiplier': best['params']['bear.tp_atr_multiplier'],
                    'trailing_pct': best['params']['bear.trailing_pct'],
                    'leverage': best['params']['bear.leverage']
                }
            }
        }

        optimal_file = 'optimal_bnb_hybrid_params.json'
        with open(optimal_file, 'w') as f:
            json.dump(optimal_params, f, indent=2)

        print(f"💾 Paramètres optimaux BNB sauvegardés: {optimal_file}")

    print()
    print("=" * 80)
    print("✅ OPTIMISATION TERMINÉE")
    print("=" * 80)


if __name__ == '__main__':
    main()
