Nexo-S's picture
Update app.py
581a042 verified
# --- 🏴‍☠️ MONKEY PATCH & IMPORTS --
import huggingface_hub
try:
from huggingface_hub import HfFolder
except ImportError:
class MockHfFolder:
@staticmethod
def get_token(): return None
@staticmethod
def save_token(token): pass
@staticmethod
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
@classmethod
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
@atexit.register
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)