Nexo-S commited on
Commit
09ddf1e
·
verified ·
1 Parent(s): afc794c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -154
app.py CHANGED
@@ -3,24 +3,52 @@ import os
3
  import sqlite3
4
  import shutil
5
  import pandas as pd
6
- import ccxt
7
  import joblib
8
  import json
9
  import numpy as np
10
  import warnings
 
 
 
11
  from datetime import datetime, timezone
12
  from huggingface_hub import HfApi, hf_hub_download
13
  import gradio as gr
 
 
 
14
  from tensorflow.keras.models import load_model
15
- import gc # Garbage Collector (Nettoyeur de RAM)
 
 
 
 
 
 
 
16
 
17
  # --- CONFIGURATION GPS ---
18
  current_dir = os.path.dirname(os.path.abspath(__file__))
19
  if current_dir not in sys.path:
20
  sys.path.append(current_dir)
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # --- 🛰️ IMPORT SÉCURISÉ DES SATELLITES ---
23
- # On définit des fonctions de secours (fallbacks) au cas où l'import d'un fichier rate
24
  try:
25
  from sentiment_engine import get_crypto_sentiment
26
  print("✅ Sentiment Engine : Connecté.")
@@ -33,7 +61,8 @@ try:
33
  print("✅ Ensemble Engine : Connecté.")
34
  except Exception as e:
35
  print(f"⚠️ Erreur Ensemble Engine : {e}")
36
- def combine_scores(s, t, m, l, sent, r): return (t+m+l+sent)/4, 0.25, 0.25, 0.25, 0.25
 
37
 
38
  # --- CONFIG HUB & DB ---
39
  REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
@@ -47,7 +76,7 @@ def get_rsi(series, period=14):
47
  delta = series.diff()
48
  gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
49
  loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
50
- rs = gain / loss
51
  return 100 - (100 / (1 + rs))
52
 
53
  def get_atr(df, period=14):
@@ -56,6 +85,16 @@ def get_atr(df, period=14):
56
  low_close = (df['low'] - df['close'].shift()).abs()
57
  return pd.concat([high_low, high_close, low_close], axis=1).max(axis=1).rolling(period).mean()
58
 
 
 
 
 
 
 
 
 
 
 
59
  # --- ☁️ SYNC CLOUD ---
60
  def sync_down():
61
  if not HF_TOKEN: return
@@ -64,9 +103,6 @@ def sync_down():
64
  shutil.copy(path, DB_NAME); print("✅ DB Cloud récupérée.")
65
  except: print("ℹ️ Nouvelle DB créée.")
66
 
67
- def sync_up():
68
- pass # 🛑 ON DÉSACTIVE L'UPLOAD POUR EMPÊCHER LES REDÉMARRAGES
69
-
70
  def init_db():
71
  try:
72
  conn = sqlite3.connect(DB_NAME); cursor = conn.cursor()
@@ -77,11 +113,11 @@ def init_db():
77
  conn.commit(); conn.close()
78
  except Exception as e: print(f"❌ Erreur DB Init : {e}")
79
 
80
- # --- 🚀 INITIALISATION ---
81
  sync_down()
82
  init_db()
83
  warnings.filterwarnings('ignore')
84
 
 
85
  try:
86
  ml_model = joblib.load("ml_model_v9.pkl")
87
  time_model = joblib.load("time_model.pkl")
@@ -91,14 +127,23 @@ try:
91
  print("✅ IA chargées en RAM.")
92
  except Exception as e: print(f"⚠️ Modèles IA manquants : {e}")
93
 
94
- # --- 📊 PRÉPARATION FEATURES ---
95
- def prepare_all_features(symbol):
96
  try:
97
- exchange = ccxt.kucoin()
98
- bars = exchange.fetch_ohlcv(symbol, timeframe='1h', limit=600)
99
- df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
 
 
 
 
 
 
 
 
100
  if len(df) < 250: return pd.DataFrame()
101
 
 
102
  df["RSI"] = get_rsi(df["close"], 14)
103
  df["EMA50"] = get_ema(df["close"], 50)
104
  df["EMA200"] = get_ema(df["close"], 200)
@@ -126,88 +171,76 @@ def prepare_all_features(symbol):
126
  df["RSI_Macro"] = df["RSI"]
127
 
128
  return df.dropna().copy()
129
- except: return pd.DataFrame()
 
 
130
 
131
- # --- 🎯 API PRÉDICTION ---
132
  async def predict_signal(symbol):
133
  try:
 
134
  symbol = str(symbol).strip().upper()
135
  if "/USDT" not in symbol: symbol += "/USDT"
136
- df = prepare_all_features(symbol)
137
- if df.empty: return json.dumps({"status": "error", "message": "Data insuffisante"})
 
 
138
  last_row = df.iloc[[-1]]
139
 
 
140
  regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
141
  regime_pred = int(regime_model.predict(regime_scaled)[0])
142
 
143
- 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])
144
- 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])
 
145
 
 
 
 
 
146
  lstm_seq = np.expand_dims(df[["close", "RSI", "EMA50", "EMA200", "ATR"]].iloc[-30:].values, axis=0)
147
  lstm_prob = float(lstm_brain.predict(lstm_seq, verbose=0)[0][0])
148
 
 
149
  p_sent = await get_crypto_sentiment(symbol)
150
  final_p, wt, wm, wl, ws = combine_scores(symbol, time_prob, ml_prob, lstm_prob, p_sent, regime_pred)
151
- # Calcul de la Volatilité (déjà dans tes features)
152
- volatilite = float(last_row['ATR_pct'].iloc[0])
153
-
154
- # Calcul de la Confluence (écart type inversé des probabilités)
155
- all_probs = [time_prob, ml_prob, lstm_prob, p_sent]
156
- confluence = 1 - np.std(all_probs)
157
 
158
- # Calcul de la Position (Gestion simplifiée)
159
- position_size = (final_p * 2) if final_p > 0.5 else ((1 - final_p) * 2)
160
-
161
  prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
162
  tp = prix + (atr * 3) if final_p > 0.5 else prix - (atr * 3)
163
  sl = prix - (atr * 1.5) if final_p > 0.5 else prix + (atr * 1.5)
164
-
165
- # ... (ton code précédent avec calcul du TP et SL) ...
166
 
167
- # --- SAUVEGARDE EN BASE DE DONNÉES ---
168
- conn = sqlite3.connect(DB_NAME); cursor = conn.cursor()
169
- cursor.execute('INSERT INTO signals (date, symbol, direction, prob, price, tp, sl, status, regime, prob_time, prob_ml, prob_lstm, prob_sent) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)',
170
- (datetime.now().isoformat(), symbol, 'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl, 'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent))
171
- conn.commit(); conn.close(); sync_up()
172
-
173
- # --- 1. STOCKAGE DU RÉSULTAT (Sans faire de return) ---
174
- resultat_final = json.dumps({
175
- "symbol": symbol, "final_score": round(final_p, 4), "volatility": round(volatilite, 2), "confluence": round(confluence * 100, 1),
176
- "pos_size": round(position_size, 2), "regime": int(regime_pred),
177
- "probs": {"xgb": float(time_prob), "rf": float(ml_prob), "lstm": float(lstm_prob), "sent": float(p_sent)},
178
- "weights": {"w_xgb": float(wt), "w_rf": float(wm), "w_lstm": float(wl), "w_sent": float(ws)},
179
- "price": float(prix), "tp": float(tp), "sl": float(sl), "status": "success"
180
- })
181
-
182
- # --- 🧹 2. OPÉRATION VIDANGE RAM (ANTI-CRASH 137) ---
183
- # A. On détruit le gros tableau Pandas
184
- if 'df' in locals():
185
- del df
186
-
187
- # B. On vide le cache de TensorFlow (CRUCIAL POUR LE LSTM)
188
- from tensorflow.keras import backend as K
189
- K.clear_session()
190
-
191
- # C. On force Python à vider la RAM physique
192
- import gc
193
- gc.collect()
194
-
195
- # --- 3. SORTIE DE LA FONCTION ---
196
- return resultat_final
197
-
198
- except Exception as e:
199
- return json.dumps({"status": "error", "message": str(e)})
200
 
201
- # --- ⚖️ LE JUGE ---
 
 
 
202
  async def run_judge_api():
203
  try:
204
  conn = sqlite3.connect(DB_NAME); conn.row_factory = sqlite3.Row; cursor = conn.cursor()
205
  cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
206
  trades = cursor.fetchall()
207
  if not trades: return "⚖️ Aucun trade actif."
208
- exchange, updates = ccxt.kucoin(), 0
 
209
  for t in trades:
210
- curr = exchange.fetch_ticker(t['symbol'])['last']
211
  new_s = 'EN_COURS'
212
  if 'HAUSSIER' in t['direction']:
213
  if curr >= t['tp']: new_s = 'GAGNÉ ✅'
@@ -215,97 +248,39 @@ async def run_judge_api():
215
  else:
216
  if curr <= t['tp']: new_s = 'GAGNÉ ✅'
217
  elif curr >= t['sl']: new_s = 'PERDU ❌'
 
218
  if new_s != 'EN_COURS':
219
  cursor.execute("UPDATE signals SET status = ? WHERE id = ?", (new_s, t['id']))
220
  updates += 1
221
- conn.commit(); conn.close(); sync_up()
222
  return f"✅ {updates} trade(s) mis à jour."
223
  except Exception as e: return f"❌ Erreur Juge : {e}"
224
 
225
- # --- 📊 STATS ENGINE ---
226
- def get_db_stats(mode, symbol=None, n=3):
227
  try:
228
- if not os.path.exists(DB_NAME): return json.dumps({"status": "error", "message": "DB introuvable"})
229
  conn = sqlite3.connect(DB_NAME); conn.row_factory = sqlite3.Row
230
  if mode == "global":
231
  df = pd.read_sql_query("SELECT status, prob FROM signals WHERE status!='EN_COURS'", conn)
232
- if len(df) < 3: return json.dumps({"status": "error"})
233
- wins = df[df['status'].str.contains("GAGNÉ")]; losses = df[~df['status'].str.contains("GAGNÉ")]
234
- wr = (len(wins) / len(df)) * 100
235
- exp = (wins['prob'].mean() * len(wins) - losses['prob'].mean() * len(losses)) / len(df)
236
- res = {"total": len(df), "winrate": round(wr, 1), "expectancy": round(float(exp), 3), "note": "Edge OK" if exp > 0 else "Edge bas"}
237
- elif mode == "summary":
238
- cursor = conn.cursor(); cursor.execute("SELECT COUNT(*), COUNT(CASE WHEN status LIKE 'GAGNÉ%' THEN 1 END) FROM signals WHERE status!='EN_COURS'")
239
- t, w = cursor.fetchone(); res = {"total": t, "winrate": round((w/t*100), 1) if t > 0 else 0}
240
- elif mode == "top":
241
- df = pd.read_sql_query("SELECT symbol, status FROM signals WHERE status!='EN_COURS'", conn)
242
- stats = df.groupby('symbol')['status'].apply(lambda x: (x.str.contains('GAGNÉ').sum() / len(x)) * 100).reset_index()
243
- counts = df.groupby('symbol').size().reset_index(name='count')
244
- res = stats.merge(counts, on='symbol').sort_values(by='status', ascending=False).head(n).to_dict(orient='records')
245
- elif mode == "recent":
246
- cursor = conn.cursor(); cursor.execute("SELECT symbol, direction, prob, status FROM signals ORDER BY id DESC LIMIT 5")
247
- res = [dict(r) for r in cursor.fetchall()]
248
  else:
249
  cursor = conn.cursor(); cursor.execute("SELECT * FROM signals ORDER BY id DESC LIMIT 1")
250
  res = dict(cursor.fetchone()) if cursor.fetchone() else {}
251
- conn.close(); return json.dumps(res)
252
- except Exception as e: return json.dumps({"status": "error", "message": str(e)})
253
-
254
- def keep_alive_ping(): return json.dumps({"status": "awake", "time": datetime.now().isoformat()})
255
 
256
- # --- 📊 FONCTION DE DIAGNOSTIC DES DONNÉES ---
257
- def check_data_count(symbol):
258
- try:
259
- exchange = ccxt.kucoin()
260
- # On demande 1000 pour être large
261
- bars = exchange.fetch_ohlcv(symbol, timeframe='1h', limit=1000)
262
- count = len(bars)
263
- # Seuil de 250 pour être opérationnel
264
- percent = min(100, (count / 250) * 100)
265
- return json.dumps({"count": count, "percent": round(percent, 1), "needed": 250})
266
- except Exception as e:
267
- return json.dumps({"status": "error", "message": str(e)})
268
-
269
- # --- 🧠 MOTEUR D'ENTRAÎNEMENT (TRAINING ENGINE) ---
270
  def trigger_training(symbol="BTC/USDT"):
271
- try:
272
- # On importe les fonctions ici pour ne pas bloquer le démarrage global
273
- from ml_model import train_model as train_ml
274
- from time_model import train_time_model as train_time
275
-
276
- # 1. Récupération des données fraîches
277
- exchange = ccxt.kucoin()
278
- bars = exchange.fetch_ohlcv(symbol, timeframe='1h', limit=1000)
279
- df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
280
-
281
- if len(df) < 250:
282
- return f"❌ Données insuffisantes : {len(df)} bougies récupérées."
283
-
284
- # 2. Lancement de l'apprentissage
285
- train_ml(df)
286
- train_time(df)
287
-
288
- # 3. Rechargement des nouveaux cerveaux en RAM
289
- global ml_model, time_model
290
- import joblib
291
- ml_model = joblib.load("ml_model_v9.pkl")
292
- time_model = joblib.load("time_model.pkl")
293
-
294
- return f"✅ IA ré-entraînée avec succès sur {symbol} ({len(df)} bougies)."
295
- except Exception as e:
296
- return f"❌ Erreur lors de l'entraînement : {str(e)}"
297
 
298
- # --- 🎨 INTERFACE GRADIO (LE TABLEAU DE BORD) ---
299
- with gr.Blocks(theme=gr.themes.Monochrome(), title="Alpha V10 PRO") as iface:
300
  gr.Markdown("# 📡 Alpha V15 Master Engine")
301
 
302
- with gr.Tab("Admin"):
303
- train_btn = gr.Button("Forcer Entraînement")
304
- train_btn.click(fn=trigger_training, outputs=gr.Textbox(), api_name="trigger_training")
305
-
306
  with gr.Tab("🎯 Prédictions"):
307
- sym_input = gr.Textbox(label="Symbole")
308
- btn = gr.Button("Predict")
309
  out = gr.JSON()
310
  btn.click(fn=predict_signal, inputs=sym_input, outputs=out, api_name="predict_signal")
311
 
@@ -314,20 +289,8 @@ with gr.Blocks(theme=gr.themes.Monochrome(), title="Alpha V10 PRO") as iface:
314
  out_j = gr.Textbox()
315
  btn_j.click(fn=run_judge_api, outputs=out_j, api_name="run_judge_api")
316
 
317
- # LES BOUTONS CACHÉS SONT MAINTENANT BIEN ALIGNÉS ICI
318
- data_check_btn = gr.Button("Vérifier Données", visible=False)
319
- data_check_btn.click(fn=check_data_count, inputs=sym_input, outputs=out, api_name="check_data_status")
320
-
321
- btn_ping = gr.Button("Ping", visible=False)
322
- btn_ping.click(fn=keep_alive_ping, outputs=gr.JSON(), api_name="keep_alive_ping")
323
-
324
- with gr.Tab("📊 Stats"):
325
- m_in = gr.Textbox(label="Mode")
326
- s_in = gr.Textbox(label="Symbol")
327
- st_btn = gr.Button("Fetch")
328
- st_out = gr.JSON()
329
- st_btn.click(fn=get_db_stats, inputs=[m_in, s_in], outputs=st_out, api_name="get_db_stats")
330
 
331
- # --- LANCEMENT ---
332
  if __name__ == "__main__":
333
- iface.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, debug=False, show_api=True)
 
3
  import sqlite3
4
  import shutil
5
  import pandas as pd
 
6
  import joblib
7
  import json
8
  import numpy as np
9
  import warnings
10
+ import asyncio
11
+ import psutil
12
+ import gc
13
  from datetime import datetime, timezone
14
  from huggingface_hub import HfApi, hf_hub_download
15
  import gradio as gr
16
+
17
+ # --- OPTIMISATION TENSORFLOW & RAM ---
18
+ import tensorflow as tf
19
  from tensorflow.keras.models import load_model
20
+ from tensorflow.keras import backend as K
21
+
22
+ # On bride TensorFlow pour éviter qu'il ne sature le CPU du Space Free
23
+ tf.config.threading.set_intra_op_parallelism_threads(2)
24
+ tf.config.threading.set_inter_op_parallelism_threads(2)
25
+
26
+ import ccxt
27
+ import ccxt.async_support as ccxt_async
28
 
29
  # --- CONFIGURATION GPS ---
30
  current_dir = os.path.dirname(os.path.abspath(__file__))
31
  if current_dir not in sys.path:
32
  sys.path.append(current_dir)
33
 
34
+ # --- SINGLETON EXCHANGE ---
35
+ # On réutilise les mêmes connexions pour éviter de se faire bannir par KuCoin
36
+ exchange_sync = ccxt.kucoin({
37
+ "enableRateLimit": True,
38
+ "timeout": 30000
39
+ })
40
+
41
+ exchange_async = ccxt_async.kucoin({
42
+ "enableRateLimit": True,
43
+ "timeout": 30000
44
+ })
45
+
46
+ # --- CACHE MARCHÉ ---
47
+ market_cache = {}
48
+ last_fetch_time = {}
49
+ CACHE_DURATION = 300 # 5 minutes
50
+
51
  # --- 🛰️ IMPORT SÉCURISÉ DES SATELLITES ---
 
52
  try:
53
  from sentiment_engine import get_crypto_sentiment
54
  print("✅ Sentiment Engine : Connecté.")
 
61
  print("✅ Ensemble Engine : Connecté.")
62
  except Exception as e:
63
  print(f"⚠️ Erreur Ensemble Engine : {e}")
64
+ def combine_scores(s, t, m, l, sent, r):
65
+ return (t+m+l+sent)/4, 0.25, 0.25, 0.25, 0.25
66
 
67
  # --- CONFIG HUB & DB ---
68
  REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
 
76
  delta = series.diff()
77
  gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
78
  loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
79
+ rs = gain / (loss + 1e-9)
80
  return 100 - (100 / (1 + rs))
81
 
82
  def get_atr(df, period=14):
 
85
  low_close = (df['low'] - df['close'].shift()).abs()
86
  return pd.concat([high_low, high_close, low_close], axis=1).max(axis=1).rolling(period).mean()
87
 
88
+ # --- 🛡️ RAM GUARD ---
89
+ def ram_usage():
90
+ return psutil.virtual_memory().percent
91
+
92
+ def memory_guard():
93
+ if ram_usage() > 85:
94
+ print("⚠️ RAM haute → Nettoyage forcé en cours...")
95
+ K.clear_session()
96
+ gc.collect()
97
+
98
  # --- ☁️ SYNC CLOUD ---
99
  def sync_down():
100
  if not HF_TOKEN: return
 
103
  shutil.copy(path, DB_NAME); print("✅ DB Cloud récupérée.")
104
  except: print("ℹ️ Nouvelle DB créée.")
105
 
 
 
 
106
  def init_db():
107
  try:
108
  conn = sqlite3.connect(DB_NAME); cursor = conn.cursor()
 
113
  conn.commit(); conn.close()
114
  except Exception as e: print(f"❌ Erreur DB Init : {e}")
115
 
 
116
  sync_down()
117
  init_db()
118
  warnings.filterwarnings('ignore')
119
 
120
+ # --- 🧠 CHARGEMENT IA ---
121
  try:
122
  ml_model = joblib.load("ml_model_v9.pkl")
123
  time_model = joblib.load("time_model.pkl")
 
127
  print("✅ IA chargées en RAM.")
128
  except Exception as e: print(f"⚠️ Modèles IA manquants : {e}")
129
 
130
+ # --- 📊 PRÉPARATION FEATURES ASYNC ---
131
+ async def prepare_all_features(symbol):
132
  try:
133
+ now = datetime.now().timestamp()
134
+
135
+ # Gestion du Cache
136
+ if symbol in market_cache and now - last_fetch_time[symbol] < CACHE_DURATION:
137
+ df = market_cache[symbol].copy()
138
+ else:
139
+ bars = await exchange_async.fetch_ohlcv(symbol, timeframe='1h', limit=600)
140
+ df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
141
+ market_cache[symbol] = df.copy()
142
+ last_fetch_time[symbol] = now
143
+
144
  if len(df) < 250: return pd.DataFrame()
145
 
146
+ # Indicateurs techniques
147
  df["RSI"] = get_rsi(df["close"], 14)
148
  df["EMA50"] = get_ema(df["close"], 50)
149
  df["EMA200"] = get_ema(df["close"], 200)
 
171
  df["RSI_Macro"] = df["RSI"]
172
 
173
  return df.dropna().copy()
174
+ except Exception as e:
175
+ print(f"❌ Feature Error: {e}")
176
+ return pd.DataFrame()
177
 
178
+ # --- 🎯 API PRÉDICTION OPTIMISÉE ---
179
  async def predict_signal(symbol):
180
  try:
181
+ memory_guard()
182
  symbol = str(symbol).strip().upper()
183
  if "/USDT" not in symbol: symbol += "/USDT"
184
+
185
+ df = await prepare_all_features(symbol)
186
+ if df.empty: return {"status": "error", "message": "Data insuffisante"}
187
+
188
  last_row = df.iloc[[-1]]
189
 
190
+ # 1. Régime
191
  regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
192
  regime_pred = int(regime_model.predict(regime_scaled)[0])
193
 
194
+ # 2. ML & Time Probabilities
195
+ ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
196
+ ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1])
197
 
198
+ time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']
199
+ time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1])
200
+
201
+ # 3. LSTM
202
  lstm_seq = np.expand_dims(df[["close", "RSI", "EMA50", "EMA200", "ATR"]].iloc[-30:].values, axis=0)
203
  lstm_prob = float(lstm_brain.predict(lstm_seq, verbose=0)[0][0])
204
 
205
+ # 4. Sentiment & Ensemble
206
  p_sent = await get_crypto_sentiment(symbol)
207
  final_p, wt, wm, wl, ws = combine_scores(symbol, time_prob, ml_prob, lstm_prob, p_sent, regime_pred)
 
 
 
 
 
 
208
 
209
+ # 5. Gestion Risque
 
 
210
  prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
211
  tp = prix + (atr * 3) if final_p > 0.5 else prix - (atr * 3)
212
  sl = prix - (atr * 1.5) if final_p > 0.5 else prix + (atr * 1.5)
 
 
213
 
214
+ # 6. Sauvegarde DB
215
+ with sqlite3.connect(DB_NAME) as conn:
216
+ cursor = conn.cursor()
217
+ cursor.execute('''INSERT INTO signals
218
+ (date, symbol, direction, prob, price, tp, sl, status, regime, prob_time, prob_ml, prob_lstm, prob_sent)
219
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''',
220
+ (datetime.now().isoformat(), symbol, 'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl, 'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent))
221
+ conn.commit()
222
+
223
+ return {
224
+ "symbol": symbol, "final_score": round(final_p, 4), "price": prix,
225
+ "tp": round(tp, 6), "sl": round(sl, 6), "regime": regime_pred,
226
+ "status": "success", "volatility": round(float(last_row['ATR_pct'].iloc[0]), 2),
227
+ "confluence": round((1 - np.std([time_prob, ml_prob, lstm_prob, p_sent])) * 100, 1)
228
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
+ except Exception as e:
231
+ return {"status": "error", "message": str(e)}
232
+
233
+ # --- ⚖️ LE JUGE OPTIMISÉ ---
234
  async def run_judge_api():
235
  try:
236
  conn = sqlite3.connect(DB_NAME); conn.row_factory = sqlite3.Row; cursor = conn.cursor()
237
  cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
238
  trades = cursor.fetchall()
239
  if not trades: return "⚖️ Aucun trade actif."
240
+
241
+ updates = 0
242
  for t in trades:
243
+ curr = exchange_sync.fetch_ticker(t['symbol'])['last']
244
  new_s = 'EN_COURS'
245
  if 'HAUSSIER' in t['direction']:
246
  if curr >= t['tp']: new_s = 'GAGNÉ ✅'
 
248
  else:
249
  if curr <= t['tp']: new_s = 'GAGNÉ ✅'
250
  elif curr >= t['sl']: new_s = 'PERDU ❌'
251
+
252
  if new_s != 'EN_COURS':
253
  cursor.execute("UPDATE signals SET status = ? WHERE id = ?", (new_s, t['id']))
254
  updates += 1
255
+ conn.commit(); conn.close()
256
  return f"✅ {updates} trade(s) mis à jour."
257
  except Exception as e: return f"❌ Erreur Juge : {e}"
258
 
259
+ # --- 📊 STATS & TOOLS ---
260
+ def get_db_stats(mode, symbol=None):
261
  try:
 
262
  conn = sqlite3.connect(DB_NAME); conn.row_factory = sqlite3.Row
263
  if mode == "global":
264
  df = pd.read_sql_query("SELECT status, prob FROM signals WHERE status!='EN_COURS'", conn)
265
+ if len(df) < 1: return {"status": "empty"}
266
+ wr = (len(df[df['status'].str.contains("GAGNÉ")]) / len(df)) * 100
267
+ res = {"total": len(df), "winrate": round(wr, 1)}
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  else:
269
  cursor = conn.cursor(); cursor.execute("SELECT * FROM signals ORDER BY id DESC LIMIT 1")
270
  res = dict(cursor.fetchone()) if cursor.fetchone() else {}
271
+ conn.close(); return res
272
+ except Exception as e: return {"error": str(e)}
 
 
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  def trigger_training(symbol="BTC/USDT"):
275
+ return "🛠️ Entraînement désactivé en mode direct pour préserver la RAM."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ # --- 🎨 INTERFACE GRADIO ---
278
+ with gr.Blocks(theme=gr.themes.Monochrome(), title="Alpha V15 MASTER") as iface:
279
  gr.Markdown("# 📡 Alpha V15 Master Engine")
280
 
 
 
 
 
281
  with gr.Tab("🎯 Prédictions"):
282
+ sym_input = gr.Textbox(label="Symbole (ex: BTC/USDT)")
283
+ btn = gr.Button("Predict", variant="primary")
284
  out = gr.JSON()
285
  btn.click(fn=predict_signal, inputs=sym_input, outputs=out, api_name="predict_signal")
286
 
 
289
  out_j = gr.Textbox()
290
  btn_j.click(fn=run_judge_api, outputs=out_j, api_name="run_judge_api")
291
 
292
+ # API Hidden endpoints pour le Radar
293
+ gr.Button("Ping", visible=False).click(fn=lambda: {"status": "awake"}, outputs=gr.JSON(), api_name="keep_alive_ping")
 
 
 
 
 
 
 
 
 
 
 
294
 
 
295
  if __name__ == "__main__":
296
+ iface.launch(server_name="0.0.0.0", server_port=7860, auth=("admin", os.environ.get("ADMIN_PWD", "Alpha2026!")), debug=False, show_api=True)