Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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):
|
|
|
|
| 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 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 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 |
-
|
| 137 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 144 |
-
|
|
|
|
| 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 |
-
#
|
| 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 |
-
#
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
"
|
| 178 |
-
"
|
| 179 |
-
"
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 209 |
for t in trades:
|
| 210 |
-
curr =
|
| 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()
|
| 222 |
return f"✅ {updates} trade(s) mis à jour."
|
| 223 |
except Exception as e: return f"❌ Erreur Juge : {e}"
|
| 224 |
|
| 225 |
-
# --- 📊 STATS
|
| 226 |
-
def get_db_stats(mode, symbol=None
|
| 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) <
|
| 233 |
-
|
| 234 |
-
|
| 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
|
| 252 |
-
except Exception as e: return
|
| 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 |
-
|
| 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
|
| 299 |
-
with gr.Blocks(theme=gr.themes.Monochrome(), title="Alpha
|
| 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 |
-
#
|
| 318 |
-
|
| 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,
|
|
|
|
| 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)
|