#!/usr/bin/env python3
"""
SERVICE D'OPTIMISATION CONTINUE

Fonctionne en boucle:
1. Télécharge candles fraîches (14 jours)
2. Lance optimisation BULL/BEAR (V10 Range fixe)
3. Analyse résultats et sauvegarde meilleurs params
4. Attend X heures
5. Recommence

Les meilleurs params sont sauvegardés dans: optimal_params.json
La stratégie live peut les charger pour s'adapter automatiquement
"""

import pandas as pd
import json
import time
import logging
from datetime import datetime, timedelta
from pathlib import Path
from binance.client import Client

from optimize_bull_bear import BullBearOptimizer, V10_OPTIMAL_PARAMS

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


class ContinuousOptimizerService:
    """Service d'optimisation continue des paramètres BULL/BEAR"""

    def __init__(
        self,
        update_interval_hours: int = 6,
        lookback_days: int = 14,
        tests_per_run: int = 200,
        optimal_params_file: str = 'optimal_params.json'
    ):
        self.update_interval = update_interval_hours * 3600  # en secondes
        self.lookback_days = lookback_days
        self.tests_per_run = tests_per_run
        self.optimal_params_file = optimal_params_file
        self.binance_client = Client()

        logger.info("=" * 80)
        logger.info("🔄 SERVICE D'OPTIMISATION CONTINUE")
        logger.info("=" * 80)
        logger.info(f"Intervalle mise à jour: {update_interval_hours}h")
        logger.info(f"Lookback période: {lookback_days} jours")
        logger.info(f"Tests par run: {tests_per_run}")
        logger.info(f"Fichier optimal params: {optimal_params_file}")
        logger.info("=" * 80)

    def download_fresh_candles(self) -> pd.DataFrame:
        """Télécharge les candles fraîches depuis Binance"""
        logger.info("📥 Téléchargement candles fraîches...")

        symbol = 'BTCUSDT'
        interval = Client.KLINE_INTERVAL_5MINUTE
        end_time = datetime.now()
        start_time = end_time - timedelta(days=self.lookback_days)

        logger.info(f"   Période: {start_time.strftime('%Y-%m-%d %H:%M')} → "
                   f"{end_time.strftime('%Y-%m-%d %H:%M')}")

        klines = self.binance_client.get_historical_klines(
            symbol,
            interval,
            start_time.strftime('%Y-%m-%d %H:%M:%S'),
            end_time.strftime('%Y-%m-%d %H:%M:%S')
        )

        # Convertir en DataFrame
        df = pd.DataFrame(klines, 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)

        logger.info(f"✅ {len(df)} candles téléchargées")
        logger.info(f"   Prix actuel: ${df.iloc[-1]['close']:.2f}")

        # Sauvegarder
        csv_file = f"candles_{self.lookback_days}d_latest.csv"
        df.to_csv(csv_file, index=False)

        return df

    def run_optimization(self, df: pd.DataFrame) -> dict:
        """Lance l'optimisation et retourne les meilleurs params"""
        logger.info("🔧 Lancement optimisation BULL/BEAR...")

        # Générer nom de fichier unique avec timestamp
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        results_file = f"optimization_results_{timestamp}.json"

        optimizer = BullBearOptimizer(df, results_file=results_file)
        optimizer.optimize(max_tests=self.tests_per_run)

        # Charger résultats
        with open(results_file, 'r') as f:
            results = json.load(f)

        if not results:
            logger.warning("❌ Aucun résultat d'optimisation")
            return None

        # Filtrer configs profitables
        profitable = [r for r in results
                     if r['pnl_pct'] > 0 and r['profit_factor'] > 1.0]

        if not profitable:
            logger.warning("❌ Aucune config profitable")
            return None

        logger.info(f"✅ {len(profitable)}/{len(results)} configs profitables "
                   f"({len(profitable)/len(results)*100:.1f}%)")

        # Trier par PnL
        profitable.sort(key=lambda x: x['pnl_pct'], reverse=True)

        # Prendre la meilleure
        best = profitable[0]

        logger.info(f"🏆 Meilleure config:")
        logger.info(f"   PnL: {best['pnl_pct']:+.2f}%")
        logger.info(f"   PF: {best['profit_factor']:.2f}")
        logger.info(f"   Trades: {best['trades']} {best['trades_by_strategy']}")
        logger.info(f"   Win Rate: {best['win_rate']:.0f}%")

        return {
            'timestamp': timestamp,
            'optimization_results_file': results_file,
            'total_tests': len(results),
            'profitable_tests': len(profitable),
            'best_pnl': best['pnl_pct'],
            'best_profit_factor': best['profit_factor'],
            'best_config': best['config'],
            'v10_range_params': V10_OPTIMAL_PARAMS,
            'trades_distribution': best['trades_by_strategy']
        }

    def save_optimal_params(self, optimization_result: dict):
        """Sauvegarde les paramètres optimaux"""
        logger.info(f"💾 Sauvegarde params optimaux dans {self.optimal_params_file}...")

        with open(self.optimal_params_file, 'w') as f:
            json.dump(optimization_result, f, indent=2)

        logger.info("✅ Params sauvegardés")

    def run_cycle(self):
        """Exécute un cycle complet d'optimisation"""
        logger.info("")
        logger.info("=" * 80)
        logger.info(f"🔄 NOUVEAU CYCLE - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        logger.info("=" * 80)

        try:
            # 1. Télécharger candles
            df = self.download_fresh_candles()

            # 2. Lancer optimisation
            result = self.run_optimization(df)

            if result:
                # 3. Sauvegarder params optimaux
                self.save_optimal_params(result)

                logger.info("")
                logger.info("✅ Cycle terminé avec succès")
            else:
                logger.warning("⚠️ Cycle terminé sans résultat profitable")

        except Exception as e:
            logger.error(f"❌ Erreur durant le cycle: {e}", exc_info=True)

        logger.info("=" * 80)

    def run(self):
        """Boucle principale du service"""
        logger.info("🚀 Démarrage du service d'optimisation continue...")

        cycle_count = 0

        while True:
            cycle_count += 1
            logger.info(f"\n📊 CYCLE #{cycle_count}")

            # Exécuter cycle
            self.run_cycle()

            # Attendre avant prochain cycle
            logger.info(f"⏳ Attente {self.update_interval/3600:.0f}h avant prochain cycle...")
            time.sleep(self.update_interval)


def main():
    """Point d'entrée"""
    import argparse

    parser = argparse.ArgumentParser(
        description='Service d\'optimisation continue BULL/BEAR'
    )
    parser.add_argument(
        '--interval',
        type=int,
        default=6,
        help='Intervalle entre mises à jour (heures) [défaut: 6]'
    )
    parser.add_argument(
        '--lookback',
        type=int,
        default=14,
        help='Nombre de jours de données pour backtest [défaut: 14]'
    )
    parser.add_argument(
        '--tests',
        type=int,
        default=200,
        help='Nombre de tests par optimisation [défaut: 200]'
    )
    parser.add_argument(
        '--once',
        action='store_true',
        help='Exécuter une seule fois et quitter'
    )

    args = parser.parse_args()

    service = ContinuousOptimizerService(
        update_interval_hours=args.interval,
        lookback_days=args.lookback,
        tests_per_run=args.tests
    )

    if args.once:
        logger.info("Mode single-run activé")
        service.run_cycle()
    else:
        service.run()


if __name__ == '__main__':
    main()
