Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -79,7 +79,7 @@ except:
|
|
| 79 |
|
| 80 |
# --- DB & SYNC ---
|
| 81 |
REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
|
| 82 |
-
DB_NAME = "
|
| 83 |
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 84 |
|
| 85 |
def init_db():
|
|
@@ -90,7 +90,6 @@ def init_db():
|
|
| 90 |
cursor = conn.execute("PRAGMA table_info(signals)")
|
| 91 |
columns = [col[1] for col in cursor.fetchall()]
|
| 92 |
conn.close()
|
| 93 |
-
# 🔍 On ajoute 'confirmed' dans la vérification de structure
|
| 94 |
if 'date' not in columns or 'peak_price' not in columns or 'confirmed' not in columns:
|
| 95 |
reset_needed = True
|
| 96 |
except Exception as e:
|
|
@@ -98,21 +97,19 @@ def init_db():
|
|
| 98 |
reset_needed = True
|
| 99 |
|
| 100 |
if reset_needed:
|
| 101 |
-
print("⚠️ Structure obsolète. Reset de la base de données
|
| 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 (MISE À JOUR) ---
|
| 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,
|
| 113 |
-
confirmed INTEGER DEFAULT 0)''')
|
| 114 |
|
| 115 |
-
# --- 🚀 AUTO-MIGRATION V12 (Reste inchangé) ---
|
| 116 |
cursor = conn.cursor()
|
| 117 |
cursor.execute("PRAGMA table_info(agent_logic)")
|
| 118 |
al_cols = [col[1] for col in cursor.fetchall()]
|
|
@@ -135,11 +132,14 @@ def init_db():
|
|
| 135 |
|
| 136 |
cursor.execute("SELECT COUNT(*) FROM agent_logic")
|
| 137 |
if cursor.fetchone()[0] == 0:
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
('ALL', '
|
|
|
|
|
|
|
|
|
|
| 141 |
conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?)", defaults)
|
| 142 |
-
print("✅ Base de données
|
| 143 |
except Exception as e:
|
| 144 |
print(f"❌ Erreur critique création DB: {e}")
|
| 145 |
|
|
@@ -191,6 +191,12 @@ try:
|
|
| 191 |
except Exception as e: print(f"⚠️ Erreur IA : {e}")
|
| 192 |
|
| 193 |
# --- 📊 FEATURES ENGINE ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
|
| 195 |
try:
|
| 196 |
now = datetime.now().timestamp()
|
|
@@ -208,21 +214,29 @@ def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
|
|
| 208 |
|
| 209 |
if len(df) < 250: return pd.DataFrame()
|
| 210 |
|
|
|
|
| 211 |
df["RSI"] = get_rsi(df["close"])
|
|
|
|
| 212 |
df["EMA50"] = get_ema(df["close"], 50)
|
| 213 |
df["EMA200"] = get_ema(df["close"], 200)
|
|
|
|
| 214 |
df["ATR"] = get_atr(df)
|
| 215 |
df["ATR_pct"] = (df["ATR"] / df["close"]) * 100
|
| 216 |
df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
|
| 217 |
df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
|
|
|
|
|
|
|
| 218 |
df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min()
|
| 219 |
df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
|
| 220 |
df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
|
| 221 |
df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
|
| 222 |
df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1
|
|
|
|
| 223 |
df["ATR_ratio"] = df["ATR"] / df["close"]
|
| 224 |
df["VOL_ratio"] = df["vol"] / df["vol"].rolling(24).mean()
|
|
|
|
| 225 |
|
|
|
|
| 226 |
diff = df["High_24h"] - df["Low_24h"]
|
| 227 |
df["Fib_618"] = df["Low_24h"] + (diff * 0.618)
|
| 228 |
df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"]
|
|
@@ -245,6 +259,8 @@ def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
|
|
| 245 |
p_low, p_high = df["low"].rolling(24).min().shift(1), df["high"].rolling(24).max().shift(1)
|
| 246 |
df["Sweep_Low"] = ((df["low"] < p_low) & (df["close"] > p_low)).astype(int)
|
| 247 |
df["Sweep_High"] = ((df["high"] > p_high) & (df["close"] < p_high)).astype(int)
|
|
|
|
|
|
|
| 248 |
df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12)
|
| 249 |
df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2)
|
| 250 |
df['VOL_RATIO'] = df['vol'] / df['vol'].rolling(20).mean()
|
|
@@ -258,14 +274,16 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 258 |
memory_guard()
|
| 259 |
symbol = str(symbol).strip().upper()
|
| 260 |
|
| 261 |
-
# 1. Préparation des données
|
| 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 |
-
#
|
| 269 |
regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
|
| 270 |
regime_pred = int(regime_model.predict(regime_scaled)[0])
|
| 271 |
ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
|
|
@@ -277,14 +295,17 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 277 |
lstm_prob = float(lstm_brain.predict(np.expand_dims(lstm_data.values, axis=0), verbose=0)[0][0])
|
| 278 |
p_sent = await get_crypto_sentiment(symbol)
|
| 279 |
|
| 280 |
-
#
|
| 281 |
-
if timeframe in ["5m", "15m"]:
|
| 282 |
-
|
| 283 |
-
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
|
| 286 |
|
| 287 |
-
#
|
| 288 |
sweep_low, sweep_high = int(last_row["Sweep_Low"].iloc[0]), int(last_row["Sweep_High"].iloc[0])
|
| 289 |
smc_status = "AUCUN"
|
| 290 |
if sweep_low == 1:
|
|
@@ -293,18 +314,22 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 293 |
elif sweep_high == 1:
|
| 294 |
final_p = max(0.05, final_p - 0.20)
|
| 295 |
smc_status = "SHORT SWEEP 🐋"
|
| 296 |
-
|
| 297 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
with sqlite3.connect(DB_NAME) as conn:
|
| 299 |
-
# On cherche les stats pour CE symbole précis
|
| 300 |
res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ?", (symbol, timeframe)).fetchone()
|
| 301 |
if res:
|
| 302 |
tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res
|
| 303 |
else:
|
| 304 |
-
# Si le symbole n'existe pas encore, on prend les stats de base ('ALL')
|
| 305 |
res_fallback = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
|
| 306 |
-
tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res_fallback if res_fallback else (
|
| 307 |
-
# On initialise immédiatement le nouveau cerveau dans la DB
|
| 308 |
conn.execute("INSERT OR IGNORE INTO agent_logic (symbol, timeframe, tp_mult, sl_mult, score, last_pnl, min_prob, min_tp_dist) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
| 309 |
(symbol, timeframe, tp_m, sl_m, 0, 0, agent_min_prob, agent_min_tp_dist))
|
| 310 |
conn.commit()
|
|
@@ -312,7 +337,7 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 312 |
tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
|
| 313 |
sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
|
| 314 |
|
| 315 |
-
#
|
| 316 |
strength = abs(final_p - 0.5) * 2
|
| 317 |
conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, lstm_prob, p_sent])))
|
| 318 |
score_base = (strength * 45) + (conf_val * 40)
|
|
@@ -320,15 +345,14 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 320 |
composite_score = max(0, min(100, score_base + regime_bonus))
|
| 321 |
|
| 322 |
# ==========================================
|
| 323 |
-
# 🛑 LE VETO DYNAMIQUE (BOUCLIER EMA200)
|
| 324 |
# ==========================================
|
| 325 |
veto = False
|
| 326 |
veto_reason = ""
|
| 327 |
dist_tp_pct = abs(tp - prix) / prix
|
| 328 |
dist_fib = float(last_row["Dist_Fib_618"].iloc[0])
|
| 329 |
mkt_trend = float(last_row["Market_Trend"].iloc[0])
|
| 330 |
-
|
| 331 |
-
ema200_val = float(last_row["EMA200"].iloc[0]) # 🚀 NOUVEAU: On extrait la ligne EMA 200
|
| 332 |
|
| 333 |
if final_p < agent_min_prob and final_p > (1 - agent_min_prob):
|
| 334 |
veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}"
|
|
@@ -339,14 +363,13 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 339 |
|
| 340 |
elif final_p > 0.5 and dist_fib < -0.03: veto, veto_reason = True, "Support Fib 61.8 trop loin"
|
| 341 |
elif final_p < 0.5 and dist_fib > 0.03: veto, veto_reason = True, "Résistance Fib 61.8 trop loin"
|
| 342 |
-
elif timeframe in ["5m", "15m"] and smc_status == "AUCUN": veto, veto_reason = True, "Aucun Sweep SMC"
|
| 343 |
elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible"
|
| 344 |
|
| 345 |
-
# 🛡️ LE GARDE-FOU ABSOLU (Interdiction stricte de contrer le TGV)
|
| 346 |
-
elif final_p < 0.5 and prix > ema200_val:
|
| 347 |
-
veto, veto_reason = True, "Garde-Fou: Prix > EMA200 (Short
|
| 348 |
-
elif final_p > 0.5 and prix < ema200_val:
|
| 349 |
-
veto, veto_reason = True, "Garde-Fou: Prix < EMA200 (Long
|
| 350 |
|
| 351 |
if veto:
|
| 352 |
print(f"🛑 [VETO] {symbol}: {veto_reason}")
|
|
@@ -405,8 +428,8 @@ def trigger_training(symbol="BTC/USD"):
|
|
| 405 |
return f"❌ Erreur Training : {str(e)}"
|
| 406 |
|
| 407 |
# --- 🚀 MOTEUR AUTO-PILOTE ---
|
| 408 |
-
AUTO_SYMBOLS = ["BTC/USD"
|
| 409 |
-
AUTO_TIMEFRAMES = ["5m", "15m", "1h", "4h"
|
| 410 |
|
| 411 |
async def auto_predict_loop():
|
| 412 |
while True:
|
|
@@ -522,7 +545,7 @@ def run_judge_api():
|
|
| 522 |
ticker = exchange_sync.fetch_ticker(fetch_symbol)
|
| 523 |
current_price = ticker['last']
|
| 524 |
|
| 525 |
-
# --- 🚀 LOGIQUE TRAILING STOP (
|
| 526 |
sl_dynamique = t['sl']
|
| 527 |
peak = t['peak_price']
|
| 528 |
|
|
@@ -534,26 +557,25 @@ def run_judge_api():
|
|
| 534 |
|
| 535 |
nouveau_sl = sl_dynamique
|
| 536 |
|
| 537 |
-
# --- 🛡️ LA STRATÉGIE DE L'ÉTAU (
|
| 538 |
-
# On laisse le marché respirer pour éviter de se faire tuer par le Spread
|
| 539 |
if t['direction'] == 'HAUSSIER':
|
| 540 |
-
# PALIER 3 :
|
| 541 |
-
if progression >= 0.
|
| 542 |
nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.60))
|
| 543 |
-
# PALIER 2 :
|
| 544 |
-
elif progression >= 0.
|
| 545 |
-
nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.
|
| 546 |
-
# PALIER 1 :
|
| 547 |
-
elif progression >= 0.
|
| 548 |
-
nouveau_sl = max(sl_dynamique, t['price'])
|
| 549 |
|
| 550 |
else: # BAISSIER (SELL)
|
| 551 |
-
if progression >= 0.
|
| 552 |
nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.60))
|
| 553 |
-
elif progression >= 0.
|
| 554 |
-
nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.
|
| 555 |
-
elif progression >= 0.
|
| 556 |
-
nouveau_sl = min(sl_dynamique, t['price'])
|
| 557 |
|
| 558 |
sl_dynamique = nouveau_sl
|
| 559 |
cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, sl_dynamique, t['id']))
|
|
@@ -579,55 +601,47 @@ def run_judge_api():
|
|
| 579 |
|
| 580 |
if outcome:
|
| 581 |
# =========================================================
|
| 582 |
-
# --- 🪞 LA VÉRITÉ BRUTALE (
|
| 583 |
# =========================================================
|
| 584 |
-
|
| 585 |
-
taille_position_virtuelle = 500.0
|
| 586 |
|
| 587 |
-
#
|
| 588 |
-
|
|
|
|
| 589 |
|
| 590 |
-
# 1. Calcul du gain brut basé sur le VRAI mouvement de prix
|
| 591 |
if t['direction'] == 'HAUSSIER':
|
| 592 |
pnl_brut = ((current_price - t['price']) / t['price']) * taille_position_virtuelle
|
| 593 |
else:
|
| 594 |
pnl_brut = ((t['price'] - current_price) / t['price']) * taille_position_virtuelle
|
| 595 |
|
| 596 |
-
|
| 597 |
-
pnl_dollars = pnl_brut - spread_commission
|
| 598 |
|
| 599 |
-
# 3. Correction honnête du statut Discord
|
| 600 |
if outcome == "SL TOUCHÉ 🛡️" and pnl_dollars < 0:
|
| 601 |
-
outcome = "TUÉ PAR LE SPREAD 🩸"
|
| 602 |
elif outcome == "GAGNÉ (PARTIEL) 💸" and pnl_dollars <= 0:
|
| 603 |
-
outcome = "FAUX GAIN (
|
| 604 |
|
| 605 |
-
#
|
| 606 |
-
# --- 🎮 V12 : ÉVOLUTION RPG (ISOLATION + ANTI YO-YO) ---
|
| 607 |
-
# =========================================================
|
| 608 |
cursor.execute("SELECT tp_mult, sl_mult, score, min_prob FROM agent_logic WHERE symbol = ? AND timeframe = ?", (t['symbol'], t['timeframe']))
|
| 609 |
row = cursor.fetchone()
|
| 610 |
|
| 611 |
if row:
|
| 612 |
tp_m, sl_m, score_ia, min_p = row
|
| 613 |
else:
|
| 614 |
-
|
| 615 |
-
tp_m, sl_m, score_ia, min_p = (3.0, 1.5, 0, 0.55)
|
| 616 |
cursor.execute("INSERT INTO agent_logic (symbol, timeframe, tp_mult, sl_mult, score, min_prob) VALUES (?, ?, ?, ?, ?, ?)",
|
| 617 |
(t['symbol'], t['timeframe'], tp_m, sl_m, score_ia, min_p))
|
| 618 |
|
| 619 |
-
# LISSAGE ANTI YO-YO (Amortissement à 1.5%)
|
| 620 |
if reward > 0:
|
| 621 |
-
tp_m = min(
|
| 622 |
min_p = max(0.50, min_p - 0.005)
|
| 623 |
elif reward < 0:
|
| 624 |
-
sl_m = max(
|
| 625 |
-
tp_m = max(1.
|
| 626 |
min_p = min(0.65, min_p + 0.01)
|
| 627 |
|
| 628 |
cursor.execute("UPDATE agent_logic SET tp_mult=?, sl_mult=?, score=score+?, min_prob=? WHERE symbol=? AND timeframe=?",
|
| 629 |
(tp_m, sl_m, reward, min_p, t['symbol'], t['timeframe']))
|
| 630 |
-
# =========================================================
|
| 631 |
|
| 632 |
cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
|
| 633 |
|
|
|
|
| 79 |
|
| 80 |
# --- DB & SYNC ---
|
| 81 |
REPO_ID = "Nexo-S/AlphaV15-Quant-Engine"
|
| 82 |
+
DB_NAME = "alphatrade_v22_scalp.db" # 🎮 NOUVEAU MONDE - NIVEAU 0
|
| 83 |
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 84 |
|
| 85 |
def init_db():
|
|
|
|
| 90 |
cursor = conn.execute("PRAGMA table_info(signals)")
|
| 91 |
columns = [col[1] for col in cursor.fetchall()]
|
| 92 |
conn.close()
|
|
|
|
| 93 |
if 'date' not in columns or 'peak_price' not in columns or 'confirmed' not in columns:
|
| 94 |
reset_needed = True
|
| 95 |
except Exception as e:
|
|
|
|
| 97 |
reset_needed = True
|
| 98 |
|
| 99 |
if reset_needed:
|
| 100 |
+
print("⚠️ Structure obsolète. Reset de la base de données...")
|
| 101 |
if os.path.exists(DB_NAME): os.remove(DB_NAME)
|
| 102 |
|
| 103 |
try:
|
| 104 |
with sqlite3.connect(DB_NAME) as conn:
|
|
|
|
| 105 |
conn.execute('''CREATE TABLE IF NOT EXISTS signals (
|
| 106 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 107 |
date TEXT, symbol TEXT, timeframe TEXT, direction TEXT,
|
| 108 |
prob REAL, price REAL, tp REAL, sl REAL, status TEXT,
|
| 109 |
regime INTEGER, prob_time REAL, prob_ml REAL, prob_lstm REAL, prob_sent REAL,
|
| 110 |
peak_price REAL,
|
| 111 |
+
confirmed INTEGER DEFAULT 0)''')
|
| 112 |
|
|
|
|
| 113 |
cursor = conn.cursor()
|
| 114 |
cursor.execute("PRAGMA table_info(agent_logic)")
|
| 115 |
al_cols = [col[1] for col in cursor.fetchall()]
|
|
|
|
| 132 |
|
| 133 |
cursor.execute("SELECT COUNT(*) FROM agent_logic")
|
| 134 |
if cursor.fetchone()[0] == 0:
|
| 135 |
+
# ⚡ NOUVEAU: Defaults ultra-agressifs pour le Scalping
|
| 136 |
+
defaults = [('ALL', '1m', 1.0, 0.8, 0, 0, 0.65, 0.001),
|
| 137 |
+
('ALL', '5m', 1.2, 0.9, 0, 0, 0.62, 0.002),
|
| 138 |
+
('ALL', '15m', 1.5, 1.0, 0, 0, 0.60, 0.003),
|
| 139 |
+
('ALL', '1h', 2.0, 1.5, 0, 0, 0.55, 0.005),
|
| 140 |
+
('ALL', '4h', 3.0, 2.0, 0, 0, 0.50, 0.008)]
|
| 141 |
conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?)", defaults)
|
| 142 |
+
print("✅ Base de données Scalping (MT5 Exness) opérationnelle.")
|
| 143 |
except Exception as e:
|
| 144 |
print(f"❌ Erreur critique création DB: {e}")
|
| 145 |
|
|
|
|
| 191 |
except Exception as e: print(f"⚠️ Erreur IA : {e}")
|
| 192 |
|
| 193 |
# --- 📊 FEATURES ENGINE ---
|
| 194 |
+
def get_vwap(df):
|
| 195 |
+
"""Calcul du Volume Weighted Average Price (Outil #1 du Scalpeur)"""
|
| 196 |
+
v = df['vol']
|
| 197 |
+
tp = (df['high'] + df['low'] + df['close']) / 3
|
| 198 |
+
return (tp * v).cumsum() / v.cumsum()
|
| 199 |
+
|
| 200 |
def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
|
| 201 |
try:
|
| 202 |
now = datetime.now().timestamp()
|
|
|
|
| 214 |
|
| 215 |
if len(df) < 250: return pd.DataFrame()
|
| 216 |
|
| 217 |
+
# --- FONDATIONS TECHNIQUES ---
|
| 218 |
df["RSI"] = get_rsi(df["close"])
|
| 219 |
+
df["RSI_9"] = get_rsi(df["close"], 9) # ⚡ NOUVEAU: RSI Nerveux pour Scalping
|
| 220 |
df["EMA50"] = get_ema(df["close"], 50)
|
| 221 |
df["EMA200"] = get_ema(df["close"], 200)
|
| 222 |
+
df["VWAP"] = get_vwap(df) # ⚡ NOUVEAU: Le juge de paix
|
| 223 |
df["ATR"] = get_atr(df)
|
| 224 |
df["ATR_pct"] = (df["ATR"] / df["close"]) * 100
|
| 225 |
df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
|
| 226 |
df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
|
| 227 |
+
|
| 228 |
+
# --- DISTANCES & SLOPES ---
|
| 229 |
df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min()
|
| 230 |
df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
|
| 231 |
df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
|
| 232 |
df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
|
| 233 |
df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1
|
| 234 |
+
df["Price_vs_VWAP"] = (df["close"] - df["VWAP"]) / df["VWAP"] # ⚡ Distance au VWAP
|
| 235 |
df["ATR_ratio"] = df["ATR"] / df["close"]
|
| 236 |
df["VOL_ratio"] = df["vol"] / df["vol"].rolling(24).mean()
|
| 237 |
+
df["Vol_Spike"] = df["vol"] / df["vol"].rolling(5).mean() # ⚡ NOUVEAU: Explosion de volume
|
| 238 |
|
| 239 |
+
# --- SMC & FIBO ---
|
| 240 |
diff = df["High_24h"] - df["Low_24h"]
|
| 241 |
df["Fib_618"] = df["Low_24h"] + (diff * 0.618)
|
| 242 |
df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"]
|
|
|
|
| 259 |
p_low, p_high = df["low"].rolling(24).min().shift(1), df["high"].rolling(24).max().shift(1)
|
| 260 |
df["Sweep_Low"] = ((df["low"] < p_low) & (df["close"] > p_low)).astype(int)
|
| 261 |
df["Sweep_High"] = ((df["high"] > p_high) & (df["close"] < p_high)).astype(int)
|
| 262 |
+
|
| 263 |
+
# --- FEATURES POUR LES MODELES ---
|
| 264 |
df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12)
|
| 265 |
df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2)
|
| 266 |
df['VOL_RATIO'] = df['vol'] / df['vol'].rolling(20).mean()
|
|
|
|
| 274 |
memory_guard()
|
| 275 |
symbol = str(symbol).strip().upper()
|
| 276 |
|
|
|
|
| 277 |
df = prepare_features_sync(symbol, timeframe)
|
| 278 |
if df.empty: return {"status": "error", "message": f"Data insuffisante pour {timeframe}"}
|
| 279 |
|
| 280 |
last_row = df.iloc[[-1]]
|
| 281 |
prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
|
| 282 |
+
vwap = float(last_row['VWAP'].iloc[0]) # ⚡ Extraction VWAP
|
| 283 |
+
vol_spike = float(last_row['Vol_Spike'].iloc[0]) # ⚡ Extraction Spike
|
| 284 |
+
rsi_9 = float(last_row['RSI_9'].iloc[0]) # ⚡ Extraction RSI Rapide
|
| 285 |
|
| 286 |
+
# --- 🧠 CERVEAUX IA ---
|
| 287 |
regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
|
| 288 |
regime_pred = int(regime_model.predict(regime_scaled)[0])
|
| 289 |
ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
|
|
|
|
| 295 |
lstm_prob = float(lstm_brain.predict(np.expand_dims(lstm_data.values, axis=0), verbose=0)[0][0])
|
| 296 |
p_sent = await get_crypto_sentiment(symbol)
|
| 297 |
|
| 298 |
+
# --- ⚖️ PONDÉRATION ADAPTATIVE (FOCUS MOMENTUM SCALPING) ---
|
| 299 |
+
if timeframe in ["1m", "5m", "15m"]:
|
| 300 |
+
wt, wm, wl, ws = 0.50, 0.25, 0.15, 0.10 # On fait confiance au TimeModel pour la vitesse
|
| 301 |
+
elif timeframe in ["1h", "4h", "1d"]:
|
| 302 |
+
wl, wm, wt, ws = 0.60, 0.15, 0.15, 0.10
|
| 303 |
+
else:
|
| 304 |
+
wt, wm, wl, ws = 0.25, 0.25, 0.25, 0.25
|
| 305 |
|
| 306 |
final_p = (time_prob * wt) + (ml_prob * wm) + (lstm_prob * wl) + (p_sent * ws)
|
| 307 |
|
| 308 |
+
# --- 🐋 BONUS SMART MONEY & SCALPING NITRO ---
|
| 309 |
sweep_low, sweep_high = int(last_row["Sweep_Low"].iloc[0]), int(last_row["Sweep_High"].iloc[0])
|
| 310 |
smc_status = "AUCUN"
|
| 311 |
if sweep_low == 1:
|
|
|
|
| 314 |
elif sweep_high == 1:
|
| 315 |
final_p = max(0.05, final_p - 0.20)
|
| 316 |
smc_status = "SHORT SWEEP 🐋"
|
| 317 |
+
|
| 318 |
+
# ⚡ NITRO SCALPING : Si on a un pic de volume dans le sens de la tendance
|
| 319 |
+
if timeframe in ["1m", "5m"]:
|
| 320 |
+
if final_p > 0.5 and vol_spike > 1.5 and rsi_9 < 70:
|
| 321 |
+
final_p = min(0.95, final_p + 0.10) # Coup de boost à l'achat
|
| 322 |
+
elif final_p < 0.5 and vol_spike > 1.5 and rsi_9 > 30:
|
| 323 |
+
final_p = max(0.05, final_p - 0.10) # Coup de boost à la vente
|
| 324 |
+
|
| 325 |
+
# --- 🛡️ CALCUL TP/SL (BASE DE DONNÉES ISOLÉE) ---
|
| 326 |
with sqlite3.connect(DB_NAME) as conn:
|
|
|
|
| 327 |
res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ?", (symbol, timeframe)).fetchone()
|
| 328 |
if res:
|
| 329 |
tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res
|
| 330 |
else:
|
|
|
|
| 331 |
res_fallback = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
|
| 332 |
+
tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res_fallback if res_fallback else (1.5, 1.0, 0.55, 0.002)
|
|
|
|
| 333 |
conn.execute("INSERT OR IGNORE INTO agent_logic (symbol, timeframe, tp_mult, sl_mult, score, last_pnl, min_prob, min_tp_dist) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
| 334 |
(symbol, timeframe, tp_m, sl_m, 0, 0, agent_min_prob, agent_min_tp_dist))
|
| 335 |
conn.commit()
|
|
|
|
| 337 |
tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
|
| 338 |
sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
|
| 339 |
|
| 340 |
+
# Scores et Regimes
|
| 341 |
strength = abs(final_p - 0.5) * 2
|
| 342 |
conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, lstm_prob, p_sent])))
|
| 343 |
score_base = (strength * 45) + (conf_val * 40)
|
|
|
|
| 345 |
composite_score = max(0, min(100, score_base + regime_bonus))
|
| 346 |
|
| 347 |
# ==========================================
|
| 348 |
+
# 🛑 LE VETO DYNAMIQUE (BOUCLIER EMA200 + VWAP)
|
| 349 |
# ==========================================
|
| 350 |
veto = False
|
| 351 |
veto_reason = ""
|
| 352 |
dist_tp_pct = abs(tp - prix) / prix
|
| 353 |
dist_fib = float(last_row["Dist_Fib_618"].iloc[0])
|
| 354 |
mkt_trend = float(last_row["Market_Trend"].iloc[0])
|
| 355 |
+
ema200_val = float(last_row["EMA200"].iloc[0])
|
|
|
|
| 356 |
|
| 357 |
if final_p < agent_min_prob and final_p > (1 - agent_min_prob):
|
| 358 |
veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}"
|
|
|
|
| 363 |
|
| 364 |
elif final_p > 0.5 and dist_fib < -0.03: veto, veto_reason = True, "Support Fib 61.8 trop loin"
|
| 365 |
elif final_p < 0.5 and dist_fib > 0.03: veto, veto_reason = True, "Résistance Fib 61.8 trop loin"
|
|
|
|
| 366 |
elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible"
|
| 367 |
|
| 368 |
+
# 🛡️ LE GARDE-FOU ABSOLU (Interdiction stricte de contrer le TGV institutionnel)
|
| 369 |
+
elif final_p < 0.5 and (prix > ema200_val or prix > vwap):
|
| 370 |
+
veto, veto_reason = True, "Garde-Fou: Prix > EMA200/VWAP (Short Interdit 🚫)"
|
| 371 |
+
elif final_p > 0.5 and (prix < ema200_val or prix < vwap):
|
| 372 |
+
veto, veto_reason = True, "Garde-Fou: Prix < EMA200/VWAP (Long Interdit 🚫)"
|
| 373 |
|
| 374 |
if veto:
|
| 375 |
print(f"🛑 [VETO] {symbol}: {veto_reason}")
|
|
|
|
| 428 |
return f"❌ Erreur Training : {str(e)}"
|
| 429 |
|
| 430 |
# --- 🚀 MOTEUR AUTO-PILOTE ---
|
| 431 |
+
AUTO_SYMBOLS = ["BTC/USD"]
|
| 432 |
+
AUTO_TIMEFRAMES = ["1m", "5m", "15m", "1h", "4h"]
|
| 433 |
|
| 434 |
async def auto_predict_loop():
|
| 435 |
while True:
|
|
|
|
| 545 |
ticker = exchange_sync.fetch_ticker(fetch_symbol)
|
| 546 |
current_price = ticker['last']
|
| 547 |
|
| 548 |
+
# --- 🚀 LOGIQUE TRAILING STOP (SCALPING AGRESSIF) ---
|
| 549 |
sl_dynamique = t['sl']
|
| 550 |
peak = t['peak_price']
|
| 551 |
|
|
|
|
| 557 |
|
| 558 |
nouveau_sl = sl_dynamique
|
| 559 |
|
| 560 |
+
# --- 🛡️ LA STRATÉGIE DE L'ÉTAU (SERRÉ POUR SCALPING) ---
|
|
|
|
| 561 |
if t['direction'] == 'HAUSSIER':
|
| 562 |
+
# PALIER 3 : 75% du but -> On verrouille 60%
|
| 563 |
+
if progression >= 0.75:
|
| 564 |
nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.60))
|
| 565 |
+
# PALIER 2 : 50% du but -> On verrouille 25% (On est déjà en profit assuré)
|
| 566 |
+
elif progression >= 0.50:
|
| 567 |
+
nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.25))
|
| 568 |
+
# PALIER 1 : 25% du but -> BREAK-EVEN (Serrage très rapide)
|
| 569 |
+
elif progression >= 0.25:
|
| 570 |
+
nouveau_sl = max(sl_dynamique, t['price'] + (chemin_total * 0.05)) # BE + Micro profit pour couvrir commission
|
| 571 |
|
| 572 |
else: # BAISSIER (SELL)
|
| 573 |
+
if progression >= 0.75:
|
| 574 |
nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.60))
|
| 575 |
+
elif progression >= 0.50:
|
| 576 |
+
nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.25))
|
| 577 |
+
elif progression >= 0.25:
|
| 578 |
+
nouveau_sl = min(sl_dynamique, t['price'] - (chemin_total * 0.05))
|
| 579 |
|
| 580 |
sl_dynamique = nouveau_sl
|
| 581 |
cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, sl_dynamique, t['id']))
|
|
|
|
| 601 |
|
| 602 |
if outcome:
|
| 603 |
# =========================================================
|
| 604 |
+
# --- 🪞 LA VÉRITÉ BRUTALE (EXNESS MT5 EDITION) ---
|
| 605 |
# =========================================================
|
| 606 |
+
taille_position_virtuelle = 1000.0 # Standard pour estimer
|
|
|
|
| 607 |
|
| 608 |
+
# ⚡ NOUVEAU: Exness a des spreads très serrés (surtout Raw/Zero), on ajuste l'estimation
|
| 609 |
+
# Sur BTC, le spread tourne autour de 0.5 à 2.0 pips. On compte aussi la commission.
|
| 610 |
+
spread_commission_estimee = 0.25
|
| 611 |
|
|
|
|
| 612 |
if t['direction'] == 'HAUSSIER':
|
| 613 |
pnl_brut = ((current_price - t['price']) / t['price']) * taille_position_virtuelle
|
| 614 |
else:
|
| 615 |
pnl_brut = ((t['price'] - current_price) / t['price']) * taille_position_virtuelle
|
| 616 |
|
| 617 |
+
pnl_dollars = pnl_brut - spread_commission_estimee
|
|
|
|
| 618 |
|
|
|
|
| 619 |
if outcome == "SL TOUCHÉ 🛡️" and pnl_dollars < 0:
|
| 620 |
+
outcome = "TUÉ PAR LE SPREAD/COMM 🩸"
|
| 621 |
elif outcome == "GAGNÉ (PARTIEL) 💸" and pnl_dollars <= 0:
|
| 622 |
+
outcome = "FAUX GAIN (FRAIS EXNESS) 📉"
|
| 623 |
|
| 624 |
+
# --- 🎮 ÉVOLUTION RPG ---
|
|
|
|
|
|
|
| 625 |
cursor.execute("SELECT tp_mult, sl_mult, score, min_prob FROM agent_logic WHERE symbol = ? AND timeframe = ?", (t['symbol'], t['timeframe']))
|
| 626 |
row = cursor.fetchone()
|
| 627 |
|
| 628 |
if row:
|
| 629 |
tp_m, sl_m, score_ia, min_p = row
|
| 630 |
else:
|
| 631 |
+
tp_m, sl_m, score_ia, min_p = (1.5, 1.0, 0, 0.55)
|
|
|
|
| 632 |
cursor.execute("INSERT INTO agent_logic (symbol, timeframe, tp_mult, sl_mult, score, min_prob) VALUES (?, ?, ?, ?, ?, ?)",
|
| 633 |
(t['symbol'], t['timeframe'], tp_m, sl_m, score_ia, min_p))
|
| 634 |
|
|
|
|
| 635 |
if reward > 0:
|
| 636 |
+
tp_m = min(4.0, tp_m * 1.01) # Moins d'inflation de TP pour garder le mode Scalp
|
| 637 |
min_p = max(0.50, min_p - 0.005)
|
| 638 |
elif reward < 0:
|
| 639 |
+
sl_m = max(0.8, sl_m * 0.985) # On serre le SL si on perd
|
| 640 |
+
tp_m = max(1.1, tp_m * 0.985) # On abaisse le TP si on perd trop (Scalping de survie)
|
| 641 |
min_p = min(0.65, min_p + 0.01)
|
| 642 |
|
| 643 |
cursor.execute("UPDATE agent_logic SET tp_mult=?, sl_mult=?, score=score+?, min_prob=? WHERE symbol=? AND timeframe=?",
|
| 644 |
(tp_m, sl_m, reward, min_p, t['symbol'], t['timeframe']))
|
|
|
|
| 645 |
|
| 646 |
cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
|
| 647 |
|