Nexo-S commited on
Commit
1c8e8f7
·
verified ·
1 Parent(s): d6da76d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -146
app.py CHANGED
@@ -84,60 +84,45 @@ HF_TOKEN = os.environ.get("HF_TOKEN")
84
 
85
  def init_db():
86
  reset_needed = False
87
-
88
  if os.path.exists(DB_NAME):
89
  try:
90
  conn = sqlite3.connect(DB_NAME)
91
  cursor = conn.execute("PRAGMA table_info(signals)")
92
  columns = [col[1] for col in cursor.fetchall()]
93
  conn.close()
94
- if 'date' not in columns or 'timeframe' not in columns:
 
95
  reset_needed = True
96
  except Exception as e:
97
  print(f"⚠️ Erreur check DB: {e}")
98
  reset_needed = True
99
 
100
  if reset_needed:
101
- print("⚠️ Structure obsolète détectée. Reset de la base de données...")
102
- try:
103
- if os.path.exists(DB_NAME):
104
- os.remove(DB_NAME)
105
- print("✅ Ancienne base supprimée.")
106
- except Exception as e:
107
- print(f"❌ Impossible de supprimer la DB: {e}")
108
 
109
  try:
110
  with sqlite3.connect(DB_NAME) as conn:
 
111
  conn.execute('''CREATE TABLE IF NOT EXISTS signals (
112
  id INTEGER PRIMARY KEY AUTOINCREMENT,
113
  date TEXT, symbol TEXT, timeframe TEXT, direction TEXT,
114
  prob REAL, price REAL, tp REAL, sl REAL, status TEXT,
115
- regime INTEGER, prob_time REAL, prob_ml REAL, prob_lstm REAL, prob_sent REAL)''')
 
116
 
117
- # 🎮 LA FICHE DE PERSONNAGE (STATS RPG)
118
  conn.execute('''CREATE TABLE IF NOT EXISTS agent_logic (
119
- timeframe TEXT PRIMARY KEY,
120
- tp_mult REAL,
121
- sl_mult REAL,
122
- score REAL,
123
- last_pnl REAL,
124
- min_prob REAL,
125
- min_tp_dist REAL)''')
126
 
127
  cursor = conn.cursor()
128
  cursor.execute("SELECT COUNT(*) FROM agent_logic")
129
  if cursor.fetchone()[0] == 0:
130
- # Stats de départ : (TF, Epée, Bouclier, Score, PNL, Peur, Radinerie)
131
- defaults = [
132
- ('5m', 1.5, 1.0, 0, 0, 0.60, 0.003),
133
- ('15m', 2.0, 1.2, 0, 0, 0.55, 0.004),
134
- ('1h', 3.0, 1.5, 0, 0, 0.52, 0.005),
135
- ('4h', 4.5, 2.0, 0, 0, 0.50, 0.008),
136
- ('1d', 6.0, 2.5, 0, 0, 0.50, 0.01)
137
- ]
138
  conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?)", defaults)
139
- print("✅ Mémoire RPG initialisée (Niveau 0).")
140
- print("✅ Base de données V10 Synchronisée.")
141
  except Exception as e:
142
  print(f"❌ Erreur critique création DB: {e}")
143
 
@@ -184,35 +169,21 @@ try:
184
  except Exception as e: print(f"⚠️ Erreur IA : {e}")
185
 
186
  # --- 📊 FEATURES ENGINE ---
 
187
  def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
188
- """Version Synchrone avec Bouclier Anti-Blocage Kucoin"""
189
  try:
190
  now = datetime.now().timestamp()
191
  cache_key = f"{symbol}_{timeframe}"
192
-
193
- if cache_key in market_cache and cache_key in last_fetch_time and now - last_fetch_time[cache_key] < CACHE_DURATION:
194
  df = market_cache[cache_key].copy()
195
  else:
196
- bars = None
197
- for attempt in range(3):
198
- try:
199
- bars = exchange_sync.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit_bars)
200
- break
201
- except Exception as e:
202
- print(f"⚠️ Kucoin bloque {symbol} (Essai {attempt+1}/3). Attente 2s...")
203
- time.sleep(2)
204
-
205
- if not bars:
206
- print(f"❌ Échec total de la récupération pour {symbol}.")
207
- return pd.DataFrame()
208
-
209
  df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
210
- market_cache[cache_key] = df.copy()
211
- last_fetch_time[cache_key] = now
212
 
213
- if len(df) < 250:
214
- return pd.DataFrame()
215
 
 
216
  df["RSI"] = get_rsi(df["close"])
217
  df["EMA50"] = get_ema(df["close"], 50)
218
  df["EMA200"] = get_ema(df["close"], 200)
@@ -221,8 +192,8 @@ def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
221
  df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
222
  df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
223
 
224
- df["High_24h"] = df["high"].rolling(24).max()
225
- df["Low_24h"] = df["low"].rolling(24).min()
226
  df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
227
  df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
228
  df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
@@ -230,41 +201,53 @@ def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
230
  df["ATR_ratio"] = df["ATR"] / df["close"]
231
  df["VOL_ratio"] = df["vol"] / df["vol"].rolling(24).mean()
232
 
233
- df['return_1h'] = df['close'].pct_change(1)
234
- df['return_3h'] = df['close'].pct_change(3)
235
- df['return_12h'] = df['close'].pct_change(12)
236
- df['RSI_lag1'] = df["RSI"].shift(1)
237
- df['RSI_lag2'] = df["RSI"].shift(2)
238
- df['VOL_RATIO'] = df['vol'] / df['vol'].rolling(20).mean()
239
- df['vol_lag1'] = df['vol'].shift(1)
240
- df["RSI_Macro"] = df["RSI"]
241
 
242
- prev_low = df["low"].rolling(24).min().shift(1)
243
- prev_high = df["high"].rolling(24).max().shift(1)
244
- df["Sweep_Low"] = ((df["low"] < prev_low) & (df["close"] > prev_low)).astype(int)
245
- df["Sweep_High"] = ((df["high"] > prev_high) & (df["close"] < prev_high)).astype(int)
246
 
247
- return df.dropna().copy()
248
-
249
- except Exception as e:
250
- print(f" Erreur critique calculs ({symbol}): {e}")
251
- return pd.DataFrame()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
 
 
253
 
254
 
255
- # --- 🎯 API PRÉDICTION AVEC GAMEPLAY RPG ---
256
  async def predict_signal(symbol, timeframe="1h"):
257
  try:
258
  memory_guard()
259
  symbol = str(symbol).strip().upper()
260
  if "/USDT" not in symbol: symbol += "/USDT"
261
 
 
262
  df = prepare_features_sync(symbol, timeframe)
263
  if df.empty: return {"status": "error", "message": f"Data insuffisante pour {timeframe}"}
264
 
265
  last_row = df.iloc[[-1]]
266
  prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
267
 
 
268
  regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
269
  regime_pred = int(regime_model.predict(regime_scaled)[0])
270
 
@@ -280,12 +263,17 @@ async def predict_signal(symbol, timeframe="1h"):
280
 
281
  p_sent = await get_crypto_sentiment(symbol)
282
 
283
- if timeframe in ["5m", "15m"]: wm, wt, wl, ws = 0.45, 0.35, 0.15, 0.05
284
- elif timeframe in ["4h", "1d"]: wl, wm, wt, ws = 0.60, 0.15, 0.15, 0.10
285
- else: wt, wm, wl, ws = 0.25, 0.25, 0.25, 0.25
 
 
 
 
286
 
287
  final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
288
 
 
289
  sweep_low, sweep_high = int(last_row["Sweep_Low"].iloc[0]), int(last_row["Sweep_High"].iloc[0])
290
  smc_status = "AUCUN"
291
  if sweep_low == 1:
@@ -295,7 +283,7 @@ async def predict_signal(symbol, timeframe="1h"):
295
  final_p = max(0.05, final_p - 0.20)
296
  smc_status = "SHORT SWEEP 🐋"
297
 
298
- # 🎮 LECTURE DES STATS DU JOUEUR EN DIRECT
299
  with sqlite3.connect(DB_NAME) as conn:
300
  res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE timeframe = ?", (timeframe,)).fetchone()
301
  tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res if res else (3.0, 1.5, 0.55, 0.004)
@@ -303,66 +291,66 @@ async def predict_signal(symbol, timeframe="1h"):
303
  tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
304
  sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
305
 
 
306
  strength = abs(final_p - 0.5) * 2
307
  conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, lstm_prob, p_sent])))
308
-
309
  score_base = (strength * 45) + (conf_val * 40)
310
  regime_bonus = 15 if regime_pred in [0, 1] else 5
311
  composite_score = max(0, min(100, score_base + regime_bonus))
312
 
313
  risk_pct = max(0.2, min(2.5, strength * 5.0))
314
- CAPITAL_VIRTUEL = 1000
315
- risk_usd = CAPITAL_VIRTUEL * (risk_pct / 100)
316
  gain_estime_usd = risk_usd * (tp_m / sl_m)
317
 
318
  # ==========================================
319
- # 🛑 LE VETO DYNAMIQUE (L'IA décide avec ses stats)
320
  # ==========================================
321
  veto = False
322
  veto_reason = ""
323
- distance_tp_pct = abs(tp - prix) / prix
324
-
325
- # F1/F2: Niveau de Peur Dynamique
326
- if final_p < agent_min_prob and final_p > (1 - agent_min_prob):
327
- veto = True
328
- veto_reason = f"Confiance ({round(final_p, 2)}). Requis: > {round(agent_min_prob, 2)}"
329
 
330
- # F3: Filtre SMC (Anti-Bruit)
 
 
 
331
  elif timeframe in ["5m", "15m"] and smc_status == "AUCUN":
332
- veto = True
333
- veto_reason = "Aucune manipulation (Sweep) détectée."
334
-
335
- # F4: Esquive des Frais Dynamique
336
- elif distance_tp_pct < agent_min_tp_dist:
337
- veto = True
338
- veto_reason = f"TP trop proche ({round(distance_tp_pct*100, 2)}%). Requis: > {round(agent_min_tp_dist*100, 2)}%"
339
-
340
- # F5: Filtre Macro
341
  elif symbol != "BTC/USDT" and regime_pred == 2 and final_p > 0.5:
342
- veto = True
343
- veto_reason = "Tendance macro baissière (Risque Altcoins)."
344
-
345
- # F6: Le Veto de Tendance (Anti-Kamikaze)
346
- ema200_slope_val = float(last_row["EMA200_slope"].iloc[0])
347
- if final_p < 0.5 and ema200_slope_val > 0.005: # Si on veut shorter mais que la tendance long terme monte fort
348
- veto = True
349
- veto_reason = "Tendance lourde haussière. Interdit de shorter un pump."
350
- elif final_p > 0.5 and ema200_slope_val < -0.005: # L'inverse
351
- veto = True
352
- veto_reason = "Tendance lourde baissière. Interdit d'acheter un dump."
 
 
 
 
 
 
 
353
 
354
  if veto:
355
- print(f"🛑 [VETO IA] {symbol} [{timeframe}] refusé : {veto_reason}")
356
- return {
357
- "symbol": symbol, "timeframe": timeframe, "status": "veto",
358
- "message": veto_reason, "price": prix, "final_score": round(final_p, 4)
359
- }
360
 
361
- # ==========================================
362
- # 6. SAUVEGARDE DB
363
- # ==========================================
364
- db_task = (datetime.now(timezone.utc).isoformat(), symbol, timeframe, 'HAUSSIER' if final_p > 0.5 else 'BAISSIER',
365
- final_p, prix, tp, sl, 'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent)
366
  await save_to_db(db_task)
367
 
368
  print(f"✅ [VALIDÉ] {symbol} [{timeframe}] : Ordre envoyé au Juge.")
@@ -376,7 +364,6 @@ async def predict_signal(symbol, timeframe="1h"):
376
  "probs": {"xgb": round(time_prob, 3), "rf": round(ml_prob, 3), "lstm": round(lstm_prob, 3), "sent": round(p_sent, 3)},
377
  "weights": {"w_xgb": round(wt, 2), "w_rf": round(wm, 2), "w_lstm": round(wl, 2), "w_sent": round(ws, 2)}
378
  }
379
-
380
  except Exception as e:
381
  return {"status": "error", "message": str(e)}
382
 
@@ -469,10 +456,13 @@ def run_judge_api():
469
  conn = sqlite3.connect(DB_NAME)
470
  conn.row_factory = sqlite3.Row
471
  cursor = conn.cursor()
 
 
472
  cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
473
  trades = cursor.fetchall()
474
 
475
- if not trades: return {"status": "waiting", "message": "⚖️ Observation du marché en cours..."}
 
476
 
477
  closed_trades = []
478
  for t in trades:
@@ -480,53 +470,78 @@ def run_judge_api():
480
  ticker = exchange_sync.fetch_ticker(t['symbol'])
481
  current_price = ticker['last']
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  outcome = None
484
  reward = 0
485
 
486
  if t['direction'] == 'HAUSSIER':
487
- if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3
488
- elif current_price <= t['sl']: outcome, reward = "PERDU ", -5
 
 
 
 
489
  else:
490
- if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3
491
- elif current_price >= t['sl']: outcome, reward = "PERDU ", -5
 
 
 
492
 
493
  if outcome:
494
- # ==========================================
495
- # 💰 CALCUL DU PNL (PROFIT & LOSS)
496
- # Capital: 10 000$ | Risque: 1% (100$)
497
- # ==========================================
498
  risque_usd = 100.0
499
- distance_sl = abs(t['price'] - t['sl'])
500
- distance_tp = abs(t['price'] - t['tp'])
501
-
502
- # Ratio Risk/Reward
503
- rr_ratio = distance_tp / distance_sl if distance_sl > 0 else 1
504
 
505
  if "GAGNÉ" in outcome:
506
  pnl_dollars = risque_usd * rr_ratio
 
 
507
  else:
508
  pnl_dollars = -risque_usd
509
 
510
- # 🎮 LE SYSTÈME D'EXPÉRIENCE (L'IA Modifie ses Stats)
511
  cursor.execute("SELECT tp_mult, sl_mult, score, min_prob, min_tp_dist FROM agent_logic WHERE timeframe = ?", (t['timeframe'],))
512
  row = cursor.fetchone()
513
- tp_m, sl_m, score, min_p, min_tp_d = row if row else (2.0, 1.5, 0, 0.55, 0.004)
514
 
515
- if reward > 0: # LEVEL UP !
516
  tp_m *= 1.05
517
- min_p = max(0.50, min_p - 0.01) # Baisse de la peur (Devient plus agressive)
518
- min_tp_d = max(0.002, min_tp_d * 0.95) # Tolère plus de frais
519
- else: # PUNITION !
520
- sl_m *= 0.90
521
  tp_m *= 0.95
522
- min_p = min(0.65, min_p + 0.02) # Augmente sa prudence (Devient difficile)
523
- min_tp_d = min(0.015, min_tp_d * 1.05) # Exige plus de marge de gain
524
 
525
- cursor.execute("UPDATE agent_logic SET tp_mult=?, sl_mult=?, score=score+?, min_prob=?, min_tp_dist=? WHERE timeframe=?",
526
- ((max(1.1, tp_m), max(1.0, sl_m), reward, min_p, min_tp_d, t['timeframe']))
527
  cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
528
 
529
- # 📦 ON PRÉPARE LE COLIS POUR DISCORD
530
  closed_trades.append({
531
  "symbol": t['symbol'],
532
  "timeframe": t['timeframe'],
@@ -534,26 +549,24 @@ def run_judge_api():
534
  "outcome": outcome,
535
  "entry_price": t['price'],
536
  "close_price": current_price,
537
- "tp": t['tp'],
538
- "sl": t['sl'],
539
- "pnl": round(pnl_dollars, 2), # <--- LE NOUVEAU PNL EST ICI
540
  "new_peur": round(min_p * 100, 1),
541
  "new_tp": round(tp_m, 2),
542
  "new_sl": round(sl_m, 2)
543
  })
 
544
  except Exception as inner_e:
545
  print(f"⚠️ Erreur sur {t['symbol']} : {inner_e}")
546
 
547
  conn.commit()
548
  conn.close()
549
 
550
-
551
  if closed_trades:
552
  return {"status": "updates", "data": closed_trades}
553
- return {"status": "waiting", "message": "waiting..."} # <-- On a enlevé l'émoji ici pour éviter le crash orjson
554
 
555
  except Exception as e:
556
- return {"status": "error", "message": "error"}
557
 
558
  async def shutdown():
559
  ex = ExchangeManager.get_instance()
 
84
 
85
  def init_db():
86
  reset_needed = False
 
87
  if os.path.exists(DB_NAME):
88
  try:
89
  conn = sqlite3.connect(DB_NAME)
90
  cursor = conn.execute("PRAGMA table_info(signals)")
91
  columns = [col[1] for col in cursor.fetchall()]
92
  conn.close()
93
+ # On vérifie si 'peak_price' existe, sinon on reset pour la nouvelle structure
94
+ if 'date' not in columns or 'peak_price' not in columns:
95
  reset_needed = True
96
  except Exception as e:
97
  print(f"⚠️ Erreur check DB: {e}")
98
  reset_needed = True
99
 
100
  if reset_needed:
101
+ print("⚠️ Structure obsolète. Reset de la base de données pour le Trailing Stop...")
102
+ if os.path.exists(DB_NAME): os.remove(DB_NAME)
 
 
 
 
 
103
 
104
  try:
105
  with sqlite3.connect(DB_NAME) as conn:
106
+ # Table des signaux avec 'peak_price' pour le Trailing Stop
107
  conn.execute('''CREATE TABLE IF NOT EXISTS signals (
108
  id INTEGER PRIMARY KEY AUTOINCREMENT,
109
  date TEXT, symbol TEXT, timeframe TEXT, direction TEXT,
110
  prob REAL, price REAL, tp REAL, sl REAL, status TEXT,
111
+ regime INTEGER, prob_time REAL, prob_ml REAL, prob_lstm REAL, prob_sent REAL,
112
+ peak_price REAL)''') # <--- NOUVEAU
113
 
 
114
  conn.execute('''CREATE TABLE IF NOT EXISTS agent_logic (
115
+ timeframe TEXT PRIMARY KEY, tp_mult REAL, sl_mult REAL,
116
+ score REAL, last_pnl REAL, min_prob REAL, min_tp_dist REAL)''')
 
 
 
 
 
117
 
118
  cursor = conn.cursor()
119
  cursor.execute("SELECT COUNT(*) FROM agent_logic")
120
  if cursor.fetchone()[0] == 0:
121
+ defaults = [('5m', 1.5, 1.0, 0, 0, 0.60, 0.003), ('15m', 2.0, 1.2, 0, 0, 0.55, 0.004),
122
+ ('1h', 3.0, 1.5, 0, 0, 0.52, 0.005), ('4h', 4.5, 2.0, 0, 0, 0.50, 0.008),
123
+ ('1d', 6.0, 2.5, 0, 0, 0.50, 0.01)]
 
 
 
 
 
124
  conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?)", defaults)
125
+ print("✅ Base de données V11 (Trailing Stop Ready) Synchronisée.")
 
126
  except Exception as e:
127
  print(f"❌ Erreur critique création DB: {e}")
128
 
 
169
  except Exception as e: print(f"⚠️ Erreur IA : {e}")
170
 
171
  # --- 📊 FEATURES ENGINE ---
172
+ # --- 📊 FEATURES ENGINE (Fibonacci + FVG + BTC) ---
173
  def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
 
174
  try:
175
  now = datetime.now().timestamp()
176
  cache_key = f"{symbol}_{timeframe}"
177
+ if cache_key in market_cache and now - last_fetch_time.get(cache_key, 0) < CACHE_DURATION:
 
178
  df = market_cache[cache_key].copy()
179
  else:
180
+ bars = exchange_sync.fetch_ohlcv(symbol, timeframe, limit=limit_bars)
 
 
 
 
 
 
 
 
 
 
 
 
181
  df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
182
+ market_cache[cache_key], last_fetch_time[cache_key] = df.copy(), now
 
183
 
184
+ if len(df) < 250: return pd.DataFrame()
 
185
 
186
+ # Indicateurs Standard
187
  df["RSI"] = get_rsi(df["close"])
188
  df["EMA50"] = get_ema(df["close"], 50)
189
  df["EMA200"] = get_ema(df["close"], 200)
 
192
  df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
193
  df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
194
 
195
+ # Market Structure
196
+ df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min()
197
  df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
198
  df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
199
  df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
 
201
  df["ATR_ratio"] = df["ATR"] / df["close"]
202
  df["VOL_ratio"] = df["vol"] / df["vol"].rolling(24).mean()
203
 
204
+ # Fibonacci (24h Swing)
205
+ diff = df["High_24h"] - df["Low_24h"]
206
+ df["Fib_618"] = df["Low_24h"] + (diff * 0.618)
207
+ df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"]
 
 
 
 
208
 
209
+ # FVG (Fair Value Gap)
210
+ df['FVG_bull'] = (df['low'] > df['high'].shift(2)).astype(int)
211
+ df['FVG_bear'] = (df['high'] < df['low'].shift(2)).astype(int)
 
212
 
213
+ # BTC Correlation
214
+ if symbol != "BTC/USDT":
215
+ try:
216
+ b_bars = exchange_sync.fetch_ohlcv("BTC/USDT", timeframe, limit=50)
217
+ b_c = pd.Series([b[4] for b in b_bars])
218
+ df["BTC_slope"] = (b_c.ewm(50).mean().iloc[-1] / b_c.ewm(50).mean().iloc[-6]) - 1
219
+ except: df["BTC_slope"] = 0
220
+ else: df["BTC_slope"] = df["EMA200_slope"]
221
+
222
+ # SMC Sweeps
223
+ p_low, p_high = df["low"].rolling(24).min().shift(1), df["high"].rolling(24).max().shift(1)
224
+ df["Sweep_Low"] = ((df["low"] < p_low) & (df["close"] > p_low)).astype(int)
225
+ df["Sweep_High"] = ((df["high"] > p_high) & (df["close"] < p_high)).astype(int)
226
+
227
+ # Lags pour XGBoost
228
+ df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12)
229
+ df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2)
230
+ df['VOL_RATIO'] = df['vol'] / df['vol'].rolling(20).mean()
231
+ df['vol_lag1'], df['RSI_Macro'] = df['vol'].shift(1), df["RSI"]
232
 
233
+ return df.dropna().copy()
234
+ except Exception as e: print(f"❌ Error Stats: {e}"); return pd.DataFrame()
235
 
236
 
 
237
  async def predict_signal(symbol, timeframe="1h"):
238
  try:
239
  memory_guard()
240
  symbol = str(symbol).strip().upper()
241
  if "/USDT" not in symbol: symbol += "/USDT"
242
 
243
+ # 1. Préparation des données (Contient FVG, Fib, BTC Slope)
244
  df = prepare_features_sync(symbol, timeframe)
245
  if df.empty: return {"status": "error", "message": f"Data insuffisante pour {timeframe}"}
246
 
247
  last_row = df.iloc[[-1]]
248
  prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
249
 
250
+ # 2. Analyse des 3 cerveaux + Sentiment
251
  regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
252
  regime_pred = int(regime_model.predict(regime_scaled)[0])
253
 
 
263
 
264
  p_sent = await get_crypto_sentiment(symbol)
265
 
266
+ # 3. Pondération adaptative selon le Timeframe (Tes réglages d'origine)
267
+ if timeframe in ["5m", "15m"]:
268
+ wm, wt, wl, ws = 0.45, 0.35, 0.15, 0.05
269
+ elif timeframe in ["4h", "1d"]:
270
+ wl, wm, wt, ws = 0.60, 0.15, 0.15, 0.10
271
+ else:
272
+ wt, wm, wl, ws = 0.25, 0.25, 0.25, 0.25
273
 
274
  final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
275
 
276
+ # 4. Bonus Smart Money (SMC)
277
  sweep_low, sweep_high = int(last_row["Sweep_Low"].iloc[0]), int(last_row["Sweep_High"].iloc[0])
278
  smc_status = "AUCUN"
279
  if sweep_low == 1:
 
283
  final_p = max(0.05, final_p - 0.20)
284
  smc_status = "SHORT SWEEP 🐋"
285
 
286
+ # 5. Lecture des Stats RPG et calcul TP/SL
287
  with sqlite3.connect(DB_NAME) as conn:
288
  res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE timeframe = ?", (timeframe,)).fetchone()
289
  tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res if res else (3.0, 1.5, 0.55, 0.004)
 
291
  tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
292
  sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
293
 
294
+ # 6. Scores et Risk Management
295
  strength = abs(final_p - 0.5) * 2
296
  conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, lstm_prob, p_sent])))
 
297
  score_base = (strength * 45) + (conf_val * 40)
298
  regime_bonus = 15 if regime_pred in [0, 1] else 5
299
  composite_score = max(0, min(100, score_base + regime_bonus))
300
 
301
  risk_pct = max(0.2, min(2.5, strength * 5.0))
302
+ risk_usd = 1000 * (risk_pct / 100) # Capital Virtuel 1000$
 
303
  gain_estime_usd = risk_usd * (tp_m / sl_m)
304
 
305
  # ==========================================
306
+ # 🛑 LE VETO DYNAMIQUE (F1 à F9)
307
  # ==========================================
308
  veto = False
309
  veto_reason = ""
310
+ dist_tp_pct = abs(tp - prix) / prix
311
+ dist_fib = float(last_row["Dist_Fib_618"].iloc[0])
312
+ btc_slope = float(last_row["BTC_slope"].iloc[0])
313
+ ema200_slope_val = float(last_row["EMA200_slope"].iloc[0])
314
+ fvg_bull = int(last_row["FVG_bull"].iloc[0])
 
315
 
316
+ # F1/F2: Confiance
317
+ if final_p < agent_min_prob and final_p > (1 - agent_min_prob):
318
+ veto, veto_reason = True, f"Confiance ({round(final_p, 2)}). Requis: > {round(agent_min_prob, 2)}"
319
+ # F3: SMC (5m/15m)
320
  elif timeframe in ["5m", "15m"] and smc_status == "AUCUN":
321
+ veto, veto_reason = True, "Aucune manipulation (Sweep) détectée."
322
+ # F4: Frais
323
+ elif dist_tp_pct < agent_min_tp_dist:
324
+ veto, veto_reason = True, f"TP trop proche ({round(dist_tp_pct*100, 2)}%)."
325
+ # F5: Macro
 
 
 
 
326
  elif symbol != "BTC/USDT" and regime_pred == 2 and final_p > 0.5:
327
+ veto, veto_reason = True, "Tendance macro baissière."
328
+ # F6: Anti-Kamikaze
329
+ elif final_p < 0.5 and ema200_slope_val > 0.005:
330
+ veto, veto_reason = True, "Interdit de shorter un pump."
331
+ elif final_p > 0.5 and ema200_slope_val < -0.005:
332
+ veto, veto_reason = True, "Interdit d'acheter un dump."
333
+ # F7: Fibonacci 📏
334
+ elif final_p > 0.6 and dist_fib < -0.02:
335
+ veto, veto_reason = True, f"Support Fib trop loin ({round(dist_fib*100, 2)}%)."
336
+ # F8: Corrélation BTC 👑
337
+ elif symbol != "BTC/USDT":
338
+ if final_p < 0.5 and btc_slope > 0.002:
339
+ veto, veto_reason = True, "BTC Haussier (No Short)."
340
+ elif final_p > 0.5 and btc_slope < -0.002:
341
+ veto, veto_reason = True, "BTC Baissier (No Long)."
342
+ # F9: FVG Check (Facultatif mais enregistré)
343
+ if final_p > 0.7 and fvg_bull == 0:
344
+ print(f"⚠️ [FVG Warning] Pas de gap de liquidité pour l'entrée sur {symbol}")
345
 
346
  if veto:
347
+ print(f"🛑 [VETO] {symbol} [{timeframe}]: {veto_reason}")
348
+ return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": veto_reason, "price": prix, "final_score": round(final_p, 4)}
 
 
 
349
 
350
+ # 7. SAUVEGARDE DB (On ajoute peak_price à la fin)
351
+ db_task = (datetime.now(timezone.utc).isoformat(), symbol, timeframe,
352
+ 'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl,
353
+ 'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent, prix)
 
354
  await save_to_db(db_task)
355
 
356
  print(f"✅ [VALIDÉ] {symbol} [{timeframe}] : Ordre envoyé au Juge.")
 
364
  "probs": {"xgb": round(time_prob, 3), "rf": round(ml_prob, 3), "lstm": round(lstm_prob, 3), "sent": round(p_sent, 3)},
365
  "weights": {"w_xgb": round(wt, 2), "w_rf": round(wm, 2), "w_lstm": round(wl, 2), "w_sent": round(ws, 2)}
366
  }
 
367
  except Exception as e:
368
  return {"status": "error", "message": str(e)}
369
 
 
456
  conn = sqlite3.connect(DB_NAME)
457
  conn.row_factory = sqlite3.Row
458
  cursor = conn.cursor()
459
+
460
+ # On ne juge que les trades qui sont encore ouverts
461
  cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
462
  trades = cursor.fetchall()
463
 
464
+ if not trades:
465
+ return {"status": "waiting", "message": "⚖️ Observation du marché en cours..."}
466
 
467
  closed_trades = []
468
  for t in trades:
 
470
  ticker = exchange_sync.fetch_ticker(t['symbol'])
471
  current_price = ticker['last']
472
 
473
+ # --- 🚀 LOGIQUE TRAILING STOP (SÉCURISATION) ---
474
+ sl_dynamique = t['sl']
475
+ peak = t['peak_price']
476
+
477
+ # Mise à jour du point le plus haut (ou bas) atteint
478
+ new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price)
479
+
480
+ # Ratio de progression vers le TP (0.5 = 50% du chemin fait)
481
+ chemin_total = abs(t['tp'] - t['price'])
482
+ chemin_parcouru = abs(current_price - t['price'])
483
+ progression = chemin_parcouru / chemin_total if chemin_total > 0 else 0
484
+
485
+ # 🛡️ PROTECTION BREAK-EVEN : Si on a fait 50% du chemin, on met le SL au prix d'entrée
486
+ if progression > 0.5:
487
+ # Pour un LONG, on remonte le SL. Pour un SHORT, on le descend.
488
+ if t['direction'] == 'HAUSSIER' and sl_dynamique < t['price']:
489
+ sl_dynamique = t['price']
490
+ elif t['direction'] == 'BAISSIER' and sl_dynamique > t['price']:
491
+ sl_dynamique = t['price']
492
+
493
+ # On met à jour la base de données avec le nouveau Peak et le nouveau SL
494
+ cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, sl_dynamique, t['id']))
495
+
496
+ # --- 🏁 VÉRIFICATION DU RÉSULTAT ---
497
  outcome = None
498
  reward = 0
499
 
500
  if t['direction'] == 'HAUSSIER':
501
+ if current_price >= t['tp']:
502
+ outcome, reward = "GAGNÉ ", 3
503
+ elif current_price <= sl_dynamique:
504
+ # On distingue si c'est une perte ou une protection
505
+ outcome = "SL TOUCHÉ 🛡️" if sl_dynamique == t['price'] else "PERDU ❌"
506
+ reward = 0 if outcome == "SL TOUCHÉ 🛡️" else -5
507
  else:
508
+ if current_price <= t['tp']:
509
+ outcome, reward = "GAGNÉ ", 3
510
+ elif current_price >= sl_dynamique:
511
+ outcome = "SL TOUCHÉ 🛡️" if sl_dynamique == t['price'] else "PERDU ❌"
512
+ reward = 0 if outcome == "SL TOUCHÉ 🛡️" else -5
513
 
514
  if outcome:
515
+ # --- 💰 CALCUL DU PNL ---
 
 
 
516
  risque_usd = 100.0
517
+ dist_sl_initiale = abs(t['price'] - t['sl'])
518
+ dist_tp_initiale = abs(t['tp'] - t['price'])
519
+ rr_ratio = dist_tp_initiale / dist_sl_initiale if dist_sl_initiale > 0 else 1
 
 
520
 
521
  if "GAGNÉ" in outcome:
522
  pnl_dollars = risque_usd * rr_ratio
523
+ elif "SL TOUCHÉ" in outcome:
524
+ pnl_dollars = 0.0 # On sort à 0, capital sauvé !
525
  else:
526
  pnl_dollars = -risque_usd
527
 
528
+ # --- 🎮 ÉVOLUTION RPG ---
529
  cursor.execute("SELECT tp_mult, sl_mult, score, min_prob, min_tp_dist FROM agent_logic WHERE timeframe = ?", (t['timeframe'],))
530
  row = cursor.fetchone()
531
+ tp_m, sl_m, score_ia, min_p, min_tp_d = row if row else (3.0, 1.5, 0, 0.55, 0.004)
532
 
533
+ if reward > 0: # LEVEL UP
534
  tp_m *= 1.05
535
+ min_p = max(0.50, min_p - 0.01)
536
+ elif reward < 0: # PUNITION
537
+ sl_m = max(1.0, sl_m * 0.95) # On ne descend jamais sous 1.0 ATR
 
538
  tp_m *= 0.95
539
+ min_p = min(0.65, min_p + 0.02)
 
540
 
541
+ cursor.execute("UPDATE agent_logic SET tp_mult=?, sl_mult=?, score=score+?, min_prob=? WHERE timeframe=?",
542
+ (tp_m, sl_m, reward, min_p, t['timeframe']))
543
  cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
544
 
 
545
  closed_trades.append({
546
  "symbol": t['symbol'],
547
  "timeframe": t['timeframe'],
 
549
  "outcome": outcome,
550
  "entry_price": t['price'],
551
  "close_price": current_price,
552
+ "pnl": round(pnl_dollars, 2),
 
 
553
  "new_peur": round(min_p * 100, 1),
554
  "new_tp": round(tp_m, 2),
555
  "new_sl": round(sl_m, 2)
556
  })
557
+
558
  except Exception as inner_e:
559
  print(f"⚠️ Erreur sur {t['symbol']} : {inner_e}")
560
 
561
  conn.commit()
562
  conn.close()
563
 
 
564
  if closed_trades:
565
  return {"status": "updates", "data": closed_trades}
566
+ return {"status": "waiting", "message": "waiting..."}
567
 
568
  except Exception as e:
569
+ return {"status": "error", "message": str(e)}
570
 
571
  async def shutdown():
572
  ex = ExchangeManager.get_instance()