#!/usr/bin/env python3
"""
OPTIMISEUR RAPIDE - Scalping Probabiliste BTC 1m
Version optimisée pour tourner en 7h (pour résultats demain matin)

Optimisations:
- Pré-calcul des statistiques rolling
- Opérations vectorisées NumPy
- Pas de copies inutiles de dataframes
- Skip analyse si déjà en position
- Logs de progression réguliers
"""

import pandas as pd
import numpy as np
import requests
import json
import time
from typing import Dict, List, Optional
from datetime import datetime
from scipy import stats as scipy_stats
import itertools


class FastProbabilisticStrategy:
    """Version ULTRA RAPIDE de la stratégie probabiliste"""

    def __init__(
        self,
        leverage: int = 1000,
        sl_pct: float = 0.03,
        tp_pct: float = 0.10,
        lookback_window: int = 100,
        z_threshold: float = 2.0,
        min_probability: float = 0.65,
        min_ev: float = 0.30
    ):
        self.leverage = leverage
        self.sl_pct = sl_pct / 100
        self.tp_pct = tp_pct / 100
        self.lookback_window = lookback_window
        self.z_threshold = z_threshold
        self.min_probability = min_probability
        self.min_ev = min_ev

    def precompute_statistics(self, close_prices: np.ndarray) -> Dict[str, np.ndarray]:
        """
        Pré-calcule TOUTES les statistiques rolling en UNE FOIS
        Au lieu de recalculer à chaque bougie
        """
        n = len(close_prices)
        window = self.lookback_window

        # Rolling mean et std (vectorisé)
        rolling_mean = pd.Series(close_prices).rolling(window).mean().values
        rolling_std = pd.Series(close_prices).rolling(window).std().values

        # Z-scores pré-calculés
        z_scores = np.zeros(n)
        for i in range(window, n):
            if rolling_std[i] > 0:
                z_scores[i] = (close_prices[i] - rolling_mean[i]) / rolling_std[i]

        # Coefficient de variation
        cv = np.zeros(n)
        cv[window:] = rolling_std[window:] / rolling_mean[window:]

        return {
            'mean': rolling_mean,
            'std': rolling_std,
            'z_score': z_scores,
            'cv': cv
        }

    def generate_signals(
        self,
        close_prices: np.ndarray,
        precomputed: Dict[str, np.ndarray]
    ) -> List[Dict]:
        """
        Génère TOUS les signaux en une passe
        Retourne liste de signaux avec leur index
        """
        signals = []
        z_scores = precomputed['z_score']
        std = precomputed['std']
        cv = precomputed['cv']

        for i in range(self.lookback_window + 20, len(close_prices)):
            z = z_scores[i]

            # Skip si pas assez extrême
            if abs(z) < self.z_threshold:
                continue

            # LONG signal
            if z < -self.z_threshold:
                probability = scipy_stats.norm.cdf(abs(z))

                # Validation rapide
                if probability < self.min_probability:
                    continue

                # Expected Value
                win_amount = self.tp_pct * self.leverage
                loss_amount = self.sl_pct * self.leverage
                ev = (probability * win_amount) - ((1 - probability) * loss_amount)

                if ev < self.min_ev:
                    continue

                # Confidence simple
                confidence = 50
                if abs(z) > 2.5:
                    confidence += 20
                if probability > 0.70:
                    confidence += 20
                if 0.01 < cv[i] < 0.05:
                    confidence += 10

                if confidence >= 50:
                    signals.append({
                        'index': i,
                        'side': 'LONG',
                        'price': close_prices[i],
                        'sl': close_prices[i] * (1 - self.sl_pct),
                        'tp': close_prices[i] * (1 + self.tp_pct),
                        'leverage': self.leverage,
                        'probability': probability,
                        'ev': ev,
                        'z_score': z
                    })

            # SHORT signal
            elif z > self.z_threshold:
                probability = scipy_stats.norm.cdf(z)

                if probability < self.min_probability:
                    continue

                win_amount = self.tp_pct * self.leverage
                loss_amount = self.sl_pct * self.leverage
                ev = (probability * win_amount) - ((1 - probability) * loss_amount)

                if ev < self.min_ev:
                    continue

                confidence = 50
                if abs(z) > 2.5:
                    confidence += 20
                if probability > 0.70:
                    confidence += 20
                if 0.01 < cv[i] < 0.05:
                    confidence += 10

                if confidence >= 50:
                    signals.append({
                        'index': i,
                        'side': 'SHORT',
                        'price': close_prices[i],
                        'sl': close_prices[i] * (1 + self.sl_pct),
                        'tp': close_prices[i] * (1 - self.tp_pct),
                        'leverage': self.leverage,
                        'probability': probability,
                        'ev': ev,
                        'z_score': z
                    })

        return signals


class FastOptimizer:
    """Optimiseur ULTRA RAPIDE"""

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

    def backtest_fast(
        self,
        close_prices: np.ndarray,
        high_prices: np.ndarray,
        low_prices: np.ndarray,
        strategy: FastProbabilisticStrategy
    ) -> Dict:
        """
        Backtest ULTRA RAPIDE
        Utilise arrays NumPy au lieu de DataFrames
        """
        # Pré-calculer statistiques UNE FOIS
        precomputed = strategy.precompute_statistics(close_prices)

        # Générer TOUS les signaux d'un coup
        signals = strategy.generate_signals(close_prices, precomputed)

        # Si aucun signal trouvé
        if len(signals) == 0:
            return self._empty_results(reason="NO_SIGNALS")

        # Simuler trading avec les signaux
        capital = self.initial_capital
        trades = []
        open_position = None
        signal_idx = 0

        for i in range(len(close_prices)):
            # Gérer position ouverte
            if open_position:
                current_price = close_prices[i]
                high = high_prices[i]
                low = low_prices[i]

                # Check SL/TP avec high/low
                if open_position['side'] == 'LONG':
                    hit_sl = low <= open_position['sl']
                    hit_tp = high >= open_position['tp']
                else:  # SHORT
                    hit_sl = high >= open_position['sl']
                    hit_tp = low <= open_position['tp']

                if hit_sl or hit_tp:
                    # Fermer position
                    exit_price = open_position['tp'] if hit_tp else open_position['sl']

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

                    pnl_pct = price_change_pct * open_position['leverage']
                    pnl_usd = capital * (pnl_pct / 100)
                    capital += pnl_usd

                    trades.append({
                        'pnl_pct': pnl_pct,
                        'pnl_usd': pnl_usd,
                        'exit_reason': 'TP' if hit_tp else 'SL',
                        'probability': open_position['probability'],
                        'ev': open_position['ev']
                    })

                    open_position = None

            # Chercher nouveau signal
            if not open_position and signal_idx < len(signals):
                signal = signals[signal_idx]
                if signal['index'] == i:
                    open_position = {
                        'side': signal['side'],
                        'entry': signal['price'],
                        'sl': signal['sl'],
                        'tp': signal['tp'],
                        'leverage': signal['leverage'],
                        'probability': signal['probability'],
                        'ev': signal['ev']
                    }
                    signal_idx += 1

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

        winning = [t for t in trades if t['pnl_pct'] > 0]
        losing = [t for t in trades if t['pnl_pct'] <= 0]

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

        pf = (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

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

    def _empty_results(self, reason: str = "UNKNOWN") -> Dict:
        """Résultats vides avec raison"""
        return {
            'total_trades': 0,
            'winning_trades': 0,
            'losing_trades': 0,
            'win_rate': 0,
            'profit_factor': 0,
            'total_pnl_pct': 0,
            'total_pnl_usd': 0,
            'final_capital': self.initial_capital,
            'avg_win': 0,
            'avg_loss': 0,
            'num_signals': 0,
            'empty_reason': reason,
            'trades': []
        }

    def optimize_grid(
        self,
        close_prices: np.ndarray,
        high_prices: np.ndarray,
        low_prices: np.ndarray,
        param_grid: Dict,
        max_duration_hours: float = 7.0
    ) -> List[Dict]:
        """
        Grid search avec limite de temps (7h)
        """
        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"\n🔬 OPTIMISATION RAPIDE - 7H MAX")
        print(f"   Tests prévus: {total_tests}")
        print(f"   Durée max: {max_duration_hours}h")
        print(f"   Paramètres: {param_names}")
        print()

        start_time = time.time()
        max_duration = max_duration_hours * 3600
        test_count = 0

        for combination in itertools.product(*param_values):
            # Check temps écoulé
            elapsed = time.time() - start_time
            if elapsed >= max_duration:
                print(f"\n⏰ TEMPS LIMITE ATTEINT ({elapsed/3600:.1f}h)")
                print(f"   Tests effectués: {test_count}/{total_tests}")
                break

            test_count += 1

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

            # Créer stratégie
            strategy = FastProbabilisticStrategy(
                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_fast(close_prices, high_prices, low_prices, strategy)
            result['params'] = params
            results.append(result)

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

                # Statistiques
                profitable = [r for r in results if r['total_pnl_pct'] > 0 and r['profit_factor'] > 1.0]

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

                # Top 3 si profitables trouvés
                if len(profitable) >= 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"PF: {r['profit_factor']:.2f} | "
                              f"Trades: {r['total_trades']} | "
                              f"Lev: {r['params']['leverage']}x")

        # AUTO-ANALYSE si pas assez de signaux
        no_signals_count = sum(1 for r in results if r.get('empty_reason') == 'NO_SIGNALS')
        if no_signals_count > len(results) * 0.5:
            print(f"\n⚠️  AUTO-ANALYSE: {no_signals_count}/{len(results)} tests sans signaux")
            print(f"   → Paramètres probablement trop stricts (z_threshold, min_probability, min_ev)")
            print(f"   → Suggestion: Réduire z_threshold ou min_probability")

        # 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"""
    print(f"📊 Récupération BTC 1m ({days} jours)...")

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

    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 len(all_data) >= total_candles:
                break

        except Exception as e:
            print(f"   ⚠️  Erreur: {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")
    print(f"   📅 {df['timestamp'].iloc[0]} → {df['timestamp'].iloc[-1]}")
    print(f"   💰 ${df['close'].iloc[0]:.2f} → ${df['close'].iloc[-1]:.2f}")

    return df


def main():
    print("=" * 80)
    print("🚀 OPTIMISEUR RAPIDE - SCALPING PROBABILISTE BTC 1M")
    print("=" * 80)
    print("\n🎯 OBJECTIF: Résultats en 7h max (pour demain matin)")
    print()

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

    # Convertir en arrays NumPy (RAPIDE)
    close_prices = df['close'].values
    high_prices = df['high'].values
    low_prices = df['low'].values

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

    # Grille de paramètres (raisonnable pour 7h)
    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.6, 1.8, 2.0, 2.2],
        'min_probability': [0.60, 0.65, 0.70],
        'min_ev': [0.20, 0.30, 0.40]
    }
    # Total: 3×3×3×3×4×3×3 = 2,916 tests

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

    # Optimiser (7h max)
    optimizer = FastOptimizer(initial_capital=100.0)
    results = optimizer.optimize_grid(
        close_prices,
        high_prices,
        low_prices,
        param_grid,
        max_duration_hours=7.0
    )

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

    print()
    print("=" * 80)
    print("📊 RÉSULTATS FINAUX")
    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:")
        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']} | Signals: {r['num_signals']}")
            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
    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('fast_probabilistic_results.json', 'w') as f:
        json.dump(output, f, indent=2, default=str)

    print(f"\n💾 Résultats: fast_probabilistic_results.json")

    if profitable:
        best = profitable[0]
        with open('best_probabilistic_params.json', 'w') as f:
            json.dump({
                'version': 'fast_probabilistic_v1',
                'date': datetime.now().isoformat(),
                'performance': {
                    'pnl_pct': best['total_pnl_pct'],
                    'win_rate': best['win_rate'],
                    'profit_factor': best['profit_factor'],
                    'trades': best['total_trades']
                },
                'params': best['params']
            }, f, indent=2)
        print(f"💾 Meilleurs params: best_probabilistic_params.json")

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


if __name__ == '__main__':
    main()
