#!/usr/bin/env python3
"""
Smart Optimizer pour Stratégie de Scalping Probabiliste
Backtest et optimisation continue sur BTC 1m
"""

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

from probabilistic_scalping_strategy import ProbabilisticScalpingStrategy


class SmartScalpingOptimizer:
    """
    Optimiseur intelligent pour scalping probabiliste
    Teste en continu différentes configurations et garde les meilleures
    """

    def __init__(self, initial_capital: float = 100.0):
        self.initial_capital = initial_capital
        self.results_history = []

    def backtest(
        self,
        df: pd.DataFrame,
        strategy: ProbabilisticScalpingStrategy
    ) -> Dict:
        """Execute backtest avec une configuration de stratégie"""
        capital = self.initial_capital
        trades = []
        open_position = None

        # Parcourir données
        for i in range(150, len(df)):  # Besoin de 150 bougies minimum
            window = df.iloc[:i+1].copy()

            # Gérer position ouverte
            if open_position:
                current_price = window['close'].iloc[-1]

                # Calculer PnL
                if open_position['side'] == 'LONG':
                    price_change_pct = ((current_price - open_position['entry_price']) / open_position['entry_price']) * 100
                else:  # SHORT
                    price_change_pct = ((open_position['entry_price'] - current_price) / open_position['entry_price']) * 100

                pnl_pct = price_change_pct * open_position['leverage']

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

                    # PnL en USD
                    pnl_usd = capital * (pnl_pct / 100)
                    capital += pnl_usd

                    trades.append({
                        'entry_time': open_position['entry_time'],
                        'exit_time': window['timestamp'].iloc[-1] if 'timestamp' in window.columns else i,
                        'side': open_position['side'],
                        'entry_price': open_position['entry_price'],
                        'exit_price': exit_price,
                        'leverage': open_position['leverage'],
                        'pnl_pct': pnl_pct,
                        'pnl_usd': pnl_usd,
                        'exit_reason': exit_reason,
                        'probability': open_position['probability'],
                        'expected_value': open_position['expected_value']
                    })

                    open_position = None

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

                if signal:
                    open_position = {
                        'entry_time': window['timestamp'].iloc[-1] if 'timestamp' in window.columns else i,
                        'side': signal.side,
                        'entry_price': signal.entry_price,
                        'stop_loss': signal.stop_loss,
                        'take_profit': signal.take_profit,
                        'leverage': signal.leverage,
                        'probability': signal.probability,
                        'expected_value': signal.expected_value
                    }

        # Calculer résultats
        if len(trades) == 0:
            return self._empty_results()

        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

        # Statistiques avancées
        avg_probability = np.mean([t['probability'] for t in trades])
        avg_ev = np.mean([t['expected_value'] for t in trades])

        # Trades par heure (approximation)
        if len(trades) > 0:
            trades_per_hour = len(trades) / (len(df) / 60)  # 1min candles
        else:
            trades_per_hour = 0

        return {
            'total_trades': len(trades),
            'winning_trades': len(winning_trades),
            'losing_trades': len(losing_trades),
            'win_rate': (len(winning_trades) / len(trades)) * 100 if trades else 0,
            'profit_factor': profit_factor,
            'total_pnl_usd': total_pnl,
            'total_pnl_pct': total_pnl_pct,
            'final_capital': capital,
            'avg_win': np.mean([t['pnl_pct'] for t in winning_trades]) if winning_trades else 0,
            'avg_loss': np.mean([t['pnl_pct'] for t in losing_trades]) if losing_trades else 0,
            'avg_probability': avg_probability,
            'avg_expected_value': avg_ev,
            'trades_per_hour': trades_per_hour,
            'trades': trades
        }

    def _empty_results(self) -> Dict:
        """Résultats vides si aucun trade"""
        return {
            'total_trades': 0,
            'winning_trades': 0,
            'losing_trades': 0,
            'win_rate': 0,
            'profit_factor': 0,
            'total_pnl_usd': 0,
            'total_pnl_pct': 0,
            'final_capital': self.initial_capital,
            'avg_win': 0,
            'avg_loss': 0,
            'avg_probability': 0,
            'avg_expected_value': 0,
            'trades_per_hour': 0,
            'trades': []
        }

    def optimize_grid_search(
        self,
        df: pd.DataFrame,
        param_grid: Dict
    ) -> List[Dict]:
        """
        Grid search intelligent sur les paramètres

        Args:
            df: DataFrame avec données BTC 1m
            param_grid: Grille de paramètres à tester

        Returns:
            Liste des résultats triés par performance
        """
        results = []
        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"🔬 Smart Optimizer - Grid Search")
        print(f"   Tests à effectuer: {total_tests}")
        print(f"   Paramètres: {param_names}")
        print()

        test_count = 0
        start_time = time.time()

        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 = ProbabilisticScalpingStrategy(
                leverage=params['leverage'],
                sl_pct=params['sl_pct'],
                tp_pct=params['tp_pct'],
                lookback_window=params['lookback_window'],
                z_threshold=params['z_threshold'],
                min_probability=params['min_probability'],
                min_ev=params['min_ev']
            )

            # Backtest
            result = self.backtest(df, strategy)
            result['params'] = params

            results.append(result)
            self.results_history.append({
                'timestamp': datetime.now().isoformat(),
                'test_number': test_count,
                'params': params,
                'results': result
            })

            # Log progress
            if test_count % 10 == 0 or test_count == total_tests:
                elapsed = time.time() - start_time
                rate = test_count / elapsed
                eta = (total_tests - test_count) / rate if rate > 0 else 0

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

                print(f"  [{test_count}/{total_tests}] "
                      f"Profitables: {len(profitable)}/{len(results)} ({len(profitable)/len(results)*100:.1f}%) | "
                      f"ETA: {eta/60:.1f}min")

                if len(profitable) > 0:
                    # Top 3
                    top = sorted(profitable, key=lambda x: x['total_pnl_pct'], reverse=True)[:3]
                    print(f"    💰 TOP 3:")
                    for i, r in enumerate(top, 1):
                        print(f"       #{i} PnL: {r['total_pnl_pct']:+.1f}% | "
                              f"WR: {r['win_rate']:.0f}% | "
                              f"PF: {r['profit_factor']:.2f} | "
                              f"Trades: {r['total_trades']} | "
                              f"Lev: {r['params']['leverage']}x")

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

        return results


def get_btc_1m_data(days: int = 10) -> pd.DataFrame:
    """Récupère données BTC 1m (multi-requêtes)"""
    print(f"📊 Récupération données BTC 1m ({days} jours)...")

    total_candles = days * 1440
    num_requests = (total_candles // 1000) + 1

    print(f"   → {total_candles} bougies nécessaires ({num_requests} requêtes)")

    all_data = []
    end_time = None

    for i in range(num_requests):
        url = "https://api.binance.com/api/v3/klines"
        params = {
            'symbol': 'BTCUSDT',
            'interval': '1m',
            'limit': 1000
        }

        if end_time:
            params['endTime'] = end_time

        try:
            response = requests.get(url, params=params, timeout=10)
            response.raise_for_status()
            data = response.json()

            if not data:
                break

            all_data = data + all_data
            end_time = data[0][0] - 1

            if (i + 1) % 5 == 0:
                print(f"   → Requête {i+1}/{num_requests}")

            if len(all_data) >= total_candles:
                break

        except Exception as e:
            print(f"   ⚠️  Erreur requête {i+1}: {e}")
            break

    all_data = all_data[-total_candles:]

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

    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}")

    return df


def main():
    print("=" * 80)
    print("🎯 SMART OPTIMIZER - SCALPING PROBABILISTE BTC 1M")
    print("=" * 80)
    print()

    # Charger données BTC 1m
    df = get_btc_1m_data(days=10)

    print()
    print("=" * 80)
    print("🔬 OPTIMISATION INTELLIGENTE")
    print("=" * 80)
    print()

    # Grille de paramètres optimisée (pas trop large)
    param_grid = {
        'leverage': [500, 750, 1000],
        'sl_pct': [0.02, 0.03, 0.04],
        'tp_pct': [0.08, 0.10, 0.12],
        'lookback_window': [80, 100, 120],
        'z_threshold': [1.8, 2.0, 2.2],
        'min_probability': [0.62, 0.65, 0.68],
        'min_ev': [0.25, 0.30, 0.35]
    }
    # Total: 3^7 = 2,187 combinaisons (~20-30min)

    print(f"📊 Grille de paramètres:")
    for param, values in param_grid.items():
        print(f"   {param}: {values}")
    print()

    # Optimiser
    optimizer = SmartScalpingOptimizer(initial_capital=100.0)
    results = optimizer.optimize_grid_search(df, param_grid)

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

    print()
    print("=" * 80)
    print("📊 RÉSULTATS OPTIMISATION")
    print("=" * 80)
    print(f"\nTotal tests: {len(results)}")
    print(f"Profitables: {len(profitable)} ({len(profitable)/len(results)*100:.1f}%)")

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

        for i, r in enumerate(profitable[:10], 1):
            p = r['params']
            print(f"\n#{i}")
            print(f"  PnL: {r['total_pnl_pct']:+.2f}% (${r['total_pnl_usd']:+.2f})")
            print(f"  Win Rate: {r['win_rate']:.1f}% | PF: {r['profit_factor']:.2f}")
            print(f"  Trades: {r['total_trades']} ({r['trades_per_hour']:.1f}/h)")
            print(f"  Avg Win: {r['avg_win']:.1f}% | Avg Loss: {r['avg_loss']:.1f}%")
            print(f"  Avg Probability: {r['avg_probability']*100:.1f}%")
            print(f"  Avg EV: {r['avg_expected_value']:.1f}%")
            print(f"  Params: Lev={p['leverage']}x SL={p['sl_pct']}% TP={p['tp_pct']}% "
                  f"Z={p['z_threshold']} P>={p['min_probability']} EV>={p['min_ev']}")

    # Sauvegarder résultats
    output = {
        'optimization_date': datetime.now().isoformat(),
        'data_period': f"{df['timestamp'].iloc[0]} to {df['timestamp'].iloc[-1]}",
        'total_tests': len(results),
        'profitable_count': len(profitable),
        'top_results': profitable[:20] if profitable else results[:20],
        'all_results': results
    }

    with open('scalping_probabilistic_optimization.json', 'w') as f:
        json.dump(output, f, indent=2, default=str)

    print(f"\n💾 Résultats sauvegardés: scalping_probabilistic_optimization.json")

    # Sauvegarder meilleurs params
    if profitable:
        best = profitable[0]
        optimal_params = {
            'version': '1.0_probabilistic_scalping',
            'optimization_date': datetime.now().isoformat(),
            'performance': {
                'total_pnl_pct': best['total_pnl_pct'],
                'win_rate': best['win_rate'],
                'profit_factor': best['profit_factor'],
                'total_trades': best['total_trades'],
                'trades_per_hour': best['trades_per_hour']
            },
            'params': best['params']
        }

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

        print(f"💾 Meilleurs params: optimal_probabilistic_scalping_params.json")

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


if __name__ == '__main__':
    main()
