Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -109,6 +109,7 @@ def backup_db_to_hf():
|
|
| 109 |
def init_db():
|
| 110 |
try:
|
| 111 |
with sqlite3.connect(DB_NAME) as conn:
|
|
|
|
| 112 |
conn.execute('''CREATE TABLE IF NOT EXISTS signals (
|
| 113 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 114 |
date TEXT, symbol TEXT, timeframe TEXT, direction TEXT,
|
|
@@ -117,25 +118,53 @@ def init_db():
|
|
| 117 |
peak_price REAL, confirmed INTEGER DEFAULT 0)''')
|
| 118 |
|
| 119 |
cursor = conn.cursor()
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
defaults = [
|
| 131 |
-
|
| 132 |
-
('ALL', '
|
| 133 |
-
('ALL', '
|
| 134 |
-
('ALL', '
|
| 135 |
-
('ALL', '
|
|
|
|
| 136 |
]
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
async def save_to_db(data):
|
| 141 |
try:
|
|
@@ -266,6 +295,8 @@ def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
|
|
| 266 |
return df.dropna().copy()
|
| 267 |
except Exception as e: print(f"❌ Error Stats: {e}"); return pd.DataFrame()
|
| 268 |
|
|
|
|
|
|
|
| 269 |
async def predict_signal(symbol, timeframe="1h"):
|
| 270 |
try:
|
| 271 |
memory_guard()
|
|
@@ -279,14 +310,26 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 279 |
vol_spike = float(last_row['Vol_Spike'].iloc[0])
|
| 280 |
rsi_9 = float(last_row['RSI_9'].iloc[0])
|
| 281 |
|
|
|
|
| 282 |
futures_data = await fetch_kucoin_futures_data(symbol)
|
| 283 |
oi, cvd = futures_data["oi"], futures_data["cvd"]
|
|
|
|
| 284 |
|
|
|
|
|
|
|
| 285 |
regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
|
| 286 |
-
|
| 287 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
|
| 289 |
ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1])
|
|
|
|
| 290 |
time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']
|
| 291 |
time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1])
|
| 292 |
|
|
@@ -294,85 +337,229 @@ async def predict_signal(symbol, timeframe="1h"):
|
|
| 294 |
if dino_brain:
|
| 295 |
try: dino_prob = float(dino_brain.predict(last_row[ml_cols].values)[0])
|
| 296 |
except: dino_prob = 0.5
|
| 297 |
-
|
| 298 |
-
p_sent = await get_crypto_sentiment(symbol)
|
| 299 |
|
| 300 |
-
# ⚡ ENSEMBLE V30
|
| 301 |
-
final_p, wt, wm, wl, ws = combine_scores(symbol, timeframe, time_prob, ml_prob, dino_prob, p_sent,
|
| 302 |
|
|
|
|
| 303 |
smc_status = "AUCUN"
|
|
|
|
| 304 |
if int(last_row["Sweep_Low"].iloc[0]) == 1 and cvd > 0:
|
| 305 |
-
final_p = min(0.95, final_p + 0.20)
|
| 306 |
-
smc_status = "LONG SWEEP + CVD 🐋"
|
| 307 |
elif int(last_row["Sweep_High"].iloc[0]) == 1 and cvd < 0:
|
| 308 |
-
final_p = max(0.05, final_p - 0.20)
|
| 309 |
-
smc_status = "SHORT SWEEP + CVD 🐋"
|
| 310 |
|
|
|
|
| 311 |
if timeframe in ["1m", "5m"]:
|
| 312 |
if final_p > 0.5 and vol_spike > 1.5 and rsi_9 < 70: final_p = min(0.95, final_p + 0.10)
|
| 313 |
elif final_p < 0.5 and vol_spike > 1.5 and rsi_9 > 30: final_p = max(0.05, final_p - 0.10)
|
| 314 |
|
|
|
|
| 315 |
with sqlite3.connect(DB_NAME) as conn:
|
| 316 |
-
res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ?", (symbol, timeframe)).fetchone()
|
| 317 |
-
if
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
tp_m, sl_m, agent_min_prob, agent_min_tp_dist =
|
| 321 |
-
|
|
|
|
| 322 |
conn.commit()
|
|
|
|
| 323 |
|
|
|
|
| 324 |
tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
|
| 325 |
sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
|
| 326 |
|
|
|
|
| 327 |
strength = abs(final_p - 0.5) * 2
|
| 328 |
conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, dino_prob, p_sent])))
|
| 329 |
-
composite_score = max(0, min(100, (strength * 45) + (conf_val * 40) + (15 if
|
| 330 |
|
| 331 |
-
|
| 332 |
dist_tp_pct = abs(tp - prix) / prix
|
| 333 |
-
dist_fib = float(last_row["Dist_Fib_618"].iloc[0])
|
| 334 |
-
mkt_trend = float(last_row["Market_Trend"].iloc[0])
|
| 335 |
ema200_val = float(last_row["EMA200"].iloc[0])
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
| 337 |
if final_p < agent_min_prob and final_p > (1 - agent_min_prob): veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}"
|
| 338 |
-
elif "BTC" not in symbol and "ETH" not in symbol:
|
| 339 |
-
if final_p < 0.5 and mkt_trend > 0.002: veto, veto_reason = True, "Marché Global Haussier"
|
| 340 |
-
elif final_p > 0.5 and mkt_trend < -0.002: veto, veto_reason = True, "Marché Global Baissier"
|
| 341 |
-
elif final_p > 0.5 and dist_fib < -0.03: veto, veto_reason = True, "Support Fib 61.8 trop loin"
|
| 342 |
-
elif final_p < 0.5 and dist_fib > 0.03: veto, veto_reason = True, "Résistance Fib 61.8 trop loin"
|
| 343 |
elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible"
|
| 344 |
-
elif final_p < 0.5 and (prix > ema200_val or prix > vwap): veto, veto_reason = True, "Short Interdit
|
| 345 |
-
elif final_p > 0.5 and (prix < ema200_val or prix < vwap): veto, veto_reason = True, "Long Interdit
|
|
|
|
|
|
|
|
|
|
| 346 |
|
| 347 |
if veto:
|
| 348 |
-
|
|
|
|
| 349 |
|
| 350 |
-
|
|
|
|
| 351 |
await save_to_db(db_task)
|
| 352 |
|
| 353 |
return {
|
| 354 |
"symbol": symbol, "timeframe": timeframe, "status": "success", "final_score": round(final_p, 4), "score": int(composite_score),
|
| 355 |
-
"smart_money": smc_status, "price": prix, "tp": round(tp, 6), "sl": round(sl, 6), "
|
| 356 |
}
|
| 357 |
except Exception as e: return {"status": "error", "message": str(e)}
|
| 358 |
|
| 359 |
# --- 🧬 FONCTION D'ÉVOLUTION ---
|
| 360 |
-
def mutate_agent(symbol, timeframe, success=True):
|
|
|
|
| 361 |
try:
|
| 362 |
with sqlite3.connect(DB_NAME) as conn:
|
| 363 |
conn.row_factory = sqlite3.Row
|
| 364 |
-
|
| 365 |
-
|
|
|
|
| 366 |
|
| 367 |
-
|
| 368 |
-
if
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
-
# ☁️ SAUVEGARDE HF APRÈS MUTATION
|
| 374 |
backup_db_to_hf()
|
| 375 |
-
except Exception as e: print(f"🧬 Erreur Mutation : {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
|
| 377 |
# --- 🧠 TRAINING ENGINE ---
|
| 378 |
def trigger_training(symbol="BTC/USD"):
|
|
@@ -440,21 +627,31 @@ async def dream_simulation_loop():
|
|
| 440 |
df = prepare_features_sync(symbol, timeframe=tf, limit_bars=200)
|
| 441 |
if df.empty or len(df) < 50: continue
|
| 442 |
|
|
|
|
| 443 |
idx = random.randint(10, len(df) - 30)
|
|
|
|
| 444 |
pred = await predict_signal(symbol, timeframe=tf)
|
|
|
|
| 445 |
if pred.get("status") == "success":
|
|
|
|
|
|
|
|
|
|
| 446 |
future_data = df.iloc[idx+1 : idx+25]
|
| 447 |
win = False
|
| 448 |
for _, row in future_data.iterrows():
|
| 449 |
-
if pred["final_score"] > 0.5:
|
| 450 |
if row['high'] >= pred['tp']: win = True; break
|
| 451 |
if row['low'] <= pred['sl']: win = False; break
|
| 452 |
-
else:
|
| 453 |
if row['low'] <= pred['tp']: win = True; break
|
| 454 |
if row['high'] >= pred['sl']: win = False; break
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
| 456 |
await asyncio.sleep(3)
|
| 457 |
-
except Exception:
|
|
|
|
| 458 |
await asyncio.sleep(60)
|
| 459 |
|
| 460 |
# --- ⚖️ TOOLS ---
|
|
@@ -482,29 +679,35 @@ def run_judge_api():
|
|
| 482 |
try:
|
| 483 |
with sqlite3.connect(DB_NAME, timeout=10) as conn:
|
| 484 |
conn.row_factory, cursor = sqlite3.Row, conn.cursor()
|
|
|
|
| 485 |
cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS' AND confirmed = 1")
|
| 486 |
trades = cursor.fetchall()
|
|
|
|
| 487 |
if not trades: return {"status": "waiting"}
|
| 488 |
closed_trades = []
|
|
|
|
| 489 |
for t in trades:
|
| 490 |
try:
|
| 491 |
sym_db, current_price = t['symbol'], None
|
|
|
|
| 492 |
if MT5_AVAILABLE:
|
| 493 |
epic = sym_db.replace("/", "").replace("USDT", "USD")
|
| 494 |
if not epic.endswith("m"): epic += "m"
|
| 495 |
tick = mt5.symbol_info_tick(epic)
|
| 496 |
if tick: current_price = tick.last
|
|
|
|
| 497 |
if current_price is None:
|
| 498 |
f_sym = sym_db if "/USDT" in sym_db else sym_db.replace("/USD", "/USDT")
|
| 499 |
if "/" not in f_sym: f_sym += "/USDT"
|
| 500 |
current_price = exchange_sync.fetch_ticker(f_sym)['last']
|
| 501 |
|
| 502 |
-
|
| 503 |
sl_dyn, peak = t['sl'], t['peak_price']
|
| 504 |
new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price)
|
| 505 |
progression = abs(current_price - t['price']) / abs(t['tp'] - t['price']) if abs(t['tp'] - t['price']) > 0 else 0
|
| 506 |
|
| 507 |
nouveau_sl = sl_dyn
|
|
|
|
| 508 |
if t['direction'] == 'HAUSSIER':
|
| 509 |
if progression >= 0.75: nouveau_sl = max(sl_dyn, t['price'] + (abs(t['tp'] - t['price']) * 0.60))
|
| 510 |
elif progression >= 0.50: nouveau_sl = max(sl_dyn, t['price'] + (abs(t['tp'] - t['price']) * 0.25))
|
|
@@ -513,6 +716,8 @@ def run_judge_api():
|
|
| 513 |
elif progression >= 0.50: nouveau_sl = min(sl_dyn, t['price'] - (abs(t['tp'] - t['price']) * 0.25))
|
| 514 |
|
| 515 |
cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, nouveau_sl, t['id']))
|
|
|
|
|
|
|
| 516 |
outcome, reward = None, 0
|
| 517 |
if t['direction'] == 'HAUSSIER':
|
| 518 |
if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3
|
|
@@ -521,15 +726,22 @@ def run_judge_api():
|
|
| 521 |
if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3
|
| 522 |
elif current_price >= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl < t['price'] else ("PERDU ❌", -5)
|
| 523 |
|
|
|
|
| 524 |
if outcome:
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
| 526 |
cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
|
| 527 |
-
closed_trades.append({"symbol": t['symbol'], "id": t['id']})
|
| 528 |
-
|
|
|
|
|
|
|
|
|
|
| 529 |
conn.commit()
|
| 530 |
return {"status": "updates", "data": closed_trades} if closed_trades else {"status": "waiting"}
|
| 531 |
-
except Exception as e:
|
| 532 |
-
|
| 533 |
def training_wrapper(symbol, *args): return trigger_training(str(symbol).strip().upper() if isinstance(symbol, str) else "BTC/USD")
|
| 534 |
def get_bot_skills():
|
| 535 |
try:
|
|
|
|
| 109 |
def init_db():
|
| 110 |
try:
|
| 111 |
with sqlite3.connect(DB_NAME) as conn:
|
| 112 |
+
# 1. Table des Signaux (Historique)
|
| 113 |
conn.execute('''CREATE TABLE IF NOT EXISTS signals (
|
| 114 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 115 |
date TEXT, symbol TEXT, timeframe TEXT, direction TEXT,
|
|
|
|
| 118 |
peak_price REAL, confirmed INTEGER DEFAULT 0)''')
|
| 119 |
|
| 120 |
cursor = conn.cursor()
|
| 121 |
+
|
| 122 |
+
# 2. Table de l'ADN (Logique des Agents)
|
| 123 |
+
# On vérifie si la table existe déjà pour éviter les conflits de structure
|
| 124 |
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='agent_logic'")
|
| 125 |
+
table_exists = cursor.fetchone()
|
| 126 |
+
|
| 127 |
+
if not table_exists:
|
| 128 |
+
conn.execute('''CREATE TABLE agent_logic (
|
| 129 |
+
symbol TEXT,
|
| 130 |
+
timeframe TEXT,
|
| 131 |
+
regime INTEGER, -- ⬅️ Ajouté ici
|
| 132 |
+
tp_mult REAL,
|
| 133 |
+
sl_mult REAL,
|
| 134 |
+
score REAL,
|
| 135 |
+
last_pnl REAL,
|
| 136 |
+
min_prob REAL,
|
| 137 |
+
min_tp_dist REAL,
|
| 138 |
+
generation INTEGER DEFAULT 1,
|
| 139 |
+
best_tp REAL,
|
| 140 |
+
best_sl REAL,
|
| 141 |
+
PRIMARY KEY (symbol, timeframe, regime))''') # ⬅️ CLÉ PRIMAIRE TRIPLE
|
| 142 |
+
|
| 143 |
+
# 3. Insertion des réglages par défaut
|
| 144 |
+
# On initialise les réglages sur le régime 3 (RANGE/CHAOS) par défaut
|
| 145 |
defaults = [
|
| 146 |
+
# Symbol, TF, Regime, TP, SL, Score, PNL, Prob, Dist, Gen, B_TP, B_SL
|
| 147 |
+
('ALL', '1m', 3, 1.0, 0.8, 0, 0, 0.65, 0.001, 1, 0, 0),
|
| 148 |
+
('ALL', '5m', 3, 1.2, 0.9, 0, 0, 0.62, 0.002, 1, 0, 0),
|
| 149 |
+
('ALL', '15m', 3, 1.5, 1.0, 0, 0, 0.60, 0.003, 1, 0, 0),
|
| 150 |
+
('ALL', '1h', 3, 2.0, 1.5, 0, 0, 0.55, 0.005, 1, 0, 0),
|
| 151 |
+
('ALL', '4h', 3, 3.0, 2.0, 0, 0, 0.50, 0.008, 1, 0, 0)
|
| 152 |
]
|
| 153 |
+
# On utilise 12 points d'interrogation pour les 12 colonnes
|
| 154 |
+
conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", defaults)
|
| 155 |
+
conn.commit()
|
| 156 |
+
print("✅ [DB] Table agent_logic initialisée avec Scénarios.")
|
| 157 |
+
else:
|
| 158 |
+
# Optionnel : Vérifier si la colonne regime existe déjà (pour les mises à jour sans tout supprimer)
|
| 159 |
+
cursor.execute("PRAGMA table_info(agent_logic)")
|
| 160 |
+
columns = [col[1] for col in cursor.fetchall()]
|
| 161 |
+
if 'regime' not in columns:
|
| 162 |
+
print("⚠️ [DB] Ancienne structure détectée. Suppression pour mise à jour...")
|
| 163 |
+
conn.execute("DROP TABLE agent_logic")
|
| 164 |
+
init_db() # On relance pour recréer proprement
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
print(f"❌ Erreur DB: {e}")
|
| 168 |
|
| 169 |
async def save_to_db(data):
|
| 170 |
try:
|
|
|
|
| 295 |
return df.dropna().copy()
|
| 296 |
except Exception as e: print(f"❌ Error Stats: {e}"); return pd.DataFrame()
|
| 297 |
|
| 298 |
+
import numpy as np
|
| 299 |
+
|
| 300 |
async def predict_signal(symbol, timeframe="1h"):
|
| 301 |
try:
|
| 302 |
memory_guard()
|
|
|
|
| 310 |
vol_spike = float(last_row['Vol_Spike'].iloc[0])
|
| 311 |
rsi_9 = float(last_row['RSI_9'].iloc[0])
|
| 312 |
|
| 313 |
+
# 🌊 1. COLLECTE DES DONNÉES (Flux, Sentiment, OI)
|
| 314 |
futures_data = await fetch_kucoin_futures_data(symbol)
|
| 315 |
oi, cvd = futures_data["oi"], futures_data["cvd"]
|
| 316 |
+
p_sent = await get_crypto_sentiment(symbol)
|
| 317 |
|
| 318 |
+
# 🧠 2. DÉTECTION DU SCÉNARIO (L'œil + L'IA)
|
| 319 |
+
# On calcule le régime via le modèle ML
|
| 320 |
regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
|
| 321 |
+
regime_ml = int(regime_model.predict(regime_scaled)[0])
|
| 322 |
|
| 323 |
+
# On calcule le pattern via ton nouveau détecteur (Continuation/Reversal)
|
| 324 |
+
pattern_id = detect_chart_scenario(df)
|
| 325 |
+
|
| 326 |
+
# Priorité : Si un pattern fort (Squeeze/Reversal) est vu, il devient le Maître
|
| 327 |
+
final_scenario = pattern_id if pattern_id in [4, 5] else regime_ml
|
| 328 |
+
|
| 329 |
+
# 🧠 3. CALCUL DES PROBABILITÉS IA
|
| 330 |
ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
|
| 331 |
ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1])
|
| 332 |
+
|
| 333 |
time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']
|
| 334 |
time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1])
|
| 335 |
|
|
|
|
| 337 |
if dino_brain:
|
| 338 |
try: dino_prob = float(dino_brain.predict(last_row[ml_cols].values)[0])
|
| 339 |
except: dino_prob = 0.5
|
|
|
|
|
|
|
| 340 |
|
| 341 |
+
# ⚡ 4. ENSEMBLE V30 (Poids dynamiques basés sur le scénario final)
|
| 342 |
+
final_p, wt, wm, wl, ws = combine_scores(symbol, timeframe, time_prob, ml_prob, dino_prob, p_sent, final_scenario)
|
| 343 |
|
| 344 |
+
# 🐋 5. BOOSTS (Smart Money & Scalping)
|
| 345 |
smc_status = "AUCUN"
|
| 346 |
+
# Boost Smart Money (CVD + Sweep)
|
| 347 |
if int(last_row["Sweep_Low"].iloc[0]) == 1 and cvd > 0:
|
| 348 |
+
final_p = min(0.95, final_p + 0.20); smc_status = "LONG SWEEP + CVD 🐋"
|
|
|
|
| 349 |
elif int(last_row["Sweep_High"].iloc[0]) == 1 and cvd < 0:
|
| 350 |
+
final_p = max(0.05, final_p - 0.20); smc_status = "SHORT SWEEP + CVD 🐋"
|
|
|
|
| 351 |
|
| 352 |
+
# Boost Scalping (Vol Spike)
|
| 353 |
if timeframe in ["1m", "5m"]:
|
| 354 |
if final_p > 0.5 and vol_spike > 1.5 and rsi_9 < 70: final_p = min(0.95, final_p + 0.10)
|
| 355 |
elif final_p < 0.5 and vol_spike > 1.5 and rsi_9 > 30: final_p = max(0.05, final_p - 0.10)
|
| 356 |
|
| 357 |
+
# 🛡️ 6. RÉCUPÉRATION ADN (Multi-Scénarios)
|
| 358 |
with sqlite3.connect(DB_NAME) as conn:
|
| 359 |
+
res = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?", (symbol, timeframe, final_scenario)).fetchone()
|
| 360 |
+
if not res:
|
| 361 |
+
# Fallback sur l'ADN 'ALL' par défaut
|
| 362 |
+
res_def = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
|
| 363 |
+
tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res_def if res_def else (1.5, 1.0, 0.55, 0.002)
|
| 364 |
+
# On initialise l'ADN pour ce nouveau scénario
|
| 365 |
+
conn.execute("INSERT OR IGNORE INTO agent_logic (symbol, timeframe, regime, tp_mult, sl_mult, min_prob, min_tp_dist, generation) VALUES (?, ?, ?, ?, ?, ?, ?, 1)", (symbol, timeframe, final_scenario, tp_m, sl_m, agent_min_prob, agent_min_tp_dist))
|
| 366 |
conn.commit()
|
| 367 |
+
else: tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res
|
| 368 |
|
| 369 |
+
# 📐 7. CALCUL DES NIVEAUX (TP/SL)
|
| 370 |
tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
|
| 371 |
sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
|
| 372 |
|
| 373 |
+
# 📊 Stats de confiance pour le dashboard
|
| 374 |
strength = abs(final_p - 0.5) * 2
|
| 375 |
conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, dino_prob, p_sent])))
|
| 376 |
+
composite_score = max(0, min(100, (strength * 45) + (conf_val * 40) + (15 if final_scenario in [0, 1] else 5)))
|
| 377 |
|
| 378 |
+
# 🛑 8. SYSTÈME DE VÉTO (Sécurité Maximale)
|
| 379 |
dist_tp_pct = abs(tp - prix) / prix
|
|
|
|
|
|
|
| 380 |
ema200_val = float(last_row["EMA200"].iloc[0])
|
| 381 |
+
mkt_trend = float(last_row["Market_Trend"].iloc[0])
|
| 382 |
+
dist_fib = float(last_row["Dist_Fib_618"].iloc[0])
|
| 383 |
+
|
| 384 |
+
veto, veto_reason = False, ""
|
| 385 |
if final_p < agent_min_prob and final_p > (1 - agent_min_prob): veto, veto_reason = True, f"Confiance ({round(final_p, 2)}) < {round(agent_min_prob, 2)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible"
|
| 387 |
+
elif final_p < 0.5 and (prix > ema200_val or prix > vwap): veto, veto_reason = True, "Short Interdit (Prix > Bull Lines)"
|
| 388 |
+
elif final_p > 0.5 and (prix < ema200_val or prix < vwap): veto, veto_reason = True, "Long Interdit (Prix < Bull Lines)"
|
| 389 |
+
elif "BTC" not in symbol and "ETH" not in symbol:
|
| 390 |
+
if final_p < 0.5 and mkt_trend > 0.002: veto, veto_reason = True, "Trend Global Haussier"
|
| 391 |
+
elif final_p > 0.5 and mkt_trend < -0.002: veto, veto_reason = True, "Trend Global Baissier"
|
| 392 |
|
| 393 |
if veto:
|
| 394 |
+
print(f"🛑 [VETO] {symbol} (Scénario {final_scenario}): {veto_reason}")
|
| 395 |
+
return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": veto_reason, "scenario": final_scenario}
|
| 396 |
|
| 397 |
+
# 💾 9. ENREGISTREMENT DB ET RÉPONSE
|
| 398 |
+
db_task = (datetime.now(timezone.utc).isoformat(), symbol, timeframe, 'HAUSSIER' if final_p > 0.5 else 'BAISSIER', final_p, prix, tp, sl, 'EN_COURS', final_scenario, time_prob, ml_prob, dino_prob, p_sent, prix)
|
| 399 |
await save_to_db(db_task)
|
| 400 |
|
| 401 |
return {
|
| 402 |
"symbol": symbol, "timeframe": timeframe, "status": "success", "final_score": round(final_p, 4), "score": int(composite_score),
|
| 403 |
+
"smart_money": smc_status, "price": prix, "tp": round(tp, 6), "sl": round(sl, 6), "scenario": final_scenario, "confluence": round(conf_val * 100, 1)
|
| 404 |
}
|
| 405 |
except Exception as e: return {"status": "error", "message": str(e)}
|
| 406 |
|
| 407 |
# --- 🧬 FONCTION D'ÉVOLUTION ---
|
| 408 |
+
def mutate_agent(symbol, timeframe, regime, success=True):
|
| 409 |
+
"""Mutation chirurgicale : Le bot apprend spécifiquement pour CE scénario"""
|
| 410 |
try:
|
| 411 |
with sqlite3.connect(DB_NAME) as conn:
|
| 412 |
conn.row_factory = sqlite3.Row
|
| 413 |
+
# On cherche l'ADN spécifique (Symbole + TF + Regime)
|
| 414 |
+
row = conn.execute("SELECT * FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?",
|
| 415 |
+
(symbol, timeframe, regime)).fetchone()
|
| 416 |
|
| 417 |
+
# Si le bot n'a jamais vu ce scénario, on crée une ligne à partir des défauts
|
| 418 |
+
if not row:
|
| 419 |
+
res_def = conn.execute("SELECT * FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
|
| 420 |
+
tp, sl, prob = (res_def['tp_mult'], res_def['sl_mult'], res_def['min_prob']) if res_def else (1.5, 1.0, 0.60)
|
| 421 |
+
else:
|
| 422 |
+
tp, sl, prob = row['tp_mult'], row['sl_mult'], row['min_prob']
|
| 423 |
+
|
| 424 |
+
# LOGIQUE DE MUTATION : Plus agressive si succès, plus prudente si échec
|
| 425 |
+
if success:
|
| 426 |
+
# On augmente le TP (On laisse courir les gains) et on réduit un peu le SL
|
| 427 |
+
new_tp, new_sl, new_prob = tp * random.uniform(1.02, 1.08), sl * random.uniform(0.97, 1.00), max(0.55, prob - 0.005)
|
| 428 |
+
else:
|
| 429 |
+
# On réduit le TP (Objectif plus proche) et on écarte le SL (Donner de l'air)
|
| 430 |
+
new_tp, new_sl, new_prob = tp * random.uniform(0.92, 0.98), sl * random.uniform(1.03, 1.12), min(0.85, prob + 0.01)
|
| 431 |
+
|
| 432 |
+
# Mise à jour ou Insertion
|
| 433 |
+
conn.execute('''INSERT OR REPLACE INTO agent_logic
|
| 434 |
+
(symbol, timeframe, regime, tp_mult, sl_mult, min_prob, min_tp_dist, generation)
|
| 435 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, COALESCE((SELECT generation FROM agent_logic WHERE symbol=? AND timeframe=? AND regime=?)+1, 1))''',
|
| 436 |
+
(symbol, timeframe, regime, new_tp, new_sl, new_prob, 0.001, symbol, timeframe, regime))
|
| 437 |
+
conn.commit()
|
| 438 |
|
|
|
|
| 439 |
backup_db_to_hf()
|
| 440 |
+
except Exception as e: print(f"🧬 Erreur Mutation Scénario : {e}")
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def detect_chart_scenario(df):
|
| 444 |
+
try:
|
| 445 |
+
df = df.copy().dropna()
|
| 446 |
+
|
| 447 |
+
# ========================
|
| 448 |
+
# DATA
|
| 449 |
+
# ========================
|
| 450 |
+
last = df.tail(100)
|
| 451 |
+
|
| 452 |
+
c = last['close'].values
|
| 453 |
+
h = last['high'].values
|
| 454 |
+
l = last['low'].values
|
| 455 |
+
v = last['volume'].values
|
| 456 |
+
|
| 457 |
+
ema200 = df['EMA200']
|
| 458 |
+
ema50 = df['EMA50']
|
| 459 |
+
|
| 460 |
+
# ========================
|
| 461 |
+
# 1. VOLATILITY / SQUEEZE
|
| 462 |
+
# ========================
|
| 463 |
+
range_start = np.mean(h[:20] - l[:20])
|
| 464 |
+
range_end = np.mean(h[-20:] - l[-20:])
|
| 465 |
+
|
| 466 |
+
squeeze = range_end < range_start * 0.65
|
| 467 |
+
|
| 468 |
+
# ========================
|
| 469 |
+
# 2. TREND (EMA + STRUCTURE)
|
| 470 |
+
# ========================
|
| 471 |
+
slope_ema200 = (ema200.iloc[-1] / ema200.iloc[-50]) - 1
|
| 472 |
+
slope_ema50 = (ema50.iloc[-1] / ema50.iloc[-20]) - 1
|
| 473 |
+
|
| 474 |
+
trend_strength = abs(slope_ema200) + abs(slope_ema50)
|
| 475 |
+
|
| 476 |
+
trend_up = slope_ema200 > 0 and slope_ema50 > 0
|
| 477 |
+
trend_down = slope_ema200 < 0 and slope_ema50 < 0
|
| 478 |
+
|
| 479 |
+
# ========================
|
| 480 |
+
# 3. BREAK OF STRUCTURE (BOS)
|
| 481 |
+
# ========================
|
| 482 |
+
recent_high = np.max(h[-20:])
|
| 483 |
+
prev_high = np.max(h[-40:-20])
|
| 484 |
+
|
| 485 |
+
recent_low = np.min(l[-20:])
|
| 486 |
+
prev_low = np.min(l[-40:-20])
|
| 487 |
+
|
| 488 |
+
bos_up = recent_high > prev_high
|
| 489 |
+
bos_down = recent_low < prev_low
|
| 490 |
+
|
| 491 |
+
# ========================
|
| 492 |
+
# 4. DOUBLE TOP / BOTTOM
|
| 493 |
+
# ========================
|
| 494 |
+
top_1, top_2 = np.max(h[-30:-15]), np.max(h[-15:])
|
| 495 |
+
low_1, low_2 = np.min(l[-30:-15]), np.min(l[-15:])
|
| 496 |
+
|
| 497 |
+
double_top = abs(top_1 - top_2) / top_1 < 0.004
|
| 498 |
+
double_bottom = abs(low_1 - low_2) / low_1 < 0.004
|
| 499 |
+
|
| 500 |
+
# ========================
|
| 501 |
+
# 5. RSI (DIVERGENCE)
|
| 502 |
+
# ========================
|
| 503 |
+
if 'RSI' in df.columns:
|
| 504 |
+
rsi = df['RSI'].values
|
| 505 |
+
|
| 506 |
+
rsi_high_1 = np.max(rsi[-30:-15])
|
| 507 |
+
rsi_high_2 = np.max(rsi[-15:])
|
| 508 |
+
|
| 509 |
+
rsi_low_1 = np.min(rsi[-30:-15])
|
| 510 |
+
rsi_low_2 = np.min(rsi[-15:])
|
| 511 |
+
|
| 512 |
+
bearish_div = top_2 >= top_1 and rsi_high_2 < rsi_high_1
|
| 513 |
+
bullish_div = low_2 <= low_1 and rsi_low_2 > rsi_low_1
|
| 514 |
+
else:
|
| 515 |
+
bearish_div = bullish_div = False
|
| 516 |
+
|
| 517 |
+
# ========================
|
| 518 |
+
# 6. VOLUME ANALYSIS
|
| 519 |
+
# ========================
|
| 520 |
+
vol_recent = np.mean(v[-15:])
|
| 521 |
+
vol_past = np.mean(v[-40:-15])
|
| 522 |
+
|
| 523 |
+
volume_increasing = vol_recent > vol_past * 1.2
|
| 524 |
+
volume_dropping = vol_recent < vol_past * 0.8
|
| 525 |
+
|
| 526 |
+
# ========================
|
| 527 |
+
# 7. MOMENTUM (PRICE SPEED)
|
| 528 |
+
# ========================
|
| 529 |
+
momentum = (c[-1] / c[-10]) - 1
|
| 530 |
+
|
| 531 |
+
# ========================
|
| 532 |
+
# DECISION LOGIC
|
| 533 |
+
# ========================
|
| 534 |
+
|
| 535 |
+
# 🔥 CONTINUATION (SQUEEZE + TREND + VOLUME BUILDUP)
|
| 536 |
+
if squeeze and trend_strength > 0.004 and volume_dropping:
|
| 537 |
+
return 4
|
| 538 |
+
|
| 539 |
+
# 🔴 REVERSAL (STRUCTURE + DIVERGENCE)
|
| 540 |
+
if (double_top or bearish_div) and trend_up:
|
| 541 |
+
return 5
|
| 542 |
+
|
| 543 |
+
if (double_bottom or bullish_div) and trend_down:
|
| 544 |
+
return 5
|
| 545 |
+
|
| 546 |
+
# 🟢 STRONG TREND
|
| 547 |
+
if trend_strength > 0.006 and volume_increasing:
|
| 548 |
+
return 0 if trend_up else 1
|
| 549 |
+
|
| 550 |
+
# 🟡 BREAKOUT (BOS + VOLUME)
|
| 551 |
+
if bos_up and volume_increasing:
|
| 552 |
+
return 0
|
| 553 |
+
|
| 554 |
+
if bos_down and volume_increasing:
|
| 555 |
+
return 1
|
| 556 |
+
|
| 557 |
+
# ⚪ RANGE / CHAOS
|
| 558 |
+
return 3
|
| 559 |
+
|
| 560 |
+
except Exception as e:
|
| 561 |
+
print("Error:", e)
|
| 562 |
+
return 3
|
| 563 |
|
| 564 |
# --- 🧠 TRAINING ENGINE ---
|
| 565 |
def trigger_training(symbol="BTC/USD"):
|
|
|
|
| 627 |
df = prepare_features_sync(symbol, timeframe=tf, limit_bars=200)
|
| 628 |
if df.empty or len(df) < 50: continue
|
| 629 |
|
| 630 |
+
# Simulation sur un point aléatoire du passé
|
| 631 |
idx = random.randint(10, len(df) - 30)
|
| 632 |
+
# On récupère la prédiction (qui contient maintenant le 'scenario')
|
| 633 |
pred = await predict_signal(symbol, timeframe=tf)
|
| 634 |
+
|
| 635 |
if pred.get("status") == "success":
|
| 636 |
+
# On récupère le scénario détecté pour la mutation
|
| 637 |
+
current_scenario = pred.get("scenario", 3)
|
| 638 |
+
|
| 639 |
future_data = df.iloc[idx+1 : idx+25]
|
| 640 |
win = False
|
| 641 |
for _, row in future_data.iterrows():
|
| 642 |
+
if pred["final_score"] > 0.5: # LONG
|
| 643 |
if row['high'] >= pred['tp']: win = True; break
|
| 644 |
if row['low'] <= pred['sl']: win = False; break
|
| 645 |
+
else: # SHORT
|
| 646 |
if row['low'] <= pred['tp']: win = True; break
|
| 647 |
if row['high'] >= pred['sl']: win = False; break
|
| 648 |
+
|
| 649 |
+
# ✅ CORRECTION ICI : On passe le scénario à la mutation
|
| 650 |
+
mutate_agent(symbol, tf, current_scenario, success=win)
|
| 651 |
+
|
| 652 |
await asyncio.sleep(3)
|
| 653 |
+
except Exception as e:
|
| 654 |
+
print(f"⚠️ Erreur Dream Loop: {e}")
|
| 655 |
await asyncio.sleep(60)
|
| 656 |
|
| 657 |
# --- ⚖️ TOOLS ---
|
|
|
|
| 679 |
try:
|
| 680 |
with sqlite3.connect(DB_NAME, timeout=10) as conn:
|
| 681 |
conn.row_factory, cursor = sqlite3.Row, conn.cursor()
|
| 682 |
+
# On récupère tout, y compris la colonne 'regime' enregistrée à l'ouverture
|
| 683 |
cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS' AND confirmed = 1")
|
| 684 |
trades = cursor.fetchall()
|
| 685 |
+
|
| 686 |
if not trades: return {"status": "waiting"}
|
| 687 |
closed_trades = []
|
| 688 |
+
|
| 689 |
for t in trades:
|
| 690 |
try:
|
| 691 |
sym_db, current_price = t['symbol'], None
|
| 692 |
+
# --- 1. RÉCUPÉRATION PRIX TEMPS RÉEL (MT5 ou KuCoin) ---
|
| 693 |
if MT5_AVAILABLE:
|
| 694 |
epic = sym_db.replace("/", "").replace("USDT", "USD")
|
| 695 |
if not epic.endswith("m"): epic += "m"
|
| 696 |
tick = mt5.symbol_info_tick(epic)
|
| 697 |
if tick: current_price = tick.last
|
| 698 |
+
|
| 699 |
if current_price is None:
|
| 700 |
f_sym = sym_db if "/USDT" in sym_db else sym_db.replace("/USD", "/USDT")
|
| 701 |
if "/" not in f_sym: f_sym += "/USDT"
|
| 702 |
current_price = exchange_sync.fetch_ticker(f_sym)['last']
|
| 703 |
|
| 704 |
+
# --- 2. CALCUL TRAILING STOP DYNAMIQUE ---
|
| 705 |
sl_dyn, peak = t['sl'], t['peak_price']
|
| 706 |
new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price)
|
| 707 |
progression = abs(current_price - t['price']) / abs(t['tp'] - t['price']) if abs(t['tp'] - t['price']) > 0 else 0
|
| 708 |
|
| 709 |
nouveau_sl = sl_dyn
|
| 710 |
+
# On sécurise les profits selon l'avancement vers le TP
|
| 711 |
if t['direction'] == 'HAUSSIER':
|
| 712 |
if progression >= 0.75: nouveau_sl = max(sl_dyn, t['price'] + (abs(t['tp'] - t['price']) * 0.60))
|
| 713 |
elif progression >= 0.50: nouveau_sl = max(sl_dyn, t['price'] + (abs(t['tp'] - t['price']) * 0.25))
|
|
|
|
| 716 |
elif progression >= 0.50: nouveau_sl = min(sl_dyn, t['price'] - (abs(t['tp'] - t['price']) * 0.25))
|
| 717 |
|
| 718 |
cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, nouveau_sl, t['id']))
|
| 719 |
+
|
| 720 |
+
# --- 3. VÉRIFICATION SORTIE (TP ou SL) ---
|
| 721 |
outcome, reward = None, 0
|
| 722 |
if t['direction'] == 'HAUSSIER':
|
| 723 |
if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3
|
|
|
|
| 726 |
if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3
|
| 727 |
elif current_price >= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl < t['price'] else ("PERDU ❌", -5)
|
| 728 |
|
| 729 |
+
# --- 4. MUTATION CHIRURGICALE (Le moment clé) ---
|
| 730 |
if outcome:
|
| 731 |
+
# ✅ ON PASSE LE RÉGIME SPÉCIFIQUE DU TRADE !
|
| 732 |
+
# Ça permet de muter l'ADN 'Triangle' si le trade était un 'Triangle'
|
| 733 |
+
mutate_agent(t['symbol'], t['timeframe'], t['regime'], success=(reward > 0))
|
| 734 |
+
|
| 735 |
cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
|
| 736 |
+
closed_trades.append({"symbol": t['symbol'], "id": t['id'], "outcome": outcome})
|
| 737 |
+
|
| 738 |
+
except Exception as inner_e:
|
| 739 |
+
print(f"⚠️ Erreur Juge sur {t['symbol']}: {inner_e}")
|
| 740 |
+
|
| 741 |
conn.commit()
|
| 742 |
return {"status": "updates", "data": closed_trades} if closed_trades else {"status": "waiting"}
|
| 743 |
+
except Exception as e:
|
| 744 |
+
return {"status": "error", "message": str(e)}
|
| 745 |
def training_wrapper(symbol, *args): return trigger_training(str(symbol).strip().upper() if isinstance(symbol, str) else "BTC/USD")
|
| 746 |
def get_bot_skills():
|
| 747 |
try:
|