Spaces:
Sleeping
Sleeping
| # --- 🏴☠️ MONKEY PATCH & IMPORTS -- | |
| import huggingface_hub | |
| try: | |
| from huggingface_hub import HfFolder | |
| except ImportError: | |
| class MockHfFolder: | |
| def get_token(): return None | |
| def save_token(token): pass | |
| def delete_token(): pass | |
| huggingface_hub.HfFolder = MockHfFolder | |
| import sys, os, sqlite3, shutil, pandas as pd, asyncio, joblib, json, numpy as np, gc, warnings, psutil | |
| import ccxt.async_support as ccxt_async | |
| from datetime import datetime, timezone | |
| from huggingface_hub import HfApi, hf_hub_download | |
| import gradio as gr | |
| import ccxt | |
| import time | |
| import random | |
| import aiohttp | |
| from types import ModuleType | |
| import yfinance as yf | |
| import warnings | |
| warnings.filterwarnings("ignore", category=UserWarning, module="sklearn") | |
| # --- 🦖 IMPORT LIGHTGBM --- | |
| try: | |
| import lightgbm as lgb | |
| LGBM_AVAILABLE = True | |
| except ImportError: | |
| LGBM_AVAILABLE = False | |
| print("⚠️ LightGBM non installé.") | |
| try: | |
| import MetaTrader5 as mt5 | |
| MT5_AVAILABLE = True | |
| except ImportError: | |
| MT5_AVAILABLE = False | |
| print("🌐 [CLOUD MODE] MetaTrader5 non détecté.") | |
| # --- 🥷 NINJA HACK : MOCK PANDAS_TA --- | |
| if "pandas_ta" not in sys.modules: | |
| mock_ta = ModuleType("pandas_ta") | |
| sys.modules["pandas_ta"] = mock_ta | |
| if not hasattr(pd.DataFrame, "ta"): | |
| class FakeTA: | |
| def __getattr__(self, name): return lambda *args, **kwargs: None | |
| pd.DataFrame.ta = property(lambda self: FakeTA()) | |
| # --- 🛑 ANTI-CRASH & CPU OPTIMIZATION --- | |
| os.environ["CUDA_VISIBLE_DEVICES"] = "-1" | |
| import tensorflow as tf | |
| from tensorflow.keras import backend as K | |
| tf.config.threading.set_intra_op_parallelism_threads(2) | |
| tf.config.threading.set_inter_op_parallelism_threads(2) | |
| current_dir = os.path.dirname(os.path.abspath(__file__)) | |
| if current_dir not in sys.path: sys.path.append(current_dir) | |
| # --- SINGLETON EXCHANGE --- | |
| class ExchangeManager: | |
| _instance = None | |
| def get_instance(cls): | |
| if cls._instance is None: | |
| cls._instance = ccxt_async.kucoin({"enableRateLimit": True, "timeout": 30000}) | |
| return cls._instance | |
| exchange_sync = ccxt.kucoin({"enableRateLimit": True, "timeout": 30000}) | |
| market_cache, last_fetch_time = {}, {} | |
| # 🛡️ FIX : Cache dynamique selon la timeframe pour éviter l'amnésie | |
| def get_cache_ttl(tf): | |
| if tf == '1m': return 20 # 20 secondes de cache pour le 1m | |
| if tf == '5m': return 60 # 1 minute pour le 5m | |
| if tf == '15m': return 180 # 3 minutes pour le 15m | |
| return 300 # 5 minutes pour le 1h et 4h | |
| DREAM_MODE_ACTIVE = True | |
| try: | |
| from sentiment_engine import get_crypto_sentiment | |
| except: | |
| async def get_crypto_sentiment(symbol): return 0.5 | |
| try: | |
| from ensemble import combine_scores | |
| except: | |
| def combine_scores(symbol, timeframe, t, m, l, sent, r): | |
| # Formule PRO : 30% Time, 30% ML, 30% LightGBM, 10% Sentiment | |
| score = (t * 0.30) + (m * 0.30) + (l * 0.30) + (sent * 0.10) | |
| return score, 0.30, 0.30, 0.30, 0.10 | |
| # --- DB, SYNC & HUGGING FACE BACKUP --- | |
| DB_NAME = "alphatrade_v31_dino.db" | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| HF_REPO_ID = "Nexo-S/AlphaTrade-DB" | |
| def restore_db_from_hf(): | |
| if not HF_TOKEN: return | |
| # 1. Tentative de restauration de l'ADN (DB) | |
| try: | |
| print("🔄 [SYSTEM] Restauration ADN depuis HF...") | |
| file_path = hf_hub_download(repo_id=HF_REPO_ID, filename=DB_NAME, repo_type="dataset", token=HF_TOKEN) | |
| shutil.copy(file_path, DB_NAME) | |
| print("✅ [RESTORE] ADN récupéré !") | |
| except Exception as e: | |
| print(f"⚠️ Aucun backup ADN trouvé (1er lancement) : {e}") | |
| # 2. Tentative de restauration du modèle DINO | |
| # 2. Tentative de restauration des modèles DINO (Multiples) | |
| global dino_brains | |
| dino_brains = {} | |
| for crypto in ["BTC", "ETH", "SOL"]: | |
| filename = f"dino_lgbm_{crypto}.txt" | |
| try: | |
| print(f"🔄 [SYSTEM] Restauration du DINO {crypto} depuis HF...") | |
| file_path = hf_hub_download(repo_id=HF_REPO_ID, filename=filename, repo_type="dataset", local_dir=".", token=HF_TOKEN) | |
| # 🛡️ LE VACCIN EST ICI : On vérifie que le fichier pèse plus de 100 octets (qu'il n'est pas vide) | |
| if os.path.exists(file_path) and os.path.getsize(file_path) > 100: | |
| dino_brains[crypto] = lgb.Booster(model_file=file_path) | |
| print(f"✅ [RESTORE] Modèle Dino {crypto} récupéré et chargé !") | |
| else: | |
| print(f"⚠️ [ALERTE] Fichier {filename} vide ou corrompu. Mode aveugle activé pour {crypto}.") | |
| except Exception as e: | |
| print(f"⚠️ Aucun modèle trouvé pour {crypto}. Pense à Forcer l'Entraînement de {crypto}.") | |
| def backup_db_to_hf(): | |
| if not HF_TOKEN: return | |
| try: | |
| api = HfApi() | |
| api.upload_file(path_or_fileobj=DB_NAME, path_in_repo=DB_NAME, repo_id=HF_REPO_ID, repo_type="dataset", token=HF_TOKEN) | |
| print("☁️ [BACKUP] ADN sauvegardé sur Hugging Face !") | |
| except Exception as e: print(f"❌ Erreur Backup HF : {e}") | |
| def init_db(): | |
| try: | |
| with sqlite3.connect(DB_NAME) as conn: | |
| # 1. Table des Signaux (Historique) | |
| conn.execute('''CREATE TABLE IF NOT EXISTS signals ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| date TEXT, symbol TEXT, timeframe TEXT, direction TEXT, | |
| prob REAL, price REAL, tp REAL, sl REAL, status TEXT, | |
| regime INTEGER, prob_time REAL, prob_ml REAL, prob_lstm REAL, prob_sent REAL, | |
| peak_price REAL, confirmed INTEGER DEFAULT 0)''') | |
| cursor = conn.cursor() | |
| # 2. Table de l'ADN (Logique des Agents) | |
| # On vérifie si la table existe déjà pour éviter les conflits de structure | |
| cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='agent_logic'") | |
| table_exists = cursor.fetchone() | |
| if not table_exists: | |
| conn.execute('''CREATE TABLE agent_logic ( | |
| symbol TEXT, | |
| timeframe TEXT, | |
| regime INTEGER, -- ⬅️ Ajouté ici | |
| tp_mult REAL, | |
| sl_mult REAL, | |
| score REAL, | |
| last_pnl REAL, | |
| min_prob REAL, | |
| min_tp_dist REAL, | |
| generation INTEGER DEFAULT 1, | |
| best_tp REAL, | |
| best_sl REAL, | |
| PRIMARY KEY (symbol, timeframe, regime))''') # ⬅️ CLÉ PRIMAIRE TRIPLE | |
| # 3. Insertion des réglages par défaut | |
| # On initialise les réglages sur le régime 3 (RANGE/CHAOS) par défaut | |
| defaults = [ | |
| # Symbol, TF, Regime, TP, SL, Score, PNL, Prob, Dist, Gen, B_TP, B_SL | |
| ('ALL', '15m', 3, 1.5, 1.0, 0, 0, 0.60, 0.003, 1, 0, 0), | |
| ('ALL', '1h', 3, 2.0, 1.5, 0, 0, 0.55, 0.005, 1, 0, 0), | |
| ('ALL', '4h', 3, 3.0, 2.0, 0, 0, 0.50, 0.008, 1, 0, 0) | |
| ] | |
| # On utilise 12 points d'interrogation pour les 12 colonnes | |
| conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", defaults) | |
| conn.commit() | |
| print("✅ [DB] Table agent_logic initialisée avec Scénarios.") | |
| else: | |
| # Optionnel : Vérifier si la colonne regime existe déjà (pour les mises à jour sans tout supprimer) | |
| cursor.execute("PRAGMA table_info(agent_logic)") | |
| columns = [col[1] for col in cursor.fetchall()] | |
| if 'regime' not in columns: | |
| print("⚠️ [DB] Ancienne structure détectée. Suppression pour mise à jour...") | |
| conn.execute("DROP TABLE agent_logic") | |
| init_db() # On relance pour recréer proprement | |
| except Exception as e: | |
| print(f"❌ Erreur DB: {e}") | |
| import asyncio | |
| # --- 💾 SAUVEGARDE BLINDÉE (ANTI-EMBOUTEILLAGE) --- | |
| async def save_to_db(data_tuple): | |
| for tentative in range(5): # Le Cerveau va insister 5 fois si la porte est fermée | |
| try: | |
| with sqlite3.connect(DB_NAME, timeout=20) as conn: | |
| conn.execute('''INSERT INTO signals | |
| (date, symbol, timeframe, direction, prob, price, tp, sl, status, regime, prob_time, prob_ml, prob_lstm, prob_sent, peak_price) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', data_tuple) | |
| conn.commit() | |
| return # Succès, on quitte la fonction | |
| except sqlite3.OperationalError as e: | |
| if "locked" in str(e): | |
| await asyncio.sleep(1) # La base est lue par le Radar, on attend 1 seconde | |
| else: | |
| print(f"❌ [ERREUR DB] Sauvegarde échouée : {e}") | |
| break | |
| # 🚀 INITIALISATION | |
| restore_db_from_hf() | |
| init_db() | |
| # --- 🛡️ STATE MANAGER --- | |
| active_signals_state = {} | |
| last_signals_sent = {} # ⬅️ LA RUSTINE CRITIQUE EST LÀ | |
| def clean_old_db_signals(symbol, timeframe): | |
| try: | |
| with sqlite3.connect(DB_NAME, timeout=10) as conn: | |
| # 🛡️ LA SÉCURITÉ : On ne touche qu'aux signaux NON-CONFIRMÉS (Les fantômes) | |
| # Et on retire le "confirmed = 0" du SET (il ne faut pas dé-confirmer un trade) | |
| conn.execute("UPDATE signals SET status = 'REMPLACÉ ♻️' WHERE symbol = ? AND timeframe = ? AND status = 'EN_COURS' AND confirmed = 0", (symbol, timeframe)) | |
| conn.commit() | |
| except Exception as e: | |
| print(f"⚠️ Erreur nettoyage DB : {e}") | |
| def memory_guard(): | |
| if psutil.virtual_memory().percent > 80: | |
| K.clear_session() | |
| gc.collect() | |
| # --- 🛠️ MOTEUR MATHS --- | |
| def get_ema(series, period): return series.ewm(span=period, adjust=False).mean() | |
| def get_rsi(series, period=14): | |
| delta = series.diff() | |
| gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() | |
| loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() | |
| return 100 - (100 / (1 + (gain / (loss + 1e-9)))) | |
| def get_atr(df, period=14): | |
| h_l = df['high'] - df['low'] | |
| h_c = (df['high'] - df['close'].shift()).abs() | |
| l_c = (df['low'] - df['close'].shift()).abs() | |
| return pd.concat([h_l, h_c, l_c], axis=1).max(axis=1).rolling(period).mean() | |
| def get_vwap(df): | |
| v = df['vol'] | |
| tp = (df['high'] + df['low'] + df['close']) / 3 | |
| return (tp * v).cumsum() / (v.cumsum() + 1e-9) | |
| # --- 🧠 CHARGEMENT IA --- | |
| try: | |
| ml_model = joblib.load("ml_model_v9.pkl") | |
| time_model = joblib.load("time_model.pkl") | |
| regime_model = joblib.load("regime_model.pkl") | |
| regime_scaler = joblib.load("regime_scaler.pkl") | |
| except Exception as e: print(f"⚠️ Erreur IA Classique : {e}") | |
| try: | |
| dino_brain = lgb.Booster(model_file='dino_lgbm_model.txt') if LGBM_AVAILABLE else None | |
| except: | |
| dino_brain = None | |
| # --- 🌐 HTTP SESSION MANAGER --- | |
| global_aio_session = None | |
| async def get_aio_session(): | |
| global global_aio_session | |
| if global_aio_session is None or global_aio_session.closed: | |
| # On force un connecteur limité pour éviter les fuites de mémoire (Max 100 connexions) | |
| connector = aiohttp.TCPConnector(limit=100) | |
| global_aio_session = aiohttp.ClientSession(connector=connector) | |
| return global_aio_session | |
| async def fetch_kucoin_futures_data(symbol): | |
| try: | |
| ku_sym = symbol.replace("/", "").replace("BTC", "XBT") + "M" | |
| session = await get_aio_session() # On utilise la session globale ouverte | |
| oi_url = f"https://api-futures.kucoin.com/api/v1/open-interest?symbol={ku_sym}" | |
| trade_url = f"https://api-futures.kucoin.com/api/v1/trade/history?symbol={ku_sym}" | |
| async with session.get(oi_url) as r1, session.get(trade_url) as r2: | |
| oi_json = await r1.json() | |
| trades = (await r2.json()).get("data", []) | |
| cvd = sum([float(t.get("size", 0)) if t.get("side") == "buy" else -float(t.get("size", 0)) for t in trades]) | |
| return {"oi": float(oi_json.get("data", {}).get("value", 0)), "cvd": cvd} | |
| except Exception as e: | |
| print(f"⚠️ Erreur Flux KuCoin ({symbol}) : {e}") | |
| return {"oi": 0, "cvd": 0} | |
| def prepare_features_sync(symbol, timeframe='1h', limit_bars=600): | |
| try: | |
| now = datetime.now().timestamp() | |
| cache_key = f"{symbol}_{timeframe}" | |
| # 🛡️ FIX : On utilise le TTL dynamique ici | |
| ttl = get_cache_ttl(timeframe) | |
| if cache_key in market_cache and now - last_fetch_time.get(cache_key, 0) < ttl: | |
| df = market_cache[cache_key].copy() | |
| else: | |
| fetch_symbol = symbol if "/USDT" in symbol else symbol.replace("/USD", "/USDT") | |
| if "/" not in fetch_symbol: fetch_symbol += "/USDT" | |
| df = pd.DataFrame() | |
| for attempt in range(3): | |
| try: | |
| bars = exchange_sync.fetch_ohlcv(fetch_symbol, timeframe, limit=limit_bars) | |
| df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol']) | |
| if not df.empty: break | |
| except: time.sleep(2) | |
| if df.empty: return pd.DataFrame() | |
| market_cache[cache_key], last_fetch_time[cache_key] = df.copy(), now | |
| if len(df) < 50: return pd.DataFrame() | |
| df["RSI"], df["RSI_9"] = get_rsi(df["close"]), get_rsi(df["close"], 9) | |
| df["EMA50"], df["EMA200"] = get_ema(df["close"], 50), get_ema(df["close"], 200) | |
| df["VWAP"] = get_vwap(df) | |
| df["ATR"] = get_atr(df) | |
| df["ATR_pct"] = (df["ATR"] / df["close"]) * 100 | |
| df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1 | |
| df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1 | |
| df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min() | |
| df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"] | |
| df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"] | |
| df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"] | |
| df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1 | |
| df["Price_vs_VWAP"] = (df["close"] - df["VWAP"]) / df["VWAP"] | |
| df["ATR_ratio"] = df["ATR"] / df["close"] | |
| df["VOL_ratio"] = df["vol"] / (df["vol"].rolling(24).mean() + 1e-9) | |
| df["Vol_Spike"] = df["vol"] / (df["vol"].rolling(5).mean() + 1e-9) | |
| diff = df["High_24h"] - df["Low_24h"] | |
| df["Fib_618"] = df["Low_24h"] + (diff * 0.618) | |
| df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"] | |
| df["Market_Trend"] = df["EMA200_slope"] | |
| p_low, p_high = df["low"].rolling(24).min().shift(1), df["high"].rolling(24).max().shift(1) | |
| df["Sweep_Low"] = ((df["low"] < p_low) & (df["close"] > p_low)).astype(int) | |
| df["Sweep_High"] = ((df["high"] > p_high) & (df["close"] < p_high)).astype(int) | |
| df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12) | |
| df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2) | |
| df['VOL_RATIO'] = df['vol'] / (df['vol'].rolling(20).mean() + 1e-9) | |
| df['vol_lag1'], df['RSI_Macro'] = df['vol'].shift(1), df["RSI"] | |
| return df.dropna().copy() | |
| except Exception as e: print(f"❌ Error Stats: {e}"); return pd.DataFrame() | |
| import numpy as np | |
| def detect_chart_scenario(df, df_15m=None, df_1h=None, df_4h=None): | |
| try: | |
| # 🛠️ Fix colonnes KuCoin | |
| if 'vol' in df.columns and 'volume' not in df.columns: | |
| df = df.rename(columns={'vol': 'volume'}) | |
| df = df.copy().dropna() | |
| if len(df) < 100: return 3 | |
| last = df.tail(100) | |
| c, h, l, v = last['close'].values, last['high'].values, last['low'].values, last['volume'].values | |
| ema200, ema50 = df['EMA200'], df['EMA50'] | |
| # --- 1. VWAP INSTITUTIONNEL (Sécurisé) --- | |
| typical_price = (df['high'] + df['low'] + df['close']) / 3 | |
| vwap = (typical_price * df['volume']).cumsum() / (df['volume'].cumsum() + 1e-9) | |
| price_above_vwap = df['close'].iloc[-1] > vwap.iloc[-1] | |
| price_below_vwap = df['close'].iloc[-1] < vwap.iloc[-1] | |
| # ======================== | |
| # 🔥 VWAP BANDS (Institutional zones) | |
| # ======================== | |
| vwap_std = df['close'].rolling(20).std() | |
| vwap_upper = vwap + vwap_std | |
| vwap_lower = vwap - vwap_std | |
| price_extended_high = df['close'].iloc[-1] > vwap_upper.iloc[-1] | |
| price_extended_low = df['close'].iloc[-1] < vwap_lower.iloc[-1] | |
| # --- 2. MULTI TIMEFRAME MACRO (15m, 1h, 4h) --- | |
| mtf_bull = mtf_bear = 0 | |
| def analyze_tf(tf_df): | |
| if tf_df is None or len(tf_df) < 50 or 'EMA200' not in tf_df.columns: return 0 | |
| slope = (tf_df['EMA200'].iloc[-1] / tf_df['EMA200'].iloc[-20]) - 1 | |
| return 1 if slope > 0.0001 else (-1 if slope < -0.0001 else 0) | |
| for tf_data in [df_15m, df_1h, df_4h]: | |
| res = analyze_tf(tf_data) | |
| if res == 1: mtf_bull += 1 | |
| elif res == -1: mtf_bear += 1 | |
| # ======================== | |
| # 🔥 ORDER BLOCK STRENGTH | |
| # ======================== | |
| last_candles = df.tail(20) | |
| bullish_ob = False | |
| bearish_ob = False | |
| ob_strength = 0 | |
| for i in range(len(last_candles) - 3): | |
| c1 = last_candles.iloc[i] | |
| c2 = last_candles.iloc[i + 1] | |
| body_size = abs(c2['close'] - c2['open']) | |
| candle_range = c2['high'] - c2['low'] + 1e-9 | |
| strength = body_size / candle_range | |
| if c1['close'] < c1['open'] and c2['close'] > c2['open']: | |
| bullish_ob = True | |
| ob_strength = max(ob_strength, strength) | |
| if c1['close'] > c1['open'] and c2['close'] < c2['open']: | |
| bearish_ob = True | |
| ob_strength = max(ob_strength, strength) | |
| # --- 4. LIQUIDITY SWEEP --- | |
| recent_high, prev_high = np.max(h[-10:]), np.max(h[-30:-10]) | |
| recent_low, prev_low = np.min(l[-10:]), np.min(l[-30:-10]) | |
| liquidity_sweep_high = recent_high > prev_high and c[-1] < recent_high | |
| liquidity_sweep_low = recent_low < prev_low and c[-1] > recent_low | |
| liquidity_high_zone = np.mean(h[-50:]) | |
| liquidity_low_zone = np.mean(l[-50:]) | |
| near_liquidity_high = abs(c[-1] - liquidity_high_zone) / c[-1] < 0.002 | |
| near_liquidity_low = abs(c[-1] - liquidity_low_zone) / c[-1] < 0.002 | |
| # --- 5. STRUCTURE ET RSI --- | |
| range_start, range_end = np.mean(h[:20] - l[:20]), np.mean(h[-20:] - l[-20:]) | |
| squeeze = range_end < range_start * 0.65 | |
| slope_ema200 = (ema200.iloc[-1] / ema200.iloc[-50]) - 1 | |
| slope_ema50 = (ema50.iloc[-1] / ema50.iloc[-20]) - 1 | |
| trend_strength = abs(slope_ema200) + abs(slope_ema50) | |
| trend_up, trend_down = (slope_ema200 > 0 and slope_ema50 > 0), (slope_ema200 < 0 and slope_ema50 < 0) | |
| bos_up = np.max(h[-20:]) > np.max(h[-40:-20]) | |
| bos_down = np.min(l[-20:]) < np.min(l[-40:-20]) | |
| # ======================== | |
| # 🔥 FAKE BREAKOUT FILTER (Déplacé ici pour corriger le bug !) | |
| # ======================== | |
| fake_breakout_up = bos_up and c[-1] < recent_high | |
| fake_breakout_down = bos_down and c[-1] > recent_low | |
| bullish_div = bearish_div = False | |
| if 'RSI' in df.columns: | |
| rsi = df['RSI'].values | |
| bearish_div = np.max(h[-15:]) >= np.max(h[-30:-15]) and np.max(rsi[-15:]) < np.max(rsi[-30:-15]) | |
| bullish_div = np.min(l[-15:]) <= np.min(l[-30:-15]) and np.min(rsi[-15:]) > np.min(rsi[-30:-15]) | |
| vol_recent, vol_past = np.mean(v[-15:]), np.mean(v[-40:-15]) | |
| vol_inc = vol_recent > vol_past * 1.2 | |
| # 🔥 CONTINUATION PRO MAX | |
| if squeeze and trend_strength > 0.004 and price_above_vwap and mtf_trend_up and not price_extended_high: | |
| return 4 | |
| # 🔴 REVERSAL SMART MONEY | |
| if (liquidity_sweep_high or bearish_ob or bearish_div or price_extended_high or near_liquidity_high) and trend_up: | |
| return 5 | |
| if (liquidity_sweep_low or bullish_ob or bullish_div or price_extended_low or near_liquidity_low) and trend_down: | |
| return 5 | |
| # 🟢 TREND INSTITUTIONNEL | |
| if trend_strength > 0.006 and vol_inc and ob_strength > 0.5: | |
| if price_above_vwap and mtf_trend_up and not fake_breakout_up: | |
| return 0 | |
| if price_below_vwap and mtf_trend_down and not fake_breakout_down: | |
| return 1 | |
| # 🟡 BREAKOUT PROPRE | |
| if vol_inc and not fake_breakout_up and bos_up and price_above_vwap: | |
| return 0 | |
| if vol_inc and not fake_breakout_down and bos_down and price_below_vwap: | |
| return 1 | |
| return 3 # RANGE / CHAOS par défaut | |
| except Exception as e: | |
| print(f"⚠️ Erreur Detect Scenario: {e}") | |
| return 3 | |
| async def predict_signal(symbol, timeframe="1h"): | |
| try: | |
| memory_guard() | |
| symbol = str(symbol).strip().upper() | |
| df = prepare_features_sync(symbol, timeframe) | |
| if df.empty: return {"status": "error", "message": "Data insuffisante"} | |
| last_row = df.iloc[[-1]] | |
| prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0]) | |
| vwap = float(last_row['VWAP'].iloc[0]) | |
| vol_spike = float(last_row['Vol_Spike'].iloc[0]) | |
| rsi_9 = float(last_row['RSI_9'].iloc[0]) | |
| # 🌊 1. COLLECTE DES DONNÉES (Flux, Sentiment, OI) | |
| futures_data = await fetch_kucoin_futures_data(symbol) | |
| oi, cvd = futures_data["oi"], futures_data["cvd"] | |
| p_sent = await get_crypto_sentiment(symbol) | |
| # 🏗️ 1.5 CHARGEMENT MULTI-TIMEFRAME MACRO | |
| df_15m = prepare_features_sync(symbol, "15m", limit_bars=150) | |
| df_1h = prepare_features_sync(symbol, "1h", limit_bars=150) | |
| df_4h = prepare_features_sync(symbol, "4h", limit_bars=150) | |
| # 🧠 2. DÉTECTION DU SCÉNARIO (L'œil + L'IA) | |
| regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]]) | |
| regime_ml = int(regime_model.predict(regime_scaled)[0]) | |
| # On passe les dataframes MTF à la fonction | |
| pattern_id = detect_chart_scenario(df, df_15m, df_1h, df_4h) | |
| final_scenario = pattern_id if pattern_id in [4, 5] else regime_ml | |
| # 🧠 3. CALCUL DES PROBABILITÉS IA | |
| ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"] | |
| ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1]) | |
| time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO'] | |
| time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1]) | |
| # 🧠 SÉLECTION DU CERVEAU DINO (Schizophrénie) | |
| sym_clean = symbol.replace("/", "").replace("USDT", "").replace("USD", "").upper() | |
| if 'dino_brains' in globals() and sym_clean in dino_brains: | |
| dino_prob = float(dino_brains[sym_clean].predict(last_row[ml_cols].values)[0]) | |
| elif 'dino_brain' in globals() and dino_brain: # Sécurité : Fallback sur l'ancien modèle si besoin | |
| dino_prob = float(dino_brain.predict(last_row[ml_cols].values)[0]) | |
| else: | |
| dino_prob = 0.5 # Si aucun cerveau n'est entraîné, on reste neutre | |
| # ⚡ 4. ENSEMBLE V30 | |
| final_p, wt, wm, wl, ws = combine_scores(symbol, timeframe, time_prob, ml_prob, dino_prob, p_sent, final_scenario) | |
| # 🐋 5. BOOSTS (Smart Money & Scalping) | |
| smc_status = "AUCUN" | |
| if int(last_row["Sweep_Low"].iloc[0]) == 1 and cvd > 0: | |
| final_p = min(0.95, final_p + 0.20); smc_status = "LONG SWEEP + CVD 🐋" | |
| elif int(last_row["Sweep_High"].iloc[0]) == 1 and cvd < 0: | |
| final_p = max(0.05, final_p - 0.20); smc_status = "SHORT SWEEP + CVD 🐋" | |
| if timeframe in ["1m", "5m"]: | |
| if final_p > 0.5 and vol_spike > 1.5 and rsi_9 < 70: final_p = min(0.95, final_p + 0.10) | |
| elif final_p < 0.5 and vol_spike > 1.5 and rsi_9 > 30: final_p = max(0.05, final_p - 0.10) | |
| # 🛡️ 6. RÉCUPÉRATION ADN (Multi-Scénarios) + BOUCLIER V32 | |
| with sqlite3.connect(DB_NAME) as conn: | |
| res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?", (symbol, timeframe, final_scenario)).fetchone() | |
| if not res: | |
| res_def = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone() | |
| tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res_def if res_def else (1.5, 1.0, 0.55, 0.002) | |
| conn.execute("INSERT OR IGNORE INTO agent_logic (symbol, timeframe, regime, tp_mult, sl_mult, min_prob, min_tp_dist, generation) VALUES (?, ?, ?, ?, ?, ?, ?, 1)", (symbol, timeframe, final_scenario, tp_m, sl_m, agent_min_prob, agent_min_tp_dist)) | |
| conn.commit() | |
| else: | |
| tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res | |
| tp_m, sl_m = safe_agent_values(tp_m, sl_m) | |
| # 🔥 LE PATCH V32 (ANTI-CORRUPTION) EST LÀ : | |
| if sl_m > 5.0 or tp_m > 5.0: | |
| print(f"🚨 [V32] CORRUPTION DETECTÉE SUR {symbol} (SLx{round(sl_m,1)}) → NEUTRALISATION") | |
| tp_m, sl_m = 1.5, 1.0 # On force le reset local | |
| else: | |
| # On bride les valeurs par sécurité extrême | |
| tp_m = float(np.clip(tp_m, 0.5, 3.0)) | |
| sl_m = float(np.clip(sl_m, 0.5, 2.0)) | |
| # 📐 7. CALCUL DES NIVEAUX (TP/SL) | |
| tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m) | |
| sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m) | |
| strength = abs(final_p - 0.5) * 2 | |
| conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, dino_prob, p_sent]))) | |
| composite_score = max(0, min(100, (strength * 45) + (conf_val * 40) + (15 if final_scenario in [0, 1] else 5))) | |
| # 🛑 8. SYSTÈME DE VÉTO | |
| dist_tp_pct = abs(tp - prix) / prix | |
| ema200_val = float(last_row["EMA200"].iloc[0]) | |
| mkt_trend = float(last_row["Market_Trend"].iloc[0]) | |
| print(f"🧠 [IA] {symbol} [{timeframe}] | Scénario: {final_scenario} | Proba: {round(final_p, 4)} | SMC: {smc_status}") | |
| veto, veto_reason = False, "" | |
| if final_p < agent_min_prob and final_p > (1 - agent_min_prob): veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}" | |
| elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible" | |
| elif final_p < 0.5 and (prix > ema200_val or prix > vwap): veto, veto_reason = True, "Short Interdit (Prix > Bull Lines)" | |
| elif final_p > 0.5 and (prix < ema200_val or prix < vwap): veto, veto_reason = True, "Long Interdit (Prix < Bull Lines)" | |
| elif "BTC" not in symbol and "ETH" not in symbol and "SOL" not in symbol: | |
| if final_p < 0.5 and mkt_trend > 0.002: veto, veto_reason = True, "Trend Global Haussier" | |
| elif final_p > 0.5 and mkt_trend < -0.002: veto, veto_reason = True, "Trend Global Baissier" | |
| if veto: | |
| return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": veto_reason, "scenario": final_scenario} | |
| # 🛑 8.5 ANTI-SPAM (On bloque ici si le signal a déjà été envoyé) | |
| if not should_send_signal(symbol, timeframe, final_p): | |
| return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": "Anti-Spam Actif (Déjà envoyé)", "scenario": final_scenario} | |
| # 💾 9. ENREGISTREMENT DB | |
| db_task = (datetime.now(timezone.utc).isoformat(), symbol, timeframe, 'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl, 'EN_COURS', final_scenario, time_prob, ml_prob, dino_prob, p_sent, prix) | |
| await save_to_db(db_task) | |
| print(f"💾 [DB] Signal {symbol} [{timeframe}] sauvegardé. En attente du Bras...") | |
| return { | |
| "symbol": symbol, "timeframe": timeframe, "status": "success", "final_score": round(final_p, 4), "score": int(composite_score), | |
| "smart_money": smc_status, "price": prix, "tp": round(tp, 6), "sl": round(sl, 6), "scenario": final_scenario, "confluence": round(conf_val * 100, 1) | |
| } | |
| except Exception as e: return {"status": "error", "message": str(e)} | |
| # ============================= | |
| def mutate_agent(symbol, timeframe, regime, success=True): | |
| import sqlite3, random, numpy as np, time | |
| for tentative in range(5): # 🔄 Tente 5 fois si la base est verrouillée | |
| try: | |
| with sqlite3.connect(DB_NAME, timeout=20) as conn: | |
| conn.row_factory = sqlite3.Row | |
| row = conn.execute( | |
| "SELECT * FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?", | |
| (symbol, timeframe, regime) | |
| ).fetchone() | |
| if not row: | |
| res_def = conn.execute( | |
| "SELECT * FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", | |
| (timeframe,) | |
| ).fetchone() | |
| tp, sl, prob = (res_def['tp_mult'], res_def['sl_mult'], res_def['min_prob']) if res_def else (1.5, 1.0, 0.60) | |
| else: | |
| tp, sl, prob = row['tp_mult'], row['sl_mult'], row['min_prob'] | |
| # 🧠 MUTATION DOUCE | |
| if success: | |
| new_tp = tp * random.uniform(1.01, 1.05) | |
| new_sl = sl * random.uniform(0.98, 1.00) | |
| new_prob = max(0.55, prob - 0.003) | |
| else: | |
| new_tp = tp * random.uniform(0.95, 0.99) | |
| new_sl = sl * random.uniform(1.01, 1.05) | |
| new_prob = min(0.85, prob + 0.005) | |
| # 🛡️ HARD CLAMP (ANTI-EXPLOSION) | |
| new_tp = float(np.clip(new_tp, 0.5, 3.0)) | |
| new_sl = float(np.clip(new_sl, 0.5, 2.0)) | |
| conn.execute(''' | |
| INSERT OR REPLACE INTO agent_logic | |
| (symbol, timeframe, regime, tp_mult, sl_mult, min_prob, min_tp_dist, generation) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, | |
| COALESCE((SELECT generation FROM agent_logic WHERE symbol=? AND timeframe=? AND regime=?)+1, 1)) | |
| ''', ( | |
| symbol, timeframe, regime, | |
| new_tp, new_sl, new_prob, 0.001, | |
| symbol, timeframe, regime | |
| )) | |
| conn.commit() | |
| return # ✅ Succès, on sort de la fonction | |
| except sqlite3.OperationalError as e: | |
| if "locked" in str(e): | |
| time.sleep(1) # La porte est bloquée, on attend 1 seconde | |
| else: | |
| print(f"❌ [ERREUR DB] Mutation échouée : {e}") | |
| break | |
| except Exception as e: | |
| print(f"🧬 Erreur Mutation V32: {e}") | |
| break | |
| # ============================= | |
| # 🛡️ 3. PROTECTION LECTURE DB | |
| # ============================= | |
| def safe_agent_values(tp_m, sl_m): | |
| import numpy as np | |
| # 🔥 Anti corruption | |
| if sl_m > 10 or tp_m > 10: | |
| print("🚨 CORRUPTION DETECTÉE → RESET LOCAL") | |
| return 1.5, 1.0 | |
| tp_m = float(np.clip(tp_m, 0.5, 3.0)) | |
| sl_m = float(np.clip(sl_m, 0.5, 2.0)) | |
| return tp_m, sl_m | |
| # --- 🌙 DREAM LOOP V31 (CORRIGÉE) --- | |
| async def dream_simulation_loop(): | |
| while True: | |
| if DREAM_MODE_ACTIVE: | |
| print("🌙 [DREAM MODE] Le bot rêve et s'entraîne à la salle... 🥊") | |
| for symbol in AUTO_SYMBOLS: | |
| for tf in AUTO_TIMEFRAMES: | |
| try: | |
| df = prepare_features_sync(symbol, timeframe=tf, limit_bars=200) | |
| if df.empty or len(df) < 50: continue | |
| # 1. On voyage dans le temps (une bougie au hasard) | |
| idx = random.randint(10, len(df) - 30) | |
| row_actuelle = df.iloc[idx] | |
| prix = float(row_actuelle['close']) | |
| atr = float(row_actuelle['ATR']) | |
| # 2. On récupère le régime de marché (Scénario) | |
| # 2. On calcule le VRAI scénario de cette époque ! | |
| # On coupe le graphique pile au moment du passé qu'on a choisi | |
| df_past = df.iloc[:idx+1] | |
| # Le bot analyse la structure du marché de l'époque | |
| current_scenario = detect_chart_scenario(df_past, df_past, df_past, df_past, df_past) | |
| # 3. On extrait l'ADN actuel (les multiplicateurs) | |
| with sqlite3.connect(DB_NAME) as conn: | |
| res = conn.execute("SELECT tp_mult, sl_mult FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?", (symbol, tf, current_scenario)).fetchone() | |
| tp_m, sl_m = res if res else (1.5, 1.0) | |
| # 4. Simulation de combat (Long ou Short) depuis CETTE époque | |
| is_long = random.choice([True, False]) | |
| tp = prix + (atr * tp_m) if is_long else prix - (atr * tp_m) | |
| sl = prix - (atr * sl_m) if is_long else prix + (atr * sl_m) | |
| # 5. On regarde les 24 bougies suivantes pour voir qui gagne | |
| future_data = df.iloc[idx+1 : idx+25] | |
| win = False | |
| for _, row in future_data.iterrows(): | |
| if is_long: | |
| if row['high'] >= tp: win = True; break | |
| if row['low'] <= sl: win = False; break | |
| else: | |
| if row['low'] <= tp: win = True; break | |
| if row['high'] >= sl: win = False; break | |
| # 6. 🧬 ÉVOLUTION : On récompense ou on punit l'ADN ! | |
| mutate_agent(symbol, tf, current_scenario, success=win) | |
| await asyncio.sleep(5) | |
| except Exception as e: | |
| pass # On reste silencieux pendant le sommeil | |
| await asyncio.sleep(60) | |
| def should_send_signal(symbol, timeframe, proba): | |
| global last_signals_sent | |
| key = (symbol, timeframe) | |
| now = time.time() | |
| # 🛡️ FIX : La prison est adaptée à la Timeframe | |
| tf_blocks = {"1m": 60, "5m": 300, "15m": 900, "1h": 3600, "4h": 14400} | |
| block_time = tf_blocks.get(timeframe, 3600) # Par défaut 1h si inconnu | |
| if key in last_signals_sent: | |
| last_proba, last_time = last_signals_sent[key] | |
| # Tolérance de 5% d'écart max, et blocage selon le tf_block | |
| if abs(last_proba - proba) < 0.05 and (now - last_time) < block_time: | |
| return False | |
| last_signals_sent[key] = (proba, now) | |
| return True | |
| # --- ⚖️ JUDGE API V31 (VERSION NETTOYÉE POUR SL PHYSIQUE) --- | |
| def run_judge_api(live_prices_json="{}"): | |
| try: | |
| import json | |
| try: | |
| raw_prices = json.loads(live_prices_json) if isinstance(live_prices_json, str) else {} | |
| live_prices = {k.lower(): v for k, v in raw_prices.items()} | |
| except: | |
| live_prices = {} | |
| with sqlite3.connect(DB_NAME, timeout=10) as conn: | |
| conn.row_factory = sqlite3.Row | |
| cursor = conn.cursor() | |
| cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'") | |
| trades = cursor.fetchall() | |
| closed_trades = [] | |
| watching_info = [] # 🎯 Contiendra uniquement des dicts pour le Bras | |
| current_time = datetime.now(timezone.utc) | |
| max_age_minutes = {"15m": 30, "1h": 60, "4h": 240} # Le 4h expire après 4 heures | |
| for t in trades: | |
| try: | |
| sym_db = t['symbol'] | |
| epic = sym_db.replace("/", "").replace("USDT", "USD").lower() | |
| if not epic.endswith("m"): epic += "m" | |
| is_live = epic in live_prices | |
| if is_live and t['confirmed'] == 0: | |
| cursor.execute("UPDATE signals SET confirmed = 1 WHERE id = ?", (t['id'],)) | |
| print(f"✅ [AUTO-CONFIRM] ID: {t['id']}") | |
| if not is_live: | |
| date_str = t['date'].replace('Z', '+00:00') | |
| signal_time = datetime.fromisoformat(date_str) | |
| age_minutes = (current_time - signal_time).total_seconds() / 60.0 | |
| max_age = max_age_minutes.get(t['timeframe'], 60) | |
| if t['confirmed'] == 1: | |
| cursor.execute("UPDATE signals SET status = 'FERMÉ PAR MT5 🛑' WHERE id = ?", (t['id'],)) | |
| closed_trades.append({"symbol": t['symbol'], "id": t['id'], "direction": t['direction']}) | |
| elif age_minutes > max_age: | |
| cursor.execute("UPDATE signals SET status = 'EXPIRÉ ⏰' WHERE id = ?", (t['id'],)) | |
| continue | |
| # --- CALCUL DU TRAILING --- | |
| current_price = float(live_prices[epic]) | |
| sl_dyn, peak = t['sl'], t['peak_price'] | |
| new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price) | |
| dist_totale = abs(t['tp'] - t['price']) | |
| if t['direction'] == 'HAUSSIER': | |
| mouvement = current_price - t['price'] | |
| else: | |
| mouvement = t['price'] - current_price | |
| progression = mouvement / dist_totale if (dist_totale > 0 and mouvement > 0) else 0 | |
| nouveau_sl = sl_dyn | |
| dist_sl_initial = abs(t['price'] - sl_dyn) | |
| # LOGIQUE AGRESSIVE | |
| if t['direction'] == 'HAUSSIER': | |
| if progression >= 0.60: nouveau_sl = max(sl_dyn, t['price'] + (dist_totale * 0.50)) | |
| elif progression >= 0.40: nouveau_sl = max(sl_dyn, t['price'] + (dist_totale * 0.20)) | |
| elif progression >= 0.20: nouveau_sl = max(sl_dyn, t['price'] + (dist_totale * 0.02)) | |
| elif progression >= 0.10: nouveau_sl = max(sl_dyn, t['price'] - (dist_sl_initial * 0.50)) | |
| else: | |
| if progression >= 0.60: nouveau_sl = min(sl_dyn, t['price'] - (dist_totale * 0.50)) | |
| elif progression >= 0.40: nouveau_sl = min(sl_dyn, t['price'] - (dist_totale * 0.20)) | |
| elif progression >= 0.20: nouveau_sl = min(sl_dyn, t['price'] - (dist_totale * 0.02)) | |
| elif progression >= 0.10: nouveau_sl = min(sl_dyn, t['price'] + (dist_sl_initial * 0.50)) | |
| cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, nouveau_sl, t['id'])) | |
| # --- DÉCISION DE CLÔTURE --- | |
| outcome, reward = None, 0 | |
| if t['direction'] == 'HAUSSIER': | |
| if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3 | |
| elif current_price <= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl > t['price'] else ("PERDU ❌", -5) | |
| else: | |
| if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3 | |
| elif current_price >= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl < t['price'] else ("PERDU ❌", -5) | |
| if outcome: | |
| mutate_agent(t['symbol'], t['timeframe'], t['regime'], success=(reward>0)) | |
| cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id'])) | |
| closed_trades.append({"symbol": t['symbol'], "id": t['id'], "direction": t['direction']}) | |
| else: | |
| # 🎯 ON N'AJOUTE DANS WATCHING QUE SI LE TRADE EST ENCORE OUVERT | |
| watching_info.append({ | |
| "id": t['id'], | |
| "symbol": t['symbol'], | |
| "sl": round(nouveau_sl, 6), | |
| "prog": round(progression, 4) | |
| }) | |
| except Exception as e: | |
| print(f"⚠️ Erreur Trade ID {t['id']}: {e}") | |
| conn.commit() | |
| # Retour propre : data contient les fermetures, watching contient les mises à jour SL | |
| return { | |
| "status": "updates" if closed_trades else "waiting", | |
| "data": closed_trades, | |
| "watching": watching_info | |
| } | |
| except Exception as e: return {"status": "error", "message": str(e)} | |
| # --- 📡 LECTURE RADAR OPTIMISÉE --- | |
| def get_active_signals(): | |
| try: | |
| with sqlite3.connect(DB_NAME, timeout=20) as conn: | |
| conn.row_factory = sqlite3.Row | |
| cursor = conn.cursor() | |
| # 🎯 On ne prend QUE les signaux NON CONFIRMÉS pour ne pas surcharger le Bras | |
| cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS' AND confirmed = 0") | |
| signaux = [dict(row) for row in cursor.fetchall()] | |
| print(f"📡 [API RADAR] {len(signaux)} nouveaux signaux trouvés pour le Bras.") | |
| return signaux | |
| except Exception as e: | |
| print(f"❌ [ERREUR CRITIQUE CLOUD] get_active_signals a planté : {e}") | |
| return [] | |
| # --- 📊 GET BOT SKILLS (UI Dashboard) --- | |
| def get_bot_skills(): | |
| try: | |
| scenario_map = {0: "BULL RUN", 1: "BEAR RUN", 2: "PULLBACK", 3: "RANGE/CHAOS", 4: "SQUEEZE", 5: "REVERSAL"} | |
| with sqlite3.connect(DB_NAME) as conn: | |
| return [[r[0], r[1], scenario_map.get(r[2], "INCONNU"), f"x{round(r[3], 2)}", f"x{round(r[4], 2)}", f"{round(r[5]*100)}%", f"🧬 Gen {r[6]}"] | |
| for r in conn.cursor().execute("SELECT symbol, timeframe, regime, tp_mult, sl_mult, min_prob, generation FROM agent_logic ORDER BY symbol, timeframe, regime").fetchall()] | |
| except Exception as e: return [[f"Erreur: {str(e)}", "-", "-", "-", "-", "-", "-"]] | |
| # --- 🧠 TRAINING ENGINE --- | |
| def trigger_training(symbol="BTC/USD"): | |
| try: | |
| memory_guard() | |
| # 🎯 1. Nettoyage du symbole pour le nom de fichier (ex: BTC/USDT -> BTC) | |
| sym_clean = symbol.replace("/", "").replace("USDT", "").replace("USD", "").upper() | |
| if not sym_clean: sym_clean = "DEFAULT" | |
| # On crée un nom de fichier unique par crypto | |
| model_filename = f"dino_lgbm_{sym_clean}.txt" | |
| bars = exchange_sync.fetch_ohlcv(symbol, timeframe='1h', limit=1500) | |
| df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol']) | |
| if len(df) < 500: return f"❌ Historique insuffisant pour {symbol}." | |
| df_final = prepare_features_sync(symbol, '1h', limit_bars=1000) | |
| if df_final.empty or len(df_final) < 100: return f"❌ Données vides pour {symbol}." | |
| if LGBM_AVAILABLE: | |
| ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"] | |
| df_final['Target'] = (df_final['close'].shift(-1) > df_final['close']).astype(int) | |
| df_train = df_final.dropna(subset=ml_cols + ['Target']) | |
| X, y = df_train[ml_cols], df_train['Target'] | |
| params = {'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'learning_rate': 0.05, 'num_leaves': 31, 'verbose': -1} | |
| model = lgb.train(params, lgb.Dataset(X, label=y), 100) | |
| # 💾 2. Sauvegarde avec le nom unique | |
| model.save_model(model_filename) | |
| try: | |
| api = HfApi() | |
| api.upload_file( | |
| path_or_fileobj=model_filename, | |
| path_in_repo=model_filename, | |
| repo_id=HF_REPO_ID, | |
| repo_type="dataset", | |
| token=HF_TOKEN | |
| ) | |
| print(f"☁️ [BACKUP] {model_filename} sauvegardé sur le Cloud !") | |
| except Exception as e: | |
| print(f"⚠️ Erreur backup modèle {sym_clean} : {e}") | |
| # 🧠 3. On met à jour le dictionnaire global des cerveaux DINO | |
| global dino_brains | |
| if 'dino_brains' not in globals(): | |
| dino_brains = {} | |
| dino_brains[sym_clean] = lgb.Booster(model_file=model_filename) | |
| print(f"🧠 [IA] Cerveau {sym_clean} chargé en mémoire vive.") | |
| # Ces deux là restent uniques car ils sont sûrement génériques dans ton code | |
| global ml_model, time_model | |
| try: | |
| ml_model, time_model = joblib.load("ml_model_v9.pkl"), joblib.load("time_model.pkl") | |
| except: pass # Si les pkl n'existent pas, on ne crashe pas tout | |
| gc.collect() | |
| return f"✅ IA ré-entraînée et sauvegardée pour {symbol} ({model_filename})." | |
| except Exception as e: return f"❌ Erreur Training {symbol} : {e}" | |
| # --- 🚀 MOTEURS AUTO-PILOTE --- | |
| AUTO_SYMBOLS = ["BTC/USD", "ETH/USD", "SOL/USD"] | |
| AUTO_TIMEFRAMES = ["15m", "1h", "4h"] | |
| def set_bot_mode(mode): | |
| global DREAM_MODE_ACTIVE | |
| target_mode = mode[0] if isinstance(mode, list) else str(mode) | |
| if "LIVE" in target_mode.upper(): DREAM_MODE_ACTIVE, msg = False, "🛰️ Mode LIVE activé" | |
| else: DREAM_MODE_ACTIVE, msg = True, "🌙 Mode DREAM activé" | |
| return {"status": "success", "mode": "DREAM" if DREAM_MODE_ACTIVE else "LIVE", "message": msg} | |
| async def universal_scanner_loop(): | |
| print("👁️ [SCANNER] Le Cerveau Global est en ligne H24...") | |
| while True: | |
| for symbol in AUTO_SYMBOLS: | |
| for tf in AUTO_TIMEFRAMES: | |
| try: | |
| # 🧹 1. ON NETTOIE AVANT DE PRÉDIRE (On vire les vieux fantômes) | |
| clean_old_db_signals(symbol, tf) | |
| # 🧠 2. ON PRÉDIT ET ON SAUVEGARDE LE NOUVEAU SIGNAL | |
| pred = await predict_signal(symbol, timeframe=tf) | |
| if pred and pred.get("status") == "success": | |
| print(f"✅ [SIGNAL VALIDÉ] {symbol} [{tf}] - Probabilité: {pred['final_score']}") | |
| # Ligne clean_old supprimée d'ici pour ne pas tuer le trade ! | |
| active_signals_state[(symbol, tf)] = pred | |
| if not DREAM_MODE_ACTIVE: | |
| print(f"🎯 [LIVE] Action requise pour {symbol} [{tf}] : Passage d'ordre.") | |
| await asyncio.sleep(5) | |
| except Exception as e: | |
| print(f"⚠️ Erreur Scanner {symbol} [{tf}]: {e}") | |
| await asyncio.sleep(60) | |
| # --- ⚖️ TOOLS --- | |
| def keep_alive_ping(): return {"status": "awake", "time": datetime.now(timezone.utc).isoformat()} | |
| def confirm_trade_api(trade_id, real_price): | |
| try: | |
| with sqlite3.connect(DB_NAME) as conn: | |
| conn.row_factory, cursor = sqlite3.Row, conn.cursor() | |
| cursor.execute("SELECT price, tp, sl FROM signals WHERE id = ?", (int(trade_id),)) | |
| t = cursor.fetchone() | |
| if not t: return {"status": "error"} | |
| ecart = float(real_price) - t['price'] | |
| cursor.execute("UPDATE signals SET confirmed = 1, price = ?, tp = ?, sl = ?, peak_price = ? WHERE id = ?", (float(real_price), t['tp'] + ecart, t['sl'] + ecart, float(real_price), int(trade_id))) | |
| conn.commit() | |
| print(f"🤝 [BRIDGE] Le Bras a exécuté l'ID {trade_id} à {real_price}$. Synchronisation OK.") | |
| return {"status": "success"} | |
| except: return {"status": "error"} | |
| def cancel_trade_api(trade_id): | |
| try: | |
| with sqlite3.connect(DB_NAME) as conn: conn.execute("UPDATE signals SET status = 'ANNULÉ 🗑️', confirmed = 0 WHERE id = ?", (int(trade_id),)); conn.commit() | |
| return {"status": "success"} | |
| except: return {"status": "error"} | |
| def reset_trade_history(): | |
| """ | |
| Supprime UNIQUEMENT les trades (la table signals) | |
| Garde intacte la mémoire génétique (la table agents) | |
| """ | |
| try: | |
| with sqlite3.connect(DB_NAME, timeout=10) as conn: | |
| cursor = conn.cursor() | |
| # 1. On supprime tout l'historique des trades (brouillons, validés, expirés...) | |
| cursor.execute("DELETE FROM signals") | |
| # 2. (Optionnel mais propre) On remet le compteur d'ID à 1 | |
| cursor.execute("DELETE FROM sqlite_sequence WHERE name='signals'") | |
| conn.commit() | |
| return "✅ RESET RÉUSSI : Tous les trades ont été effacés. L'ADN est conservé !" | |
| except Exception as e: | |
| return f"❌ ERREUR RESET : {e}" | |
| def training_wrapper(symbol, *args): return trigger_training(str(symbol).strip().upper() if isinstance(symbol, str) else "BTC/USD") | |
| import atexit | |
| def close_session(): | |
| global global_aio_session | |
| if global_aio_session and not global_aio_session.closed: | |
| asyncio.run(global_aio_session.close()) | |
| # --- 🎨 INTERFACE GRADIO V30 --- | |
| with gr.Blocks(theme=gr.themes.Monochrome()) as iface: | |
| gr.Markdown("# 🦖 Alpha V30 Dinosaur Engine (Master Edition)") | |
| with gr.Tab("Admin"): | |
| gr.Button("Forcer Entraînement", variant="stop").click(fn=training_wrapper, inputs=gr.Textbox(label="Symbole à recalibrer", value="BTC/USDT"), outputs=gr.Textbox(), api_name="trigger_training") | |
| gr.Markdown("---") | |
| gr.Markdown("### ⚠️ Zone de Danger") | |
| reset_btn = gr.Button("🗑️ Purger tous les trades (Garder l'ADN)", variant="primary") | |
| reset_out = gr.Textbox(label="Résultat du nettoyage") | |
| reset_btn.click(fn=reset_trade_history, inputs=[], outputs=reset_out, api_name="reset_trades") | |
| with gr.Tab("🎯 Prédictions"): | |
| gr.Button("Predict", variant="primary").click(fn=predict_signal, inputs=[gr.Textbox(label="Symbole", value="BTC/USD"), gr.Dropdown(choices=["15m", "1h", "4h"], value="1h")], outputs=gr.JSON()) | |
| with gr.Tab("⚖️ Système"): | |
| gr.Button("Judge").click(fn=run_judge_api, inputs=gr.Textbox(value="{}", visible=False), outputs=gr.JSON(), api_name="run_judge_api") | |
| gr.Button("Get Active", visible=False).click(fn=get_active_signals, outputs=gr.JSON(), api_name="get_active_signals") | |
| gr.Button("Ping", visible=False).click(fn=keep_alive_ping, outputs=gr.JSON(), api_name="keep_alive_ping") | |
| gr.Button("Set Mode", visible=False).click(fn=set_bot_mode, inputs=gr.Textbox(visible=False), outputs=gr.JSON(), api_name="set_bot_mode") | |
| with gr.Tab("📊 Stats (ADN)"): | |
| skills_table = gr.Dataframe(headers=["Marché", "Timeframe", "Scénario", "Cible (TP)", "Risque (SL)", "Peur Min.", "Génération"], value=get_bot_skills(), interactive=False) | |
| with gr.Row(): | |
| gr.Button("🔄 Actualiser").click(get_bot_skills, outputs=skills_table) | |
| gr.Button(visible=False).click(fn=confirm_trade_api, inputs=[gr.Textbox(visible=False), gr.Textbox(visible=False)], outputs=gr.JSON(), api_name="confirm_trade_api") | |
| gr.Button(visible=False).click(fn=cancel_trade_api, inputs=gr.Textbox(visible=False), outputs=gr.JSON(), api_name="cancel_trade_api") | |
| iface.load(get_bot_skills, outputs=skills_table) | |
| # --- ☁️ SAUVEGARDE CLOUD SILENCIEUSE --- | |
| async def hf_backup_loop(): | |
| while True: | |
| await asyncio.sleep(1800) # Attendre 30 minutes (1800 secondes) | |
| print("☁️ [SYSTEM] Lancement du backup planifié...") | |
| backup_db_to_hf() | |
| import threading | |
| _auto_pilot_started = False | |
| def run_auto_pilot(): | |
| global _auto_pilot_started | |
| if _auto_pilot_started: return | |
| _auto_pilot_started = True | |
| print("⏳ [SYSTEM] Attente 15s avant propulsion Master V31...") | |
| time.sleep(15) | |
| try: | |
| loop = asyncio.new_event_loop() | |
| asyncio.set_event_loop(loop) | |
| loop.create_task(universal_scanner_loop()) | |
| loop.create_task(dream_simulation_loop()) | |
| loop.create_task(hf_backup_loop()) # ⬅️ LA SAUVEGARDE EST BIEN ICI | |
| loop.run_forever() | |
| except Exception as e: print(f"⚠️ Erreur Auto-Pilote : {e}") | |
| if __name__ == "__main__": | |
| try: threading.Thread(target=run_auto_pilot, daemon=True).start() | |
| except Exception as e: print(f"⚠️ Erreur Thread : {e}") | |
| # 🛑 LE LAUNCH DOIT TOUJOURS ÊTRE LA TOUTE DERNIÈRE LIGNE DU FICHIER ! | |
| iface.launch(server_name="0.0.0.0", server_port=7860, show_api=True) |