#!/usr/bin/env python3
"""
Backtest Stratégie Probabiliste Améliorée sur BNB 1m
Avec limitation -100% par trade (isolated margin)
"""

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

from improved_probabilistic_strategy import ImprovedProbabilisticStrategy


class ImprovedProbabilisticBacktest:
    """Backtest avec gestion réaliste du risque"""

    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 ET limite à -100%

        IMPORTANT: En isolated margin, la perte max = 100% de la marge
        """
        entry = position['entry_price']
        leverage = position['leverage']

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

        pnl_pct = price_change_pct * leverage

        # LIMITE: -100% max (liquidation)
        if pnl_pct < -100:
            pnl_pct = -100

        return pnl_pct

    def run(self, strategy: ImprovedProbabilisticStrategy, strategy_name: str = "Improved Probabilistic") -> Dict:
        """Execute backtest avec risk management"""
        capital = self.initial_capital
        trades = []
        open_position = None
        liquidations = 0

        print(f"\n🔄 Backtest {strategy_name}...")
        print(f"   Leverage: {strategy.leverage}x")
        print(f"   SL base: {strategy.sl_pct*100:.3f}% | TP base: {strategy.tp_pct*100:.3f}%")
        print(f"   Z-threshold: {strategy.z_threshold}")
        print(f"   Trend filter: {'ON' if strategy.trend_filter_enabled else 'OFF'}")

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

            # Gérer position ouverte
            if open_position:
                pnl_pct = self._calculate_pnl(open_position, current_price)

                # Check liquidation (-100%)
                if pnl_pct <= -100:
                    # LIQUIDATION
                    position_size = capital * 1.0  # 100% allocation
                    pnl_usd = position_size * -1.0  # -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': current_price,
                        'leverage': open_position['leverage'],
                        'pnl_pct': -100,
                        'pnl_usd': pnl_usd,
                        'exit_reason': 'LIQUIDATION',
                        'probability': open_position['probability'],
                        'expected_value': open_position['expected_value'],
                        'confidence': open_position['confidence'],
                        'trend_filter': open_position['trend_filter']
                    })

                    liquidations += 1
                    open_position = None
                    continue

                # Check SL/TP normaux
                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:
                    exit_reason = 'TP' if hit_tp else 'SL'
                    exit_price = open_position['take_profit'] if hit_tp else open_position['stop_loss']

                    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'],
                        'trend_filter': open_position['trend_filter']
                    })

                    open_position = None

            # Chercher nouveau signal
            if not open_position and capital > 0:  # Seulement si capital restant
                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,
                        'trend_filter': signal.trend_filter
                    }

        # Résultats
        final_capital = capital

        if len(trades) == 0:
            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)

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

        max_dd = self._calculate_max_drawdown(trades)
        prob_stats = self._calculate_probability_stats(trades)
        trend_stats = self._calculate_trend_stats(trades)

        return {
            'strategy': strategy_name,
            'total_trades': len(trades),
            'winning_trades': len(winning_trades),
            'losing_trades': len(losing_trades),
            'liquidations': liquidations,
            '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,
            'trend_stats': trend_stats,
            'trades': trades
        }

    def _empty_results(self, strategy_name: str, final_capital: float) -> Dict:
        return {
            'strategy': strategy_name,
            'total_trades': 0,
            'winning_trades': 0,
            'losing_trades': 0,
            'liquidations': 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': {},
            'trend_stats': {},
            'trades': []
        }

    def _calculate_max_drawdown(self, trades: List[Dict]) -> float:
        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 peak > 0 else 0
            if dd > max_dd:
                max_dd = dd

        return max_dd

    def _calculate_probability_stats(self, trades: List[Dict]) -> Dict:
        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

        actual_win_rate = (len(winning_trades) / len(trades)) * 100
        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': np.mean([t['expected_value'] for t in trades]),
            'avg_confidence': np.mean([t['confidence'] for t in trades]),
            'actual_win_rate': actual_win_rate,
            'theoretical_win_rate': avg_prob_all * 100,
            'calibration_error': calibration_error
        }

    def _calculate_trend_stats(self, trades: List[Dict]) -> Dict:
        """Stats par type de trend filter"""
        if not trades:
            return {}

        trend_counts = {}
        trend_wins = {}

        for t in trades:
            tf = t.get('trend_filter', 'UNKNOWN')
            if tf not in trend_counts:
                trend_counts[tf] = 0
                trend_wins[tf] = 0

            trend_counts[tf] += 1
            if t['pnl_pct'] > 0:
                trend_wins[tf] += 1

        trend_win_rates = {}
        for tf in trend_counts:
            trend_win_rates[tf] = (trend_wins[tf] / trend_counts[tf]) * 100 if trend_counts[tf] > 0 else 0

        return {
            'counts': trend_counts,
            'win_rates': trend_win_rates
        }


def get_bnb_data(days: int = 1, interval: str = '1m') -> pd.DataFrame:
    """Récupère données BNB"""
    print(f"📊 Récupération BNB ({days}j, {interval})...")

    limit = min(1000, days * 1440) if interval == '1m' else min(1000, days * 288)

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

    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 | {df['timestamp'].iloc[0]} → {df['timestamp'].iloc[-1]}")
    return df


def print_results(results: Dict):
    """Affichage détaillé"""
    print("\n" + "=" * 90)
    print(f"📊 {results['strategy']}")
    print("=" * 90)

    print(f"\n📈 PERFORMANCE:")
    print(f"  Trades: {results['total_trades']} (W:{results['winning_trades']} / L:{results['losing_trades']} / LIQ:{results['liquidations']})")
    print(f"  Win rate: {results['win_rate']:.1f}%")
    print(f"  Profit factor: {results['profit_factor']:.2f}")
    print(f"  PnL: {results['total_pnl_pct']:.2f}% (${results['total_pnl_usd']:.2f})")
    print(f"  Capital final: ${results['final_capital']:.2f}")
    print(f"  Avg win: ${results['avg_win']:.2f} | Avg loss: ${results['avg_loss']:.2f}")
    print(f"  Max DD: {results['max_drawdown']:.2f}%")

    if results.get('probability_stats'):
        prob = results['probability_stats']
        print(f"\n🎲 CALIBRATION:")
        print(f"  Théorique: {prob['theoretical_win_rate']:.1f}% | Réel: {prob['actual_win_rate']:.1f}%")
        print(f"  Erreur: {prob['calibration_error']:.1f}% {'✅' if prob['calibration_error'] < 10 else '🔴'}")

    if results.get('trend_stats'):
        ts = results['trend_stats']
        print(f"\n🌊 TRADES PAR TENDANCE:")
        for trend, count in ts['counts'].items():
            wr = ts['win_rates'][trend]
            print(f"  {trend}: {count} trades, {wr:.1f}% win rate")


def main():
    print("=" * 80)
    print("🎯 BACKTEST STRATÉGIE PROBABILISTE AMÉLIORÉE - BNB 1m")
    print("=" * 80)

    df = get_bnb_data(days=1, interval='1m')

    # TEST 1: Version améliorée standard
    print("\n📊 TEST 1: Stratégie Améliorée (Standard)")
    strategy1 = ImprovedProbabilisticStrategy(
        leverage=100,
        sl_pct=0.05,
        tp_pct=0.10,
        z_threshold=2.0,
        min_probability=0.55,
        trend_filter_enabled=True,
        calibration_factor=0.60
    )
    backtest1 = ImprovedProbabilisticBacktest(df)
    results1 = backtest1.run(strategy1, "Améliorée Standard (Lev 100x)")
    print_results(results1)

    # TEST 2: Leverage plus agressif
    print("\n📊 TEST 2: Leverage Agressif (150x)")
    strategy2 = ImprovedProbabilisticStrategy(
        leverage=150,
        sl_pct=0.05,
        tp_pct=0.10,
        z_threshold=2.0,
        min_probability=0.55,
        trend_filter_enabled=True,
        calibration_factor=0.60
    )
    backtest2 = ImprovedProbabilisticBacktest(df)
    results2 = backtest2.run(strategy2, "Agressif (Lev 150x)")
    print_results(results2)

    # TEST 3: Conservative (leverage 50x)
    print("\n📊 TEST 3: Conservative (50x)")
    strategy3 = ImprovedProbabilisticStrategy(
        leverage=50,
        sl_pct=0.06,
        tp_pct=0.12,
        z_threshold=2.2,
        min_probability=0.58,
        trend_filter_enabled=True,
        calibration_factor=0.65
    )
    backtest3 = ImprovedProbabilisticBacktest(df)
    results3 = backtest3.run(strategy3, "Conservative (Lev 50x)")
    print_results(results3)

    # TEST 4: Sans filtre de tendance (pour comparer)
    print("\n📊 TEST 4: SANS Filtre de Tendance")
    strategy4 = ImprovedProbabilisticStrategy(
        leverage=100,
        sl_pct=0.05,
        tp_pct=0.10,
        z_threshold=2.0,
        min_probability=0.55,
        trend_filter_enabled=False,  # OFF
        calibration_factor=0.60
    )
    backtest4 = ImprovedProbabilisticBacktest(df)
    results4 = backtest4.run(strategy4, "Sans Trend Filter")
    print_results(results4)

    # Comparaison
    print("\n" + "=" * 100)
    print("🏆 COMPARAISON")
    print("=" * 100)

    all_res = [
        ("Standard 100x", results1),
        ("Agressif 150x", results2),
        ("Conservateur 50x", results3),
        ("Sans Trend Filter", results4)
    ]

    print(f"\n{'Stratégie':<25} | {'Trades':<7} | {'Win%':<7} | {'PF':<7} | {'PnL%':<10} | {'DD%':<8} | {'CalibErr':<9}")
    print("-" * 100)

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

    for name, res in all_res:
        calib = res['probability_stats'].get('calibration_error', 0) if res['probability_stats'] else 0
        print(f"{name:<25} | {res['total_trades']:<7} | {res['win_rate']:<7.1f} | "
              f"{res['profit_factor']:<7.2f} | {res['total_pnl_pct']:<10.2f} | "
              f"{res['max_drawdown']:<8.2f} | {calib:<9.1f}")

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

    print("-" * 100)
    print(f"\n🏆 MEILLEUR: {best_name} ({best_pnl:.2f}%)")

    # Sauvegarder
    output = {
        'asset': 'BNBUSDT',
        'interval': '1m',
        'period': f"{df['timestamp'].iloc[0]} to {df['timestamp'].iloc[-1]}",
        'date': datetime.now().isoformat(),
        'standard_100x': results1,
        'aggressive_150x': results2,
        'conservative_50x': results3,
        'no_trend_filter': results4,
        'best_strategy': best_name
    }

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

    print("\n💾 Résultats: improved_probabilistic_results.json")
    print("\n✅ BACKTEST TERMINÉ\n")


if __name__ == '__main__':
    main()
