#!/usr/bin/env python3
"""
TEST DE VARIANTES STRATÉGIQUES - Optimisation au niveau conceptuel

Au lieu d'optimiser les paramètres, testons différentes LOGIQUES:
1. Baseline (volatility expansion + confirmations)
2. Mean Reversion (RSI extreme + bounce)
3. Trend Following pur (MA crossover + momentum)
4. Breakout (range breakout + volume)
5. Combined (meilleur de chaque)
"""

import pandas as pd
import numpy as np
from martingale_backtester import MartingaleBacktester
import json

class StrategyVariant:
    """Base class pour variantes stratégiques"""

    def __init__(self, initial_capital=1000):
        self.capital = initial_capital
        self.initial_capital = initial_capital
        self.position = None
        self.trades = []

    def detect_entry_signal(self, df, i):
        """À implémenter par chaque variante"""
        raise NotImplementedError

    def calculate_params(self, df, i):
        """Params adaptatifs basés ATR (commun)"""
        recent = df.iloc[max(0, i-50):i]

        high_low = recent['high'] - recent['low']
        atr = high_low.mean()
        atr_pct = (atr / recent['close'].mean()) * 100

        returns = recent['close'].pct_change()
        vol = returns.std()

        if vol > 0.03:
            lev = 15
        elif vol > 0.02:
            lev = 25
        else:
            lev = 35

        tp_pct = max(0.8, min(atr_pct * 2.0, 4.0))
        sl_pct = max(0.4, min(atr_pct * 1.0, 2.5))

        position_size = (self.capital * 0.02) / (sl_pct/100 * lev)
        position_size = min(position_size, self.capital * 0.3)

        return {'leverage': lev, 'tp_pct': tp_pct, 'sl_pct': sl_pct, 'position_size': position_size}

    def should_exit(self, df, i):
        """Exit logic (commun)"""
        if not self.position:
            return False, None

        current = df.iloc[i]
        params = self.position['params']

        if self.position['side'] == 'LONG':
            tp = self.position['entry'] * (1 + params['tp_pct']/100)
            sl = self.position['entry'] * (1 - params['sl_pct']/100)
            if current['close'] >= tp:
                return True, 'TP'
            if current['close'] <= sl:
                return True, 'SL'
        else:
            tp = self.position['entry'] * (1 - params['tp_pct']/100)
            sl = self.position['entry'] * (1 + params['sl_pct']/100)
            if current['close'] <= tp:
                return True, 'TP'
            if current['close'] >= sl:
                return True, 'SL'

        return False, None

    def close_trade(self, exit_price, exit_type):
        params = self.position['params']

        if self.position['side'] == 'LONG':
            pnl_pct = (exit_price - self.position['entry']) / self.position['entry']
        else:
            pnl_pct = (self.position['entry'] - exit_price) / self.position['entry']

        pnl_pct *= params['leverage']
        pnl_pct -= 0.001

        pnl_usd = params['position_size'] * pnl_pct
        self.capital += pnl_usd

        self.trades.append({
            'pnl_usd': pnl_usd,
            'pnl_pct': pnl_pct * 100,
            'won': pnl_usd > 0
        })

        self.position = None

    def backtest(self, df):
        for i in range(100, len(df)):
            if self.position:
                should_exit, exit_type = self.should_exit(df, i)
                if should_exit:
                    self.close_trade(df.iloc[i]['close'], exit_type)
                continue

            signal = self.detect_entry_signal(df, i)
            if signal:
                params = self.calculate_params(df, i)
                self.position = {
                    'entry': df.iloc[i]['close'],
                    'side': signal['side'],
                    'params': params
                }

        if not self.trades:
            return None

        wins = [t for t in self.trades if t['won']]
        losses = [t for t in self.trades if not t['won']]

        return {
            'roi': ((self.capital - self.initial_capital) / self.initial_capital) * 100,
            'win_rate': len(wins) / len(self.trades) if self.trades else 0,
            'trades': len(self.trades),
            'profit_factor': abs(sum([t['pnl_usd'] for t in wins]) / sum([t['pnl_usd'] for t in losses])) if losses else 999
        }


class VolatilityExpansion(StrategyVariant):
    """Baseline - Volatility Expansion"""

    def detect_entry_signal(self, df, i):
        current = df.iloc[i]
        recent = df.iloc[max(0, i-50):i]

        # Vol expansion
        current_range = current['high'] - current['low']
        avg_range = (recent['high'] - recent['low']).mean()

        if current_range <= avg_range * 1.5:
            return None

        # Direction
        side = 'LONG' if current['close'] > current['open'] else 'SHORT'

        # Volume
        vol_ma = recent['volume'].mean()
        if current['volume'] < vol_ma * 1.3:
            return None

        return {'side': side}


class MeanReversion(StrategyVariant):
    """Mean Reversion - RSI extremes"""

    def detect_entry_signal(self, df, i):
        if i < 20:
            return None

        current = df.iloc[i]
        recent = df.iloc[i-20:i]

        # RSI
        deltas = recent['close'].diff()
        gains = deltas.clip(lower=0).rolling(14).mean()
        losses = -deltas.clip(upper=0).rolling(14).mean()

        if losses.iloc[-1] == 0:
            return None

        rs = gains.iloc[-1] / losses.iloc[-1]
        rsi = 100 - (100 / (1 + rs))

        # Mean reversion sur RSI extremes
        if rsi < 30:  # Oversold → LONG
            return {'side': 'LONG'}
        elif rsi > 70:  # Overbought → SHORT
            return {'side': 'SHORT'}

        return None


class TrendFollowing(StrategyVariant):
    """Trend Following - MA crossover"""

    def detect_entry_signal(self, df, i):
        if i < 50:
            return None

        recent = df.iloc[i-50:i]
        current = df.iloc[i]

        # MA crossover
        ma10 = recent['close'].tail(10).mean()
        ma30 = recent['close'].tail(30).mean()
        prev_ma10 = recent['close'].iloc[-11:-1].mean()
        prev_ma30 = recent['close'].iloc[-31:-1].mean()

        # Crossover UP → LONG
        if prev_ma10 <= prev_ma30 and ma10 > ma30:
            return {'side': 'LONG'}

        # Crossover DOWN → SHORT
        if prev_ma10 >= prev_ma30 and ma10 < ma30:
            return {'side': 'SHORT'}

        return None


class Breakout(StrategyVariant):
    """Breakout - Range breakout + volume"""

    def detect_entry_signal(self, df, i):
        if i < 30:
            return None

        current = df.iloc[i]
        recent = df.iloc[i-30:i]

        # Range 20 dernières bougies
        recent_20 = df.iloc[i-20:i]
        high_20 = recent_20['high'].max()
        low_20 = recent_20['low'].min()

        # Volume spike
        vol_ma = recent['volume'].mean()
        if current['volume'] < vol_ma * 1.5:
            return None

        # Breakout UP
        if current['close'] > high_20:
            return {'side': 'LONG'}

        # Breakout DOWN
        if current['close'] < low_20:
            return {'side': 'SHORT'}

        return None


class CombinedStrategy(StrategyVariant):
    """Combined - Multi-strategy voting"""

    def detect_entry_signal(self, df, i):
        if i < 50:
            return None

        current = df.iloc[i]
        recent = df.iloc[i-50:i]

        votes = {'LONG': 0, 'SHORT': 0}

        # 1. Volatility expansion
        current_range = current['high'] - current['low']
        avg_range = (recent['high'] - recent['low']).mean()
        if current_range > avg_range * 1.5:
            side = 'LONG' if current['close'] > current['open'] else 'SHORT'
            votes[side] += 1

        # 2. RSI
        deltas = recent['close'].diff()
        gains = deltas.clip(lower=0).rolling(14).mean()
        losses = -deltas.clip(upper=0).rolling(14).mean()
        if losses.iloc[-1] != 0:
            rs = gains.iloc[-1] / losses.iloc[-1]
            rsi = 100 - (100 / (1 + rs))
            if rsi < 40:
                votes['LONG'] += 1
            elif rsi > 60:
                votes['SHORT'] += 1

        # 3. Trend
        ma20 = recent['close'].tail(20).mean()
        if current['close'] > ma20 * 1.01:
            votes['LONG'] += 1
        elif current['close'] < ma20 * 0.99:
            votes['SHORT'] += 1

        # Vote majoritaire avec minimum 2
        if votes['LONG'] >= 2:
            return {'side': 'LONG'}
        elif votes['SHORT'] >= 2:
            return {'side': 'SHORT'}

        return None


# ============ TESTS ============

print("=" * 80)
print("🔬 TEST VARIANTES STRATÉGIQUES")
print("=" * 80)

backtester = MartingaleBacktester(
    initial_capital=1000.0,
    trading_fee_pct=0.0005,
    timeout_minutes=60
)

print("\n📥 Téléchargement BTC 4h (1000 bougies)...")
df = backtester.fetch_binance_data('BTCUSDT', '4h', 1000)

if df is None:
    exit(1)

strategies = {
    'Volatility Expansion (Baseline)': VolatilityExpansion,
    'Mean Reversion (RSI)': MeanReversion,
    'Trend Following (MA)': TrendFollowing,
    'Breakout (Range)': Breakout,
    'Combined (Multi-vote)': CombinedStrategy
}

# Test sur 3 périodes
n = len(df)
periods = [
    ("P1", df.iloc[:n//3]),
    ("P2", df.iloc[n//3:2*n//3]),
    ("P3", df.iloc[2*n//3:])
]

all_results = {}

for strat_name, StratClass in strategies.items():
    print(f"\n🧪 {strat_name}:")
    period_results = []

    for period_name, period_df in periods:
        strat = StratClass(initial_capital=1000)
        result = strat.backtest(period_df)

        if result:
            period_results.append(result)
            status = "✅" if result['roi'] > 15 else "⚠️" if result['roi'] > 5 else "❌"
            print(f"   {period_name}: {status} ROI: {result['roi']:+6.2f}% | WR: {result['win_rate']*100:4.1f}% | Trades: {result['trades']:2d}")
        else:
            print(f"   {period_name}: ❌ Aucun trade")
            period_results.append({'roi': 0, 'win_rate': 0, 'trades': 0, 'profit_factor': 0})

    # Moyenne
    if period_results:
        avg_roi = np.mean([r['roi'] for r in period_results])
        std_roi = np.std([r['roi'] for r in period_results])
        avg_wr = np.mean([r['win_rate'] for r in period_results])
        all_results[strat_name] = {
            'avg_roi': avg_roi,
            'std_roi': std_roi,
            'avg_wr': avg_wr,
            'periods': period_results
        }

# ============ COMPARAISON ============

print("\n\n" + "=" * 80)
print("🏆 COMPARAISON DES STRATÉGIES")
print("=" * 80)

print(f"\n{'Stratégie':<30} {'ROI Moy':>10} {'Std':>8} {'WR%':>6} {'Sharpe':>7}")
print("-" * 80)

best_strat = None
best_sharpe = 0

for strat_name, res in sorted(all_results.items(), key=lambda x: x[1]['avg_roi'], reverse=True):
    sharpe = res['avg_roi'] / res['std_roi'] if res['std_roi'] > 0 else 0
    status = "✅" if res['avg_roi'] > 15 else "⚠️" if res['avg_roi'] > 10 else "🔵"

    print(f"{strat_name:<30} {res['avg_roi']:>9.2f}% {res['std_roi']:>7.2f}% {res['avg_wr']*100:>5.1f}% {sharpe:>7.2f} {status}")

    if sharpe > best_sharpe:
        best_sharpe = sharpe
        best_strat = strat_name

# Verdict
print(f"\n🎯 MEILLEURE STRATÉGIE:")
print(f"   {best_strat}")
print(f"   Sharpe: {best_sharpe:.2f}")

# Sauvegarder
with open('strategy_variants_results.json', 'w') as f:
    json.dump(all_results, f, indent=2, default=str)

print(f"\n💾 Résultats: strategy_variants_results.json")
print("=" * 80)
