Nexo-S commited on
Commit
eb8d75c
·
verified ·
1 Parent(s): aff4507

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -522
app.py CHANGED
@@ -19,16 +19,16 @@ from huggingface_hub import HfApi, hf_hub_download
19
  import gradio as gr
20
  import ccxt
21
  import time
22
- import sys
23
  from types import ModuleType
24
- import pandas as pd
25
  import yfinance as yf
 
26
  try:
27
  import MetaTrader5 as mt5
28
  MT5_AVAILABLE = True
29
  except ImportError:
30
  MT5_AVAILABLE = False
31
  print("🌐 [CLOUD MODE] MetaTrader5 non détecté. Bascule sur l'antenne de secours (CCXT).")
 
32
  # --- 🥷 NINJA HACK : MOCK PANDAS_TA ---
33
  if "pandas_ta" not in sys.modules:
34
  mock_ta = ModuleType("pandas_ta")
@@ -86,7 +86,7 @@ except:
86
 
87
  # --- DB & SYNC ---
88
  REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
89
- DB_NAME = "alphatrade_v22_scalp.db" # 🎮 NOUVEAU MONDE - NIVEAU 0
90
  HF_TOKEN = os.environ.get("HF_TOKEN")
91
 
92
  def init_db():
@@ -121,17 +121,7 @@ def init_db():
121
  cursor.execute("PRAGMA table_info(agent_logic)")
122
  al_cols = [col[1] for col in cursor.fetchall()]
123
 
124
- if al_cols and 'symbol' not in al_cols:
125
- print("⚠️ Migration BDD : Mise à jour de agent_logic...")
126
- conn.execute("ALTER TABLE agent_logic RENAME TO agent_logic_old")
127
- conn.execute('''CREATE TABLE agent_logic (
128
- symbol TEXT, timeframe TEXT, tp_mult REAL, sl_mult REAL,
129
- score REAL, last_pnl REAL, min_prob REAL, min_tp_dist REAL,
130
- PRIMARY KEY (symbol, timeframe))''')
131
- conn.execute("INSERT INTO agent_logic (symbol, timeframe, tp_mult, sl_mult, score, last_pnl, min_prob, min_tp_dist) SELECT 'ALL', timeframe, tp_mult, sl_mult, score, last_pnl, min_prob, min_tp_dist FROM agent_logic_old")
132
- conn.execute("DROP TABLE agent_logic_old")
133
- conn.commit()
134
- elif not al_cols:
135
  conn.execute('''CREATE TABLE IF NOT EXISTS agent_logic (
136
  symbol TEXT, timeframe TEXT, tp_mult REAL, sl_mult REAL,
137
  score REAL, last_pnl REAL, min_prob REAL, min_tp_dist REAL,
@@ -139,18 +129,16 @@ def init_db():
139
 
140
  cursor.execute("SELECT COUNT(*) FROM agent_logic")
141
  if cursor.fetchone()[0] == 0:
142
- # NOUVEAU: Defaults ultra-agressifs pour le Scalping
143
- defaults = [('ALL', '1m', 1.0, 0.8, 0, 0, 0.60, 0.001),
144
- ('ALL', '5m', 1.2, 0.9, 0, 0, 0.57, 0.002),
145
- ('ALL', '15m', 1.5, 1.0, 0, 0, 0.55, 0.003),
146
- ('ALL', '1h', 2.0, 1.5, 0, 0, 0.53, 0.005),
147
- ('ALL', '4h', 3.0, 2.0, 0, 0, 0.50, 0.008)]
148
  conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?)", defaults)
149
- print("✅ Base de données Scalping (MT5 Exness) opérationnelle.")
150
  except Exception as e:
151
  print(f"❌ Erreur critique création DB: {e}")
152
 
153
-
154
  async def save_to_db(data):
155
  try:
156
  with sqlite3.connect(DB_NAME) as conn:
@@ -161,19 +149,17 @@ async def save_to_db(data):
161
  prob_time, prob_ml, prob_lstm, prob_sent, peak_price
162
  ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', data)
163
  conn.commit()
164
- print(f"💾 [DB SUCCESS] {data[1]} enregistré avec Peak Price.")
165
  except Exception as e:
166
- print(f"❌ DB Error (Erreur de Sauvegarde) : {e}")
167
 
168
  init_db()
169
 
170
- # --- 🛡️ RAM GUARD ---
171
  def memory_guard():
172
  if psutil.virtual_memory().percent > 80:
173
  K.clear_session()
174
  gc.collect()
175
 
176
- # --- 🛠️ MOTEUR MATHS ---
177
  def get_ema(series, period): return series.ewm(span=period, adjust=False).mean()
178
  def get_rsi(series, period=14):
179
  delta = series.diff()
@@ -187,7 +173,16 @@ def get_atr(df, period=14):
187
  l_c = (df['low'] - df['close'].shift()).abs()
188
  return pd.concat([h_l, h_c, l_c], axis=1).max(axis=1).rolling(period).mean()
189
 
190
- # --- 🧠 CHARGEMENT IA ---
 
 
 
 
 
 
 
 
 
191
  try:
192
  ml_model = joblib.load("ml_model_v9.pkl")
193
  time_model = joblib.load("time_model.pkl")
@@ -197,104 +192,73 @@ try:
197
  print("✅ Cerveaux opérationnels.")
198
  except Exception as e: print(f"⚠️ Erreur IA : {e}")
199
 
200
- # --- 📊 FEATURES ENGINE ---
201
- def get_vwap(df):
202
- """Calcul du Volume Weighted Average Price (Outil #1 du Scalpeur)"""
203
- v = df['vol']
204
- tp = (df['high'] + df['low'] + df['close']) / 3
205
- return (tp * v).cumsum() / v.cumsum()
206
-
207
  def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
208
  try:
209
  now = datetime.now().timestamp()
210
  cache_key = f"{symbol}_{timeframe}"
211
 
212
- # --- 🛡️ GESTION DU CACHE (BIEN ALIGNÉ ICI) ---
213
  if cache_key in market_cache and now - last_fetch_time.get(cache_key, 0) < CACHE_DURATION:
214
  df = market_cache[cache_key].copy()
215
  else:
216
- # 🟡 MODE OR (XAU) - Uniquement si XAU est dans le nom
217
  if "XAU" in symbol.upper():
218
- import yfinance as yf
219
  ticker = "GC=F"
220
  yf_map = {"1m":"1m", "5m":"5m", "15m":"15m", "1h":"1h", "4h":"1h", "1d":"1d"}
221
  interval = yf_map.get(timeframe, "1h")
222
-
223
- # On demande 5 jours pour avoir les données du vendredi (car on est samedi !)
224
  data = yf.download(ticker, period="5d", interval=interval, progress=False)
225
-
226
- if data.empty:
227
- print(f"😴 [XAU] Marché fermé ou pas de data.")
228
- return pd.DataFrame()
229
-
230
- df = data.reset_index()
231
- # On sélectionne les colonnes par index pour éviter les erreurs de noms
232
- df = df.iloc[:, [0, 1, 2, 3, 4, 6]]
233
  df.columns = ['ts', 'open', 'high', 'low', 'close', 'vol']
234
-
235
  else:
236
- # 🔵 MODE CRYPTO (KUCOIN)
237
  fetch_symbol = symbol if "/USDT" in symbol else symbol.replace("/USD", "/USDT")
238
- if "/USDT" not in fetch_symbol and "/" not in fetch_symbol: fetch_symbol += "/USDT"
239
-
240
  bars = exchange_sync.fetch_ohlcv(fetch_symbol, timeframe, limit=limit_bars)
241
  df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
242
 
243
  market_cache[cache_key], last_fetch_time[cache_key] = df.copy(), now
244
 
245
- if df.empty or len(df) < 50:
246
- return pd.DataFrame()
247
 
248
- # --- FONDATIONS TECHNIQUES ---
 
 
 
 
 
 
249
  df["RSI"] = get_rsi(df["close"])
250
- df["RSI_9"] = get_rsi(df["close"], 9) # ⚡ NOUVEAU: RSI Nerveux pour Scalping
251
  df["EMA50"] = get_ema(df["close"], 50)
252
  df["EMA200"] = get_ema(df["close"], 200)
253
- df["VWAP"] = get_vwap(df) # ⚡ NOUVEAU: Le juge de paix
254
  df["ATR"] = get_atr(df)
255
  df["ATR_pct"] = (df["ATR"] / df["close"]) * 100
256
  df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
257
  df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
258
 
259
- # --- DISTANCES & SLOPES ---
 
 
 
 
260
  df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min()
261
  df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
262
  df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
263
  df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
264
  df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1
265
- df["Price_vs_VWAP"] = (df["close"] - df["VWAP"]) / df["VWAP"] # ⚡ Distance au VWAP
266
  df["ATR_ratio"] = df["ATR"] / df["close"]
267
- df["VOL_ratio"] = df["vol"] / df["vol"].rolling(24).mean()
268
- df["Vol_Spike"] = df["vol"] / df["vol"].rolling(5).mean() # NOUVEAU: Explosion de volume
269
 
270
- # --- SMC & FIBO ---
271
  diff = df["High_24h"] - df["Low_24h"]
272
  df["Fib_618"] = df["Low_24h"] + (diff * 0.618)
273
  df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"]
274
- df["FVG_bull"] = (df['low'] > df['high'].shift(2)).astype(int)
275
- df["FVG_bear"] = (df['high'] < df['low'].shift(2)).astype(int)
276
-
277
- if "BTC" not in symbol and "ETH" not in symbol:
278
- try:
279
- b_bars = exchange_sync.fetch_ohlcv("BTC/USD", timeframe, limit=50)
280
- e_bars = exchange_sync.fetch_ohlcv("ETH/USD", timeframe, limit=50)
281
- b_c = pd.Series([b[4] for b in b_bars])
282
- e_c = pd.Series([e[4] for e in e_bars])
283
- b_slope = (b_c.ewm(span=50).mean().iloc[-1] / b_c.ewm(span=50).mean().iloc[-6]) - 1
284
- e_slope = (e_c.ewm(span=50).mean().iloc[-1] / e_c.ewm(span=50).mean().iloc[-6]) - 1
285
- df["Market_Trend"] = (b_slope * 0.6) + (e_slope * 0.4)
286
- except: df["Market_Trend"] = 0
287
- else:
288
- df["Market_Trend"] = df["EMA200_slope"]
289
 
290
- p_low, p_high = df["low"].rolling(24).min().shift(1), df["high"].rolling(24).max().shift(1)
291
- df["Sweep_Low"] = ((df["low"] < p_low) & (df["close"] > p_low)).astype(int)
292
- df["Sweep_High"] = ((df["high"] > p_high) & (df["close"] < p_high)).astype(int)
293
-
294
- # --- FEATURES POUR LES MODELES ---
295
  df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12)
296
  df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2)
297
- df['VOL_RATIO'] = df['vol'] / df['vol'].rolling(20).mean()
298
  df['vol_lag1'], df['RSI_Macro'] = df['vol'].shift(1), df["RSI"]
299
 
300
  return df.dropna().copy()
@@ -306,117 +270,157 @@ async def predict_signal(symbol, timeframe="1h"):
306
  symbol = str(symbol).strip().upper()
307
 
308
  df = prepare_features_sync(symbol, timeframe)
309
- if df.empty: return {"status": "error", "message": f"Data insuffisante pour {timeframe}"}
310
 
311
  last_row = df.iloc[[-1]]
312
  prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
313
- vwap = float(last_row['VWAP'].iloc[0]) # ⚡ Extraction VWAP
314
- vol_spike = float(last_row['Vol_Spike'].iloc[0]) # ⚡ Extraction Spike
315
- rsi_9 = float(last_row['RSI_9'].iloc[0]) # ⚡ Extraction RSI Rapide
316
 
317
- # --- 🧠 CERVEAUX IA ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
319
  regime_pred = int(regime_model.predict(regime_scaled)[0])
320
- ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
321
- ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1])
322
- time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']
323
- time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1])
324
  lstm_data = df[["close","RSI","EMA50","EMA200","ATR"]].iloc[-30:]
325
  lstm_data = (lstm_data - lstm_data.mean()) / (lstm_data.std() + 1e-9)
326
  lstm_prob = float(lstm_brain.predict(np.expand_dims(lstm_data.values, axis=0), verbose=0)[0][0])
327
  p_sent = await get_crypto_sentiment(symbol)
328
 
329
- # --- ⚖️ PONDÉRATION ADAPTATIVE (FOCUS MOMENTUM SCALPING) ---
330
- if timeframe in ["1m", "5m", "15m"]:
331
- wt, wm, wl, ws = 0.50, 0.25, 0.15, 0.10 # On fait confiance au TimeModel pour la vitesse
332
- elif timeframe in ["1h", "4h", "1d"]:
333
- wl, wm, wt, ws = 0.60, 0.15, 0.15, 0.10
334
- else:
335
- wt, wm, wl, ws = 0.25, 0.25, 0.25, 0.25
336
-
337
  final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
338
 
339
- # --- 🐋 BONUS SMART MONEY & SCALPING NITRO ---
340
- sweep_low, sweep_high = int(last_row["Sweep_Low"].iloc[0]), int(last_row["Sweep_High"].iloc[0])
341
  smc_status = "AUCUN"
342
- if sweep_low == 1:
343
- final_p = min(0.95, final_p + 0.20)
344
- smc_status = "LONG SWEEP 🐋"
345
- elif sweep_high == 1:
346
- final_p = max(0.05, final_p - 0.20)
347
- smc_status = "SHORT SWEEP 🐋"
348
-
349
- # ⚡ NITRO SCALPING : Si on a un pic de volume dans le sens de la tendance
350
- if timeframe in ["1m", "5m"]:
351
- if final_p > 0.5 and vol_spike > 1.5 and rsi_9 < 70:
352
- final_p = min(0.95, final_p + 0.10) # Coup de boost à l'achat
353
- elif final_p < 0.5 and vol_spike > 1.5 and rsi_9 > 30:
354
- final_p = max(0.05, final_p - 0.10) # Coup de boost à la vente
355
-
356
- # --- 🛡️ CALCUL TP/SL (BASE DE DONNÉES ISOLÉE) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  with sqlite3.connect(DB_NAME) as conn:
358
  res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ?", (symbol, timeframe)).fetchone()
359
- if res:
360
- tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res
361
  else:
362
  res_fallback = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
363
- tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res_fallback if res_fallback else (1.5, 1.0, 0.55, 0.002)
364
- conn.execute("INSERT OR IGNORE INTO agent_logic (symbol, timeframe, tp_mult, sl_mult, score, last_pnl, min_prob, min_tp_dist) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
365
- (symbol, timeframe, tp_m, sl_m, 0, 0, agent_min_prob, agent_min_tp_dist))
366
- conn.commit()
367
-
368
- tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
369
- sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
370
-
371
- # Scores et Regimes
372
- strength = abs(final_p - 0.5) * 2
373
- conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, lstm_prob, p_sent])))
374
- score_base = (strength * 45) + (conf_val * 40)
375
- regime_bonus = 15 if regime_pred in [0, 1] else 5
376
- composite_score = max(0, min(100, score_base + regime_bonus))
377
-
378
- # ==========================================
379
- # 🛑 LE VETO DYNAMIQUE (BOUCLIER EMA200 + VWAP)
380
- # ==========================================
381
  veto = False
382
  veto_reason = ""
383
  dist_tp_pct = abs(tp - prix) / prix
384
- dist_fib = float(last_row["Dist_Fib_618"].iloc[0])
385
- mkt_trend = float(last_row["Market_Trend"].iloc[0])
386
- ema200_val = float(last_row["EMA200"].iloc[0])
387
 
388
  if final_p < agent_min_prob and final_p > (1 - agent_min_prob):
389
  veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}"
390
-
391
- elif "BTC" not in symbol and "ETH" not in symbol:
392
- if final_p < 0.5 and mkt_trend > 0.002: veto, veto_reason = True, "Marché Global Haussier"
393
- elif final_p > 0.5 and mkt_trend < -0.002: veto, veto_reason = True, "Marché Global Baissier"
394
-
395
- elif final_p > 0.5 and dist_fib < -0.03: veto, veto_reason = True, "Support Fib 61.8 trop loin"
396
- elif final_p < 0.5 and dist_fib > 0.03: veto, veto_reason = True, "Résistance Fib 61.8 trop loin"
397
- elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible"
398
 
399
- # 🛡️ LE GARDE-FOU ABSOLU (Interdiction stricte de contrer le TGV institutionnel)
400
- elif final_p < 0.5 and (prix > ema200_val or prix > vwap):
401
- veto, veto_reason = True, "Garde-Fou: Prix > EMA200/VWAP (Short Interdit 🚫)"
402
- elif final_p > 0.5 and (prix < ema200_val or prix < vwap):
403
- veto, veto_reason = True, "Garde-Fou: Prix < EMA200/VWAP (Long Interdit 🚫)"
 
 
 
 
 
 
 
 
 
404
 
405
  if veto:
406
- print(f"🛑 [VETO] {symbol}: {veto_reason}")
407
  return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": veto_reason, "price": prix, "final_score": round(final_p, 4)}
408
 
409
- # 7. Sauvegarde DB
410
  db_task = (datetime.now(timezone.utc).isoformat(), symbol, timeframe,
411
  'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl,
412
  'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent, prix)
413
  await save_to_db(db_task)
414
 
 
 
415
  return {
416
  "symbol": symbol, "timeframe": timeframe, "status": "success",
417
- "final_score": round(final_p, 4), "score": int(composite_score),
418
- "smart_money": smc_status, "price": prix, "tp": round(tp, 6), "sl": round(sl, 6),
419
- "regime": regime_pred, "confluence": round(conf_val * 100, 1),
420
  "probs": {"xgb": round(time_prob, 3), "rf": round(ml_prob, 3), "lstm": round(lstm_prob, 3), "sent": round(p_sent, 3)}
421
  }
422
  except Exception as e:
@@ -427,36 +431,22 @@ def trigger_training(symbol="SOL/USD"):
427
  try:
428
  print(f"⚙️ Tentative d'entraînement pour {symbol}...")
429
  memory_guard()
430
-
431
  bars = exchange_sync.fetch_ohlcv(symbol, timeframe='1h', limit=1500)
432
  df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
433
-
434
- if len(df) < 500:
435
- return f"❌ Erreur : Historique insuffisant pour {symbol} ({len(df)}/500 min)."
436
-
437
  df_final = prepare_features_sync(symbol, '1h', limit_bars=1000)
438
 
439
- if df_final.empty or len(df_final) < 100:
440
- return f"❌ Erreur : Données vides après calcul des indicateurs techniques."
441
-
442
- print("🔍 DIAGNOSTIC DES COLONNES VIDES (NaN) :")
443
- print(df_final.isna().sum())
444
-
445
  from ml_model import train_model as train_ml
446
  from time_model import train_time_model as train_time
447
-
448
  train_ml(df_final)
449
  train_time(df_final)
450
 
451
  global ml_model, time_model
452
  ml_model = joblib.load("ml_model_v9.pkl")
453
  time_model = joblib.load("time_model.pkl")
454
-
455
  gc.collect()
456
- return f"✅ IA ré-entraînée avec succès ({len(df_final)} échantillons)."
457
-
458
- except Exception as e:
459
- return f"❌ Erreur Training : {str(e)}"
460
 
461
  # --- 🚀 MOTEUR AUTO-PILOTE ---
462
  AUTO_SYMBOLS = ["ETH/USD"]
@@ -465,247 +455,148 @@ AUTO_TIMEFRAMES = ["1m", "5m", "15m", "1h"]
465
  async def auto_predict_loop():
466
  while True:
467
  try:
468
- print(f"🤖 [AUTO-PILOTE] Scan des marchés...")
469
  with sqlite3.connect(DB_NAME) as conn:
470
  cursor = conn.cursor()
471
  for symbol in AUTO_SYMBOLS:
472
  for tf in AUTO_TIMEFRAMES:
473
- cursor.execute("""
474
- SELECT id FROM signals
475
- WHERE symbol = ? AND timeframe = ? AND status = 'EN_COURS'
476
- """, (symbol, tf))
477
-
478
- if cursor.fetchone():
479
- continue
480
-
481
  print(f"🎯 [AUTO] Nouvelle analyse : {symbol} [{tf}]")
482
  await predict_signal(symbol, timeframe=tf)
483
  await asyncio.sleep(2)
484
-
485
- print("✅ [AUTO-PILOTE] Scan terminé. Repos...")
486
  await asyncio.sleep(60)
487
-
488
  except Exception as e:
489
- print(f"⚠️ Erreur critique Auto-Pilote : {e}")
490
  await asyncio.sleep(60)
491
 
492
  # --- ⚖️ TOOLS ---
493
- def keep_alive_ping():
494
- return {"status": "awake", "time": datetime.now(timezone.utc).isoformat()}
495
 
496
  def confirm_trade_api(trade_id, real_price):
497
  try:
498
- t_id = int(trade_id)
499
- r_price = float(real_price)
500
-
501
  with sqlite3.connect(DB_NAME) as conn:
502
  conn.row_factory = sqlite3.Row
503
  cursor = conn.cursor()
504
-
505
- # 1. On récupère les données Kucoin (théoriques)
506
  cursor.execute("SELECT price, tp, sl FROM signals WHERE id = ?", (t_id,))
507
  t = cursor.fetchone()
508
-
509
- if not t: return {"status": "error", "message": "Signal non trouvé"}
510
-
511
- # 2. On calcule l'écart (ex: +10$)
512
  ecart = r_price - t['price']
513
-
514
- # 3. On décale le TP et le SL du même montant pour garder le même profit cible
515
- nouveau_tp = t['tp'] + ecart
516
- nouveau_sl = t['sl'] + ecart
517
-
518
- # 4. On met à jour la DB avec les chiffres RÉELS de Capital.com
519
- cursor.execute("""
520
- UPDATE signals
521
- SET confirmed = 1, price = ?, tp = ?, sl = ?, peak_price = ?
522
- WHERE id = ?
523
- """, (r_price, nouveau_tp, nouveau_sl, r_price, t_id))
524
-
525
  conn.commit()
526
-
527
- print(f"⚓ [SYNC-PRIX] Signal {t_id} aligné sur Capital.com : Entrée {r_price}")
528
- return {"status": "success", "message": f"Prix aligné à {r_price}"}
529
- except Exception as e:
530
- return {"status": "error", "message": str(e)}
531
 
532
  def cancel_trade_api(trade_id):
533
  try:
534
- t_id = int(trade_id)
535
  with sqlite3.connect(DB_NAME) as conn:
536
- cursor = conn.cursor()
537
- # On passe le statut en ANNULÉ pour que le bot ne le retente plus jamais
538
- cursor.execute("UPDATE signals SET status = 'ANNULÉ 🗑️', confirmed = 0 WHERE id = ?", (t_id,))
539
  conn.commit()
540
-
541
- print(f"🧹 [CLEANUP] Signal {t_id} annulé suite à une erreur Broker (Sécurité anti-péremption).")
542
- return {"status": "success", "message": f"Signal {t_id} détruit."}
543
- except Exception as e:
544
- return {"status": "error", "message": str(e)}
545
-
546
- async def check_data_count(symbol):
547
- try:
548
- ex = ExchangeManager.get_instance()
549
- bars = await ex.fetch_ohlcv(symbol, timeframe='1h', limit=1000)
550
- count = len(bars)
551
- needed = 250
552
- percent = min(100, (count / needed) * 100)
553
- return {"count": count, "percent": round(percent, 1), "needed": needed}
554
- except Exception as e:
555
- return {"status": "error", "message": str(e)}
556
 
557
  def run_judge_api():
558
  try:
559
- # ⚡ AJOUT DU TIMEOUT POUR ÉVITER LE VERROUILLAGE
560
  with sqlite3.connect(DB_NAME, timeout=10) as conn:
561
  conn.row_factory = sqlite3.Row
562
  cursor = conn.cursor()
563
-
564
  cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS' AND confirmed = 1")
565
  trades = cursor.fetchall()
566
-
567
- if not trades:
568
- return {"status": "waiting", "message": "⚖️ Observation du marché en cours..."}
569
 
570
  closed_trades = []
571
  for t in trades:
572
  try:
573
- # --- 🛰️ SYNC PRIX HYBRIDE (NOUVEAU) ---
574
- symbol_db = t['symbol']
575
  current_price = None
576
-
577
- # 1. Tentative locale avec MT5 (Exness)
578
  if MT5_AVAILABLE:
579
- epic = symbol_db.replace("/", "").replace("USDT", "USD")
580
  if not epic.endswith("m"): epic += "m"
581
  tick = mt5.symbol_info_tick(epic)
582
  if tick: current_price = tick.last
583
 
584
- # 2. Tentative Cloud avec CCXT (Kucoin) si MT5 échoue
585
  if current_price is None:
586
- fetch_symbol = symbol_db if "/USDT" in symbol_db else symbol_db.replace("/USD", "/USDT")
587
  if "/" not in fetch_symbol: fetch_symbol += "/USDT"
588
- ticker = exchange_sync.fetch_ticker(fetch_symbol)
589
- current_price = ticker['last']
590
 
591
- # --- 📊 DASHBOARD LIVE PNL (CONSERVÉ) ---
592
  lots = 0.1
593
  diff = (t['price'] - current_price) if t['direction'] == 'BAISSIER' else (current_price - t['price'])
594
  pnl_live = diff * lots * 10
595
-
596
- status_mode = "🏠 LOCAL" if MT5_AVAILABLE else "🌐 CLOUD"
597
  color = "🟢" if pnl_live >= 0 else "🔴"
598
- print(f"{color} [{status_mode}] {symbol_db} | PnL: {round(pnl_live, 2)}$ | Prix: {current_price}")
599
 
600
- # --- 🚀 LOGIQUE TRAILING STOP (CONSERVÉE À 100%) ---
601
- sl_dynamique = t['sl']
602
- peak = t['peak_price']
603
  new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price)
604
-
605
  chemin_total = abs(t['tp'] - t['price'])
606
- chemin_parcouru = abs(current_price - t['price'])
607
- progression = chemin_parcouru / chemin_total if chemin_total > 0 else 0
608
 
609
  nouveau_sl = sl_dynamique
610
-
611
- # --- 🛡️ TA STRATÉGIE DE L'ÉTAU (CONSERVÉE À 100%) ---
612
  if t['direction'] == 'HAUSSIER':
613
  if progression >= 0.75: nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.60))
614
  elif progression >= 0.50: nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.25))
615
  elif progression >= 0.25: nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.05))
616
- else: # BAISSIER (SELL)
617
  if progression >= 0.75: nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.60))
618
  elif progression >= 0.50: nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.25))
619
  elif progression >= 0.25: nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.05))
620
 
621
- sl_dynamique = nouveau_sl
622
- cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, sl_dynamique, t['id']))
623
 
624
- # --- 🏁 VÉRIFICATION DU RÉSULTAT (CONSERVÉE À 100%) ---
625
- outcome = None
626
- reward = 0
627
  if t['direction'] == 'HAUSSIER':
628
  if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3
629
- elif current_price <= sl_dynamique:
630
- outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if sl_dynamique > t['price'] else ("PERDU ❌", -5)
631
- else: # BAISSIER
632
  if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3
633
- elif current_price >= sl_dynamique:
634
- outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if sl_dynamique < t['price'] else ("PERDU ❌", -5)
635
 
636
  if outcome:
637
- # --- 🪞 TA VÉRITÉ BRUTALE (CONSERVÉE À 100%) ---
638
- taille_position_virtuelle = 1000.0
639
- spread_commission_estimee = 0.25
640
- pnl_brut = ((abs(current_price - t['price'])) / t['price']) * taille_position_virtuelle
641
- pnl_dollars = pnl_brut - spread_commission_estimee
642
-
643
  if outcome == "SL TOUCHÉ 🛡️" and pnl_dollars < 0: outcome = "TUÉ PAR LE SPREAD/COMM 🩸"
644
  elif outcome == "GAGNÉ (PARTIEL) 💸" and pnl_dollars <= 0: outcome = "FAUX GAIN (FRAIS EXNESS) 📉"
645
 
646
- # --- 🎮 TA LOGIQUE ÉVOLUTION RPG (CONSERVÉE À 100%) ---
647
- cursor.execute("SELECT tp_mult, sl_mult, score, min_prob FROM agent_logic WHERE symbol = ? AND timeframe = ?", (t['symbol'], t['timeframe']))
648
- row = cursor.fetchone()
649
  tp_m, sl_m, score_ia, min_p = (row['tp_mult'], row['sl_mult'], row['score'], row['min_prob']) if row else (1.5, 1.0, 0, 0.55)
650
 
651
- if reward > 0:
652
- tp_m, min_p = min(4.0, tp_m * 1.01), max(0.50, min_p - 0.005)
653
- elif reward < 0:
654
- sl_m, tp_m, min_p = max(0.8, sl_m * 0.985), max(1.1, tp_m * 0.985), min(0.65, min_p + 0.01)
655
 
656
- cursor.execute("UPDATE agent_logic SET tp_mult=?, sl_mult=?, score=score+?, min_prob=? WHERE symbol=? AND timeframe=?",
657
- (tp_m, sl_m, reward, min_p, t['symbol'], t['timeframe']))
658
  cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
659
-
660
- closed_trades.append({
661
- "symbol": t['symbol'], "timeframe": t['timeframe'], "direction": t['direction'],
662
- "outcome": outcome, "pnl": round(pnl_dollars, 2)
663
- })
664
-
665
- except Exception as inner_e:
666
- print(f"⚠️ Erreur sur {t['symbol']} : {inner_e}")
667
-
668
  conn.commit()
669
 
670
  if closed_trades: return {"status": "updates", "data": closed_trades}
671
- return {"status": "waiting", "message": "monitoring..."}
672
-
673
- except Exception as e:
674
- return {"status": "error", "message": str(e)}
675
-
676
- async def shutdown():
677
- ex = ExchangeManager.get_instance()
678
- await ex.close()
679
 
680
- # --- 🏗️ WRAPPER DE SÉCURITÉ ---
681
  def training_wrapper(symbol, *args):
682
- if not isinstance(symbol, str) or len(str(symbol)) < 2:
683
- symbol = "BTC/USD"
684
- symbol = str(symbol).strip().upper()
685
- return trigger_training(symbol)
686
 
687
- def get_rpg_stats():
688
- """Récupère la fiche de personnage de l'IA pour Discord (Mise à jour V12)"""
689
  try:
690
  with sqlite3.connect(DB_NAME) as conn:
691
  cursor = conn.cursor()
692
- # On trie par Symbole pour que ce soit lisible sur Discord
693
- cursor.execute("SELECT symbol, timeframe, tp_mult, sl_mult, min_prob, score FROM agent_logic ORDER BY symbol, timeframe")
694
- rows = cursor.fetchall()
695
-
696
- if not rows: return "Aucune stat trouvée."
697
-
698
- report = []
699
- for r in rows:
700
- sym, tf, tp, sl, peur, score = r[0], r[1], round(r[2],2), round(r[3],2), round(r[4]*100), int(r[5])
701
- report.append(f"[{sym} | {tf}] 🗡️ TP: x{tp} | 🛡️ SL: x{sl} | 😨 Peur: {peur}% | 🏆 Score: {score}")
702
- return "\n".join(report)
703
- except Exception as e:
704
- return f"Erreur DB : {e}"
705
 
706
  def get_active_signals():
707
  try:
708
- # ⚡ AJOUT DU TIMEOUT POUR ÉVITER LE VERROUILLAGE
709
  conn = sqlite3.connect(DB_NAME, timeout=10)
710
  conn.row_factory = sqlite3.Row
711
  cursor = conn.cursor()
@@ -713,232 +604,57 @@ def get_active_signals():
713
  trades = [dict(row) for row in cursor.fetchall()]
714
  conn.close()
715
  return trades
716
- except Exception as e:
717
- print(f"❌ [API CRITIQUE] Erreur lecture BDD : {e}") # ⚡ ON AFFICHE L'ERREUR
718
- return []
719
-
720
- # --- 📊 SYSTEME DE STATISTIQUES POUR DISCORD ---
721
- def get_db_stats(action, symbol=""):
722
- try:
723
- conn = sqlite3.connect(DB_NAME)
724
- conn.row_factory = sqlite3.Row
725
- cursor = conn.cursor()
726
-
727
- if action == "active":
728
- cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
729
- return [dict(r) for r in cursor.fetchall()]
730
-
731
- elif action in ["global", "summary"]:
732
- cursor.execute("SELECT status FROM signals WHERE status LIKE '%GAGNÉ%' OR status LIKE '%PERDU%'")
733
- rows = cursor.fetchall()
734
- total = len(rows)
735
- wins = sum(1 for r in rows if "GAGNÉ" in r['status'])
736
- wr = round((wins / total) * 100, 1) if total > 0 else 0
737
- return {"total": total, "winrate": wr, "expectancy": "Calcul IA..."}
738
-
739
- elif action == "last":
740
- cursor.execute("SELECT * FROM signals ORDER BY id DESC LIMIT 1")
741
- row = cursor.fetchone()
742
- return dict(row) if row else {"error": "Aucun trade enregistré"}
743
-
744
- elif action == "history":
745
- cursor.execute("SELECT status, COUNT(*) as count FROM signals WHERE symbol = ? GROUP BY status", (symbol,))
746
- return {r['status']: r['count'] for r in cursor.fetchall()}
747
-
748
- elif action == "top":
749
- cursor.execute("SELECT symbol, COUNT(*) as count, SUM(CASE WHEN status LIKE '%GAGNÉ%' THEN 1 ELSE 0 END) as wins FROM signals WHERE status LIKE '%GAGNÉ%' OR status LIKE '%PERDU%' GROUP BY symbol HAVING count > 0")
750
- rows = cursor.fetchall()
751
- top = [{"symbol": r['symbol'], "status": (r['wins']/r['count'])*100, "count": r['count']} for r in rows]
752
- top.sort(key=lambda x: x['status'], reverse=True)
753
- return top[:5]
754
-
755
- elif action == "recent":
756
- cursor.execute("SELECT * FROM signals ORDER BY id DESC LIMIT 5")
757
- return [{"symbol": r['symbol'], "direction": r['direction'], "status": r['status']} for r in cursor.fetchall()]
758
-
759
- elif action == "edge":
760
- cursor.execute("SELECT status FROM signals WHERE status LIKE '%GAGNÉ%' OR status LIKE '%PERDU%'")
761
- rows = cursor.fetchall()
762
- total = len(rows)
763
- wins = sum(1 for r in rows if "GAGNÉ" in r['status'])
764
- wr = round((wins / total) * 100, 1) if total > 0 else 0
765
- return {"edge": round(wr - 50, 1) if total > 0 else 0}
766
-
767
- except Exception as e:
768
- return {"error": str(e)}
769
- finally:
770
- if 'conn' in locals():
771
- conn.close()
772
-
773
- def get_bot_skills():
774
- import sqlite3
775
- try:
776
- with sqlite3.connect(DB_NAME) as conn:
777
- conn.row_factory = sqlite3.Row
778
- cursor = conn.cursor()
779
- # On récupère les stats triées par Score (tes meilleurs timeframes en haut)
780
- cursor.execute("SELECT symbol, timeframe, tp_mult, sl_mult, score, min_prob FROM agent_logic ORDER BY score DESC")
781
- rows = cursor.fetchall()
782
-
783
- data = []
784
- for r in rows:
785
- # On transforme les données pour le tableau
786
- data.append([
787
- r['symbol'],
788
- r['timeframe'],
789
- f"x{round(r['tp_mult'], 2)}",
790
- f"x{round(r['sl_mult'], 2)}",
791
- f"{r['score']} XP",
792
- f"{round(r['min_prob'] * 100, 1)}%"
793
- ])
794
- return data
795
- except Exception as e:
796
- return [[f"Erreur: {str(e)}", "-", "-", "-", "-", "-"]]
797
 
798
- def force_scalping_mode():
799
- import sqlite3
800
- try:
801
- with sqlite3.connect(DB_NAME) as conn:
802
- # On force tous les timeframes à x1.8 pour encaisser les profits rapidement
803
- conn.execute("UPDATE agent_logic SET tp_mult = 1.8")
804
- conn.commit()
805
- # On renvoie les nouvelles stats pour mettre à jour le tableau
806
- return get_bot_skills()
807
- except Exception as e:
808
- print(f"❌ Erreur Reset Scalping : {e}")
809
- return [[f"Erreur: {str(e)}", "-", "-", "-", "-", "-"]]
810
-
811
  # --- 🎨 INTERFACE GRADIO ---
812
  with gr.Blocks(theme=gr.themes.Monochrome()) as iface:
813
- gr.Markdown("# 📡 Alpha V21.2 Master Engine RPG")
814
 
815
  with gr.Tab("Admin"):
816
- admin_sym = gr.Textbox(label="Symbole à recalibrer", value="BTC/USDT")
817
- train_btn = gr.Button("Forcer Entraînement Hebdomadaire", variant="stop")
818
  train_out = gr.Textbox(label="Résultat")
819
- train_btn.click(fn=training_wrapper, inputs=admin_sym, outputs=train_out, api_name="trigger_training")
820
 
821
  with gr.Tab("🎯 Prédictions"):
822
- sym_input = gr.Textbox(label="Symbole (ex: BTC/USD)")
823
- tf_input = gr.Dropdown(choices=["5m", "15m", "1h", "4h", "1d"], value="1h", label="Timeframe")
824
  btn_pred = gr.Button("Predict", variant="primary")
825
  out_json = gr.JSON()
826
- btn_pred.click(fn=predict_signal, inputs=[sym_input, tf_input], outputs=out_json)
827
 
828
  with gr.Tab("⚖️ Système"):
829
- btn_j = gr.Button("Judge")
830
- out_j = gr.JSON()
831
- btn_j.click(fn=run_judge_api, outputs=out_j, api_name="run_judge_api")
832
-
833
- btn_rpg = gr.Button("RPG Stats", visible=False)
834
- out_rpg = gr.Textbox()
835
- btn_rpg.click(fn=get_rpg_stats, outputs=out_rpg, api_name="get_rpg_stats")
836
-
837
- btn_active = gr.Button("Get Active", visible=False)
838
- btn_active.click(fn=get_active_signals, outputs=out_json, api_name="get_active_signals")
839
-
840
- btn_stats = gr.Button("Get Stats", visible=False)
841
- out_stats = gr.JSON()
842
- btn_stats.click(
843
- fn=get_db_stats,
844
- inputs=[gr.Textbox(visible=False), gr.Textbox(visible=False)],
845
- outputs=out_stats,
846
- api_name="get_db_stats"
847
- )
848
-
849
- btn_ping = gr.Button("Ping", visible=False)
850
- btn_ping.click(fn=keep_alive_ping, outputs=gr.JSON(), api_name="keep_alive_ping")
851
 
852
- # --- 📊 Onglet Stats du Cerveau ---
853
  with gr.Tab("📊 Stats du Cerveau"):
854
- gr.Markdown("### 🧠 Compétences actuelles de l'IA (Auto-Learning)")
855
- skills_table = gr.Dataframe(
856
- headers=["Marché", "Timeframe", "Cible (TP)", "Risque (SL)", "Expérience", "Confiance Min."],
857
- datatype=["str", "str", "str", "str", "str", "str"],
858
- value=get_bot_skills(),
859
- interactive=False
860
- )
861
-
862
  with gr.Row():
863
- refresh_btn = gr.Button("🔄 Actualiser", variant="secondary")
864
- scalp_btn = gr.Button("⚡ Mode Scalping Force", variant="primary")
865
-
866
- # Logique des boutons
867
- refresh_btn.click(get_bot_skills, outputs=skills_table)
868
- scalp_btn.click(force_scalping_mode, outputs=skills_table)
869
-
870
- # =========================================================
871
- # --- ⚓ LE HANDSHAKE API (SECRET POUR PTERODACTYL) ---
872
- # =========================================================
873
- # Dans ton interface gr.Blocks :
874
- api_inputs = [gr.Textbox(visible=False), gr.Textbox(visible=False)] # Deux entrées !
875
- api_confirm_btn = gr.Button(visible=False)
876
- api_confirm_btn.click(
877
- fn=confirm_trade_api,
878
- inputs=api_inputs, # On lie les deux
879
- outputs=gr.JSON(),
880
- api_name="confirm_trade_api"
881
- )
882
- # =========================================================
883
- # =========================================================
884
- # --- 🗑️ LE KILL SWITCH API (POUR ANNULER LES TRADES PÉRIMÉS) ---
885
- # =========================================================
886
- api_cancel_input = gr.Textbox(visible=False)
887
- api_cancel_btn = gr.Button(visible=False)
888
- api_cancel_btn.click(
889
- fn=cancel_trade_api,
890
- inputs=api_cancel_input,
891
- outputs=gr.JSON(),
892
- api_name="cancel_trade_api"
893
- )
894
-
895
- # --- 🕒 SYSTÈME DE MISE À JOUR AUTOMATIQUE ---
896
- iface.load(get_bot_skills, outputs=skills_table)
897
-
898
- try:
899
- timer = gr.Timer(30)
900
- timer.tick(get_bot_skills, outputs=skills_table)
901
- except AttributeError:
902
- print("⚠️ Gradio trop ancien pour gr.Timer. Rafraîchissement auto désactivé.")
903
-
904
-
905
 
 
 
 
 
906
 
907
-
908
  import threading
909
- import asyncio
910
-
911
- # --- 🔒 LE CADENAS ANTI-CLONAGE ---
912
  _auto_pilot_started = False
913
 
914
  def run_auto_pilot():
915
  global _auto_pilot_started
916
-
917
- # Si le videur voit que c'est déjà lancé, il bloque le nouveau clone !
918
- if _auto_pilot_started:
919
- print("🛑 [ANTI-CLONAGE] Un Auto-Pilote tourne déjà en fond. Annulation du clone.")
920
- return
921
-
922
  _auto_pilot_started = True
923
- # -----------------------------------
924
-
925
  print("⏳ [SYSTEM] Auto-Pilote en pause pour laisser Gradio démarrer...")
926
- time.sleep(30) # 🛡️ LE BOUCLIER ANTI-CRASH : On attend 30 secondes
927
- print("🚀 [SYSTEM] Auto-Pilote RPG propulsé en arrière-plan.")
928
-
929
  try:
930
  new_loop = asyncio.new_event_loop()
931
  asyncio.set_event_loop(new_loop)
932
  new_loop.run_until_complete(auto_predict_loop())
933
  except Exception as e:
934
- print(f"⚠️ Erreur critique boucle Auto-Pilote : {e}")
935
- _auto_pilot_started = False # On retire le cadenas en cas de crash pour pouvoir redémarrer
936
 
937
  if __name__ == "__main__":
938
- try:
939
- threading.Thread(target=run_auto_pilot, daemon=True).start()
940
- except Exception as e:
941
- print(f"⚠️ Erreur au lancement du thread Auto-Pilote : {e}")
942
-
943
- # On lance Gradio immédiatement, l'Auto-pilote attendra son tour !
944
  iface.launch(show_api=True)
 
19
  import gradio as gr
20
  import ccxt
21
  import time
 
22
  from types import ModuleType
 
23
  import yfinance as yf
24
+
25
  try:
26
  import MetaTrader5 as mt5
27
  MT5_AVAILABLE = True
28
  except ImportError:
29
  MT5_AVAILABLE = False
30
  print("🌐 [CLOUD MODE] MetaTrader5 non détecté. Bascule sur l'antenne de secours (CCXT).")
31
+
32
  # --- 🥷 NINJA HACK : MOCK PANDAS_TA ---
33
  if "pandas_ta" not in sys.modules:
34
  mock_ta = ModuleType("pandas_ta")
 
86
 
87
  # --- DB & SYNC ---
88
  REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
89
+ DB_NAME = "alphatrade_v24_gemini.db" # 🎮 NOUVEAU MONDE V24 GEMINI
90
  HF_TOKEN = os.environ.get("HF_TOKEN")
91
 
92
  def init_db():
 
121
  cursor.execute("PRAGMA table_info(agent_logic)")
122
  al_cols = [col[1] for col in cursor.fetchall()]
123
 
124
+ if not al_cols:
 
 
 
 
 
 
 
 
 
 
125
  conn.execute('''CREATE TABLE IF NOT EXISTS agent_logic (
126
  symbol TEXT, timeframe TEXT, tp_mult REAL, sl_mult REAL,
127
  score REAL, last_pnl REAL, min_prob REAL, min_tp_dist REAL,
 
129
 
130
  cursor.execute("SELECT COUNT(*) FROM agent_logic")
131
  if cursor.fetchone()[0] == 0:
132
+ defaults = [('ALL', '1m', 3.0, 1.5, 0, 0, 0.65, 0.0005),
133
+ ('ALL', '5m', 3.5, 2.0, 0, 0, 0.62, 0.0010),
134
+ ('ALL', '15m', 4.0, 2.0, 0, 0, 0.60, 0.0020),
135
+ ('ALL', '1h', 5.0, 2.5, 0, 0, 0.55, 0.0040),
136
+ ('ALL', '4h', 6.0, 3.0, 0, 0, 0.50, 0.0080)]
 
137
  conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?)", defaults)
138
+ print("✅ Base de données V24 GEMINI (MT5 Exness) opérationnelle.")
139
  except Exception as e:
140
  print(f"❌ Erreur critique création DB: {e}")
141
 
 
142
  async def save_to_db(data):
143
  try:
144
  with sqlite3.connect(DB_NAME) as conn:
 
149
  prob_time, prob_ml, prob_lstm, prob_sent, peak_price
150
  ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', data)
151
  conn.commit()
 
152
  except Exception as e:
153
+ print(f"❌ DB Error : {e}")
154
 
155
  init_db()
156
 
 
157
  def memory_guard():
158
  if psutil.virtual_memory().percent > 80:
159
  K.clear_session()
160
  gc.collect()
161
 
162
+ # --- 🛠️ MOTEUR MATHS V24 (GEMINI QUANT) ---
163
  def get_ema(series, period): return series.ewm(span=period, adjust=False).mean()
164
  def get_rsi(series, period=14):
165
  delta = series.diff()
 
173
  l_c = (df['low'] - df['close'].shift()).abs()
174
  return pd.concat([h_l, h_c, l_c], axis=1).max(axis=1).rolling(period).mean()
175
 
176
+ def get_zscore(series, period=20):
177
+ mean = series.rolling(period).mean()
178
+ std = series.rolling(period).std()
179
+ return (series - mean) / (std + 1e-9)
180
+
181
+ def get_vwap(df):
182
+ v = df['vol']
183
+ tp = (df['high'] + df['low'] + df['close']) / 3
184
+ return (tp * v).cumsum() / (v.cumsum() + 1e-9)
185
+
186
  try:
187
  ml_model = joblib.load("ml_model_v9.pkl")
188
  time_model = joblib.load("time_model.pkl")
 
192
  print("✅ Cerveaux opérationnels.")
193
  except Exception as e: print(f"⚠️ Erreur IA : {e}")
194
 
195
+ # --- 📊 FEATURES ENGINE V24 ---
 
 
 
 
 
 
196
  def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
197
  try:
198
  now = datetime.now().timestamp()
199
  cache_key = f"{symbol}_{timeframe}"
200
 
 
201
  if cache_key in market_cache and now - last_fetch_time.get(cache_key, 0) < CACHE_DURATION:
202
  df = market_cache[cache_key].copy()
203
  else:
 
204
  if "XAU" in symbol.upper():
 
205
  ticker = "GC=F"
206
  yf_map = {"1m":"1m", "5m":"5m", "15m":"15m", "1h":"1h", "4h":"1h", "1d":"1d"}
207
  interval = yf_map.get(timeframe, "1h")
 
 
208
  data = yf.download(ticker, period="5d", interval=interval, progress=False)
209
+ if data.empty: return pd.DataFrame()
210
+ df = data.reset_index().iloc[:, [0, 1, 2, 3, 4, 6]]
 
 
 
 
 
 
211
  df.columns = ['ts', 'open', 'high', 'low', 'close', 'vol']
 
212
  else:
 
213
  fetch_symbol = symbol if "/USDT" in symbol else symbol.replace("/USD", "/USDT")
214
+ if "/" not in fetch_symbol: fetch_symbol += "/USDT"
 
215
  bars = exchange_sync.fetch_ohlcv(fetch_symbol, timeframe, limit=limit_bars)
216
  df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
217
 
218
  market_cache[cache_key], last_fetch_time[cache_key] = df.copy(), now
219
 
220
+ if df.empty or len(df) < 50: return pd.DataFrame()
 
221
 
222
+ # V24 : Microtrend Acceleration
223
+ df["EMA9"] = get_ema(df["close"], 9)
224
+ df["EMA21"] = get_ema(df["close"], 21)
225
+ df["EMA9_slope"] = df["EMA9"].diff()
226
+ df["Momentum_Acc"] = df["EMA9_slope"].diff() # Dérivée Seconde (Accélération)
227
+
228
+ df["ZScore"] = get_zscore(df["close"], 20)
229
  df["RSI"] = get_rsi(df["close"])
230
+ df["RSI_9"] = get_rsi(df["close"], 9)
231
  df["EMA50"] = get_ema(df["close"], 50)
232
  df["EMA200"] = get_ema(df["close"], 200)
233
+ df["VWAP"] = get_vwap(df)
234
  df["ATR"] = get_atr(df)
235
  df["ATR_pct"] = (df["ATR"] / df["close"]) * 100
236
  df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
237
  df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
238
 
239
+ df["High_50"] = df["high"].rolling(50).max().shift(2)
240
+ df["Low_50"] = df["low"].rolling(50).min().shift(2)
241
+ df["Liquidity_Trap_Long"] = ((df["close"] < df["Low_50"]) & (df["RSI_9"] < 30)).astype(int)
242
+ df["Liquidity_Trap_Short"] = ((df["close"] > df["High_50"]) & (df["RSI_9"] > 70)).astype(int)
243
+
244
  df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min()
245
  df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
246
  df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
247
  df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
248
  df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1
249
+ df["Price_vs_VWAP"] = (df["close"] - df["VWAP"]) / df["VWAP"]
250
  df["ATR_ratio"] = df["ATR"] / df["close"]
251
+ df["VOL_ratio"] = df["vol"] / (df["vol"].rolling(24).mean() + 1e-9)
252
+ df["Vol_Spike"] = df["vol"] / (df["vol"].rolling(5).mean() + 1e-9)
253
 
 
254
  diff = df["High_24h"] - df["Low_24h"]
255
  df["Fib_618"] = df["Low_24h"] + (diff * 0.618)
256
  df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"]
257
+ df["Market_Trend"] = df["EMA200_slope"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
 
 
 
 
 
259
  df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12)
260
  df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2)
261
+ df['VOL_RATIO'] = df['vol'] / (df['vol'].rolling(20).mean() + 1e-9)
262
  df['vol_lag1'], df['RSI_Macro'] = df['vol'].shift(1), df["RSI"]
263
 
264
  return df.dropna().copy()
 
270
  symbol = str(symbol).strip().upper()
271
 
272
  df = prepare_features_sync(symbol, timeframe)
273
+ if df.empty: return {"status": "error", "message": f"Data insuffisante"}
274
 
275
  last_row = df.iloc[[-1]]
276
  prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
277
+ vwap = float(last_row['VWAP'].iloc[0])
278
+ vol_spike = float(last_row['Vol_Spike'].iloc[0])
279
+ rsi_9 = float(last_row['RSI_9'].iloc[0])
280
 
281
+ z_score = float(last_row['ZScore'].iloc[0])
282
+ ema9, ema21 = float(last_row['EMA9'].iloc[0]), float(last_row['EMA21'].iloc[0])
283
+ micro_trend_acc = float(last_row['Momentum_Acc'].iloc[0]) # V24 Accélération
284
+ liq_trap_long = int(last_row['Liquidity_Trap_Long'].iloc[0])
285
+ liq_trap_short = int(last_row['Liquidity_Trap_Short'].iloc[0])
286
+ volatility_pct = float(last_row["ATR_pct"].iloc[0])
287
+
288
+ regime_state = "RANGE"
289
+ if volatility_pct > 0.8: regime_state = "CHAOS"
290
+ elif volatility_pct > 0.2: regime_state = "TREND"
291
+
292
+ micro_trend = 1 if ema9 > ema21 else -1
293
+
294
+ fetch_symbol = symbol if "/USDT" in symbol else symbol.replace("/USD", "/USDT")
295
+ if "/" not in fetch_symbol: fetch_symbol += "/USDT"
296
+
297
+ # 🧱 V24: ORDERBOOK ENGINE (IMBALANCE)
298
+ imbalance = 1.0
299
+ try:
300
+ orderbook = exchange_sync.fetch_order_book(fetch_symbol, limit=20)
301
+ bids_vol = sum([b[1] for b in orderbook['bids']])
302
+ asks_vol = sum([a[1] for a in orderbook['asks']])
303
+ imbalance = bids_vol / (asks_vol + 1e-9)
304
+ except Exception as e: pass
305
+
306
+ # 🌊 V24: LIQUIDATION ENGINE (FUNDING RATE)
307
+ funding_rate = 0.0
308
+ try:
309
+ funding = exchange_sync.fetch_funding_rate(fetch_symbol)
310
+ if funding and 'fundingRate' in funding:
311
+ funding_rate = float(funding['fundingRate'])
312
+ except Exception as e: pass
313
+
314
+ # 🩸 LECTURE DU SPREAD RÉEL
315
+ try:
316
+ ticker = exchange_sync.fetch_ticker(fetch_symbol)
317
+ spread = (ticker['ask'] - ticker['bid']) if ticker.get('ask') and ticker.get('bid') else (prix * 0.0002)
318
+ if spread <= 0: spread = prix * 0.0002
319
+ except:
320
+ spread = prix * 0.0002
321
+
322
+ # --- CERVEAUX IA ---
323
  regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
324
  regime_pred = int(regime_model.predict(regime_scaled)[0])
325
+ ml_prob = float(ml_model.predict_proba(last_row[["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]])[0][1])
326
+ time_prob = float(time_model.predict_proba(last_row[['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']])[0][1])
327
+
 
328
  lstm_data = df[["close","RSI","EMA50","EMA200","ATR"]].iloc[-30:]
329
  lstm_data = (lstm_data - lstm_data.mean()) / (lstm_data.std() + 1e-9)
330
  lstm_prob = float(lstm_brain.predict(np.expand_dims(lstm_data.values, axis=0), verbose=0)[0][0])
331
  p_sent = await get_crypto_sentiment(symbol)
332
 
333
+ if timeframe in ["1m", "5m"]: wt, wm, wl, ws = 0.50, 0.25, 0.15, 0.10
334
+ else: wt, wm, wl, ws = 0.30, 0.30, 0.25, 0.15
 
 
 
 
 
 
335
  final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
336
 
337
+ # --- 🐋 SMART MONEY, IMBALANCE & THANOS BOOSTS ---
 
338
  smc_status = "AUCUN"
339
+
340
+ if liq_trap_long == 1:
341
+ final_p = min(0.95, final_p + 0.25)
342
+ smc_status = "LIQUIDITY TRAP (LONG) 🐋"
343
+ elif liq_trap_short == 1:
344
+ final_p = max(0.05, final_p - 0.25)
345
+ smc_status = "LIQUIDITY TRAP (SHORT) 🐋"
346
+
347
+ if regime_state == "RANGE":
348
+ if z_score < -2.0 and prix < vwap:
349
+ final_p = min(0.90, final_p + 0.15)
350
+ smc_status = "Z-SCORE REVERSION (LONG) 🎯"
351
+ elif z_score > 2.0 and prix > vwap:
352
+ final_p = max(0.10, final_p - 0.15)
353
+ smc_status = "Z-SCORE REVERSION (SHORT) 🎯"
354
+
355
+ # V24: Imbalance Boost
356
+ if imbalance > 2.0 and final_p > 0.5:
357
+ final_p = min(0.95, final_p + 0.15)
358
+ smc_status = "BID WALL (ACHAT) 🧱"
359
+ elif imbalance < 0.5 and final_p < 0.5:
360
+ final_p = max(0.05, final_p - 0.15)
361
+ smc_status = "ASK WALL (VENTE) 🧱"
362
+
363
+ # --- 🛡️ CALCUL TP/SL V24 (LE PÉAGE EXNESS) ---
364
+ commission_estimee = prix * 0.0001 # 0.01%
365
+ slippage_estime = prix * 0.00015 # 0.015%
366
+ cost_buffer = spread + commission_estimee + slippage_estime
367
+
368
  with sqlite3.connect(DB_NAME) as conn:
369
  res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ?", (symbol, timeframe)).fetchone()
370
+ if res: tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res
 
371
  else:
372
  res_fallback = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
373
+ tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res_fallback if res_fallback else (3.0, 1.5, 0.65, 0.0005)
374
+
375
+ tp_distance = max(cost_buffer * tp_m * 2, prix * 0.0005)
376
+ sl_distance = max(cost_buffer * sl_m * 1.5, prix * 0.0003)
377
+
378
+ tp = prix + tp_distance if final_p > 0.5 else prix - tp_distance
379
+ sl = prix - sl_distance if final_p > 0.5 else prix + sl_distance
380
+
381
+ # --- 🛑 LE VETO DYNAMIQUE V24 ---
 
 
 
 
 
 
 
 
 
382
  veto = False
383
  veto_reason = ""
384
  dist_tp_pct = abs(tp - prix) / prix
 
 
 
385
 
386
  if final_p < agent_min_prob and final_p > (1 - agent_min_prob):
387
  veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}"
388
+ elif regime_state == "CHAOS":
389
+ veto, veto_reason = True, "Régime CHAOS (Risque Imprévisible)"
390
+ elif regime_state == "TREND" and ((final_p > 0.5 and micro_trend == -1) or (final_p < 0.5 and micro_trend == 1)):
391
+ veto, veto_reason = True, "Contre la Micro-Tendance (EMA9/21)"
 
 
 
 
392
 
393
+ # V24: Veto Accélération
394
+ elif final_p > 0.5 and micro_trend_acc < 0:
395
+ veto, veto_reason = True, "Momentum Acheteur en décélération"
396
+ elif final_p < 0.5 and micro_trend_acc > 0:
397
+ veto, veto_reason = True, "Momentum Vendeur en décélération"
398
+
399
+ # V24: Veto Funding Rate (Anti-Liquidation)
400
+ elif final_p > 0.5 and funding_rate > 0.001:
401
+ veto, veto_reason = True, "Surchauffe Longs (Risque Liquidation)"
402
+ elif final_p < 0.5 and funding_rate < -0.001:
403
+ veto, veto_reason = True, "Surchauffe Shorts (Risque Squeeze)"
404
+
405
+ elif dist_tp_pct < agent_min_tp_dist:
406
+ veto, veto_reason = True, f"Gain potentiel faible vs Péage Broker"
407
 
408
  if veto:
409
+ print(f"🛑 [VETO] {symbol} ({regime_state}): {veto_reason}")
410
  return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": veto_reason, "price": prix, "final_score": round(final_p, 4)}
411
 
 
412
  db_task = (datetime.now(timezone.utc).isoformat(), symbol, timeframe,
413
  'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl,
414
  'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent, prix)
415
  await save_to_db(db_task)
416
 
417
+ confidence_score = abs(final_p - 0.5) * 2
418
+
419
  return {
420
  "symbol": symbol, "timeframe": timeframe, "status": "success",
421
+ "final_score": round(final_p, 4), "confidence": round(confidence_score, 2),
422
+ "smart_money": smc_status, "regime_state": regime_state, "price": prix,
423
+ "tp": round(tp, 6), "sl": round(sl, 6),
424
  "probs": {"xgb": round(time_prob, 3), "rf": round(ml_prob, 3), "lstm": round(lstm_prob, 3), "sent": round(p_sent, 3)}
425
  }
426
  except Exception as e:
 
431
  try:
432
  print(f"⚙️ Tentative d'entraînement pour {symbol}...")
433
  memory_guard()
 
434
  bars = exchange_sync.fetch_ohlcv(symbol, timeframe='1h', limit=1500)
435
  df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
436
+ if len(df) < 500: return f"❌ Historique insuffisant."
 
 
 
437
  df_final = prepare_features_sync(symbol, '1h', limit_bars=1000)
438
 
 
 
 
 
 
 
439
  from ml_model import train_model as train_ml
440
  from time_model import train_time_model as train_time
 
441
  train_ml(df_final)
442
  train_time(df_final)
443
 
444
  global ml_model, time_model
445
  ml_model = joblib.load("ml_model_v9.pkl")
446
  time_model = joblib.load("time_model.pkl")
 
447
  gc.collect()
448
+ return f"✅ IA ré-entraînée avec succès."
449
+ except Exception as e: return f"❌ Erreur Training : {str(e)}"
 
 
450
 
451
  # --- 🚀 MOTEUR AUTO-PILOTE ---
452
  AUTO_SYMBOLS = ["ETH/USD"]
 
455
  async def auto_predict_loop():
456
  while True:
457
  try:
 
458
  with sqlite3.connect(DB_NAME) as conn:
459
  cursor = conn.cursor()
460
  for symbol in AUTO_SYMBOLS:
461
  for tf in AUTO_TIMEFRAMES:
462
+ cursor.execute("SELECT id FROM signals WHERE symbol = ? AND timeframe = ? AND status = 'EN_COURS'", (symbol, tf))
463
+ if cursor.fetchone(): continue
 
 
 
 
 
 
464
  print(f"🎯 [AUTO] Nouvelle analyse : {symbol} [{tf}]")
465
  await predict_signal(symbol, timeframe=tf)
466
  await asyncio.sleep(2)
 
 
467
  await asyncio.sleep(60)
 
468
  except Exception as e:
469
+ print(f"⚠️ Erreur Auto-Pilote : {e}")
470
  await asyncio.sleep(60)
471
 
472
  # --- ⚖️ TOOLS ---
473
+ def keep_alive_ping(): return {"status": "awake", "time": datetime.now(timezone.utc).isoformat()}
 
474
 
475
  def confirm_trade_api(trade_id, real_price):
476
  try:
477
+ t_id, r_price = int(trade_id), float(real_price)
 
 
478
  with sqlite3.connect(DB_NAME) as conn:
479
  conn.row_factory = sqlite3.Row
480
  cursor = conn.cursor()
 
 
481
  cursor.execute("SELECT price, tp, sl FROM signals WHERE id = ?", (t_id,))
482
  t = cursor.fetchone()
483
+ if not t: return {"status": "error"}
 
 
 
484
  ecart = r_price - t['price']
485
+ cursor.execute("UPDATE signals SET confirmed = 1, price = ?, tp = ?, sl = ?, peak_price = ? WHERE id = ?",
486
+ (r_price, t['tp'] + ecart, t['sl'] + ecart, r_price, t_id))
 
 
 
 
 
 
 
 
 
 
487
  conn.commit()
488
+ print(f"⚓ [SYNC-PRIX] Signal {t_id} aligné sur Exness : Entrée {r_price}")
489
+ return {"status": "success"}
490
+ except Exception as e: return {"status": "error", "message": str(e)}
 
 
491
 
492
  def cancel_trade_api(trade_id):
493
  try:
 
494
  with sqlite3.connect(DB_NAME) as conn:
495
+ conn.execute("UPDATE signals SET status = 'ANNULÉ 🗑️', confirmed = 0 WHERE id = ?", (int(trade_id),))
 
 
496
  conn.commit()
497
+ return {"status": "success"}
498
+ except Exception: return {"status": "error"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
 
500
  def run_judge_api():
501
  try:
 
502
  with sqlite3.connect(DB_NAME, timeout=10) as conn:
503
  conn.row_factory = sqlite3.Row
504
  cursor = conn.cursor()
 
505
  cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS' AND confirmed = 1")
506
  trades = cursor.fetchall()
507
+ if not trades: return {"status": "waiting"}
 
 
508
 
509
  closed_trades = []
510
  for t in trades:
511
  try:
 
 
512
  current_price = None
 
 
513
  if MT5_AVAILABLE:
514
+ epic = t['symbol'].replace("/", "").replace("USDT", "USD")
515
  if not epic.endswith("m"): epic += "m"
516
  tick = mt5.symbol_info_tick(epic)
517
  if tick: current_price = tick.last
518
 
 
519
  if current_price is None:
520
+ fetch_symbol = t['symbol'] if "/USDT" in t['symbol'] else t['symbol'].replace("/USD", "/USDT")
521
  if "/" not in fetch_symbol: fetch_symbol += "/USDT"
522
+ current_price = exchange_sync.fetch_ticker(fetch_symbol)['last']
 
523
 
 
524
  lots = 0.1
525
  diff = (t['price'] - current_price) if t['direction'] == 'BAISSIER' else (current_price - t['price'])
526
  pnl_live = diff * lots * 10
 
 
527
  color = "🟢" if pnl_live >= 0 else "🔴"
528
+ print(f"{color} [LIVE] {t['symbol']} | PnL: {round(pnl_live, 2)}$ | Prix: {current_price}")
529
 
530
+ sl_dynamique, peak = t['sl'], t['peak_price']
 
 
531
  new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price)
 
532
  chemin_total = abs(t['tp'] - t['price'])
533
+ progression = abs(current_price - t['price']) / chemin_total if chemin_total > 0 else 0
 
534
 
535
  nouveau_sl = sl_dynamique
 
 
536
  if t['direction'] == 'HAUSSIER':
537
  if progression >= 0.75: nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.60))
538
  elif progression >= 0.50: nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.25))
539
  elif progression >= 0.25: nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.05))
540
+ else:
541
  if progression >= 0.75: nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.60))
542
  elif progression >= 0.50: nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.25))
543
  elif progression >= 0.25: nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.05))
544
 
545
+ cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, nouveau_sl, t['id']))
 
546
 
547
+ outcome, reward = None, 0
 
 
548
  if t['direction'] == 'HAUSSIER':
549
  if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3
550
+ elif current_price <= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl > t['price'] else ("PERDU ❌", -5)
551
+ else:
 
552
  if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3
553
+ elif current_price >= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl < t['price'] else ("PERDU ❌", -5)
 
554
 
555
  if outcome:
556
+ pnl_brut = ((abs(current_price - t['price'])) / t['price']) * 1000.0
557
+ pnl_dollars = pnl_brut - 0.25
 
 
 
 
558
  if outcome == "SL TOUCHÉ 🛡️" and pnl_dollars < 0: outcome = "TUÉ PAR LE SPREAD/COMM 🩸"
559
  elif outcome == "GAGNÉ (PARTIEL) 💸" and pnl_dollars <= 0: outcome = "FAUX GAIN (FRAIS EXNESS) 📉"
560
 
561
+ row = cursor.execute("SELECT tp_mult, sl_mult, score, min_prob FROM agent_logic WHERE symbol = ? AND timeframe = ?", (t['symbol'], t['timeframe'])).fetchone()
 
 
562
  tp_m, sl_m, score_ia, min_p = (row['tp_mult'], row['sl_mult'], row['score'], row['min_prob']) if row else (1.5, 1.0, 0, 0.55)
563
 
564
+ if reward > 0: tp_m, min_p = min(4.0, tp_m * 1.01), max(0.50, min_p - 0.005)
565
+ elif reward < 0: sl_m, tp_m, min_p = max(0.8, sl_m * 0.985), max(1.1, tp_m * 0.985), min(0.65, min_p + 0.01)
 
 
566
 
567
+ cursor.execute("UPDATE agent_logic SET tp_mult=?, sl_mult=?, score=score+?, min_prob=? WHERE symbol=? AND timeframe=?", (tp_m, sl_m, reward, min_p, t['symbol'], t['timeframe']))
 
568
  cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
569
+ closed_trades.append({"symbol": t['symbol'], "outcome": outcome, "pnl": round(pnl_dollars, 2), "id": t['id']})
570
+
571
+ except Exception as inner_e: print(f"⚠️ Erreur sur {t['symbol']} : {inner_e}")
 
 
 
 
 
 
572
  conn.commit()
573
 
574
  if closed_trades: return {"status": "updates", "data": closed_trades}
575
+ return {"status": "waiting"}
576
+ except Exception as e: return {"status": "error", "message": str(e)}
 
 
 
 
 
 
577
 
 
578
  def training_wrapper(symbol, *args):
579
+ if not isinstance(symbol, str) or len(str(symbol)) < 2: symbol = "BTC/USD"
580
+ return trigger_training(str(symbol).strip().upper())
 
 
581
 
582
+ def get_bot_skills():
 
583
  try:
584
  with sqlite3.connect(DB_NAME) as conn:
585
  cursor = conn.cursor()
586
+ cursor.execute("SELECT symbol, timeframe, tp_mult, sl_mult, score, min_prob FROM agent_logic ORDER BY score DESC")
587
+ return [[r[0], r[1], f"x{round(r[2], 2)}", f"x{round(r[3], 2)}", f"{r[4]} XP", f"{round(r[5] * 100, 1)}%"] for r in cursor.fetchall()]
588
+ except Exception as e: return [[f"Erreur: {str(e)}", "-", "-", "-", "-", "-"]]
589
+
590
+ def force_scalping_mode():
591
+ try:
592
+ with sqlite3.connect(DB_NAME) as conn:
593
+ conn.execute("UPDATE agent_logic SET tp_mult = 1.8")
594
+ conn.commit()
595
+ return get_bot_skills()
596
+ except Exception as e: return [[f"Erreur: {str(e)}", "-", "-", "-", "-", "-"]]
 
 
597
 
598
  def get_active_signals():
599
  try:
 
600
  conn = sqlite3.connect(DB_NAME, timeout=10)
601
  conn.row_factory = sqlite3.Row
602
  cursor = conn.cursor()
 
604
  trades = [dict(row) for row in cursor.fetchall()]
605
  conn.close()
606
  return trades
607
+ except Exception as e: return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  # --- 🎨 INTERFACE GRADIO ---
610
  with gr.Blocks(theme=gr.themes.Monochrome()) as iface:
611
+ gr.Markdown("# 📡 Alpha V24 GEMINI Scalping Engine")
612
 
613
  with gr.Tab("Admin"):
614
+ admin_sym = gr.Textbox(label="Symbole", value="BTC/USDT")
 
615
  train_out = gr.Textbox(label="Résultat")
616
+ gr.Button("Forcer Entraînement", variant="stop").click(fn=training_wrapper, inputs=admin_sym, outputs=train_out, api_name="trigger_training")
617
 
618
  with gr.Tab("🎯 Prédictions"):
 
 
619
  btn_pred = gr.Button("Predict", variant="primary")
620
  out_json = gr.JSON()
621
+ btn_pred.click(fn=predict_signal, inputs=[gr.Textbox(label="Symbole", value="ETH/USD"), gr.Dropdown(choices=["1m", "5m", "15m", "1h"], value="1m", label="TF")], outputs=out_json)
622
 
623
  with gr.Tab("⚖️ Système"):
624
+ gr.Button("Judge").click(fn=run_judge_api, outputs=gr.JSON(), api_name="run_judge_api")
625
+ gr.Button("Get Active", visible=False).click(fn=get_active_signals, outputs=gr.JSON(), api_name="get_active_signals")
626
+ gr.Button("Ping", visible=False).click(fn=keep_alive_ping, outputs=gr.JSON(), api_name="keep_alive_ping")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
 
 
628
  with gr.Tab("📊 Stats du Cerveau"):
629
+ skills_table = gr.Dataframe(headers=["Marché", "Timeframe", "Cible (TP)", "Risque (SL)", "Expérience", "Confiance Min."], value=get_bot_skills(), interactive=False)
 
 
 
 
 
 
 
630
  with gr.Row():
631
+ gr.Button("🔄 Actualiser").click(get_bot_skills, outputs=skills_table)
632
+ gr.Button("⚡ Mode Scalping Force").click(force_scalping_mode, outputs=skills_table)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
 
634
+ api_inputs = [gr.Textbox(visible=False), gr.Textbox(visible=False)]
635
+ gr.Button(visible=False).click(fn=confirm_trade_api, inputs=api_inputs, outputs=gr.JSON(), api_name="confirm_trade_api")
636
+ gr.Button(visible=False).click(fn=cancel_trade_api, inputs=gr.Textbox(visible=False), outputs=gr.JSON(), api_name="cancel_trade_api")
637
+ iface.load(get_bot_skills, outputs=skills_table)
638
 
 
639
  import threading
 
 
 
640
  _auto_pilot_started = False
641
 
642
  def run_auto_pilot():
643
  global _auto_pilot_started
644
+ if _auto_pilot_started: return
 
 
 
 
 
645
  _auto_pilot_started = True
 
 
646
  print("⏳ [SYSTEM] Auto-Pilote en pause pour laisser Gradio démarrer...")
647
+ time.sleep(10)
648
+ print("🚀 [SYSTEM] Auto-Pilote V24 GEMINI propulsé en arrière-plan.")
 
649
  try:
650
  new_loop = asyncio.new_event_loop()
651
  asyncio.set_event_loop(new_loop)
652
  new_loop.run_until_complete(auto_predict_loop())
653
  except Exception as e:
654
+ print(f"⚠️ Erreur critique : {e}")
655
+ _auto_pilot_started = False
656
 
657
  if __name__ == "__main__":
658
+ try: threading.Thread(target=run_auto_pilot, daemon=True).start()
659
+ except Exception as e: print(f"⚠️ Erreur lancement Thread : {e}")
 
 
 
 
660
  iface.launch(show_api=True)