#!/usr/bin/env python3
"""
Backtest BNB 1m - Stratégie Probabiliste Améliorée
Focus sur l'affinement de l'approche mathématique pour BNB
"""

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

from probabilistic_scalping_strategy import ProbabilisticScalpingStrategy


class ProbabilisticBacktest:
    """Moteur de backtest pour stratégie probabiliste"""

    def __init__(self, df: pd.DataFrame, initial_capital: float = 100.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 run(self, strategy: ProbabilisticScalpingStrategy, strategy_name: str = "Probabilistic") -> Dict:
        """Execute backtest"""
        capital = self.initial_capital
        trades = []
        open_position = None

        print(f"\n🔄 Backtest {strategy_name}...")
        print(f"   Params: leverage={strategy.leverage}, sl={strategy.sl_pct*100:.3f}%, tp={strategy.tp_pct*100:.3f}%")
        print(f"   Z-threshold={strategy.z_threshold}, min_prob={strategy.min_probability:.2f}")

        # Parcourir données
        for i in range(strategy.lookback_window + 50, len(self.df)):
            window = self.df.iloc[:i+1].copy()
            current_price = window['close'].iloc[-1]

            # 1. Gérer position ouverte
            if open_position:
                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: 100% du capital (full allocation)
                    position_size = capital * 1.0
                    pnl_usd = position_size * (pnl_pct / 100)
                    capital += pnl_usd

                    trades.append({
                        'entry_time': open_position['entry_time'],
                        'exit_time': window['timestamp'].iloc[-1],
                        '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'],
                        'confidence': open_position['confidence']
                    })

                    open_position = None

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

                if signal:
                    open_position = {
                        'entry_time': window['timestamp'].iloc[-1],
                        '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,
                        'confidence': signal.confidence
                    }

        # Résultats finaux
        final_capital = capital

        if len(trades) == 0:
            print("   ⚠️  Aucun trade généré")
            return self._empty_results(strategy_name, final_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 (float('inf') if total_win > 0 else 0)

        # PnL total
        total_pnl = final_capital - self.initial_capital
        total_pnl_pct = (total_pnl / self.initial_capital) * 100

        # Drawdown
        max_dd = self._calculate_max_drawdown(trades)

        # Statistiques probabilistes
        prob_stats = self._calculate_probability_stats(trades)

        return {
            'strategy': strategy_name,
            'total_trades': len(trades),
            'winning_trades': len(winning_trades),
            'losing_trades': len(losing_trades),
            'win_rate': (len(winning_trades) / len(trades)) * 100,
            'profit_factor': profit_factor,
            'total_pnl_usd': total_pnl,
            'total_pnl_pct': total_pnl_pct,
            'final_capital': final_capital,
            'avg_pnl_per_trade': total_pnl / len(trades),
            'avg_win': total_win / len(winning_trades) if winning_trades else 0,
            'avg_loss': total_loss / len(losing_trades) if losing_trades else 0,
            'max_drawdown': max_dd,
            'probability_stats': prob_stats,
            'trades': trades
        }

    def _empty_results(self, strategy_name: str, final_capital: float) -> Dict:
        """Résultats vides"""
        return {
            'strategy': strategy_name,
            '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': final_capital,
            'avg_pnl_per_trade': 0,
            'avg_win': 0,
            'avg_loss': 0,
            'max_drawdown': 0,
            'probability_stats': {},
            'trades': []
        }

    def _calculate_max_drawdown(self, trades: List[Dict]) -> float:
        """Calcule drawdown maximum"""
        capital_curve = [self.initial_capital]
        running_capital = self.initial_capital
        for t in trades:
            running_capital += t['pnl_usd']
            capital_curve.append(running_capital)

        peak = capital_curve[0]
        max_dd = 0
        for cap in capital_curve:
            if cap > peak:
                peak = cap
            dd = ((peak - cap) / peak) * 100
            if dd > max_dd:
                max_dd = dd

        return max_dd

    def _calculate_probability_stats(self, trades: List[Dict]) -> Dict:
        """Analyse corrélation probabilités théoriques vs résultats réels"""
        if not trades:
            return {}

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

        avg_prob_all = np.mean([t['probability'] for t in trades])
        avg_prob_winners = np.mean([t['probability'] for t in winning_trades]) if winning_trades else 0
        avg_prob_losers = np.mean([t['probability'] for t in losing_trades]) if losing_trades else 0

        avg_ev_all = np.mean([t['expected_value'] for t in trades])
        avg_conf_all = np.mean([t['confidence'] for t in trades])

        actual_win_rate = (len(winning_trades) / len(trades)) * 100

        # Calibration error: différence entre prob moyenne et win rate réel
        calibration_error = abs(avg_prob_all * 100 - actual_win_rate)

        return {
            'avg_probability': avg_prob_all,
            'avg_probability_winners': avg_prob_winners,
            'avg_probability_losers': avg_prob_losers,
            'avg_expected_value': avg_ev_all,
            'avg_confidence': avg_conf_all,
            'actual_win_rate': actual_win_rate,
            'theoretical_win_rate': avg_prob_all * 100,
            'calibration_error': calibration_error
        }


def get_bnb_historical_data(days: int = 7, interval: str = '1m') -> 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 selon interval
    if interval == '1m':
        limit = min(1000, days * 1440)
    elif interval == '5m':
        limit = min(1000, days * 288)
    else:
        limit = 1000

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

    return df


def print_detailed_results(results: Dict):
    """Affiche résultats détaillés avec analyse probabiliste"""
    print("\n" + "=" * 90)
    print(f"📊 RÉSULTATS: {results['strategy']}")
    print("=" * 90)

    # Métriques principales
    print(f"\n📈 PERFORMANCE:")
    print(f"  Total trades: {results['total_trades']}")
    print(f"  Gagnants: {results['winning_trades']} | Perdants: {results['losing_trades']}")
    print(f"  Win rate: {results['win_rate']:.1f}%")
    print(f"  Profit factor: {results['profit_factor']:.2f}")
    print(f"  PnL total: {results['total_pnl_pct']:.2f}% (${results['total_pnl_usd']:.2f})")
    print(f"  Capital final: ${results['final_capital']:.2f}")
    print(f"  Avg gain: ${results['avg_win']:.2f} | Avg perte: ${results['avg_loss']:.2f}")
    print(f"  Max drawdown: {results['max_drawdown']:.2f}%")

    # Analyse probabiliste
    if results.get('probability_stats'):
        prob = results['probability_stats']
        print(f"\n🎲 ANALYSE PROBABILISTE:")
        print(f"  Probabilité moyenne (théorique): {prob['theoretical_win_rate']:.1f}%")
        print(f"  Win rate réel: {prob['actual_win_rate']:.1f}%")
        print(f"  🔴 Erreur de calibration: {prob['calibration_error']:.1f}%")
        print(f"  Prob moyenne (gagnants): {prob['avg_probability_winners']*100:.1f}%")
        print(f"  Prob moyenne (perdants): {prob['avg_probability_losers']*100:.1f}%")
        print(f"  Expected Value moyen: {prob['avg_expected_value']:.2f}%")
        print(f"  Confidence moyenne: {prob['avg_confidence']:.1f}")

    # Trades détaillés
    if results['trades']:
        print(f"\n📊 TRADES (premiers 5):")
        for i, t in enumerate(results['trades'][:5]):
            sign = "🟢" if t['pnl_pct'] > 0 else "🔴"
            print(f"  {sign} {t['side']}: {t['pnl_pct']:.1f}% | "
                  f"Prob: {t['probability']*100:.0f}% | EV: {t['expected_value']:.1f}% | "
                  f"Exit: {t['exit_reason']}")


def main():
    print("=" * 80)
    print("🎯 BACKTEST BNB 1m - APPROCHE PROBABILISTE")
    print("=" * 80)

    # Charger données BNB 1m (1 jour = 1440 bougies max via Binance API)
    df = get_bnb_historical_data(days=1, interval='1m')

    # TEST 1: Paramètres actuels (baseline)
    print("\n" + "=" * 80)
    print("📊 TEST 1: Paramètres Actuels (Baseline)")
    print("=" * 80)

    strategy_baseline = ProbabilisticScalpingStrategy(
        leverage=500,
        sl_pct=0.02,
        tp_pct=0.08,
        lookback_window=100,
        z_threshold=2.2,
        min_probability=0.60,
        min_ev=0.20
    )

    backtest = ProbabilisticBacktest(df, initial_capital=100.0)
    results_baseline = backtest.run(strategy_baseline, "Baseline (Params actuels)")
    print_detailed_results(results_baseline)

    # TEST 2: Ratio TP/SL plus conservateur (2:1 au lieu de 4:1)
    print("\n" + "=" * 80)
    print("📊 TEST 2: Ratio TP/SL Conservateur (2:1)")
    print("=" * 80)

    strategy_conservative = ProbabilisticScalpingStrategy(
        leverage=500,
        sl_pct=0.03,  # SL plus large
        tp_pct=0.06,  # TP plus réaliste
        lookback_window=100,
        z_threshold=2.2,
        min_probability=0.60,
        min_ev=0.20
    )

    backtest2 = ProbabilisticBacktest(df, initial_capital=100.0)
    results_conservative = backtest2.run(strategy_conservative, "Conservateur (TP/SL 2:1)")
    print_detailed_results(results_conservative)

    # TEST 3: Z-score plus strict (moins de trades mais plus confiants)
    print("\n" + "=" * 80)
    print("📊 TEST 3: Z-Score Plus Strict (2.5 au lieu de 2.2)")
    print("=" * 80)

    strategy_strict = ProbabilisticScalpingStrategy(
        leverage=500,
        sl_pct=0.03,
        tp_pct=0.06,
        lookback_window=100,
        z_threshold=2.5,  # Plus strict
        min_probability=0.65,  # Prob min plus élevée
        min_ev=0.25  # EV min plus élevé
    )

    backtest3 = ProbabilisticBacktest(df, initial_capital=100.0)
    results_strict = backtest3.run(strategy_strict, "Strict (Z>2.5, Prob>65%)")
    print_detailed_results(results_strict)

    # TEST 4: Fenêtre plus courte (plus réactif aux changements récents)
    print("\n" + "=" * 80)
    print("📊 TEST 4: Fenêtre Courte (50 au lieu de 100)")
    print("=" * 80)

    strategy_short_window = ProbabilisticScalpingStrategy(
        leverage=500,
        sl_pct=0.03,
        tp_pct=0.06,
        lookback_window=50,  # Plus court
        z_threshold=2.2,
        min_probability=0.60,
        min_ev=0.20
    )

    backtest4 = ProbabilisticBacktest(df, initial_capital=100.0)
    results_short = backtest4.run(strategy_short_window, "Fenêtre Courte (50)")
    print_detailed_results(results_short)

    # Comparaison finale
    print("\n" + "=" * 90)
    print("🏆 COMPARAISON FINALE")
    print("=" * 90)

    all_results = [
        ("Baseline", results_baseline),
        ("Conservateur", results_conservative),
        ("Strict", results_strict),
        ("Fenêtre Courte", results_short)
    ]

    print(f"\n{'Stratégie':<20} | {'Trades':<8} | {'Win%':<8} | {'PF':<8} | {'PnL%':<10} | {'Calib Err':<10}")
    print("-" * 90)

    best_pnl = -float('inf')
    best_name = ""

    for name, res in all_results:
        calib_err = res['probability_stats'].get('calibration_error', 0) if res['probability_stats'] else 0

        print(f"{name:<20} | {res['total_trades']:<8} | {res['win_rate']:<8.1f} | "
              f"{res['profit_factor']:<8.2f} | {res['total_pnl_pct']:<10.2f} | {calib_err:<10.1f}")

        if res['total_pnl_pct'] > best_pnl:
            best_pnl = res['total_pnl_pct']
            best_name = name

    print("-" * 90)
    print(f"\n🏆 MEILLEURE STRATÉGIE: {best_name} ({best_pnl:.2f}%)")

    # Sauvegarder tous les résultats
    output = {
        'asset': 'BNBUSDT',
        'interval': '1m',
        'period': f"{df['timestamp'].iloc[0]} to {df['timestamp'].iloc[-1]}",
        'date': datetime.now().isoformat(),
        'baseline': results_baseline,
        'conservative': results_conservative,
        'strict': results_strict,
        'short_window': results_short,
        'best_strategy': best_name
    }

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

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

    print("\n" + "=" * 80)
    print("✅ BACKTEST PROBABILISTE BNB TERMINÉ")
    print("=" * 80)


if __name__ == '__main__':
    main()
