Nexo-S commited on
Commit
63f3afd
·
verified ·
1 Parent(s): 54ff8fa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -303
app.py CHANGED
@@ -1,11 +1,18 @@
1
- import sys, os, sqlite3, shutil, pandas as pd, asyncio, joblib, json, numpy as np, gc, warnings, psutil
 
 
 
 
2
  import ccxt.async_support as ccxt_async
 
3
  from datetime import datetime, timezone
4
- from huggingface_hub import HfApi, hf_hub_download
5
  import gradio as gr
6
- import ccxt
7
- # --- 🛑 ANTI-CRASH & CPU OPTIMIZATION ---
8
- os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
 
 
 
9
  import tensorflow as tf
10
  from tensorflow.keras.models import load_model
11
  from tensorflow.keras import backend as K
@@ -13,69 +20,56 @@ from tensorflow.keras import backend as K
13
  tf.config.threading.set_intra_op_parallelism_threads(2)
14
  tf.config.threading.set_inter_op_parallelism_threads(2)
15
 
16
- # --- CONFIGURATION GPS ---
17
- current_dir = os.path.dirname(os.path.abspath(__file__))
18
- if current_dir not in sys.path:
19
- sys.path.append(current_dir)
 
 
 
 
 
20
 
21
- # --- SINGLETON EXCHANGE ---
 
 
22
  class ExchangeManager:
23
  _instance = None
24
  @classmethod
25
  def get_instance(cls):
26
  if cls._instance is None:
27
- cls._instance = ccxt_async.kucoin({"enableRateLimit": True, "timeout": 30000})
 
 
 
28
  return cls._instance
29
 
30
- exchange_sync = ccxt.kucoin({"enableRateLimit": True, "timeout": 30000})
 
 
 
31
 
32
- # --- CACHE ---
 
 
33
  market_cache = {}
34
  last_fetch_time = {}
35
- CACHE_DURATION = 300
36
-
37
- # --- 🛰️ IMPORT SÉCURISÉ DES SATELLITES ---
38
- try:
39
- # Correction du nom de la fonction importée
40
- from sentiment_engine import get_crypto_sentiment
41
- print("✅ Sentiment Engine : Connecté.")
42
- except Exception as e:
43
- print(f"⚠️ Erreur Liaison Sentiment : {e}")
44
- # Fallback si le fichier ou la lib vaderSentiment manque
45
- async def get_crypto_sentiment(symbol): return 0.5
46
- try:
47
- from ensemble import combine_scores
48
- except:
49
- def combine_scores(s, t, m, l, sent, r): return (t+m+l+sent)/4, 0.25, 0.25, 0.25, 0.25
50
-
51
- # --- DB & SYNC ---
52
- REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
53
- DB_NAME = "alphatrade_v9.db"
54
- HF_TOKEN = os.environ.get("HF_TOKEN")
55
-
56
- def init_db():
57
- with sqlite3.connect(DB_NAME) as conn:
58
- conn.execute('''CREATE TABLE IF NOT EXISTS signals (
59
- id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, symbol TEXT, direction TEXT,
60
- prob REAL, price REAL, tp REAL, sl REAL, status TEXT, regime INTEGER,
61
- prob_time REAL, prob_ml REAL, prob_lstm REAL, prob_sent REAL)''')
62
-
63
- async def save_to_db(data):
64
- try:
65
- with sqlite3.connect(DB_NAME) as conn:
66
- conn.execute('INSERT INTO signals (date, symbol, direction, prob, price, tp, sl, status, regime, prob_time, prob_ml, prob_lstm, prob_sent) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)', data)
67
- except Exception as e: print(f"❌ DB Error: {e}")
68
-
69
- init_db()
70
 
71
- # --- 🛡️ RAM GUARD ---
 
 
72
  def memory_guard():
73
  if psutil.virtual_memory().percent > 80:
74
  K.clear_session()
75
  gc.collect()
76
 
77
- # --- 🛠️ MOTEUR MATHS ---
78
- def get_ema(series, period): return series.ewm(span=period, adjust=False).mean()
 
 
 
 
79
  def get_rsi(series, period=14):
80
  delta = series.diff()
81
  gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
@@ -86,329 +80,249 @@ def get_atr(df, period=14):
86
  h_l = df['high'] - df['low']
87
  h_c = (df['high'] - df['close'].shift()).abs()
88
  l_c = (df['low'] - df['close'].shift()).abs()
89
- return pd.concat([h_l, h_c, l_c], axis=1).max(axis=1).rolling(period).mean()
 
90
 
91
- # --- 🧠 CHARGEMENT IA ---
 
 
92
  try:
93
  ml_model = joblib.load("ml_model_v9.pkl")
94
  time_model = joblib.load("time_model.pkl")
95
  regime_model = joblib.load("regime_model.pkl")
96
  regime_scaler = joblib.load("regime_scaler.pkl")
97
  lstm_brain = load_model("lstm_model.keras")
98
- print("✅ Cerveaux opérationnels.")
99
- except Exception as e: print(f"⚠️ Erreur IA : {e}")
 
100
 
101
- # --- 📊 FEATURES ENGINE ---
 
 
102
  async def prepare_all_features(symbol, timeframe='1h'):
103
  try:
104
  ex = ExchangeManager.get_instance()
105
- now = datetime.now().timestamp()
106
-
107
- # 1. CLÉ DE CACHE UNIQUE (Symbol + Timeframe)
108
- # Indispensable pour ne pas mélanger les bougies 5m avec le 1h
109
  cache_key = f"{symbol}_{timeframe}"
110
 
111
- if cache_key in market_cache and now - last_fetch_time[cache_key] < CACHE_DURATION:
112
  df = market_cache[cache_key].copy()
113
- # print(f"🚀 [CACHE] Data récupérée pour {cache_key}")
114
  else:
115
- # 2. RÉCUPÉRATION DYNAMIQUE
116
- # On utilise le paramètre 'timeframe' envoyé par le prédicteur
117
  bars = await ex.fetch_ohlcv(symbol, timeframe=timeframe, limit=600)
118
- df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
119
-
120
- # Stockage en cache avec la clé spécifique
121
  market_cache[cache_key] = df.copy()
122
  last_fetch_time[cache_key] = now
123
- # print(f"📡 [NETWORK] Data fraîche pour {cache_key}")
124
 
125
- if len(df) < 250:
126
- return pd.DataFrame()
127
 
128
- # --- CALCUL DES INDICATEURS (VECTORISÉS) ---
129
  df["RSI"] = get_rsi(df["close"])
130
- df["EMA50"] = get_ema(df["close"], 50)
131
- df["EMA200"] = get_ema(df["close"], 200)
132
  df["ATR"] = get_atr(df)
133
- df["ATR_pct"] = (df["ATR"] / df["close"]) * 100
134
- df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
135
- df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
136
-
137
- # Features pour les modèles ML
138
  df["High_24h"] = df["high"].rolling(24).max()
139
  df["Low_24h"] = df["low"].rolling(24).min()
140
- df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
141
- df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
142
- df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
143
- df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1
144
- df["ATR_ratio"] = df["ATR"] / df["close"]
145
- df["VOL_ratio"] = df["vol"] / df["vol"].rolling(24).mean()
146
-
147
- # --- RENDEMENTS (BARS ADAPTATIVES) ---
148
- # Note : pct_change(1) devient le rendement de la bougie actuelle
149
- # (5m, 1h, ou 1d selon ton choix)
150
- df['return_1h'] = df['close'].pct_change(1) # 1 bougie
151
- df['return_3h'] = df['close'].pct_change(3) # 3 bougies
152
- df['return_12h'] = df['close'].pct_change(12) # 12 bougies
153
-
154
- df['RSI_lag1'] = df["RSI"].shift(1)
155
- df['RSI_lag2'] = df["RSI"].shift(2)
156
- df['VOL_RATIO'] = df['vol'] / df['vol'].rolling(20).mean()
157
- df['vol_lag1'] = df['vol'].shift(1)
158
- df["RSI_Macro"] = df["RSI"]
159
-
160
- return df.dropna().copy()
161
-
162
  except Exception as e:
163
- print(f"❌ prepare_all_features Error ({symbol} - {timeframe}): {e}")
164
  return pd.DataFrame()
165
- # --- 🎯 API PRÉDICTION ---
166
- async def predict_signal(symbol, timeframe="1h"):
 
 
 
167
  try:
168
  memory_guard()
169
- symbol = str(symbol).strip().upper()
170
  if "/USDT" not in symbol: symbol += "/USDT"
171
-
172
- # 1. Acquisition des données selon la Timeframe choisie
173
  df = await prepare_all_features(symbol, timeframe)
174
- if df.empty: return {"status": "error", "message": f"Data insuffisante pour {timeframe}"}
175
-
176
- last_row = df.iloc[[-1]]
177
-
178
- # 2. Inférence des modèles individuels
179
- # --- Modèle de Régime ---
180
- regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
181
- regime_pred = int(regime_model.predict(regime_scaled)[0])
182
-
183
- # --- Modèles Arbres (ML) ---
184
- ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
185
- ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1])
186
-
187
- # --- Modèle Temporel (XGB) ---
188
- time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']
189
- time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1])
190
-
191
- # --- Modèle Deep Learning (LSTM) ---
192
- lstm_seq = np.expand_dims(df[["close", "RSI", "EMA50", "EMA200", "ATR"]].iloc[-30:].values, axis=0)
193
- lstm_prob = float(lstm_brain.predict(lstm_seq, verbose=0)[0][0])
194
-
195
- # --- Sentiment ---
 
 
 
 
 
 
 
 
 
 
196
  p_sent = await get_crypto_sentiment(symbol)
197
 
198
- # 3. PONDÉRATION HYBRIDE DYNAMIQUE (V22.6)
199
- # On définit les poids selon l'horizon de temps pour maximiser l'edge
200
  if timeframe in ["5m", "15m"]:
201
- # Mode Scalping : Priorité aux Arbres (Réaction rapide)
202
  wm, wt, wl, ws = 0.45, 0.35, 0.15, 0.05
203
- elif timeframe in ["4h", "1d"]:
204
- # Mode Swing : Priorité au LSTM (Mémoire des cycles)
205
  wl, wm, wt, ws = 0.60, 0.15, 0.15, 0.10
206
  else:
207
- # Mode Standard (1h) : Équilibre parfait
208
  wt, wm, wl, ws = 0.25, 0.25, 0.25, 0.25
209
 
210
  final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
211
 
212
- # 4. CALCUL DU SCORE & RISK (INSTITUTIONAL GRADE)
213
- # Force du signal (0.0 à 1.0)
214
  strength = abs(final_p - 0.5) * 2
215
- # Confluence (0.0 à 1.0) - Accord entre les 4 modèles
216
- conf_val = (1 - np.std([time_prob, ml_prob, lstm_prob, p_sent]))
217
 
218
- # Score Composite (0-100)
219
- score_base = (strength * 45) + (conf_val * 40)
220
- regime_bonus = 15 if regime_pred in [0, 1] else 5
221
  composite_score = max(0, min(100, score_base + regime_bonus))
222
 
223
- # Risk Sizing Dynamique (0.2% à 2.5%)
224
  risk_pct = max(0.2, min(2.5, strength * 5.0))
225
 
226
- # 5. SORTIES (TP/SL ADAPTATIFS)
227
- prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
228
- # Multiplicateur plus large pour le Swing
229
- mult = 4 if timeframe in ["4h", "1d"] else 3
230
- tp = prix + (atr * mult) if final_p > 0.5 else prix - (atr * mult)
231
- sl = prix - (atr * 1.5) if final_p > 0.5 else prix + (atr * 1.5)
 
 
 
 
232
 
233
- # 6. SAUVEGARDE DB (Pour le Juge et le PnL)
234
- db_task = (datetime.now(timezone.utc).isoformat(), symbol, 'HAUSSIER' if final_p > 0.5 else 'BAISSIER',
235
- final_p, prix, tp, sl, 'EN_COURS', regime_pred, time_prob, ml_prob, lstm_prob, p_sent)
236
- asyncio.create_task(save_to_db(db_task))
237
 
238
- # 7. RÉPONSE JSON COMPLÈTE
239
  return {
240
- "symbol": symbol,
241
  "timeframe": timeframe,
242
  "status": "success",
243
- "final_score": round(final_p, 4),
244
  "score": int(composite_score),
245
  "risk_percent": round(risk_pct, 2),
246
- "price": prix,
247
- "tp": round(tp, 6),
 
 
248
  "sl": round(sl, 6),
249
- "regime": regime_pred,
250
- "confluence": round(conf_val * 100, 1),
251
- "probs": {
252
- "xgb": round(time_prob, 3),
253
- "rf": round(ml_prob, 3),
254
- "lstm": round(lstm_prob, 3),
255
- "sent": round(p_sent, 3)
256
- },
257
- "weights": {
258
- "w_xgb": round(wt, 2),
259
- "w_rf": round(wm, 2),
260
- "w_lstm": round(wl, 2),
261
- "w_sent": round(ws, 2)
262
- }
263
  }
264
- except Exception as e:
265
- return {"status": "error", "message": str(e)}
266
- # --- 🧠 TRAINING ENGINE (RÉACTIVÉ) ---
267
- def trigger_training(symbol="BTC/USDT"):
268
- try:
269
- print(f"⚙️ Début de l'entraînement hebdomadaire pour {symbol}...")
270
- memory_guard() # On vide la RAM avant de commencer
271
-
272
- from ml_model import train_model as train_ml
273
- from time_model import train_time_model as train_time
274
-
275
- bars = exchange_sync.fetch_ohlcv(symbol, timeframe='1h', limit=1000)
276
- df_train = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
277
-
278
- if len(df_train) < 300: return "❌ Erreur : Pas assez de bougies."
279
-
280
- train_ml(df_train)
281
- train_time(df_train)
282
-
283
- # Rechargement à chaud
284
- global ml_model, time_model
285
- ml_model = joblib.load("ml_model_v9.pkl")
286
- time_model = joblib.load("time_model.pkl")
287
-
288
- gc.collect()
289
- return f"✅ IA ré-entraînée avec succès ({len(df_train)} bougies)."
290
- except Exception as e: return f"❌ Erreur Training : {str(e)}"
291
 
292
- # --- ⚖️ TOOLS (FONCTIONS MANQUANTES) ---
293
- def keep_alive_ping():
294
- """Répond au bot Discord pour confirmer que le cerveau est réveillé"""
295
- return {"status": "awake", "time": datetime.now(timezone.utc).isoformat()}
296
-
297
- async def check_data_count(symbol):
298
- """Vérifie la disponibilité des données OHLCV"""
299
- try:
300
- ex = ExchangeManager.get_instance()
301
- bars = await ex.fetch_ohlcv(symbol, timeframe='1h', limit=1000)
302
- count = len(bars)
303
- needed = 250
304
- percent = min(100, (count / needed) * 100)
305
- return {"count": count, "percent": round(percent, 1), "needed": needed}
306
  except Exception as e:
307
  return {"status": "error", "message": str(e)}
308
 
 
 
 
309
  async def run_judge_api():
310
  try:
311
- conn = sqlite3.connect(DB_NAME)
312
- conn.row_factory = sqlite3.Row
313
- cursor = conn.cursor()
314
-
315
- cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
316
- active_trades = cursor.fetchall()
317
-
318
- if not active_trades:
319
- conn.close()
320
- return "⚖️ [JUGE] Aucun trade actif."
321
-
322
  updates = 0
323
  trailing_updates = 0
324
- ex = exchange_sync
325
 
326
- for trade in active_trades:
327
- symbol = trade['symbol']
 
 
328
  ticker = ex.fetch_ticker(symbol)
329
- current_price = ticker['last']
330
 
331
- trade_id = trade['id']
332
- direction = trade['direction']
333
- tp = trade['tp']
334
- sl = trade['sl']
335
- entry_price = trade['price']
336
-
337
- # --- 1. CALCUL DU TRAILING STOP ---
338
- # On utilise un écart de sécurité (Buffer). Ici, on peut ré-estimer l'ATR
339
- # ou utiliser un pourcentage (ex: 1.5%).
340
- # Utilisons 1.5% du prix actuel comme distance de suivi.
341
- trail_buffer = current_price * 0.015
342
-
343
- new_status = 'EN_COURS'
344
-
345
- if direction == 'HAUSSIER':
346
- # Vérification TP/SL classique
347
- if current_price >= tp: new_status = 'GAGNÉ ✅'
348
- elif current_price <= sl: new_status = 'PERDU ❌'
349
-
350
- # Logique Trailing : Si le prix monte, on remonte le SL
351
- # Nouveau SL potentiel = Prix Actuel - Buffer
352
- potential_sl = current_price - trail_buffer
353
- if current_price > entry_price and potential_sl > sl:
354
- cursor.execute("UPDATE signals SET sl = ? WHERE id = ?", (potential_sl, trade_id))
355
- trailing_updates += 1
356
-
357
- elif direction == 'BAISSIER':
358
- # Vérification TP/SL classique
359
- if current_price <= tp: new_status = 'GAGNÉ ✅'
360
- elif current_price >= sl: new_status = 'PERDU ❌'
361
-
362
- # Logique Trailing : Si le prix baisse, on descend le SL
363
- potential_sl = current_price + trail_buffer
364
- if current_price < entry_price and potential_sl < sl:
365
- cursor.execute("UPDATE signals SET sl = ? WHERE id = ?", (potential_sl, trade_id))
366
- trailing_updates += 1
367
-
368
- # --- 2. MISE À JOUR DU STATUT FINAL ---
369
- if new_status != 'EN_COURS':
370
- cursor.execute("UPDATE signals SET status = ? WHERE id = ?", (new_status, trade_id))
371
- updates += 1
372
 
373
  conn.commit()
374
  conn.close()
375
-
376
- return f"✅ Juge : {updates} terminés | {trailing_updates} Stop-Loss ajustés (Trailing)."
377
 
378
  except Exception as e:
379
- if 'conn' in locals(): conn.close()
380
- return f"❌ Erreur Juge : {str(e)}"
381
- # --- 🎨 INTERFACE GRADIO ---
382
- with gr.Blocks(theme=gr.themes.Monochrome()) as iface:
383
- gr.Markdown("# 📡 Alpha V21.2 Master Engine")
384
- with gr.Tab("Admin"):
385
- train_btn = gr.Button("Forcer Entraînement Hebdomadaire", variant="stop")
386
- train_out = gr.Textbox(label="Résultat")
387
- train_btn.click(fn=trigger_training, outputs=train_out, api_name="trigger_training")
388
- with gr.Tab("🎯 Prédictions"):
389
- sym_input = gr.Textbox(label="Symbole")
390
- tf_input = gr.Dropdown(choices=["5m", "15m", "1h", "4h", "1d"], value="1h", label="Timeframe")
391
- btn_pred = gr.Button("Predict", variant="primary")
392
- out_json = gr.JSON()
393
- # On ajoute tf_input dans les entrées du clic
394
- btn_pred.click(fn=predict_signal, inputs=[sym_input, tf_input], outputs=out_json)
395
- with gr.Tab("⚖️ Système"):
396
- btn_j = gr.Button("Judge")
397
- out_j = gr.Textbox()
398
- btn_j.click(fn=run_judge_api, outputs=out_j, api_name="run_judge_api")
399
-
400
- # FIX : On utilise 'out_json' au lieu de 'out'
401
- # Et on utilise 'sym_input' qui est le nom défini dans ton onglet Prédictions
402
- data_check_btn = gr.Button("Vérifier Données", visible=False)
403
- data_check_btn.click(
404
- fn=check_data_count,
405
- inputs=sym_input,
406
- outputs=out_json,
407
- api_name="check_data_status"
408
- )
409
-
410
- btn_ping = gr.Button("Ping", visible=False)
411
- btn_ping.click(fn=keep_alive_ping, outputs=gr.JSON(), api_name="keep_alive_ping")
412
 
413
  if __name__ == "__main__":
414
  iface.launch(show_api=True)
 
1
+ # ================================
2
+ # 🚀 ALPHA ENGINE V22.1 (ULTIMATE)
3
+ # ================================
4
+
5
+ import sys, os, sqlite3, shutil, pandas as pd, asyncio, joblib, json, numpy as np, gc, warnings, psutil, time
6
  import ccxt.async_support as ccxt_async
7
+ import ccxt
8
  from datetime import datetime, timezone
 
9
  import gradio as gr
10
+
11
+ # --- CPU / STABILITY ---
12
+ warnings.filterwarnings("ignore")
13
+ pd.options.mode.chained_assignment = None
14
+ os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
15
+
16
  import tensorflow as tf
17
  from tensorflow.keras.models import load_model
18
  from tensorflow.keras import backend as K
 
20
  tf.config.threading.set_intra_op_parallelism_threads(2)
21
  tf.config.threading.set_inter_op_parallelism_threads(2)
22
 
23
+ # ================================
24
+ # 🛰️ SENTIMENT SATELLITE
25
+ # ================================
26
+ try:
27
+ from sentiment_engine import get_crypto_sentiment
28
+ print("✅ Sentiment Engine : Connecté.")
29
+ except Exception as e:
30
+ print(f"⚠️ Erreur Sentiment : {e}")
31
+ async def get_crypto_sentiment(symbol): return 0.5
32
 
33
+ # ================================
34
+ # 📡 EXCHANGE SINGLETON
35
+ # ================================
36
  class ExchangeManager:
37
  _instance = None
38
  @classmethod
39
  def get_instance(cls):
40
  if cls._instance is None:
41
+ cls._instance = ccxt_async.kucoin({
42
+ "enableRateLimit": True,
43
+ "timeout": 30000
44
+ })
45
  return cls._instance
46
 
47
+ exchange_sync = ccxt.kucoin({
48
+ "enableRateLimit": True,
49
+ "timeout": 30000
50
+ })
51
 
52
+ # ================================
53
+ # 📦 CACHE SYSTEM
54
+ # ================================
55
  market_cache = {}
56
  last_fetch_time = {}
57
+ CACHE_DURATION = 300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ # ================================
60
+ # 🛡️ MEMORY GUARD
61
+ # ================================
62
  def memory_guard():
63
  if psutil.virtual_memory().percent > 80:
64
  K.clear_session()
65
  gc.collect()
66
 
67
+ # ================================
68
+ # 📊 INDICATORS
69
+ # ================================
70
+ def get_ema(series, period):
71
+ return series.ewm(span=period, adjust=False).mean()
72
+
73
  def get_rsi(series, period=14):
74
  delta = series.diff()
75
  gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
 
80
  h_l = df['high'] - df['low']
81
  h_c = (df['high'] - df['close'].shift()).abs()
82
  l_c = (df['low'] - df['close'].shift()).abs()
83
+ atr = pd.concat([h_l, h_c, l_c], axis=1).max(axis=1).rolling(period).mean()
84
+ return atr
85
 
86
+ # ================================
87
+ # 🧠 LOAD MODELS
88
+ # ================================
89
  try:
90
  ml_model = joblib.load("ml_model_v9.pkl")
91
  time_model = joblib.load("time_model.pkl")
92
  regime_model = joblib.load("regime_model.pkl")
93
  regime_scaler = joblib.load("regime_scaler.pkl")
94
  lstm_brain = load_model("lstm_model.keras")
95
+ print("✅ IA chargées")
96
+ except Exception as e:
97
+ print("⚠️ erreur chargement IA", e)
98
 
99
+ # ================================
100
+ # 📊 FEATURE ENGINE
101
+ # ================================
102
  async def prepare_all_features(symbol, timeframe='1h'):
103
  try:
104
  ex = ExchangeManager.get_instance()
105
+ now = time.time()
 
 
 
106
  cache_key = f"{symbol}_{timeframe}"
107
 
108
+ if cache_key in market_cache and cache_key in last_fetch_time and now - last_fetch_time[cache_key] < CACHE_DURATION:
109
  df = market_cache[cache_key].copy()
 
110
  else:
 
 
111
  bars = await ex.fetch_ohlcv(symbol, timeframe=timeframe, limit=600)
112
+ df = pd.DataFrame(bars, columns=['ts','open','high','low','close','vol'])
 
 
113
  market_cache[cache_key] = df.copy()
114
  last_fetch_time[cache_key] = now
 
115
 
116
+ if len(df) < 250: return pd.DataFrame()
 
117
 
 
118
  df["RSI"] = get_rsi(df["close"])
119
+ df["EMA50"] = get_ema(df["close"],50)
120
+ df["EMA200"] = get_ema(df["close"],200)
121
  df["ATR"] = get_atr(df)
122
+ df["ATR_pct"] = (df["ATR"]/df["close"])*100
123
+ df["EMA200_slope"] = (df["EMA200"]/df["EMA200"].shift(10))-1
124
+ df["Drawdown"] = (df["close"]/df["close"].rolling(14).max())-1
 
 
125
  df["High_24h"] = df["high"].rolling(24).max()
126
  df["Low_24h"] = df["low"].rolling(24).min()
127
+ df["Dist_High_24h"] = (df["High_24h"]-df["close"])/df["close"]
128
+ df["Dist_Low_24h"] = (df["close"]-df["Low_24h"])/df["close"]
129
+ df["EMA_dist"] = (df["close"]-df["EMA50"])/df["EMA50"]
130
+ df["EMA_slope"] = (df["EMA50"]/df["EMA50"].shift(5))-1
131
+ df["ATR_ratio"] = df["ATR"]/df["close"]
132
+ df["VOL_ratio"] = df["vol"]/df["vol"].rolling(24).mean()
133
+ df["return_1h"]=df["close"].pct_change(1)
134
+ df["return_3h"]=df["close"].pct_change(3)
135
+ df["return_12h"]=df["close"].pct_change(12)
136
+ df["RSI_lag1"]=df["RSI"].shift(1)
137
+ df["RSI_lag2"]=df["RSI"].shift(2)
138
+ df["vol_lag1"]=df["vol"].shift(1)
139
+ df["VOL_RATIO"]=df["vol"]/df["vol"].rolling(20).mean()
140
+ df["RSI_Macro"]=df["RSI"]
141
+
142
+ return df.dropna()
 
 
 
 
 
 
143
  except Exception as e:
144
+ print("❌ feature error",e)
145
  return pd.DataFrame()
146
+
147
+ # ================================
148
+ # 🎯 PREDICTION ENGINE (SMART MONEY)
149
+ # ================================
150
+ async def predict_signal(symbol, timeframe="1h"):
151
  try:
152
  memory_guard()
153
+ symbol = symbol.upper()
154
  if "/USDT" not in symbol: symbol += "/USDT"
155
+
 
156
  df = await prepare_all_features(symbol, timeframe)
157
+ if df.empty: return {"status":"error", "message": "No data"}
158
+
159
+ last = df.iloc[-1:]
160
+ price = float(last["close"].iloc[0])
161
+ atr = float(last["ATR"].iloc[0])
162
+ if np.isnan(atr): atr = price * 0.01
163
+
164
+ # --- FILTRES DE MARCHÉ ---
165
+ if float(last["ATR_pct"].iloc[0]) < 0.3:
166
+ return {"status":"skip", "reason":"low_volatility"}
167
+
168
+ pump = abs(df["close"].pct_change().iloc[-1])
169
+ if pump > 0.05:
170
+ return {"status":"skip", "reason":"pump_detected"}
171
+
172
+ # --- INFERENCE DES MODÈLES ---
173
+ regime_scaled = regime_scaler.transform(last[["ATR_pct","EMA200_slope","Drawdown","RSI_Macro"]])
174
+ regime = int(regime_model.predict(regime_scaled)[0])
175
+
176
+ ml_cols = ["RSI","Dist_High_24h","Dist_Low_24h","EMA_dist","EMA_slope","ATR_ratio","VOL_ratio"]
177
+ ml_prob = float(ml_model.predict_proba(last[ml_cols])[0][1])
178
+
179
+ time_cols = ['return_1h','return_3h','return_12h','RSI_lag1','RSI_lag2','vol_lag1','VOL_RATIO']
180
+ time_prob = float(time_model.predict_proba(last[time_cols])[0][1])
181
+
182
+ seq = df[["close","RSI","EMA50","EMA200","ATR"]].iloc[-30:]
183
+ seq = (seq - seq.mean()) / (seq.std() + 1e-9)
184
+ seq = np.expand_dims(seq.values, axis=0)
185
+ lstm_prob = float(lstm_brain.predict(seq, verbose=0)[0][0])
186
+ del seq
187
+ gc.collect()
188
+
189
  p_sent = await get_crypto_sentiment(symbol)
190
 
191
+ # --- PONDÉRATION HYBRIDE ---
 
192
  if timeframe in ["5m", "15m"]:
 
193
  wm, wt, wl, ws = 0.45, 0.35, 0.15, 0.05
194
+ elif timeframe in ["4h", "1d", "1D"]:
 
195
  wl, wm, wt, ws = 0.60, 0.15, 0.15, 0.10
196
  else:
 
197
  wt, wm, wl, ws = 0.25, 0.25, 0.25, 0.25
198
 
199
  final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
200
 
201
+ # --- CALCUL DU SCORE & RISQUE ---
 
202
  strength = abs(final_p - 0.5) * 2
203
+ conf = max(0, min(1, 1 - np.std([ml_prob, time_prob, lstm_prob, p_sent])))
 
204
 
205
+ score_base = (strength * 45) + (conf * 40)
206
+ regime_bonus = 15 if regime in [0, 1] else 5
 
207
  composite_score = max(0, min(100, score_base + regime_bonus))
208
 
 
209
  risk_pct = max(0.2, min(2.5, strength * 5.0))
210
 
211
+ # --- TP/SL DYNAMIQUES & GAIN USD ---
212
+ if timeframe == "5m": tp_m, sl_m = 1.5, 1.0
213
+ elif timeframe == "15m": tp_m, sl_m = 2.0, 1.2
214
+ elif timeframe == "1h": tp_m, sl_m = 3.0, 1.5
215
+ elif timeframe == "4h": tp_m, sl_m = 4.5, 2.0
216
+ elif timeframe in ["1d", "1D"]: tp_m, sl_m = 6.0, 2.5
217
+ else: tp_m, sl_m = 3.0, 1.5
218
+
219
+ tp = price + (atr * tp_m) if final_p > 0.5 else price - (atr * tp_m)
220
+ sl = price - (atr * sl_m) if final_p > 0.5 else price + (atr * sl_m)
221
 
222
+ CAPITAL_VIRTUEL = 10000
223
+ risk_usd = CAPITAL_VIRTUEL * (risk_pct / 100)
224
+ gain_estime_usd = risk_usd * (tp_m / sl_m)
 
225
 
 
226
  return {
227
+ "symbol": symbol,
228
  "timeframe": timeframe,
229
  "status": "success",
230
+ "final_score": round(final_p, 4),
231
  "score": int(composite_score),
232
  "risk_percent": round(risk_pct, 2),
233
+ "estimated_profit": round(gain_estime_usd, 2),
234
+ "price": price,
235
+ "volatility": round(float(last["ATR_pct"].iloc[0]), 2),
236
+ "tp": round(tp, 6),
237
  "sl": round(sl, 6),
238
+ "regime": regime,
239
+ "confluence": round(conf * 100, 1),
240
+ "probs": {"xgb": round(time_prob, 3), "rf": round(ml_prob, 3), "lstm": round(lstm_prob, 3), "sent": round(p_sent, 3)},
241
+ "weights": {"w_xgb": round(wt, 2), "w_rf": round(wm, 2), "w_lstm": round(wl, 2), "w_sent": round(ws, 2)}
 
 
 
 
 
 
 
 
 
 
242
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  except Exception as e:
245
  return {"status": "error", "message": str(e)}
246
 
247
+ # ================================
248
+ # ⚖️ LE JUGE (AVEC TRAILING STOP)
249
+ # ================================
250
  async def run_judge_api():
251
  try:
252
+ conn = sqlite3.connect("alphatrade_v9.db")
253
+ cur = conn.cursor()
254
+ # On ajoute le prix d'entrée à la requête pour calculer le Trailing
255
+ cur.execute("SELECT id, symbol, tp, sl, direction, price FROM signals WHERE status='EN_COURS'")
256
+ rows = cur.fetchall()
257
+ ex = exchange_sync
 
 
 
 
 
258
  updates = 0
259
  trailing_updates = 0
 
260
 
261
+ for r in rows:
262
+ # Assure-toi que ta table a bien la colonne 'price'. Si elle ne l'a pas, enlève 'price' de la DB
263
+ # et retire la logique "potential_sl" qui en dépend.
264
+ id, symbol, tp, sl, dir, entry_price = r
265
  ticker = ex.fetch_ticker(symbol)
266
+ current_price = ticker["last"]
267
 
268
+ trail_buffer = current_price * 0.015 # 1.5% de buffer pour le suiveur
269
+
270
+ if dir == "HAUSSIER":
271
+ if current_price >= tp:
272
+ cur.execute("UPDATE signals SET status='WIN' WHERE id=?", (id,))
273
+ updates += 1
274
+ elif current_price <= sl:
275
+ cur.execute("UPDATE signals SET status='LOSS' WHERE id=?", (id,))
276
+ updates += 1
277
+ else:
278
+ # Trailing Stop Long
279
+ potential_sl = current_price - trail_buffer
280
+ if current_price > entry_price and potential_sl > sl:
281
+ cur.execute("UPDATE signals SET sl=? WHERE id=?", (potential_sl, id))
282
+ trailing_updates += 1
283
+
284
+ else: # BAISSIER
285
+ if current_price <= tp:
286
+ cur.execute("UPDATE signals SET status='WIN' WHERE id=?", (id,))
287
+ updates += 1
288
+ elif current_price >= sl:
289
+ cur.execute("UPDATE signals SET status='LOSS' WHERE id=?", (id,))
290
+ updates += 1
291
+ else:
292
+ # Trailing Stop Short
293
+ potential_sl = current_price + trail_buffer
294
+ if current_price < entry_price and potential_sl < sl:
295
+ cur.execute("UPDATE signals SET sl=? WHERE id=?", (potential_sl, id))
296
+ trailing_updates += 1
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
  conn.commit()
299
  conn.close()
300
+ return f"✅ Juge : {updates} trades terminés | {trailing_updates} Trailing Stops ajustés"
 
301
 
302
  except Exception as e:
303
+ return str(e)
304
+
305
+ # ================================
306
+ # 🔌 SHUTDOWN SAFE
307
+ # ================================
308
+ async def shutdown():
309
+ ex = ExchangeManager.get_instance()
310
+ await ex.close()
311
+
312
+ # ================================
313
+ # 🌐 API
314
+ # ================================
315
+ with gr.Blocks() as iface:
316
+ gr.Markdown("# 🚀 Alpha Engine V22.1 (Institutional Grade)")
317
+ sym = gr.Textbox(label="Symbol")
318
+ tf = gr.Dropdown(["5m","15m","1h","4h","1d"], value="1h")
319
+ btn = gr.Button("Predict Signal")
320
+ out = gr.JSON()
321
+ btn.click(fn=predict_signal, inputs=[sym, tf], outputs=out)
322
+
323
+ judge_btn = gr.Button("Run Judge")
324
+ judge_out = gr.Textbox()
325
+ judge_btn.click(fn=run_judge_api, outputs=judge_out)
 
 
 
 
 
 
 
 
 
 
326
 
327
  if __name__ == "__main__":
328
  iface.launch(show_api=True)