#!/usr/bin/env python3
"""
🚀 OPTIMISEUR EMA HYPERLIQUID - VERSION STANDALONE
Version simplifiée sans dépendances complexes pour éviter les conflits
"""

import sqlite3
import numpy as np
import logging
import json
import time
import os
from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')

# Configuration du logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('ema_optimization.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class BacktestResult:
    """Résultat d'un backtest"""
    def __init__(self):
        self.symbol = ""
        self.timeframe = ""
        self.fast_ema = 0
        self.slow_ema = 0
        self.total_return = 0.0
        self.annual_return = 0.0
        self.sharpe_ratio = 0.0
        self.max_drawdown = 0.0
        self.win_rate = 0.0
        self.total_trades = 0
        self.profit_factor = 0.0
        self.start_date = ""
        self.end_date = ""
        self.final_balance = 0.0
        self.avg_trade_return = 0.0
        self.best_trade = 0.0
        self.worst_trade = 0.0

class SimpleDataManager:
    """Gestionnaire de données simplifié"""
    
    def __init__(self, db_path: str = "ema_data.db"):
        self.db_path = db_path
        self.info = None
        
        self._init_api()
        self._init_database()
        
        self.timeframes = {
            "1m": {"interval": "1m", "minutes": 1},
            "5m": {"interval": "5m", "minutes": 5},
            "15m": {"interval": "15m", "minutes": 15},
            "30m": {"interval": "30m", "minutes": 30},
            "1h": {"interval": "1h", "minutes": 60},
            "4h": {"interval": "4h", "minutes": 240},  
            "1d": {"interval": "1d", "minutes": 1440}
        }
    
    def _init_api(self):
        """Initialise l'API Hyperliquid"""
        try:
            from hyperliquid.info import Info
            from hyperliquid.utils import constants
            self.info = Info(constants.MAINNET_API_URL, skip_ws=True)
            logger.info("API Hyperliquid connectee")
        except Exception as e:
            logger.warning(f"⚠️ API indisponible: {e} - Mode simulation")
            self.info = None
    
    def _init_database(self):
        """Initialise la base de données"""
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS ohlcv_data (
                    symbol TEXT NOT NULL,
                    timeframe TEXT NOT NULL,
                    timestamp INTEGER NOT NULL,
                    open REAL NOT NULL,
                    high REAL NOT NULL,
                    low REAL NOT NULL,
                    close REAL NOT NULL,
                    volume REAL NOT NULL,
                    PRIMARY KEY(symbol, timeframe, timestamp)
                )
            """)
            
            conn.commit()
            conn.close()
            logger.info(f"✅ Base de données: {self.db_path}")
            
        except Exception as e:
            logger.error(f"❌ Erreur DB: {e}")
            raise
    
    def get_symbols(self) -> List[str]:
        """Récupère les symboles à tester"""
        return ["BTC-PERP", "ETH-PERP", "SOL-PERP", "AVAX-PERP", "MATIC-PERP"]
    
    def fetch_and_store_data(self, symbol: str, timeframe: str, days: int = 365):
        """Télécharge et stocke les données RÉELLES uniquement"""
        if not self.info:
            logger.error(f"❌ API Hyperliquid indisponible - ARRÊT pour {symbol} {timeframe}")
            logger.error("❌ Impossible de continuer sans vraies données")
            return
            
        try:
            data = self._fetch_real_data(symbol, timeframe, days)
            
            if not data:
                logger.error(f"❌ Aucune donnée réelle pour {symbol} {timeframe}")
                return
                
            self._store_data(symbol, timeframe, data)
            logger.info(f"OK {symbol} {timeframe}: {len(data)} bougies REELLES")
                
        except Exception as e:
            logger.error(f"❌ Erreur téléchargement {symbol} {timeframe}: {e}")
    
    def _fetch_real_data(self, symbol: str, timeframe: str, days: int) -> List[Dict]:
        """Récupère vraies données depuis Hyperliquid"""
        try:
            coin = symbol.replace("-PERP", "")
            interval = self.timeframes[timeframe]["interval"]
            
            end_time = int(time.time() * 1000)
            start_time = end_time - (days * 24 * 60 * 60 * 1000)
            
            # API Hyperliquid correcte
            logger.info(f"Telechargement {coin} {interval}")
            
            candles = self.info.candles_snapshot(coin, interval, start_time, end_time)
            logger.info(f"Recu {len(candles) if candles else 0} bougies")
            
            if not candles:
                return []
            
            # Debug: vérifier le type de données reçues
            logger.info(f"Type candles: {type(candles)}, Contenu: {candles}")
            
            # Convertir selon le format Hyperliquid (dictionnaire)
            if isinstance(candles, list) and len(candles) > 0:
                data = []
                for candle in candles:
                    if isinstance(candle, dict):
                        # Format Hyperliquid avec clés 't', 'o', 'h', 'l', 'c', 'v'
                        data.append({
                            "timestamp": int(candle['t']),
                            "open": float(candle['o']),
                            "high": float(candle['h']),
                            "low": float(candle['l']),
                            "close": float(candle['c']),
                            "volume": float(candle['v'])
                        })
                logger.info(f"Convertis {len(data)} bougies")
                return data
            else:
                logger.error(f"Format inattendu: {type(candles)}")
                return []
            
        except Exception as e:
            logger.error(f"Erreur fetch {symbol}: {e}")
            return []
    
    # FONCTION SUPPRIMÉE : Aucune données simulées autorisées
    # Seules les données réelles Hyperliquid sont utilisées
    
    def _store_data(self, symbol: str, timeframe: str, data: List[Dict]):
        """Stocke les données en base"""
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            records = []
            for item in data:
                records.append((
                    symbol, timeframe, item["timestamp"],
                    item["open"], item["high"], item["low"],
                    item["close"], item["volume"]
                ))
            
            cursor.executemany("""
                INSERT OR REPLACE INTO ohlcv_data 
                (symbol, timeframe, timestamp, open, high, low, close, volume)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
            """, records)
            
            conn.commit()
            conn.close()
            
        except Exception as e:
            logger.error(f"Erreur stockage: {e}")
    
    def get_data(self, symbol: str, timeframe: str) -> List[Dict]:
        """Récupère les données stockées"""
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            cursor.execute("""
                SELECT timestamp, open, high, low, close, volume
                FROM ohlcv_data
                WHERE symbol = ? AND timeframe = ?
                ORDER BY timestamp ASC
            """, (symbol, timeframe))
            
            rows = cursor.fetchall()
            conn.close()
            
            data = []
            for row in rows:
                data.append({
                    "timestamp": row[0],
                    "open": row[1],
                    "high": row[2],
                    "low": row[3],
                    "close": row[4],
                    "volume": row[5]
                })
            
            return data
            
        except Exception as e:
            logger.error(f"Erreur récupération: {e}")
            return []

class SimpleEMABacktester:
    """Backtester EMA simplifié"""
    
    def __init__(self, initial_capital: float = 10000, commission: float = 0.001):
        self.initial_capital = initial_capital
        self.commission = commission
    
    def calculate_ema(self, prices: List[float], period: int) -> List[float]:
        """Calcule l'EMA de façon simple"""
        if not prices or period <= 0:
            return []
        
        alpha = 2.0 / (period + 1)
        ema = [prices[0]]
        
        for i in range(1, len(prices)):
            ema_value = alpha * prices[i] + (1 - alpha) * ema[-1]
            ema.append(ema_value)
        
        return ema
    
    def backtest(self, data: List[Dict], fast_period: int, slow_period: int) -> BacktestResult:
        """Execute un backtest EMA crossover"""
        
        result = BacktestResult()
        
        if len(data) < max(fast_period, slow_period) * 3:
            return result
        
        # Extraire les prix de clôture
        closes = [d["close"] for d in data]
        timestamps = [d["timestamp"] for d in data]
        
        # Calculer les EMAs
        ema_fast = self.calculate_ema(closes, fast_period)
        ema_slow = self.calculate_ema(closes, slow_period)
        
        if len(ema_fast) < 100 or len(ema_slow) < 100:
            return result
        
        # Générer les signaux
        signals = []
        for i in range(len(ema_fast)):
            if i == 0:
                signals.append(0)
            elif ema_fast[i] > ema_slow[i]:
                signals.append(1)  # Long
            else:
                signals.append(-1)  # Short
        
        # Simuler le trading
        balance = self.initial_capital
        position = 0
        entry_price = 0
        trades = []
        equity_curve = [balance]
        
        for i in range(1, len(signals)):
            current_price = closes[i]
            current_signal = signals[i]
            
            # Changement de position
            if position != current_signal:
                
                # Fermer position existante
                if position != 0:
                    if position == 1:  # Fermer long
                        pnl = (current_price - entry_price) / entry_price
                    else:  # Fermer short
                        pnl = (entry_price - current_price) / entry_price
                    
                    trade_balance = balance * (1 + pnl - self.commission)
                    trade_return = (trade_balance - balance) / balance
                    
                    trades.append({
                        "return": trade_return,
                        "pnl_pct": pnl * 100,
                        "position": "LONG" if position == 1 else "SHORT"
                    })
                    
                    balance = trade_balance
                
                # Ouvrir nouvelle position
                if current_signal != 0:
                    position = current_signal
                    entry_price = current_price
                    balance *= (1 - self.commission)
                else:
                    position = 0
            
            # Equity en temps réel
            if position != 0:
                if position == 1:
                    unrealized_pnl = (current_price - entry_price) / entry_price
                else:
                    unrealized_pnl = (entry_price - current_price) / entry_price
                current_equity = balance * (1 + unrealized_pnl)
            else:
                current_equity = balance
            
            equity_curve.append(current_equity)
        
        # Fermer la dernière position
        if position != 0:
            final_price = closes[-1]
            if position == 1:
                pnl = (final_price - entry_price) / entry_price
            else:
                pnl = (entry_price - final_price) / entry_price
            
            final_balance = balance * (1 + pnl - self.commission)
            trades.append({
                "return": (final_balance - balance) / balance,
                "pnl_pct": pnl * 100,
                "position": "LONG" if position == 1 else "SHORT"
            })
            balance = final_balance
        
        # Calculer les métriques
        if not trades:
            return result
        
        return self._calculate_metrics(data, trades, balance, equity_curve)
    
    def _calculate_metrics(self, data: List[Dict], trades: List[Dict], 
                          final_balance: float, equity_curve: List[float]) -> BacktestResult:
        """Calcule les métriques de performance"""
        
        result = BacktestResult()
        
        # Métriques de base
        result.total_return = (final_balance - self.initial_capital) / self.initial_capital
        result.final_balance = final_balance
        
        # Dates
        start_ts = data[0]["timestamp"]
        end_ts = data[-1]["timestamp"]
        result.start_date = datetime.fromtimestamp(start_ts/1000).strftime("%Y-%m-%d")
        result.end_date = datetime.fromtimestamp(end_ts/1000).strftime("%Y-%m-%d")
        
        # Rendement annualisé
        days = (end_ts - start_ts) / (1000 * 60 * 60 * 24)
        result.annual_return = result.total_return * (365 / max(days, 1))
        
        # Métriques des trades
        trade_returns = [t["return"] for t in trades]
        winning_trades = [t for t in trades if t["return"] > 0]
        losing_trades = [t for t in trades if t["return"] < 0]
        
        result.total_trades = len(trades)
        result.win_rate = len(winning_trades) / len(trades) if trades else 0
        result.avg_trade_return = np.mean(trade_returns) if trade_returns else 0
        
        # Profit factor
        gross_profit = sum(t["return"] for t in winning_trades)
        gross_loss = abs(sum(t["return"] for t in losing_trades))
        result.profit_factor = gross_profit / gross_loss if gross_loss > 0 else 999
        
        # Best/Worst trades
        result.best_trade = max(trade_returns) if trade_returns else 0
        result.worst_trade = min(trade_returns) if trade_returns else 0
        
        # Sharpe ratio
        if len(equity_curve) > 1:
            returns = []
            for i in range(1, len(equity_curve)):
                ret = (equity_curve[i] - equity_curve[i-1]) / equity_curve[i-1]
                returns.append(ret)
            
            if returns and np.std(returns) > 0:
                result.sharpe_ratio = (np.mean(returns) / np.std(returns)) * np.sqrt(252)
        
        # Max drawdown
        peak = equity_curve[0]
        max_dd = 0
        for equity in equity_curve:
            if equity > peak:
                peak = equity
            dd = (peak - equity) / peak
            if dd > max_dd:
                max_dd = dd
        
        result.max_drawdown = max_dd
        
        return result

class SimpleOptimizer:
    """Optimiseur EMA simplifié"""
    
    def __init__(self):
        self.data_manager = SimpleDataManager()
        self.backtester = SimpleEMABacktester()
        self.results = []
    
    def run_optimization(self):
        """Lance l'optimisation complète"""
        
        print("🚀 OPTIMISEUR EMA HYPERLIQUID - VERSION STANDALONE")
        print("=" * 50)
        
        # VÉRIFICATION CRITIQUE : API Hyperliquid disponible
        if not self.data_manager.info:
            print("❌ ERREUR FATALE: API Hyperliquid indisponible")
            print("❌ Impossible de continuer sans données réelles")
            print("❌ Vérifiez votre connexion internet et les dépendances")
            return
        
        print("✅ API Hyperliquid connectée - Données réelles garanties")
        
        # Configuration
        symbols = self.data_manager.get_symbols()
        timeframes = ["1m", "5m", "15m", "30m", "1h", "4h", "1d"]
        fast_periods = [5, 8, 12, 15, 20, 25]
        slow_periods = [20, 30, 50, 75, 100, 150]
        
        print(f"Symboles: {symbols}")
        print(f"Timeframes: {timeframes}")
        print(f"EMA rapides: {fast_periods}")
        print(f"EMA lentes: {slow_periods}")
        
        # Phase 1: Téléchargement
        print("\n📥 PHASE 1: TÉLÉCHARGEMENT")
        for symbol in symbols:
            for timeframe in timeframes:
                self.data_manager.fetch_and_store_data(symbol, timeframe, 365)
                time.sleep(0.3)
        
        # Phase 2: Optimisation
        print("\n🧮 PHASE 2: OPTIMISATION")
        total_combinations = 0
        valid_combinations = []
        
        for symbol in symbols:
            for timeframe in timeframes:
                for fast in fast_periods:
                    for slow in slow_periods:
                        if fast < slow:
                            valid_combinations.append((symbol, timeframe, fast, slow))
                            total_combinations += 1
        
        print(f"💫 {total_combinations} combinaisons à tester")
        
        completed = 0
        start_time = time.time()
        
        for symbol, timeframe, fast, slow in valid_combinations:
            completed += 1
            
            if completed % 50 == 0:
                elapsed = time.time() - start_time
                rate = completed / elapsed if elapsed > 0 else 0
                eta = (total_combinations - completed) / rate if rate > 0 else 0
                print(f"⏳ [{completed}/{total_combinations}] - ETA: {eta/60:.1f}min")
            
            try:
                data = self.data_manager.get_data(symbol, timeframe)
                
                if len(data) < max(fast, slow) * 3:
                    continue
                
                result = self.backtester.backtest(data, fast, slow)
                
                result.symbol = symbol
                result.timeframe = timeframe
                result.fast_ema = fast
                result.slow_ema = slow
                
                if result.total_trades >= 10:
                    self.results.append(result)
                
            except Exception as e:
                logger.error(f"❌ {symbol} {timeframe} EMA({fast},{slow}): {e}")
        
        elapsed = time.time() - start_time
        print(f"✅ Optimisation terminée en {elapsed/60:.1f}min")
        print(f"📈 {len(self.results)} résultats valides")
        
        # Phase 3: Rapports
        self._generate_reports()
    
    def _generate_reports(self):
        """Génère les rapports"""
        
        if not self.results:
            print("⚠️ Aucun résultat à analyser")
            return
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        # Rapport JSON
        self._save_json_report(f"ema_results_{timestamp}.json")
        
        # Rapport texte
        self._save_text_report(f"ema_top_strategies_{timestamp}.txt")
        
        # Affichage console
        self._print_top_results()
    
    def _save_json_report(self, filename: str):
        """Sauvegarde JSON"""
        data = []
        for r in self.results:
            data.append({
                "symbol": r.symbol,
                "timeframe": r.timeframe,
                "fast_ema": r.fast_ema,
                "slow_ema": r.slow_ema,
                "total_return": round(r.total_return, 4),
                "annual_return": round(r.annual_return, 4),
                "sharpe_ratio": round(r.sharpe_ratio, 4),
                "max_drawdown": round(r.max_drawdown, 4),
                "win_rate": round(r.win_rate, 4),
                "total_trades": r.total_trades,
                "profit_factor": round(r.profit_factor, 4),
                "final_balance": round(r.final_balance, 2)
            })
        
        with open(filename, "w") as f:
            json.dump(data, f, indent=2)
        
        print(f"💾 Rapport JSON: {filename}")
    
    def _save_text_report(self, filename: str):
        """Rapport texte"""
        sorted_results = sorted(self.results, key=lambda x: x.total_return, reverse=True)
        
        with open(filename, "w") as f:
            f.write("🏆 TOP 50 STRATÉGIES EMA HYPERLIQUID\n")
            f.write("=" * 50 + "\n")
            f.write(f"Généré: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n")
            f.write(f"Total testé: {len(self.results)}\n\n")
            
            for i, r in enumerate(sorted_results[:50], 1):
                f.write(f"{i:2d}. {r.symbol} | {r.timeframe} | EMA({r.fast_ema},{r.slow_ema})\n")
                f.write(f"    Return: {r.total_return*100:8.2f}% | Annual: {r.annual_return*100:8.2f}%\n")
                f.write(f"    Sharpe: {r.sharpe_ratio:6.2f} | MaxDD: {r.max_drawdown*100:6.2f}%\n")
                f.write(f"    Trades: {r.total_trades:3d} | WinRate: {r.win_rate*100:6.2f}%\n")
                f.write(f"    PF: {r.profit_factor:6.2f} | Final: ${r.final_balance:,.0f}\n\n")
        
        print(f"📋 Rapport texte: {filename}")
    
    def _print_top_results(self):
        """Affiche top 10"""
        
        print("\n" + "🏆" * 20 + " TOP 10 EMA " + "🏆" * 20)
        print("=" * 70)
        
        top_results = sorted(self.results, key=lambda x: x.total_return, reverse=True)[:10]
        
        for i, r in enumerate(top_results, 1):
            print(f"\n{i:2d}. 💰 {r.symbol} | {r.timeframe} | EMA({r.fast_ema},{r.slow_ema})")
            print(f"    📈 Return: {r.total_return*100:8.2f}% | Annual: {r.annual_return*100:8.2f}%")
            print(f"    📊 Sharpe: {r.sharpe_ratio:6.2f} | MaxDD: {r.max_drawdown*100:6.2f}%")
            print(f"    🎯 Trades: {r.total_trades:3d} | WinRate: {r.win_rate*100:6.2f}%")
            print(f"    💵 PF: {r.profit_factor:6.2f} | Final: ${r.final_balance:,.0f}")
        
        # Stats globales
        returns = [r.total_return for r in self.results]
        sharpes = [r.sharpe_ratio for r in self.results if -10 < r.sharpe_ratio < 10]
        
        print(f"\n📊 STATISTIQUES:")
        print(f"Résultats: {len(self.results)}")
        print(f"Return moyen: {np.mean(returns)*100:.2f}%")
        print(f"Return médian: {np.median(returns)*100:.2f}%")
        print(f"Sharpe moyen: {np.mean(sharpes):.2f}")
        print(f"Stratégies +: {len([r for r in self.results if r.total_return > 0])}")

def main():
    """Fonction principale"""
    
    print("🚀 HYPERLIQUID EMA OPTIMIZER - STANDALONE")
    print("Optimisation automatique des stratégies EMA crossover")
    print("Version simplifiée sans dépendances complexes")
    print()
    
    optimizer = SimpleOptimizer()
    optimizer.run_optimization()
    
    print("\n🎉 OPTIMISATION TERMINÉE !")
    print("Consultez les fichiers générés pour les résultats détaillés")

if __name__ == "__main__":
    main()