﻿#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script de Reset complet du système de trading - VERSION 3.0
============================================================

Fonctionnalités :
- Arrêt propre de TOUS les processus Python du projet
- Nettoyage des fichiers PID et cache
- Relance du dashboard_api_server.py (qui lance aussi le bot)
- Vérification que tout fonctionne

SÉCURITÉ: Ne touche PAS à VS Code, Pylance, ou autres applications !
"""

import os
import sys
import subprocess
import time
import json
import hmac
import hashlib
import urllib.request
import urllib.parse
import urllib.error
try:
    import psutil
except ImportError:
    psutil = None

# Chemin du projet
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(SCRIPT_DIR)

# Chemin du spy prod
PROD_DIR = '/home/ubuntu/crypto_trading_prod'

# Port du dashboard
DASHBOARD_PORT = 8889

# PowerShell path (Windows only)
POWERSHELL = r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'


def get_venv_python():
    """Retourne le chemin Python du venv selon l'OS (Windows ou Linux/Mac)."""
    if sys.platform == 'win32':
        path = os.path.join(SCRIPT_DIR, '.venv', 'Scripts', 'python.exe')
    else:
        path = os.path.join(SCRIPT_DIR, '.venv', 'bin', 'python3')
    return path if os.path.exists(path) else None


def _is_port_listening(port):
    """Cross-platform: vérifie si un port TCP local accepte des connexions."""
    import socket
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.settimeout(1)
        return s.connect_ex(('127.0.0.1', port)) == 0


def _pid_is_alive(pid_str):
    """Cross-platform: vérifie si un PID est actif."""
    try:
        pid = int(pid_str)
        if psutil:
            return psutil.pid_exists(pid)
        os.kill(pid, 0)
        return True
    except (ValueError, OSError):
        return False


def _spawn_detached(cmd, stdout=None, stderr=None, cwd=None):
    """Lance un processus détaché (cross-platform, sans fenêtre sur Windows)."""
    kwargs = dict(
        cwd=cwd if cwd is not None else SCRIPT_DIR,
        stdout=stdout if stdout is not None else subprocess.DEVNULL,
        stderr=stderr if stderr is not None else subprocess.DEVNULL,
        stdin=subprocess.DEVNULL,
        start_new_session=True,
    )
    if sys.platform == 'win32':
        CREATE_NO_WINDOW = 0x08000000
        DETACHED_PROCESS = 0x00000008
        CREATE_NEW_PROCESS_GROUP = 0x00000200
        kwargs['creationflags'] = CREATE_NO_WINDOW | DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
    return subprocess.Popen(cmd, **kwargs)


# Couleurs console
class Colors:
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    BLUE = '\033[94m'
    CYAN = '\033[96m'
    RESET = '\033[0m'
    BOLD = '\033[1m'


def print_header(title, phase=None):
    """Affiche un header stylisé"""
    print(f"\n{Colors.CYAN}{'=' * 60}{Colors.RESET}")
    if phase:
        print(f"{Colors.BOLD}{Colors.CYAN}  PHASE {phase}: {title}{Colors.RESET}")
    else:
        print(f"{Colors.BOLD}{Colors.CYAN}  {title}{Colors.RESET}")
    print(f"{Colors.CYAN}{'=' * 60}{Colors.RESET}\n")


def print_success(msg):
    print(f"  {Colors.GREEN}[OK] {msg}{Colors.RESET}")


def print_warning(msg):
    print(f"  {Colors.YELLOW}[!] {msg}{Colors.RESET}")


def print_error(msg):
    print(f"  {Colors.RED}[X] {msg}{Colors.RESET}")


def print_info(msg):
    print(f"  {Colors.BLUE}[i] {msg}{Colors.RESET}")


def stop_all_processes():
    """Arrête tous les processus Python du projet de manière robuste.
    
    Ordre critique:
    1. Watchdog en premier (évite qu'il respawn pendant le reset)
    2. Tous les autres processus du projet (venv ET système Python)
    3. Second passage pour tuer les survivants / processus respawnés
    4. Port 8889 forcé libre
    """
    print_header("ARRET DES PROCESSUS", 1)

    stopped_count = 0

    if sys.platform == 'win32':
        # ── Windows : PowerShell + CimInstance ──────────────────────────────
        ps_script = f'''
$ErrorActionPreference = "SilentlyContinue"
$stoppedPids = @()

function Kill-ProjectPython {{
    $all = Get-CimInstance Win32_Process -Filter "Name='python.exe' OR Name='pythonw.exe'" -ErrorAction SilentlyContinue
    foreach ($proc in $all) {{
        $cmd = $proc.CommandLine
        if ($cmd -and
            $cmd -like "*crypto_trading_bot*" -and
            $cmd -notlike "*Reset_trading*" -and
            $cmd -notlike "*jedi*" -and
            $cmd -notlike "*language_server*" -and
            $cmd -notlike "*pylance*") {{
            Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue
            $script:stoppedPids += $proc.ProcessId
        }}
    }}
}}

$watchers = Get-CimInstance Win32_Process -Filter "Name='python.exe'" -ErrorAction SilentlyContinue |
    Where-Object {{ $_.CommandLine -like "*bot_watchdog*" -or $_.CommandLine -like "*watchdog*" }}
foreach ($w in $watchers) {{
    Stop-Process -Id $w.ProcessId -Force -ErrorAction SilentlyContinue
    $stoppedPids += $w.ProcessId
}}
Start-Sleep -Milliseconds 800

$conn = Get-NetTCPConnection -LocalPort {DASHBOARD_PORT} -ErrorAction SilentlyContinue
foreach ($c in $conn) {{
    if ($c.OwningProcess -gt 4) {{
        Stop-Process -Id $c.OwningProcess -Force -ErrorAction SilentlyContinue
        $stoppedPids += $c.OwningProcess
    }}
}}

Kill-ProjectPython
Start-Sleep -Milliseconds 1200
Kill-ProjectPython

$stoppedPids | Select-Object -Unique | ForEach-Object {{ Write-Output $_ }}
'''
        try:
            result = subprocess.run(
                [POWERSHELL, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps_script],
                capture_output=True,
                text=True,
                timeout=30
            )
            for line in result.stdout.strip().split('\n'):
                if line.strip() and line.strip().isdigit():
                    print_success(f"Processus {line.strip()} arrêté")
                    stopped_count += 1
        except subprocess.TimeoutExpired:
            print_warning("Timeout lors de l'arrêt des processus")
        except Exception as e:
            print_error(f"Erreur: {e}")

    else:
        # ── Linux/Mac : psutil ────────────────────────────────────────────────
        if not psutil:
            print_warning("psutil non disponible — pip install psutil")
        else:
            current_pid = os.getpid()

            def is_project_proc(proc):
                try:
                    if 'python' not in proc.name().lower():
                        return False
                    cmd = ' '.join(proc.cmdline())
                    return (('crypto_trading_bot' in cmd or 'crypto_trading_prod' in cmd) and
                            'Reset_trading' not in cmd and
                            'jedi' not in cmd and
                            'language_server' not in cmd and
                            'pylance' not in cmd and
                            proc.pid != current_pid)
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    return False

            # Passe 1: watchdog en premier (évite respawn)
            for proc in psutil.process_iter():
                if is_project_proc(proc):
                    try:
                        cmd = ' '.join(proc.cmdline())
                        if 'bot_watchdog' in cmd or 'watchdog' in cmd:
                            proc.kill()
                            print_success(f"Processus {proc.pid} arrêté")
                            stopped_count += 1
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        pass
            time.sleep(0.5)

            # Passe 2: port DASHBOARD_PORT
            try:
                for conn in psutil.net_connections():
                    if conn.laddr.port == DASHBOARD_PORT and conn.pid and conn.pid != current_pid:
                        try:
                            psutil.Process(conn.pid).kill()
                            print_success(f"Processus {conn.pid} arrêté (port {DASHBOARD_PORT})")
                            stopped_count += 1
                        except (psutil.NoSuchProcess, psutil.AccessDenied):
                            pass
            except Exception:
                pass

            # Passes 3 et 4: tout le reste du projet
            for _ in range(2):
                for proc in psutil.process_iter():
                    if is_project_proc(proc):
                        try:
                            proc.kill()
                            print_success(f"Processus {proc.pid} arrêté")
                            stopped_count += 1
                        except (psutil.NoSuchProcess, psutil.AccessDenied):
                            pass
                time.sleep(0.8)

    # Attendre stabilisation
    time.sleep(2)

    # Nettoyage des fichiers PID
    pid_files = ['bot.pid', 'auto_updater.pid', 'dashboard_api.pid']
    for pid_file in pid_files:
        filepath = os.path.join(SCRIPT_DIR, pid_file)
        if os.path.exists(filepath):
            try:
                os.remove(filepath)
                print_info(f"{pid_file} supprime")
            except:
                pass

    # Nettoyage des fichiers temporaires
    temp_files = ['sell_all_signal.json', 'ai_realtime_status.json']
    for temp_file in temp_files:
        filepath = os.path.join(SCRIPT_DIR, temp_file)
        if os.path.exists(filepath):
            try:
                os.remove(filepath)
            except:
                pass

    if stopped_count == 0:
        print_info("Aucun processus du projet en cours")
    else:
        print_success(f"{stopped_count} processus arrêté(s)")

    return stopped_count


def _read_binance_config():
    """Lit les credentials Binance depuis config.py — helper partagé (évite triplication)."""
    config_vars = {}
    config_file = os.path.join(SCRIPT_DIR, 'config.py')
    with open(config_file, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.rstrip()
            if line.startswith('TESTNET_MODE'):
                config_vars['testnet'] = 'True' in line
            elif line.startswith('BINANCE_API_KEY') and '=' in line:
                config_vars['api_key'] = line.split('"')[1] if '"' in line else ''
            elif line.startswith('BINANCE_API_SECRET') and '=' in line:
                config_vars['api_secret'] = line.split('"')[1] if '"' in line else ''
    return config_vars


def _get_server_time_offset(base_url):
    """Retourne l'offset ms entre l'horloge locale et le serveur Binance."""
    try:
        req = urllib.request.Request(f"{base_url}/api/v3/time")
        with urllib.request.urlopen(req, timeout=5) as resp:
            data = json.loads(resp.read().decode('utf-8'))
            return data['serverTime'] - int(time.time() * 1000)
    except Exception:
        return 0


def test_binance_connection():
    """Teste la connexion à l'API Binance"""
    print_header("TEST CONNEXION BINANCE", "0")
    
    try:
        # Lire la configuration
        if not os.path.exists(os.path.join(SCRIPT_DIR, 'config.py')):
            print_error("config.py non trouvé")
            return False
        
        # Charger les credentials via le helper partagé
        config_vars = _read_binance_config()
        
        testnet_mode = config_vars.get('testnet', True)
        api_key = config_vars.get('api_key', '')
        api_secret = config_vars.get('api_secret', '')
        
        if not api_key or not api_secret:
            print_error("Clés API Binance non configurées dans config.py")
            return False
        
        # URL de l'API selon le mode
        base_url = 'https://testnet.binance.vision' if testnet_mode else 'https://api.binance.com'
        mode_str = 'TESTNET (fictif)' if testnet_mode else 'PRODUCTION (reel)'
        
        print_info(f"Mode: {mode_str}")
        print_info(f"API Key: {api_key[:8]}...{api_key[-4:]}")
        
        # Test 1: Ping
        try:
            ping_url = f"{base_url}/api/v3/ping"
            req = urllib.request.Request(ping_url)
            with urllib.request.urlopen(req, timeout=5) as response:
                if response.status == 200:
                    print_success("Ping Binance réussi")
                else:
                    print_error(f"Ping échoué: {response.status}")
                    return False
        except Exception as e:
            print_error(f"Ping échoué: {e}")
            return False
        
        # Test 1.5: Récupérer l'offset serveur via helper partagé
        server_time_offset = _get_server_time_offset(base_url)
        if server_time_offset:
            print_info(f"Synchronisation temps: offset {server_time_offset}ms")
        
        # Test 2: Account info (avec authentification)
        try:
            # Utiliser le timestamp synchronisé avec le serveur
            timestamp = int(time.time() * 1000) + server_time_offset
            # Ajouter recvWindow pour tolérance (5000ms = 5 secondes)
            query_string = f"timestamp={timestamp}&recvWindow=5000"
            signature = hmac.new(
                api_secret.encode('utf-8'),
                query_string.encode('utf-8'),
                hashlib.sha256
            ).hexdigest()
            
            account_url = f"{base_url}/api/v3/account?{query_string}&signature={signature}"
            req = urllib.request.Request(
                account_url,
                headers={'X-MBX-APIKEY': api_key}
            )
            
            with urllib.request.urlopen(req, timeout=5) as response:
                if response.status == 200:
                    data = json.loads(response.read().decode('utf-8'))
                    
                    # Afficher le solde USDT
                    usdt_balance = 0
                    for balance in data.get('balances', []):
                        if balance['asset'] == 'USDT':
                            usdt_balance = float(balance['free'])
                            break
                    
                    print_success(f"Authentification réussie")
                    print_info(f"Balance USDT: {usdt_balance:.2f} USDT")
                    
                    if usdt_balance < 10:
                        print_warning(f"Balance faible: {usdt_balance:.2f} USDT")
                        if testnet_mode:
                            print_info("Testnet: Recharger sur https://testnet.binance.vision/")
                        else:
                            print_warning("Production: Vérifier votre capital")
                    
                    return True
                else:
                    print_error(f"Authentification échouée: {response.status}")
                    return False
                    
        except urllib.error.HTTPError as e:
            error_msg = e.read().decode('utf-8')
            print_error(f"Erreur API: {e.code}")
            try:
                error_data = json.loads(error_msg)
                print_error(f"Message: {error_data.get('msg', 'Erreur inconnue')}")
            except:
                print_error(f"Details: {error_msg}")
            return False
        except Exception as e:
            print_error(f"Erreur authentification: {e}")
            return False
            
    except Exception as e:
        print_error(f"Erreur test Binance: {e}")
        return False


def clean_watchlist():
    """🔧 FIX 02/03: Nettoie la watchlist des symboles non exploitables.
    
    Filtre automatiquement:
    - Stablecoins (USD1, USDC, BUSD, etc.)
    - Fiat (EUR, GBP, etc.)
    - Or tokenisé (PAXG, XAUT)
    - Wrapped tokens (WBTC, BETH, etc.)
    - Tokens test (caractères non-ASCII)
    - Symboles inexistants sur Binance
    """
    print_header("NETTOYAGE WATCHLIST", "0.2")
    
    watchlist_file = os.path.join(SCRIPT_DIR, 'watchlist.json')
    if not os.path.exists(watchlist_file):
        print_warning("watchlist.json introuvable, rien à nettoyer")
        return True
    
    try:
        with open(watchlist_file, 'r', encoding='utf-8') as f:
            wl_data = json.load(f)
        symbols = wl_data.get('symbols', [])
        original_count = len(symbols)
        print_info(f"Watchlist actuelle: {original_count} symboles")
        
        # === SYMBOLES NON EXPLOITABLES (hardcodé) ===
        NON_EXPLOITABLE = {
            # Stablecoins
            'USDTUSDT', 'USDCUSDT', 'BUSDUSDT', 'TUSDUSDT', 'DAIUSDT',
            'FDUSDUSDT', 'USDPUSDT', 'FRAXUSDT', 'LUSDUSDT', 'USTCUSDT',
            'PYUSDUSDT', 'USD1USDT', 'GUSDUSDT',
            # Wrapped tokens
            'WBTCUSDT', 'BETHUSDT', 'CBETHUSDT', 'STETHUSDT', 'WBNBUSDT',
            'WETHUSDT', 'RETHUSDT', 'WBETHUSDT',
            # Devises fiat
            'EURUSDT', 'GBPUSDT', 'BRLUSDT', 'AUDUSDT', 'TRYUSDT',
            'BIDRUSDT', 'UAHUSDT', 'BKRWUSDT', 'JPYCUSDT', 'AEURUSDT',
            # Or / métaux tokenisés
            'PAXGUSDT', 'XAUTUSDT',
        }
        
        removed = []
        clean_symbols = []
        
        for sym in symbols:
            # Filtre 1: Caractères non-ASCII (test tokens)
            if not sym.isascii():
                removed.append((sym, "Caractères non-ASCII (test token)"))
                continue
            
            # Filtre 2: Stablecoins/Fiat/Wrapped/Or
            if sym in NON_EXPLOITABLE:
                removed.append((sym, "Non exploitable (stablecoin/fiat/or/wrapped)"))
                continue
            
            # Filtre 3: Format invalide (doit finir par USDT)
            if not sym.endswith('USDT') or len(sym) < 5:
                removed.append((sym, "Format invalide"))
                continue
            
            clean_symbols.append(sym)
        
        # === VALIDATION BINANCE: vérifier que les paires existent ===
        try:
            # Lire config pour savoir testnet ou pas
            _cfg = _read_binance_config()
            testnet = _cfg.get('testnet', True)
            base_url = 'https://testnet.binance.vision' if testnet else 'https://api.binance.com'
            req = urllib.request.Request(f'{base_url}/api/v3/exchangeInfo')
            with urllib.request.urlopen(req, timeout=10) as resp:
                info = json.loads(resp.read())
            
            active_pairs = set()
            for s in info['symbols']:
                if s['status'] == 'TRADING' and s['quoteAsset'] == 'USDT':
                    active_pairs.add(s['symbol'])
            
            validated = []
            for sym in clean_symbols:
                if sym in active_pairs:
                    validated.append(sym)
                else:
                    removed.append((sym, "Paire inexistante sur Binance"))
            
            clean_symbols = validated
            print_success(f"Validation Binance: {len(active_pairs)} paires actives vérifiées")
            
        except Exception as e:
            print_warning(f"Impossible de valider sur Binance ({e}), filtrage local uniquement")
        
        # === ÉLIMINER LES DOUBLONS ===
        seen = set()
        unique_symbols = []
        for sym in clean_symbols:
            if sym not in seen:
                seen.add(sym)
                unique_symbols.append(sym)
            else:
                removed.append((sym, "Doublon"))
        clean_symbols = unique_symbols
        
        # === RAPPORT ===
        if removed:
            print_warning(f"{len(removed)} symbole(s) retiré(s):")
            for sym, reason in removed:
                print_info(f"  {sym} → {reason}")
        
        # === SAUVEGARDER ===
        if len(clean_symbols) != original_count:
            # 🔧 FIX 29/03: Préserver auto_added et spy_injected (flux spy, séparé de symbols[])
            wl_data['symbols'] = clean_symbols
            wl_data['count'] = len(clean_symbols)
            # auto_added et spy_injected sont des flux spy, pas dans symbols[] → ne pas toucher
            wl_data.setdefault('auto_added', {})
            wl_data.setdefault('spy_injected', {})
            wl_data['updated_at'] = time.strftime('%Y-%m-%dT%H:%M:%S')
            with open(watchlist_file, 'w', encoding='utf-8') as f:
                json.dump(wl_data, f, indent=2, ensure_ascii=False)
            print_success(f"Watchlist nettoyée: {original_count} → {len(clean_symbols)} symboles exploitables")
        else:
            print_success(f"Watchlist propre: {len(clean_symbols)} symboles, aucun parasite détecté")
        
        return True
        
    except Exception as e:
        print_error(f"Erreur nettoyage watchlist: {e}")
        return False


def verify_port_free():
    """Vérifie que le port est libéré et force la fermeture si nécessaire (cross-platform)."""
    print_info(f"Vérification que le port {DASHBOARD_PORT} est libre...")

    # Tuer tout processus encore en écoute sur le port (psutil, cross-platform)
    if psutil:
        try:
            for conn in psutil.net_connections():
                if conn.laddr.port == DASHBOARD_PORT and conn.pid and conn.pid != os.getpid():
                    try:
                        psutil.Process(conn.pid).kill()
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        pass
        except Exception:
            pass

    # Attendre que le port soit libre (bind test cross-platform)
    import socket
    for attempt in range(20):  # max 40 secondes
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            s.bind(('', DASHBOARD_PORT))
            s.close()
            print_success(f"Port {DASHBOARD_PORT} est libre")
            return True
        except OSError:
            if attempt < 19:
                print(f"  ... attente port libre ({attempt+1}/20s)...", end='\r')
            time.sleep(2)

    print_warning(f"Port {DASHBOARD_PORT} encore en cours de libération (TIME_WAIT) - on continue quand même")
    return True


def start_dashboard_server():
    """Lance le dashboard_api_server.py de maniere propre"""
    print_header("LANCEMENT DU SERVEUR", 2)
    
    # Verifier que le port est libre
    verify_port_free()
    
    script_path = os.path.join(SCRIPT_DIR, 'dashboard_api_server.py')
    if not os.path.exists(script_path):
        print_error("dashboard_api_server.py non trouvé!")
        return False
    
    # Utiliser le Python du venv
    venv_python = get_venv_python()
    if not venv_python:
        print_error("❌ Python venv non trouvé. Créer le venv: python -m venv .venv")
        return False

    print_info(f"Lancement de dashboard_api_server.py...")
    print_info(f"Python: {venv_python}")

    try:
        # Ouvrir le fichier log
        log_file = os.path.join(SCRIPT_DIR, 'dashboard_log.txt')

        # Ne pas fermer le handle — le processus détaché continue d'écrire dedans (idem trading_bot)
        dashboard_log = open(log_file, 'w', encoding='utf-8')
        process = _spawn_detached([venv_python, script_path], stdout=dashboard_log, stderr=subprocess.STDOUT)

        print_success(f"Serveur lancé (PID: {process.pid})")

        # Attendre que le serveur démarre (25s pour charger les modèles IA)
        print_info("Attente du démarrage (chargement modèles IA)...")

        for i in range(25):
            time.sleep(1)

            # Vérifier si le port est en écoute
            if _is_port_listening(DASHBOARD_PORT):
                print_success(f"Serveur opérationnel sur le port {DASHBOARD_PORT}")
                return True

            print(f"  ... {i+1}/25s", end='\r')

        # Même si timeout, vérifier si le port est ouvert
        if _is_port_listening(DASHBOARD_PORT):
            print_success("Serveur finalement opérationnel")
            return True

        print_warning("Timeout - vérifier dashboard_log.txt")
        return False

    except Exception as e:
        print_error(f"Erreur: {e}")
        return False


def create_trading_pause():
    """Crée trading_pause.json pour que le bot démarre avec le trading désactivé."""
    pause_file = os.path.join(SCRIPT_DIR, 'trading_pause.json')
    # Pause d'1 an = trading désactivé jusqu'à reprise manuelle via dashboard
    paused_until = time.time() + 365 * 24 * 3600
    try:
        with open(pause_file, 'w', encoding='utf-8') as f:
            json.dump({
                'paused_until': paused_until,
                'reason': 'RESET',
                'timestamp': time.strftime('%Y-%m-%dT%H:%M:%S')
            }, f)
        print_success("Trading désactivé au démarrage (reprendre via le dashboard)")
    except Exception as e:
        print_warning(f"Impossible de créer trading_pause.json: {e}")


def start_trading_bot():
    """Lance le trading_bot.py de manière propre (SÉPARÉ du dashboard!)"""
    print_header("LANCEMENT DU TRADING BOT", "2.5")
    
    script_path = os.path.join(SCRIPT_DIR, 'trading_bot.py')
    if not os.path.exists(script_path):
        print_error("trading_bot.py non trouvé!")
        return False
    
    # Utiliser le Python du venv
    venv_python = get_venv_python()
    if not venv_python:
        print_error("❌ Python venv non trouvé. Créer le venv: python -m venv .venv")
        return False

    print_info(f"Lancement de trading_bot.py...")
    print_info(f"Python: {venv_python}")

    try:
        # Ouvrir les fichiers de log
        stdout_log = os.path.join(SCRIPT_DIR, 'bot_stdout.txt')  # .txt — cohérence avec le projet
        stderr_log = os.path.join(SCRIPT_DIR, 'bot_stderr.txt')

        # 🔴 FIX 27/02: Utiliser -u (unbuffered) pour que stdout/stderr arrivent dans les fichiers
        # et garder les handles ouverts (pas de with block qui ferme prématurément)
        out_log = open(stdout_log, 'w', encoding='utf-8')
        err_log = open(stderr_log, 'w', encoding='utf-8')

        process = _spawn_detached([venv_python, '-u', script_path], stdout=out_log, stderr=err_log)
        # Note: on ne ferme PAS out_log/err_log — le processus détaché les utilise

        # Sauvegarder le PID
        pid_file = os.path.join(SCRIPT_DIR, 'bot.pid')
        with open(pid_file, 'w') as f:
            f.write(str(process.pid))

        print_success(f"Trading Bot lancé (PID: {process.pid})")

        # Attendre que le bot démarre
        print_info("Attente du démarrage du bot...")
        time.sleep(5)

        # Verifier que le processus est toujours actif
        if _pid_is_alive(str(process.pid)):
            print_success(f"Trading Bot opérationnel (PID: {process.pid})")
            return True

        print_warning("Vérification du bot en cours...")
        return True

    except Exception as e:
        print_error(f"Erreur lancement Trading Bot: {e}")
        return False


def start_auto_updater():
    """Lance le service auto_updater en arrière-plan"""
    print_info("Lancement du service Auto Updater...")
    
    try:
        # Determiner le Python a utiliser
        venv_python = get_venv_python()
        if not venv_python:
            print_error("❌ Python venv non trouvé. Créer le venv: python -m venv .venv")
            return False

        script_path = os.path.join(SCRIPT_DIR, 'auto_updater_service.py')
        if not os.path.exists(script_path):
            print_warning("auto_updater_service.py non trouvé")
            return False

        process = _spawn_detached([venv_python, script_path, "--daemon"])
        
        # Attendre que le service crée son propre fichier PID
        time.sleep(2)
        
        # Vérifier si le fichier PID a été créé par le service
        pid_file = os.path.join(SCRIPT_DIR, 'auto_updater.pid')
        if os.path.exists(pid_file):
            with open(pid_file, 'r') as f:
                actual_pid = f.read().strip()
            print_success(f"Auto Updater lancé (PID: {actual_pid})")
        else:
            print_success(f"Auto Updater lancé (PID: {process.pid})")
        
        return True
        
    except Exception as e:
        print_error(f"Erreur lancement Auto Updater: {e}")
        return False


def start_watchdog():
    """Lance le watchdog de surveillance du bot - CRITIQUE pour éviter les arrêts!"""
    print_header("LANCEMENT WATCHDOG (SURVEILLANCE CRITIQUE)", "2.6")
    
    try:
        # Déterminer le Python à utiliser
        venv_python = get_venv_python()
        if not venv_python:
            print_error("❌ Python venv non trouvé. Créer le venv: python -m venv .venv")
            return False

        script_path = os.path.join(SCRIPT_DIR, 'bot_watchdog.py')
        if not os.path.exists(script_path):
            print_error("bot_watchdog.py non trouvé - SURVEILLANCE IMPOSSIBLE!")
            print_warning("Le bot pourra crasher sans redémarrage automatique!")
            return False

        process = _spawn_detached([venv_python, script_path])
        
        # Attendre que le watchdog démarre
        time.sleep(2)
        
        # Vérifier si le watchdog a créé son fichier log
        watchdog_log = os.path.join(SCRIPT_DIR, 'watchdog.log')
        if os.path.exists(watchdog_log):
            print_success(f"Watchdog actif (PID: {process.pid}) - Bot surveillé 24/7!")
            print_info("Le watchdog redémarrera automatiquement le bot en cas de crash")
        else:
            print_success(f"Watchdog lancé (PID: {process.pid})")
        
        return True
        
    except Exception as e:
        print_error(f"ERREUR CRITIQUE: Watchdog non lancé - {e}")
        print_warning("Le bot ne sera PAS surveillé - risque d'arrêt sans redémarrage!")
        return False


def start_prod_spy():
    """Lance le market_spy.py de production"""
    print_header("LANCEMENT SPY PROD", "2.7")

    if sys.platform == 'win32':
        print_warning("Spy prod non supporté sur Windows")
        return False

    if not os.path.isdir(PROD_DIR):
        print_warning(f"Répertoire prod introuvable: {PROD_DIR}")
        return False

    script_path = os.path.join(PROD_DIR, 'market_spy.py')
    if not os.path.exists(script_path):
        print_error("market_spy.py prod non trouvé!")
        return False

    venv_python = get_venv_python()
    if not venv_python:
        print_error("Python venv non trouvé")
        return False

    print_info(f"Lancement du spy prod depuis {PROD_DIR}...")

    try:
        log_dir = os.path.join(PROD_DIR, 'logs')
        os.makedirs(log_dir, exist_ok=True)
        log_path = os.path.join(log_dir, 'market_spy_prod.log')
        log_file = open(log_path, 'a', encoding='utf-8')
        process = _spawn_detached(
            [venv_python, '-u', script_path],
            stdout=log_file,
            stderr=subprocess.STDOUT,
            cwd=PROD_DIR
        )
        # Sauvegarder le PID
        pid_file = os.path.join(PROD_DIR, 'spy_prod.pid')
        with open(pid_file, 'w') as f:
            f.write(str(process.pid))
        time.sleep(3)
        if _pid_is_alive(str(process.pid)):
            print_success(f"Spy prod opérationnel (PID: {process.pid})")
            return True
        print_warning("Spy prod démarré mais PID non vérifié")
        return True
    except Exception as e:
        print_error(f"Erreur lancement spy prod: {e}")
        return False


def verify_services():
    """Vérifie que tous les services fonctionnent"""
    print_header("VERIFICATION", 3)
    
    all_ok = True
    
    # 1. Vérifier le port du dashboard
    if _is_port_listening(DASHBOARD_PORT):
        port_pid = None
        try:
            if psutil:
                for conn in psutil.net_connections():
                    if conn.laddr.port == DASHBOARD_PORT and conn.status == 'LISTEN':
                        port_pid = conn.pid
                        break
        except Exception:
            pass
        pid_str = f" (PID: {port_pid})" if port_pid else ""
        print_success(f"Dashboard actif sur port {DASHBOARD_PORT}{pid_str}")
    else:
        print_error(f"Dashboard non détecté sur port {DASHBOARD_PORT}")
        all_ok = False
    
    # 2. Vérifier le fichier bot.pid
    bot_pid_file = os.path.join(SCRIPT_DIR, 'bot.pid')
    if os.path.exists(bot_pid_file):
        try:
            with open(bot_pid_file, 'r') as f:
                pid = f.read().strip()
            if _pid_is_alive(pid):
                print_success(f"Bot de trading actif (PID: {pid})")
            else:
                print_error(f"Bot de trading NON ACTIF (PID {pid} introuvable)")
                all_ok = False
        except Exception as e:
            print_warning(f"bot.pid existe mais erreur vérification: {e}")
    else:
        print_error("bot.pid non trouvé - Trading Bot non démarré!")
        all_ok = False
    
    # 3. Tester l'API (plusieurs endpoints possibles)
    api_ok = False
    for endpoint in ['/api/ai-surveillance', '/api/status', '/dashboard.html']:
        try:
            req = urllib.request.Request(f'http://localhost:{DASHBOARD_PORT}{endpoint}')
            with urllib.request.urlopen(req, timeout=5) as response:
                if response.status == 200:
                    print_success(f"Dashboard accessible ({endpoint})")
                    api_ok = True
                    break
        except Exception as e:
            continue
    
    if not api_ok:
        print_warning("API non accessible (peut être en cours de chargement)")
    
    # 4. Vérifier l'auto updater
    updater_pid_file = os.path.join(SCRIPT_DIR, 'auto_updater.pid')
    if os.path.exists(updater_pid_file):
        try:
            with open(updater_pid_file, 'r') as f:
                pid = f.read().strip()
            print_success(f"Auto Updater actif (PID: {pid})")
        except:
            print_warning("auto_updater.pid existe mais illisible")
    else:
        print_warning("Auto Updater non lancé")
    
    # 5. Vérifier le spy prod
    prod_spy_active = False
    prod_pid_file = os.path.join(PROD_DIR, 'spy_prod.pid')
    if os.path.exists(prod_pid_file):
        try:
            with open(prod_pid_file) as f:
                prod_pid = f.read().strip()
            if _pid_is_alive(prod_pid):
                print_success(f"Spy prod actif (PID: {prod_pid})")
                prod_spy_active = True
            else:
                print_warning(f"Spy prod PID {prod_pid} introuvable")
        except Exception:
            pass
    if not prod_spy_active and psutil:
        for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
            try:
                cmd = ' '.join(proc.info['cmdline'] or [])
                if 'python' in proc.info['name'].lower() and 'market_spy' in cmd and 'crypto_trading_prod' in cmd:
                    print_success(f"Spy prod actif (PID: {proc.info['pid']})")
                    prod_spy_active = True
                    break
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                pass
    if not prod_spy_active:
        print_warning("Spy prod non détecté")

    # 6. Vérifier le watchdog (CRITIQUE!)
    watchdog_active = False
    watchdog_pid_file = os.path.join(SCRIPT_DIR, 'watchdog.pid')
    watchdog_log = os.path.join(SCRIPT_DIR, 'watchdog.log')
    
    # MÉTHODE 1: Vérifier via le fichier PID (plus fiable)
    if os.path.exists(watchdog_pid_file):
        try:
            with open(watchdog_pid_file, 'r') as f:
                watchdog_pid = int(f.read().strip())
            
            # Vérifier que le processus existe
            if psutil:
                if psutil.pid_exists(watchdog_pid):
                    proc = psutil.Process(watchdog_pid)
                    if 'python' in proc.name().lower():
                        watchdog_active = True
                        print_success(f"Watchdog actif (PID: {watchdog_pid}) - Bot surveillé 24/7!")
        except (ValueError, Exception):
            pass
    
    # MÉTHODE 2 (fallback): Vérifier via recherche de processus (psutil, cross-platform)
    if not watchdog_active and psutil:
        try:
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    if 'python' in proc.info['name'].lower():
                        cmd = ' '.join(proc.info['cmdline'] or [])
                        if 'bot_watchdog' in cmd:
                            watchdog_active = True
                            print_success(f"Watchdog actif (PID: {proc.info['pid']}) - Bot surveillé 24/7!")
                            break
                except (psutil.NoSuchProcess, psutil.AccessDenied):
                    pass
        except Exception:
            pass
    
    # Si watchdog inactif, le relancer automatiquement
    if not watchdog_active:
        print_warning("WATCHDOG INACTIF DÉTECTÉ - Relance automatique...")
        if start_watchdog():
            print_success("Watchdog relancé avec succès!")
            watchdog_active = True
        else:
            print_error("Échec relance watchdog - Bot NON surveillé!")
            all_ok = False
    
    # Vérifier le log du watchdog pour confirmation
    if watchdog_active and os.path.exists(watchdog_log):
        try:
            with open(watchdog_log, 'r', encoding='utf-8') as f:
                lines = f.readlines()
                if lines:
                    last_line = lines[-1].strip()
                    if "Bot actif" in last_line or "Watchdog démarré" in last_line:
                        print_info("Dernière vérification: Bot OK")
        except:
            pass
    
    return all_ok


def sell_all_positions():
    """Vend toutes les positions avant le reset"""
    print_header("VENTE DE TOUTES LES POSITIONS", "0.5")
    
    # Toujours utiliser le Python .venv (même règle que tout le reste du script)
    venv_python = get_venv_python()
    if not venv_python:
        print_error("Python .venv introuvable. Créer le venv: python -m venv .venv")
        return False
    
    try:
        # Exécuter sell_all.py
        # 🔴 FIX 27/02: Timeout augmenté à 300s (testnet peut avoir 400+ assets à vendre)
        result = subprocess.run(
            [venv_python, 'sell_all.py', '--force'],
            cwd=SCRIPT_DIR,
            capture_output=True,
            text=True,
            timeout=300
        )
        
        if result.returncode == 0:
            print_success("Toutes les positions ont été vendues")
            return True
        else:
            print_warning("Certaines positions n'ont pas pu être vendues")
            print(result.stdout)
            return True  # Continuer quand même
    except Exception as e:
        print_error(f"Erreur vente positions: {e}")
        return False


def check_and_sell_orphaned_positions():
    """🔴 FIX 27/02: Vérifie s'il y a du crypto orphelin sur Binance et le vend automatiquement.
    
    C'est la correction du bug qui causait l'accumulation de crypto non trackée:
    - L'ancien code ignorait les positions > 2h au redémarrage
    - Le crypto restait sur Binance sans suivi
    - Au fil des resets, TOUT le USDT finissait bloqué en crypto
    """
    print_header("VÉRIFICATION POSITIONS ORPHELINES", "0.3")
    
    try:
        # Charger la watchlist pour ne vérifier QUE les symboles que le bot trade
        # (le testnet a 400+ coins pré-chargés qu'on ne veut PAS toucher)
        watchlist_file = os.path.join(SCRIPT_DIR, 'watchlist.json')
        watchlist_symbols = set()
        if os.path.exists(watchlist_file):
            try:
                with open(watchlist_file, 'r', encoding='utf-8') as f:
                    wl_data = json.load(f)
                    watchlist_symbols = set(wl_data.get('symbols', []))
                    print_info(f"Watchlist: {len(watchlist_symbols)} symboles surveillés")
            except:
                pass
        
        if not watchlist_symbols:
            print_warning("Watchlist vide, impossible de détecter les orphelins")
            return True
        
        # Charger la config via le helper partagé
        config_vars = _read_binance_config()
        
        api_key = config_vars.get('api_key', '')
        api_secret = config_vars.get('api_secret', '')
        testnet_mode = config_vars.get('testnet', True)
        base_url = 'https://testnet.binance.vision' if testnet_mode else 'https://api.binance.com'
        
        # Récupérer l'offset serveur via helper partagé
        server_time_offset = _get_server_time_offset(base_url)
        
        # Récupérer le compte
        timestamp = int(time.time() * 1000) + server_time_offset
        query_string = f"timestamp={timestamp}&recvWindow=5000"
        signature = hmac.new(
            api_secret.encode('utf-8'),
            query_string.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        
        account_url = f"{base_url}/api/v3/account?{query_string}&signature={signature}"
        req = urllib.request.Request(account_url, headers={'X-MBX-APIKEY': api_key})
        
        with urllib.request.urlopen(req, timeout=10) as response:
            account_data = json.loads(response.read().decode('utf-8'))
        
        # Charger positions.json pour comparer
        positions_file = os.path.join(SCRIPT_DIR, 'positions.json')
        local_positions = {}
        if os.path.exists(positions_file):
            try:
                with open(positions_file, 'r', encoding='utf-8') as f:
                    local_positions = json.load(f)
            except:
                pass
        
        # 🔴 FIX 07/03: Inclure aussi espion_trades.json (positions market_spy)
        # Sans ça, les spy positions actives sont traitées comme orphelines
        # et les positions dont la vente a échoué ne sont jamais détectées
        spy_positions = {}
        spy_file = os.path.join(SCRIPT_DIR, 'espion_trades.json')
        if os.path.exists(spy_file):
            try:
                with open(spy_file, 'r', encoding='utf-8') as f:
                    spy_positions = json.load(f)
                if spy_positions:
                    print_info(f"Positions spy actives: {', '.join(spy_positions.keys())}")
            except:
                pass
        
        tracked_symbols = set(local_positions.keys()) | set(spy_positions.keys())
        
        # Scanner les holdings Binance
        # 🔴 FIX 27/02: assets étendus pour ignorer les stablecoins/fiat ET les coins testnet spéciaux
        stablecoins = {'USDT', 'BUSD', 'USDC', 'DAI', 'USD', 'EUR', 'BRL', 'TRY',
                      'IDR', 'ARS', 'UAH', 'ZAR', 'PLN', 'RON', 'CZK', 'MXN', 'COP', 'JPY',
                      'TUSD', 'FDUSD', 'USDP', 'AEUR', 'EURI', 'BFUSD', 'USD1', 'USDE', 
                      'RLUSD', 'XUSD', 'FRAX', 'U'}
        ignore_assets = {'BNB', 'LDBNB', 'ETHW', 'BETH', 'WBTC', 'WBETH', 'BNSOL', '这是测试币', '456', '币安人生'}
        
        # 🔴 FIX 27/02: Récupérer TOUS les prix en 1 seul appel (évite 432 appels individuels = timeout)
        all_tickers_url = f"{base_url}/api/v3/ticker/price"
        req = urllib.request.Request(all_tickers_url)
        with urllib.request.urlopen(req, timeout=10) as resp:
            all_tickers_data = json.loads(resp.read().decode('utf-8'))
        price_map = {t['symbol']: float(t['price']) for t in all_tickers_data}
        
        orphaned = []
        tracked_found = []
        dust_to_clean = []   # Positions trackées localement mais dust invendable sur Binance
        total_orphaned_value = 0
        
        for balance_item in account_data.get('balances', []):
            asset = balance_item['asset']
            if asset in stablecoins or asset in ignore_assets:
                continue
            
            total = float(balance_item['free']) + float(balance_item['locked'])
            if total <= 0:
                # Asset avec solde nul mais potentiellement dans positions.json → nettoyer
                symbol = f"{asset}USDT"
                if symbol in tracked_symbols and symbol in watchlist_symbols:
                    dust_to_clean.append(symbol)
                    print_warning(f"  Fantôme: {symbol} = 0 sur Binance mais présent dans positions.json → suppression")
                continue
            
            symbol = f"{asset}USDT"
            
            # 🔴 FIX 27/02: Ne vérifier QUE les symboles de la watchlist
            # Le testnet a 400+ coins pré-chargés qu'on ne veut pas toucher
            if symbol not in watchlist_symbols:
                continue
            
            price = price_map.get(symbol, 0)
            
            if price > 0:
                value_usdt = total * price
                
                if value_usdt >= 5.0:
                    is_tracked = symbol in tracked_symbols
                    if not is_tracked:
                        orphaned.append({
                            'symbol': symbol,
                            'asset': asset,
                            'quantity': total,
                            'price': price,
                            'value_usdt': value_usdt
                        })
                        total_orphaned_value += value_usdt
                        print_warning(f"  Orphelin: {symbol} = {total:.6f} ({value_usdt:.2f} USDT) - NON TRACKÉ")
                    else:
                        tracked_found.append(symbol)
                else:
                    # Dust invendable (< 5$ minimum Binance)
                    # S'il est encore dans positions.json, le retirer pour éviter les fausses positions
                    if symbol in tracked_symbols:
                        dust_to_clean.append(symbol)
                        print_warning(f"  Dust: {symbol} = {total:.6f} ({value_usdt:.2f}$) < 5$ min Binance → suppression de positions.json")
                    else:
                        print_info(f"  Dust ignoré: {symbol} = {value_usdt:.2f}$ (invendable, non tracké)")
        
        if tracked_found:
            print_info(f"  {len(tracked_found)} position(s) correctement trackée(s)")
        
        # ── Nettoyage dust de positions.json ─────────────────────────────────
        if dust_to_clean:
            try:
                pos_file = os.path.join(SCRIPT_DIR, 'positions.json')
                with open(pos_file, 'r', encoding='utf-8') as f:
                    current_positions = json.load(f)
                removed = []
                for sym in dust_to_clean:
                    if sym in current_positions:
                        del current_positions[sym]
                        removed.append(sym)
                if removed:
                    with open(pos_file, 'w', encoding='utf-8') as f:
                        json.dump(current_positions, f, indent=2)
                    print_success(f"  Dust supprimés de positions.json: {', '.join(removed)}")
            except Exception as e:
                print_warning(f"  Impossible de nettoyer les dust de positions.json: {e}")
        
        if not orphaned:
            print_success("Aucune position orpheline détectée")
            return True
        
        # Il y a des orphelins de la watchlist - les vendre directement via API
        # On ne peut PAS utiliser sell_all.py car il vendrait les 400+ coins testnet
        print(f"\n{Colors.YELLOW}  ⚠️ {len(orphaned)} position(s) orpheline(s) ({total_orphaned_value:.2f} USDT)")
        print(f"  → Vente ciblée des orphelins uniquement...{Colors.RESET}\n")
        
        try:
            from binance.client import Client as BClient
            sys.path.insert(0, SCRIPT_DIR)
            import config as cfg
            client = BClient(cfg.BINANCE_API_KEY, cfg.BINANCE_API_SECRET, testnet=cfg.TESTNET_MODE)
            
            sold_count = 0
            for orphan in orphaned:
                symbol = orphan['symbol']
                quantity = orphan['quantity']
                try:
                    # Récupérer le step_size pour arrondir correctement
                    symbol_info = client.get_symbol_info(symbol)
                    if not symbol_info:
                        print_warning(f"  {symbol}: Symbole invalide, ignoré")
                        continue
                    
                    step_size = None
                    min_qty = None
                    for flt in symbol_info['filters']:
                        if flt['filterType'] == 'LOT_SIZE':
                            step_size = float(flt['stepSize'])
                            min_qty = float(flt['minQty'])
                            break
                    
                    if step_size:
                        precision = len(str(step_size).rstrip('0').split('.')[-1]) if '.' in str(step_size) else 0
                        quantity = round(quantity - (quantity % step_size), precision)
                    
                    if min_qty and quantity < min_qty:
                        print_info(f"  {symbol}: Quantité trop faible ({quantity}), ignoré")
                        continue
                    
                    order = client.order_market_sell(symbol=symbol, quantity=quantity)
                    fills = order.get('fills', [])
                    total_value = sum(float(f['price']) * float(f['qty']) for f in fills)
                    print_success(f"  {symbol}: Vendu pour ${total_value:.2f}")
                    sold_count += 1
                    # 🔴 FIX 07/03: Nettoyer aussi espion_trades.json pour ce symbole
                    if symbol in spy_positions:
                        del spy_positions[symbol]
                        try:
                            with open(spy_file, 'w', encoding='utf-8') as f:
                                json.dump(spy_positions, f, indent=2)
                            print_info(f"  {symbol}: Retiré de espion_trades.json")
                        except Exception as _e:
                            print_warning(f"  Impossible de nettoyer espion_trades.json: {_e}")
                except Exception as e:
                    print_warning(f"  {symbol}: Erreur vente - {e}")
            
            if sold_count > 0:
                print_success(f"{sold_count}/{len(orphaned)} orphelins vendus")
                # 🔴 FIX 07/03: Retirer seulement les symboles vendus de positions.json
                # (ne pas vider tout le fichier si certaines ventes ont échoué)
                sold_symbols = {o['symbol'] for o in orphaned}
                try:
                    pos_file = os.path.join(SCRIPT_DIR, 'positions.json')
                    with open(pos_file, 'r', encoding='utf-8') as f:
                        current_pos = json.load(f)
                    changed = False
                    for sym in sold_symbols:
                        if sym in current_pos:
                            del current_pos[sym]
                            changed = True
                    if changed:
                        with open(pos_file, 'w', encoding='utf-8') as f:
                            json.dump(current_pos, f, indent=2)
                except Exception as _e:
                    print_warning(f"  Impossible de mettre à jour positions.json: {_e}")
            else:
                print_info("Aucun orphelin vendu, le bot les récupérera au démarrage")
                
        except ImportError:
            print_warning("Module binance non disponible, le bot récupérera les orphelins au démarrage")
        except Exception as e:
            print_error(f"Erreur vente orphelins: {e}")
        
        return True
        
    except Exception as e:
        print_error(f"Erreur vérification orphelins: {e}")
        return True  # Continuer quand même


def main():
    """Fonction principale"""
    # Vérifier si option --sell-all
    sell_all_mode = '--sell-all' in sys.argv
    
    print(f"""
{Colors.BOLD}{Colors.CYAN}
================================================================
         RESET TRADING SYSTEM v3.2 (anti-orphelins)                           
         Arrêt propre et redémarrage des services               
================================================================
{Colors.RESET}""")
    
    if sell_all_mode:
        print(f"{Colors.YELLOW}Mode: VENTE TOTALE + RESET{Colors.RESET}\n")
    
    # PHASE 0: Test connexion Binance
    binance_ok = test_binance_connection()
    if not binance_ok:
        print(f"\n{Colors.RED}{'=' * 60}{Colors.RESET}")
        print(f"{Colors.BOLD}{Colors.RED}  ERREUR CONNEXION BINANCE{Colors.RESET}")
        print(f"{Colors.RED}{'=' * 60}{Colors.RESET}")
        print(f"""
{Colors.YELLOW}Actions recommandées:{Colors.RESET}
  1. Vérifier votre connexion Internet
  2. Vérifier les clés API dans config.py
  3. Si Testnet: Recharger sur https://testnet.binance.vision/
  4. Si Production: Vérifier votre compte Binance
""")
        return 1
    
    print(f"\n{Colors.GREEN}[OK] Connexion Binance validée{Colors.RESET}")
    time.sleep(2)
    
    # FIX 02/03: Nettoyer la watchlist AVANT tout (supprime stablecoins, fiat, test tokens, etc.)
    clean_watchlist()
    time.sleep(1)
    
    # FIX 27/02: TOUJOURS vérifier les positions orphelines avant le reset
    # C'était le bug principal: les positions non trackées s'accumulaient sur Binance
    check_and_sell_orphaned_positions()
    time.sleep(1)

    # PHASE 0.5: Vente automatique si demandée
    if sell_all_mode:
        sell_all_ok = sell_all_positions()
        if not sell_all_ok:
            print(f"\n{Colors.YELLOW}Continuer malgré l'échec de vente? (O/n): {Colors.RESET}", end='')
            response = input().strip().lower()
            if response == 'n':
                return 1
        time.sleep(2)

    # PHASE 1: Arrêt
    stopped = stop_all_processes()
    
    # Attendre un peu avant de relancer
    time.sleep(3)
    
    # PHASE 2: Lancement
    server_started = start_dashboard_server()
    
    # Attendre que tout démarre (le serveur charge les modèles IA)
    time.sleep(5)
    
    # PHASE 2.5: Créer la pause de trading AVANT de lancer le bot
    create_trading_pause()

    # Lancer le trading bot (séparé du dashboard!)
    trading_bot_started = start_trading_bot()
    
    # Lancer l'auto updater (mises à jour automatiques)
    start_auto_updater()
    
    # PHASE 2.6: Lancer le watchdog (surveillance critique!)
    watchdog_started = start_watchdog()

    # PHASE 2.7: Lancer le spy prod
    start_prod_spy()
    
    # PHASE 3: Verification
    verify_ok = verify_services()
    
    # Le succès dépend de la vérification finale, pas du timeout de démarrage
    success = verify_ok
    
    # Résumé final
    print(f"\n{Colors.CYAN}{'=' * 60}{Colors.RESET}")
    if success:
        print(f"{Colors.BOLD}{Colors.GREEN}  RESET RÉUSSI - TOUS LES SERVICES ACTIFS !{Colors.RESET}")
        print(f"{Colors.GREEN}  Bot surveillé 24/7 par le watchdog{Colors.RESET}")
    else:
        print(f"{Colors.BOLD}{Colors.RED}  RESET ÉCHOUÉ - Vérifier les erreurs{Colors.RESET}")
    print(f"{Colors.CYAN}{'=' * 60}{Colors.RESET}")
    
    print(f"""
{Colors.BLUE}Dashboard:{Colors.RESET} http://localhost:{DASHBOARD_PORT}/dashboard.html
{Colors.BLUE}Logs:{Colors.RESET} dashboard_log.txt

{Colors.YELLOW}Commandes utiles:{Colors.RESET}
  - Vendre tout: python sell_all.py
  - Vendre + Reset: python Reset_trading.py --sell-all
  - Arrêter tout: python stop_all.py
  - Relancer: python Reset_trading.py
  - Santé: python quick_check.py
""")
    
    return 0 if success else 1


if __name__ == "__main__":
    try:
        sys.exit(main())
    except KeyboardInterrupt:
        print(f"\n{Colors.YELLOW}Annulé par l'utilisateur{Colors.RESET}")
        sys.exit(1)
    except Exception as e:
        print(f"\n{Colors.RED}Erreur fatale: {e}{Colors.RESET}")
        sys.exit(1)
