#!/usr/bin/env python3
"""
MEGA OPTIMIZATION - Long, Short, Leverage
==========================================
Test massivo per trovare 20+ configurazioni vincenti.
Include:
- Posizioni LONG e SHORT
- Leverage 1x, 2x, 3x, 5x
- Limit orders (maker fee 0.16%)
- Multiple timeframes: 12h, 24h, 48h, 7d
"""

import pandas as pd
import numpy as np
from datetime import datetime
from pathlib import Path
import json
from itertools import product
import warnings
warnings.filterwarnings('ignore')

# ============================================
# KRAKEN FEE STRUCTURE (REALE)
# ============================================
KRAKEN_MAKER_FEE = 0.0016      # 0.16% limit order
KRAKEN_TAKER_FEE = 0.0026      # 0.26% market order
MARGIN_OPEN_FEE = 0.0002       # 0.02% margin opening
MARGIN_ROLLOVER = 0.0002       # 0.02% ogni 4 ore
SPREAD_PCT = 0.0003            # 0.03% spread
SLIPPAGE = 0.0001              # 0.01% slippage

# Leverage limits
MAX_LEVERAGE = 5


def load_data():
    DATA_DIR = Path('/var/www/html/pippo.cuttalo.com/data')
    dfs = []
    for f in sorted(DATA_DIR.glob('prices_BTC_EUR_*.csv')):
        df = pd.read_csv(f)
        if df['timestamp'].dtype == 'int64' or str(df['timestamp'].iloc[0]).isdigit():
            ts = pd.to_numeric(df['timestamp'])
            df['timestamp'] = pd.to_datetime(ts, unit='s' if ts.iloc[0] < 1e12 else 'ms')
        else:
            df['timestamp'] = pd.to_datetime(df['timestamp'])
        dfs.append(df)
    prices = pd.concat(dfs, ignore_index=True)
    prices = prices.sort_values('timestamp').drop_duplicates(subset='timestamp').reset_index(drop=True)
    return prices


def backtest_strategy(prices_data, config):
    """
    Backtest con supporto LONG/SHORT e LEVERAGE.
    """
    close, high, low, high_ref, low_ref = prices_data

    capital = 5000.0
    start_capital = capital
    position = 0  # 0=flat, 1=long, -1=short
    entry_price = 0
    position_value = 0
    margin_used = 0
    entry_time_idx = 0

    leverage = config.get('leverage', 1)
    direction = config.get('direction', 'long')  # 'long' or 'short'
    dip_pct = config['dip']
    tp_pct = config['tp']
    sl_pct = config['sl']

    trades = []
    equity = [capital]

    start_idx = config.get('warmup', 1440)

    for i in range(start_idx, len(close)):
        current_price = close[i]
        candle_high = high[i]
        candle_low = low[i]
        ref_high = high_ref[i]
        ref_low = low_ref[i]

        if ref_high <= 0 or ref_low <= 0:
            continue

        # Calcola dip/pump
        dip_from_high = (current_price - ref_high) / ref_high
        pump_from_low = (current_price - ref_low) / ref_low

        # ==================
        # GESTIONE POSIZIONE
        # ==================
        if position != 0:
            # Calcola rollover fee (ogni 240 candele = 4 ore)
            time_in_pos = i - entry_time_idx
            rollover_periods = time_in_pos // 240
            rollover_cost = margin_used * MARGIN_ROLLOVER * rollover_periods

            if position == 1:  # LONG
                pnl_pct = (current_price - entry_price) / entry_price

                # Stop Loss
                if pnl_pct <= -sl_pct:
                    exec_price = entry_price * (1 - sl_pct) * (1 - SPREAD_PCT)
                    gross_pnl = position_value * leverage * (exec_price - entry_price) / entry_price
                    fee = abs(gross_pnl) * KRAKEN_MAKER_FEE
                    net_pnl = gross_pnl - fee - rollover_cost
                    capital += net_pnl
                    trades.append({'pnl': net_pnl, 'type': 'sl', 'dir': 'long'})
                    position = 0
                    equity.append(capital)
                    continue

                # Take Profit
                if pnl_pct >= tp_pct:
                    exec_price = entry_price * (1 + tp_pct) * (1 - SPREAD_PCT)
                    gross_pnl = position_value * leverage * (exec_price - entry_price) / entry_price
                    fee = abs(gross_pnl) * KRAKEN_MAKER_FEE
                    net_pnl = gross_pnl - fee - rollover_cost
                    capital += net_pnl
                    trades.append({'pnl': net_pnl, 'type': 'tp', 'dir': 'long'})
                    position = 0
                    equity.append(capital)
                    continue

            elif position == -1:  # SHORT
                pnl_pct = (entry_price - current_price) / entry_price

                # Stop Loss (prezzo sale)
                if pnl_pct <= -sl_pct:
                    exec_price = entry_price * (1 + sl_pct) * (1 + SPREAD_PCT)
                    gross_pnl = position_value * leverage * (entry_price - exec_price) / entry_price
                    fee = abs(gross_pnl) * KRAKEN_MAKER_FEE
                    net_pnl = gross_pnl - fee - rollover_cost
                    capital += net_pnl
                    trades.append({'pnl': net_pnl, 'type': 'sl', 'dir': 'short'})
                    position = 0
                    equity.append(capital)
                    continue

                # Take Profit (prezzo scende)
                if pnl_pct >= tp_pct:
                    exec_price = entry_price * (1 - tp_pct) * (1 + SPREAD_PCT)
                    gross_pnl = position_value * leverage * (entry_price - exec_price) / entry_price
                    fee = abs(gross_pnl) * KRAKEN_MAKER_FEE
                    net_pnl = gross_pnl - fee - rollover_cost
                    capital += net_pnl
                    trades.append({'pnl': net_pnl, 'type': 'tp', 'dir': 'short'})
                    position = 0
                    equity.append(capital)
                    continue

        # ==================
        # APERTURA POSIZIONE
        # ==================
        elif position == 0:
            if direction == 'long':
                # LONG: compra quando prezzo scende da max
                if dip_from_high <= -dip_pct:
                    position_value = capital * 0.95  # 95% del capitale
                    margin_used = position_value * leverage * MARGIN_OPEN_FEE
                    fee = position_value * KRAKEN_MAKER_FEE

                    entry_price = current_price * (1 + SPREAD_PCT + SLIPPAGE)
                    capital -= fee + margin_used
                    position = 1
                    entry_time_idx = i

            elif direction == 'short':
                # SHORT: vende quando prezzo sale da min
                if pump_from_low >= dip_pct:  # "dip" usato come pump threshold
                    position_value = capital * 0.95
                    margin_used = position_value * leverage * MARGIN_OPEN_FEE
                    fee = position_value * KRAKEN_MAKER_FEE

                    entry_price = current_price * (1 - SPREAD_PCT - SLIPPAGE)
                    capital -= fee + margin_used
                    position = -1
                    entry_time_idx = i

    # Chiudi posizione finale
    if position != 0:
        final_price = close[-1]
        time_in_pos = len(close) - 1 - entry_time_idx
        rollover_cost = margin_used * MARGIN_ROLLOVER * (time_in_pos // 240)

        if position == 1:
            gross_pnl = position_value * leverage * (final_price - entry_price) / entry_price
        else:
            gross_pnl = position_value * leverage * (entry_price - final_price) / entry_price

        fee = abs(gross_pnl) * KRAKEN_MAKER_FEE
        net_pnl = gross_pnl - fee - rollover_cost
        capital += net_pnl
        trades.append({'pnl': net_pnl, 'type': 'end', 'dir': 'long' if position == 1 else 'short'})

    if not trades or len(trades) < 3:
        return None

    # Calcola metriche
    wins = [t for t in trades if t['pnl'] > 0]
    losses = [t for t in trades if t['pnl'] <= 0]

    eq = np.array(equity)
    if len(eq) > 1:
        peak = np.maximum.accumulate(eq)
        dd = (eq - peak) / peak
        max_dd = abs(min(dd))
    else:
        max_dd = 0

    total_wins = sum(t['pnl'] for t in wins) if wins else 0
    total_losses = abs(sum(t['pnl'] for t in losses)) if losses else 1
    profit_factor = total_wins / total_losses if total_losses > 0 else 0

    return {
        **config,
        'return_pct': (capital - start_capital) / start_capital * 100,
        'final_capital': capital,
        'num_trades': len(trades),
        'win_rate': len(wins) / len(trades) if trades else 0,
        'max_dd': max_dd,
        'profit_factor': profit_factor,
        'tp_count': sum(1 for t in trades if t['type'] == 'tp'),
        'sl_count': sum(1 for t in trades if t['type'] == 'sl'),
    }


def main():
    print("="*80)
    print("MEGA OPTIMIZATION - LONG, SHORT, LEVERAGE")
    print("="*80)
    print(f"\nKraken Fees: Maker {KRAKEN_MAKER_FEE*100:.2f}% | Margin {MARGIN_OPEN_FEE*100:.3f}%")
    print(f"Leverage disponibile: 1x, 2x, 3x, 5x")

    prices = load_data()
    print(f"\nDati: {len(prices):,} candele")

    close = prices['close'].values
    high = prices['high'].values
    low = prices['low'].values

    # Calcola riferimenti
    print("\nCalcolo riferimenti temporali...")

    lookbacks = {
        '12h': 720,
        '24h': 1440,
        '48h': 2880,
        '7d': 10080,
    }

    high_refs = {}
    low_refs = {}

    for name, lb in lookbacks.items():
        high_refs[name] = pd.Series(high).rolling(window=lb, min_periods=1).max().values
        low_refs[name] = pd.Series(low).rolling(window=lb, min_periods=1).min().values

    # Buy & Hold
    start_idx = 10080
    start_price = close[start_idx]
    end_price = close[-1]
    buy_hold = (end_price - start_price) / start_price * 100
    print(f"\nBuy & Hold: {buy_hold:+.2f}%")

    # Genera configurazioni
    configs = []

    # LONG configurations
    for lb_name, lb in lookbacks.items():
        for dip in [0.05, 0.07, 0.08, 0.10, 0.12, 0.15]:
            for tp in [0.07, 0.10, 0.12, 0.15, 0.20, 0.25]:
                for sl in [0.05, 0.07, 0.10, 0.12, 0.15]:
                    for lev in [1, 2, 3]:
                        if tp > sl:
                            configs.append({
                                'direction': 'long',
                                'lookback': lb_name,
                                'dip': dip,
                                'tp': tp,
                                'sl': sl,
                                'leverage': lev,
                                'warmup': lb,
                            })

    # SHORT configurations
    for lb_name, lb in lookbacks.items():
        for pump in [0.05, 0.07, 0.10, 0.12, 0.15]:
            for tp in [0.07, 0.10, 0.12, 0.15, 0.20]:
                for sl in [0.05, 0.07, 0.10, 0.12]:
                    for lev in [1, 2, 3]:
                        if tp > sl:
                            configs.append({
                                'direction': 'short',
                                'lookback': lb_name,
                                'dip': pump,  # pump threshold
                                'tp': tp,
                                'sl': sl,
                                'leverage': lev,
                                'warmup': lb,
                            })

    print(f"\nTotale configurazioni: {len(configs):,}")

    # Esegui backtest
    results = []
    total = len(configs)

    print("\nEsecuzione backtest...")
    for i, config in enumerate(configs):
        if (i + 1) % 1000 == 0:
            print(f"  Progresso: {i+1}/{total}")

        lb_name = config['lookback']
        lb = lookbacks[lb_name]

        data = (close, high, low, high_refs[lb_name], low_refs[lb_name])
        result = backtest_strategy(data, config)

        if result:
            results.append(result)

    print(f"\nRisultati validi: {len(results)}")

    # Filtra risultati
    good_results = [r for r in results if
                    r['win_rate'] >= 0.40 and
                    r['max_dd'] <= 0.50 and
                    r['profit_factor'] >= 1.0 and
                    r['num_trades'] >= 5]

    print(f"Risultati con buone metriche: {len(good_results)}")

    # Ordina per return
    results.sort(key=lambda x: x['return_pct'], reverse=True)
    good_results.sort(key=lambda x: x['return_pct'], reverse=True)

    # TOP LONG
    long_results = [r for r in good_results if r['direction'] == 'long']
    short_results = [r for r in good_results if r['direction'] == 'short']

    print(f"\n{'='*80}")
    print(f"TOP 15 LONG (totali: {len(long_results)})")
    print("="*80)

    print(f"\n{'Dir':<5} {'LB':>4} {'Dip':>5} {'TP':>5} {'SL':>5} {'Lev':>4} {'Return':>10} "
          f"{'Trades':>7} {'Win%':>7} {'MaxDD':>7} {'Alpha':>9}")
    print("-"*80)

    for r in long_results[:15]:
        alpha = r['return_pct'] - buy_hold
        print(f"{r['direction']:<5} {r['lookback']:>4} {r['dip']*100:>4.0f}% {r['tp']*100:>4.0f}% "
              f"{r['sl']*100:>4.0f}% {r['leverage']:>3}x {r['return_pct']:>+9.1f}% "
              f"{r['num_trades']:>7} {r['win_rate']*100:>6.1f}% {r['max_dd']*100:>6.1f}% {alpha:>+8.1f}%")

    print(f"\n{'='*80}")
    print(f"TOP 15 SHORT (totali: {len(short_results)})")
    print("="*80)

    print(f"\n{'Dir':<5} {'LB':>4} {'Pump':>5} {'TP':>5} {'SL':>5} {'Lev':>4} {'Return':>10} "
          f"{'Trades':>7} {'Win%':>7} {'MaxDD':>7} {'Alpha':>9}")
    print("-"*80)

    for r in short_results[:15]:
        alpha = r['return_pct'] - buy_hold
        print(f"{r['direction']:<5} {r['lookback']:>4} {r['dip']*100:>4.0f}% {r['tp']*100:>4.0f}% "
              f"{r['sl']*100:>4.0f}% {r['leverage']:>3}x {r['return_pct']:>+9.1f}% "
              f"{r['num_trades']:>7} {r['win_rate']*100:>6.1f}% {r['max_dd']*100:>6.1f}% {alpha:>+8.1f}%")

    # Trova configurazioni che battono B&H
    beating_bh_long = [r for r in long_results if r['return_pct'] > buy_hold]
    beating_bh_short = [r for r in short_results if r['return_pct'] > 0]  # Short profittevole

    print(f"\n{'='*80}")
    print(f"CONFIGURAZIONI CHE BATTONO BUY & HOLD")
    print(f"LONG: {len(beating_bh_long)} | SHORT profittevoli: {len(beating_bh_short)}")
    print("="*80)

    # Seleziona 20 trader diversificati
    selected = []

    # 8 migliori LONG
    for r in long_results[:8]:
        name = f"L_{r['lookback']}_{r['leverage']}x"
        if not any(s[0] == name for s in selected):
            selected.append((name, r))

    # 5 migliori SHORT
    for r in short_results[:5]:
        name = f"S_{r['lookback']}_{r['leverage']}x"
        if not any(s[0] == name for s in selected):
            selected.append((name, r))

    # Aggiungi diversificazione per leverage
    for lev in [1, 2, 3]:
        lev_long = [r for r in long_results if r['leverage'] == lev]
        if lev_long and len(selected) < 20:
            name = f"L_lev{lev}"
            if not any(s[0] == name for s in selected):
                selected.append((name, lev_long[0]))

        lev_short = [r for r in short_results if r['leverage'] == lev]
        if lev_short and len(selected) < 20:
            name = f"S_lev{lev}"
            if not any(s[0] == name for s in selected):
                selected.append((name, lev_short[0]))

    # Aggiungi per lookback
    for lb in ['12h', '24h', '48h', '7d']:
        lb_long = [r for r in long_results if r['lookback'] == lb]
        if lb_long and len(selected) < 20:
            name = f"L_{lb}_best"
            if not any(s[0] == name for s in selected):
                selected.append((name, lb_long[0]))

    # Completa con i migliori rimanenti
    for r in good_results:
        if len(selected) >= 20:
            break
        name = f"{r['direction'][0].upper()}_{r['lookback']}_{r['dip']*100:.0f}"
        if not any(s[0] == name for s in selected):
            selected.append((name, r))

    print(f"\n{'='*80}")
    print(f"20 TRADER SELEZIONATI")
    print("="*80)

    print(f"\n{'#':>2} {'Name':<15} {'Dir':<6} {'LB':>4} {'Dip':>5} {'TP':>5} {'SL':>5} "
          f"{'Lev':>4} {'Return':>10} {'Trades':>6}")
    print("-"*80)

    for i, (name, r) in enumerate(selected[:20], 1):
        print(f"{i:>2} {name:<15} {r['direction']:<6} {r['lookback']:>4} "
              f"{r['dip']*100:>4.0f}% {r['tp']*100:>4.0f}% {r['sl']*100:>4.0f}% "
              f"{r['leverage']:>3}x {r['return_pct']:>+9.1f}% {r['num_trades']:>6}")

    # Salva risultati
    output = {
        'timestamp': datetime.now().isoformat(),
        'buy_hold': buy_hold,
        'total_configs': len(configs),
        'valid_results': len(results),
        'long_beating_bh': len(beating_bh_long),
        'short_profitable': len(beating_bh_short),
        'top_20_selected': [{'name': n, **{k: v for k, v in r.items()}} for n, r in selected[:20]],
        'top_long': [{k: v for k, v in r.items()} for r in long_results[:30]],
        'top_short': [{k: v for k, v in r.items()} for r in short_results[:30]],
    }

    output_path = Path('/var/www/html/pippo.cuttalo.com/optimization_results/mega_optimization.json')
    with open(output_path, 'w') as f:
        json.dump(output, f, indent=2)

    print(f"\nSalvato: {output_path}")

    return selected


if __name__ == '__main__':
    main()
