Spaces:
Sleeping
Sleeping
File size: 54,126 Bytes
a0167b8 c683db9 63f3afd 7da1e20 c586553 09e3f0b 7da1e20 09e3f0b 7da1e20 67cc9b7 fef8ec7 f681884 9ea1c3d d9703fb 855ee5d fef8ec7 ebc5d12 fef8ec7 ebc5d12 eb8d75c 8910009 ebc5d12 eb8d75c f681884 fef8ec7 f681884 8d7b6e7 7da1e20 2a9b2f8 09ddf1e 7da1e20 fef8ec7 09e3f0b 7da1e20 c586553 7da1e20 c586553 09ddf1e 7da1e20 09ddf1e fef8ec7 0c9a77a 7da1e20 fef8ec7 7da1e20 e09af54 7da1e20 ebc5d12 cc0afe2 7da1e20 ebc5d12 62707c9 ebc5d12 4d4660b ebc5d12 4d4660b f7cbf17 1f0ed46 f7cbf17 af84323 f7cbf17 ebc5d12 7da1e20 bbf43f8 63fc3a4 bbf43f8 8d7b6e7 1c8e8f7 fef8ec7 bbf43f8 63fc3a4 fef8ec7 63fc3a4 fef8ec7 63fc3a4 ce70583 d731e20 09ddf1e ebc5d12 e09af54 ebc5d12 2d31516 ebc5d12 2d31516 7da1e20 09ddf1e c586553 09ddf1e fef8ec7 7da1e20 c586553 7da1e20 eb8d75c fef8ec7 637c398 fef8ec7 e09af54 c093da6 e09af54 fef8ec7 e09af54 09d31a7 67cc9b7 d179db1 7da1e20 33c1409 8602c3d 0c9a77a 33c1409 09ddf1e ebc5d12 9ea1c3d fef8ec7 1c8e8f7 09ddf1e fef8ec7 33c1409 fef8ec7 eb8d75c c586553 7da1e20 04fa252 1c8e8f7 7da1e20 eb8d75c 7da1e20 eb8d75c 7da1e20 1c8e8f7 8602c3d fef8ec7 1c8e8f7 eb8d75c 1c8e8f7 8d7b6e7 1c8e8f7 e9ab125 63fc3a4 581a042 c17d9c4 581a042 c17d9c4 581a042 c17d9c4 7da1e20 09d31a7 09ddf1e 7da1e20 52f5775 fef8ec7 b107e51 7da1e20 33caa7b eb8d75c b107e51 63fc3a4 fef8ec7 63fc3a4 eb8d75c 581a042 610c015 581a042 610c015 63fc3a4 7da1e20 63fc3a4 eb8d75c 581a042 63fc3a4 fef8ec7 f7cbf17 610c015 63fc3a4 33c1409 63fc3a4 c8cf459 fef8ec7 63fc3a4 fef8ec7 63fc3a4 fef8ec7 eb8d75c 5b7fa0f 33caa7b 63fc3a4 fef8ec7 5b7fa0f eb8d75c 63fc3a4 fef8ec7 7a37225 fef8ec7 63fc3a4 7a37225 610c015 1c8e8f7 fef8ec7 63fc3a4 110b50c 63fc3a4 fef8ec7 63fc3a4 1f0ed46 63fc3a4 d6da76d 21a5372 63fc3a4 21a5372 e1fcbaa 610c015 63fc3a4 9e6a403 110b50c 2334f97 09ddf1e fef8ec7 63fc3a4 09ddf1e fef8ec7 f1b57fe 5b7fa0f 63fc3a4 e1fcbaa 5b7fa0f e1fcbaa 5b7fa0f e1fcbaa 5b7fa0f 63fc3a4 de4f4ea 610c015 de4f4ea 610c015 de4f4ea 610c015 de4f4ea 0eccbfd a0bfc72 de4f4ea e34f612 de4f4ea 610c015 63fc3a4 3a52629 e1fcbaa 3a52629 0c9a77a 3a52629 0c9a77a 3a52629 1984e8d 406c49f 63fc3a4 406c49f 99c64e0 406c49f 610c015 ed2b570 d40ef29 a452727 610c015 d40ef29 610c015 1984e8d a452727 581a042 a452727 610c015 406c49f 99c64e0 406c49f a452727 63fc3a4 a452727 1984e8d a452727 1984e8d a452727 1984e8d a452727 1984e8d a452727 610c015 d40ef29 e237498 1984e8d e237498 610c015 d40ef29 1984e8d 610c015 a452727 610c015 a452727 63fc3a4 610c015 d40ef29 1984e8d ed2b570 610c015 63fc3a4 610c015 1984e8d a452727 1984e8d 610c015 1984e8d d731e20 406c49f d731e20 406c49f 425ff28 d731e20 425ff28 d731e20 425ff28 406c49f 610c015 63fc3a4 eb0086b 2a6e841 3042372 f7cbf17 3042372 25d8a90 f7cbf17 25d8a90 fef8ec7 f7cbf17 fef8ec7 eb0086b ebc5d12 f7cbf17 ebc5d12 f7cbf17 4d4660b 32f043d 4d4660b f7cbf17 4d4660b f7cbf17 4d4660b f7cbf17 7da1e20 f7cbf17 7da1e20 f7cbf17 8d7b6e7 ebc5d12 1f0ed46 581a042 5fd7ba5 fef8ec7 ebc5d12 fef8ec7 ebc5d12 5fd7ba5 b2d04db 2d31516 b2d04db 2d31516 b2d04db 634ed16 2d31516 b2d04db ebc5d12 b2d04db ebc5d12 2d31516 ebc5d12 2d31516 ebc5d12 63f3afd ebc5d12 fef8ec7 09f7d3b ce70583 fef8ec7 ebc5d12 09f7d3b eb8d75c ebc5d12 ce70583 110b50c eb8d75c ebc5d12 8308461 ebc5d12 eb8d75c fef8ec7 1af8dac 8602c3d ebc5d12 c4ae83a 7db3461 1ba8caf fef8ec7 7da1e20 ebc5d12 4316814 1af8dac 4316814 581a042 4316814 7da1e20 406c49f eb8d75c fef8ec7 4316814 fef8ec7 110b50c 1f25aba eb8d75c 4316814 fef8ec7 eb8d75c 8b668a0 f1b57fe 38b7d8f 184fa54 fef8ec7 184fa54 fef8ec7 f1b57fe ebc5d12 67821f2 fef8ec7 ebc5d12 fef8ec7 38b7d8f fef8ec7 ebc5d12 184fa54 f1b57fe 38b7d8f 81bcc3c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 | # --- 🏴☠️ MONKEY PATCH & IMPORTS --
import huggingface_hub
try:
from huggingface_hub import HfFolder
except ImportError:
class MockHfFolder:
@staticmethod
def get_token(): return None
@staticmethod
def save_token(token): pass
@staticmethod
def delete_token(): pass
huggingface_hub.HfFolder = MockHfFolder
import sys, os, sqlite3, shutil, pandas as pd, asyncio, joblib, json, numpy as np, gc, warnings, psutil
import ccxt.async_support as ccxt_async
from datetime import datetime, timezone
from huggingface_hub import HfApi, hf_hub_download
import gradio as gr
import ccxt
import time
import random
import aiohttp
from types import ModuleType
import yfinance as yf
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="sklearn")
# --- 🦖 IMPORT LIGHTGBM ---
try:
import lightgbm as lgb
LGBM_AVAILABLE = True
except ImportError:
LGBM_AVAILABLE = False
print("⚠️ LightGBM non installé.")
try:
import MetaTrader5 as mt5
MT5_AVAILABLE = True
except ImportError:
MT5_AVAILABLE = False
print("🌐 [CLOUD MODE] MetaTrader5 non détecté.")
# --- 🥷 NINJA HACK : MOCK PANDAS_TA ---
if "pandas_ta" not in sys.modules:
mock_ta = ModuleType("pandas_ta")
sys.modules["pandas_ta"] = mock_ta
if not hasattr(pd.DataFrame, "ta"):
class FakeTA:
def __getattr__(self, name): return lambda *args, **kwargs: None
pd.DataFrame.ta = property(lambda self: FakeTA())
# --- 🛑 ANTI-CRASH & CPU OPTIMIZATION ---
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
import tensorflow as tf
from tensorflow.keras import backend as K
tf.config.threading.set_intra_op_parallelism_threads(2)
tf.config.threading.set_inter_op_parallelism_threads(2)
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path: sys.path.append(current_dir)
# --- SINGLETON EXCHANGE ---
class ExchangeManager:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = ccxt_async.kucoin({"enableRateLimit": True, "timeout": 30000})
return cls._instance
exchange_sync = ccxt.kucoin({"enableRateLimit": True, "timeout": 30000})
market_cache, last_fetch_time = {}, {}
# 🛡️ FIX : Cache dynamique selon la timeframe pour éviter l'amnésie
def get_cache_ttl(tf):
if tf == '1m': return 20 # 20 secondes de cache pour le 1m
if tf == '5m': return 60 # 1 minute pour le 5m
if tf == '15m': return 180 # 3 minutes pour le 15m
return 300 # 5 minutes pour le 1h et 4h
DREAM_MODE_ACTIVE = True
try:
from sentiment_engine import get_crypto_sentiment
except:
async def get_crypto_sentiment(symbol): return 0.5
try:
from ensemble import combine_scores
except:
def combine_scores(symbol, timeframe, t, m, l, sent, r):
# Formule PRO : 30% Time, 30% ML, 30% LightGBM, 10% Sentiment
score = (t * 0.30) + (m * 0.30) + (l * 0.30) + (sent * 0.10)
return score, 0.30, 0.30, 0.30, 0.10
# --- DB, SYNC & HUGGING FACE BACKUP ---
DB_NAME = "alphatrade_v31_dino.db"
HF_TOKEN = os.environ.get("HF_TOKEN")
HF_REPO_ID = "Nexo-S/AlphaTrade-DB"
def restore_db_from_hf():
if not HF_TOKEN: return
# 1. Tentative de restauration de l'ADN (DB)
try:
print("🔄 [SYSTEM] Restauration ADN depuis HF...")
file_path = hf_hub_download(repo_id=HF_REPO_ID, filename=DB_NAME, repo_type="dataset", token=HF_TOKEN)
shutil.copy(file_path, DB_NAME)
print("✅ [RESTORE] ADN récupéré !")
except Exception as e:
print(f"⚠️ Aucun backup ADN trouvé (1er lancement) : {e}")
# 2. Tentative de restauration du modèle DINO
# 2. Tentative de restauration des modèles DINO (Multiples)
global dino_brains
dino_brains = {}
for crypto in ["BTC", "ETH", "SOL"]:
filename = f"dino_lgbm_{crypto}.txt"
try:
print(f"🔄 [SYSTEM] Restauration du DINO {crypto} depuis HF...")
file_path = hf_hub_download(repo_id=HF_REPO_ID, filename=filename, repo_type="dataset", local_dir=".", token=HF_TOKEN)
# 🛡️ LE VACCIN EST ICI : On vérifie que le fichier pèse plus de 100 octets (qu'il n'est pas vide)
if os.path.exists(file_path) and os.path.getsize(file_path) > 100:
dino_brains[crypto] = lgb.Booster(model_file=file_path)
print(f"✅ [RESTORE] Modèle Dino {crypto} récupéré et chargé !")
else:
print(f"⚠️ [ALERTE] Fichier {filename} vide ou corrompu. Mode aveugle activé pour {crypto}.")
except Exception as e:
print(f"⚠️ Aucun modèle trouvé pour {crypto}. Pense à Forcer l'Entraînement de {crypto}.")
def backup_db_to_hf():
if not HF_TOKEN: return
try:
api = HfApi()
api.upload_file(path_or_fileobj=DB_NAME, path_in_repo=DB_NAME, repo_id=HF_REPO_ID, repo_type="dataset", token=HF_TOKEN)
print("☁️ [BACKUP] ADN sauvegardé sur Hugging Face !")
except Exception as e: print(f"❌ Erreur Backup HF : {e}")
def init_db():
try:
with sqlite3.connect(DB_NAME) as conn:
# 1. Table des Signaux (Historique)
conn.execute('''CREATE TABLE IF NOT EXISTS signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT, symbol TEXT, timeframe TEXT, direction TEXT,
prob REAL, price REAL, tp REAL, sl REAL, status TEXT,
regime INTEGER, prob_time REAL, prob_ml REAL, prob_lstm REAL, prob_sent REAL,
peak_price REAL, confirmed INTEGER DEFAULT 0)''')
cursor = conn.cursor()
# 2. Table de l'ADN (Logique des Agents)
# On vérifie si la table existe déjà pour éviter les conflits de structure
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='agent_logic'")
table_exists = cursor.fetchone()
if not table_exists:
conn.execute('''CREATE TABLE agent_logic (
symbol TEXT,
timeframe TEXT,
regime INTEGER, -- ⬅️ Ajouté ici
tp_mult REAL,
sl_mult REAL,
score REAL,
last_pnl REAL,
min_prob REAL,
min_tp_dist REAL,
generation INTEGER DEFAULT 1,
best_tp REAL,
best_sl REAL,
PRIMARY KEY (symbol, timeframe, regime))''') # ⬅️ CLÉ PRIMAIRE TRIPLE
# 3. Insertion des réglages par défaut
# On initialise les réglages sur le régime 3 (RANGE/CHAOS) par défaut
defaults = [
# Symbol, TF, Regime, TP, SL, Score, PNL, Prob, Dist, Gen, B_TP, B_SL
('ALL', '15m', 3, 1.5, 1.0, 0, 0, 0.60, 0.003, 1, 0, 0),
('ALL', '1h', 3, 2.0, 1.5, 0, 0, 0.55, 0.005, 1, 0, 0),
('ALL', '4h', 3, 3.0, 2.0, 0, 0, 0.50, 0.008, 1, 0, 0)
]
# On utilise 12 points d'interrogation pour les 12 colonnes
conn.executemany("INSERT INTO agent_logic VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", defaults)
conn.commit()
print("✅ [DB] Table agent_logic initialisée avec Scénarios.")
else:
# Optionnel : Vérifier si la colonne regime existe déjà (pour les mises à jour sans tout supprimer)
cursor.execute("PRAGMA table_info(agent_logic)")
columns = [col[1] for col in cursor.fetchall()]
if 'regime' not in columns:
print("⚠️ [DB] Ancienne structure détectée. Suppression pour mise à jour...")
conn.execute("DROP TABLE agent_logic")
init_db() # On relance pour recréer proprement
except Exception as e:
print(f"❌ Erreur DB: {e}")
import asyncio
# --- 💾 SAUVEGARDE BLINDÉE (ANTI-EMBOUTEILLAGE) ---
async def save_to_db(data_tuple):
for tentative in range(5): # Le Cerveau va insister 5 fois si la porte est fermée
try:
with sqlite3.connect(DB_NAME, timeout=20) as conn:
conn.execute('''INSERT INTO signals
(date, symbol, timeframe, direction, prob, price, tp, sl, status, regime, prob_time, prob_ml, prob_lstm, prob_sent, peak_price)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', data_tuple)
conn.commit()
return # Succès, on quitte la fonction
except sqlite3.OperationalError as e:
if "locked" in str(e):
await asyncio.sleep(1) # La base est lue par le Radar, on attend 1 seconde
else:
print(f"❌ [ERREUR DB] Sauvegarde échouée : {e}")
break
# 🚀 INITIALISATION
restore_db_from_hf()
init_db()
# --- 🛡️ STATE MANAGER ---
active_signals_state = {}
last_signals_sent = {} # ⬅️ LA RUSTINE CRITIQUE EST LÀ
def clean_old_db_signals(symbol, timeframe):
try:
with sqlite3.connect(DB_NAME, timeout=10) as conn:
# 🛡️ LA SÉCURITÉ : On ne touche qu'aux signaux NON-CONFIRMÉS (Les fantômes)
# Et on retire le "confirmed = 0" du SET (il ne faut pas dé-confirmer un trade)
conn.execute("UPDATE signals SET status = 'REMPLACÉ ♻️' WHERE symbol = ? AND timeframe = ? AND status = 'EN_COURS' AND confirmed = 0", (symbol, timeframe))
conn.commit()
except Exception as e:
print(f"⚠️ Erreur nettoyage DB : {e}")
def memory_guard():
if psutil.virtual_memory().percent > 80:
K.clear_session()
gc.collect()
# --- 🛠️ MOTEUR MATHS ---
def get_ema(series, period): return series.ewm(span=period, adjust=False).mean()
def get_rsi(series, period=14):
delta = series.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
return 100 - (100 / (1 + (gain / (loss + 1e-9))))
def get_atr(df, period=14):
h_l = df['high'] - df['low']
h_c = (df['high'] - df['close'].shift()).abs()
l_c = (df['low'] - df['close'].shift()).abs()
return pd.concat([h_l, h_c, l_c], axis=1).max(axis=1).rolling(period).mean()
def get_vwap(df):
v = df['vol']
tp = (df['high'] + df['low'] + df['close']) / 3
return (tp * v).cumsum() / (v.cumsum() + 1e-9)
# --- 🧠 CHARGEMENT IA ---
try:
ml_model = joblib.load("ml_model_v9.pkl")
time_model = joblib.load("time_model.pkl")
regime_model = joblib.load("regime_model.pkl")
regime_scaler = joblib.load("regime_scaler.pkl")
except Exception as e: print(f"⚠️ Erreur IA Classique : {e}")
try:
dino_brain = lgb.Booster(model_file='dino_lgbm_model.txt') if LGBM_AVAILABLE else None
except:
dino_brain = None
# --- 🌐 HTTP SESSION MANAGER ---
global_aio_session = None
async def get_aio_session():
global global_aio_session
if global_aio_session is None or global_aio_session.closed:
# On force un connecteur limité pour éviter les fuites de mémoire (Max 100 connexions)
connector = aiohttp.TCPConnector(limit=100)
global_aio_session = aiohttp.ClientSession(connector=connector)
return global_aio_session
async def fetch_kucoin_futures_data(symbol):
try:
ku_sym = symbol.replace("/", "").replace("BTC", "XBT") + "M"
session = await get_aio_session() # On utilise la session globale ouverte
oi_url = f"https://api-futures.kucoin.com/api/v1/open-interest?symbol={ku_sym}"
trade_url = f"https://api-futures.kucoin.com/api/v1/trade/history?symbol={ku_sym}"
async with session.get(oi_url) as r1, session.get(trade_url) as r2:
oi_json = await r1.json()
trades = (await r2.json()).get("data", [])
cvd = sum([float(t.get("size", 0)) if t.get("side") == "buy" else -float(t.get("size", 0)) for t in trades])
return {"oi": float(oi_json.get("data", {}).get("value", 0)), "cvd": cvd}
except Exception as e:
print(f"⚠️ Erreur Flux KuCoin ({symbol}) : {e}")
return {"oi": 0, "cvd": 0}
def prepare_features_sync(symbol, timeframe='1h', limit_bars=600):
try:
now = datetime.now().timestamp()
cache_key = f"{symbol}_{timeframe}"
# 🛡️ FIX : On utilise le TTL dynamique ici
ttl = get_cache_ttl(timeframe)
if cache_key in market_cache and now - last_fetch_time.get(cache_key, 0) < ttl:
df = market_cache[cache_key].copy()
else:
fetch_symbol = symbol if "/USDT" in symbol else symbol.replace("/USD", "/USDT")
if "/" not in fetch_symbol: fetch_symbol += "/USDT"
df = pd.DataFrame()
for attempt in range(3):
try:
bars = exchange_sync.fetch_ohlcv(fetch_symbol, timeframe, limit=limit_bars)
df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
if not df.empty: break
except: time.sleep(2)
if df.empty: return pd.DataFrame()
market_cache[cache_key], last_fetch_time[cache_key] = df.copy(), now
if len(df) < 50: return pd.DataFrame()
df["RSI"], df["RSI_9"] = get_rsi(df["close"]), get_rsi(df["close"], 9)
df["EMA50"], df["EMA200"] = get_ema(df["close"], 50), get_ema(df["close"], 200)
df["VWAP"] = get_vwap(df)
df["ATR"] = get_atr(df)
df["ATR_pct"] = (df["ATR"] / df["close"]) * 100
df["EMA200_slope"] = (df["EMA200"] / df["EMA200"].shift(10)) - 1
df["Drawdown"] = (df["close"] / df["close"].rolling(14).max()) - 1
df["High_24h"], df["Low_24h"] = df["high"].rolling(24).max(), df["low"].rolling(24).min()
df["Dist_High_24h"] = (df["High_24h"] - df["close"]) / df["close"]
df["Dist_Low_24h"] = (df["close"] - df["Low_24h"]) / df["close"]
df["EMA_dist"] = (df["close"] - df["EMA50"]) / df["EMA50"]
df["EMA_slope"] = (df["EMA50"] / df["EMA50"].shift(5)) - 1
df["Price_vs_VWAP"] = (df["close"] - df["VWAP"]) / df["VWAP"]
df["ATR_ratio"] = df["ATR"] / df["close"]
df["VOL_ratio"] = df["vol"] / (df["vol"].rolling(24).mean() + 1e-9)
df["Vol_Spike"] = df["vol"] / (df["vol"].rolling(5).mean() + 1e-9)
diff = df["High_24h"] - df["Low_24h"]
df["Fib_618"] = df["Low_24h"] + (diff * 0.618)
df["Dist_Fib_618"] = (df["close"] - df["Fib_618"]) / df["close"]
df["Market_Trend"] = df["EMA200_slope"]
p_low, p_high = df["low"].rolling(24).min().shift(1), df["high"].rolling(24).max().shift(1)
df["Sweep_Low"] = ((df["low"] < p_low) & (df["close"] > p_low)).astype(int)
df["Sweep_High"] = ((df["high"] > p_high) & (df["close"] < p_high)).astype(int)
df['return_1h'], df['return_3h'], df['return_12h'] = df['close'].pct_change(1), df['close'].pct_change(3), df['close'].pct_change(12)
df['RSI_lag1'], df['RSI_lag2'] = df["RSI"].shift(1), df["RSI"].shift(2)
df['VOL_RATIO'] = df['vol'] / (df['vol'].rolling(20).mean() + 1e-9)
df['vol_lag1'], df['RSI_Macro'] = df['vol'].shift(1), df["RSI"]
return df.dropna().copy()
except Exception as e: print(f"❌ Error Stats: {e}"); return pd.DataFrame()
import numpy as np
def detect_chart_scenario(df, df_15m=None, df_1h=None, df_4h=None):
try:
# 🛠️ Fix colonnes KuCoin
if 'vol' in df.columns and 'volume' not in df.columns:
df = df.rename(columns={'vol': 'volume'})
df = df.copy().dropna()
if len(df) < 100: return 3
last = df.tail(100)
c, h, l, v = last['close'].values, last['high'].values, last['low'].values, last['volume'].values
ema200, ema50 = df['EMA200'], df['EMA50']
# --- 1. VWAP INSTITUTIONNEL (Sécurisé) ---
typical_price = (df['high'] + df['low'] + df['close']) / 3
vwap = (typical_price * df['volume']).cumsum() / (df['volume'].cumsum() + 1e-9)
price_above_vwap = df['close'].iloc[-1] > vwap.iloc[-1]
price_below_vwap = df['close'].iloc[-1] < vwap.iloc[-1]
# ========================
# 🔥 VWAP BANDS (Institutional zones)
# ========================
vwap_std = df['close'].rolling(20).std()
vwap_upper = vwap + vwap_std
vwap_lower = vwap - vwap_std
price_extended_high = df['close'].iloc[-1] > vwap_upper.iloc[-1]
price_extended_low = df['close'].iloc[-1] < vwap_lower.iloc[-1]
# --- 2. MULTI TIMEFRAME MACRO (15m, 1h, 4h) ---
mtf_bull = mtf_bear = 0
def analyze_tf(tf_df):
if tf_df is None or len(tf_df) < 50 or 'EMA200' not in tf_df.columns: return 0
slope = (tf_df['EMA200'].iloc[-1] / tf_df['EMA200'].iloc[-20]) - 1
return 1 if slope > 0.0001 else (-1 if slope < -0.0001 else 0)
for tf_data in [df_15m, df_1h, df_4h]:
res = analyze_tf(tf_data)
if res == 1: mtf_bull += 1
elif res == -1: mtf_bear += 1
# ========================
# 🔥 ORDER BLOCK STRENGTH
# ========================
last_candles = df.tail(20)
bullish_ob = False
bearish_ob = False
ob_strength = 0
for i in range(len(last_candles) - 3):
c1 = last_candles.iloc[i]
c2 = last_candles.iloc[i + 1]
body_size = abs(c2['close'] - c2['open'])
candle_range = c2['high'] - c2['low'] + 1e-9
strength = body_size / candle_range
if c1['close'] < c1['open'] and c2['close'] > c2['open']:
bullish_ob = True
ob_strength = max(ob_strength, strength)
if c1['close'] > c1['open'] and c2['close'] < c2['open']:
bearish_ob = True
ob_strength = max(ob_strength, strength)
# --- 4. LIQUIDITY SWEEP ---
recent_high, prev_high = np.max(h[-10:]), np.max(h[-30:-10])
recent_low, prev_low = np.min(l[-10:]), np.min(l[-30:-10])
liquidity_sweep_high = recent_high > prev_high and c[-1] < recent_high
liquidity_sweep_low = recent_low < prev_low and c[-1] > recent_low
liquidity_high_zone = np.mean(h[-50:])
liquidity_low_zone = np.mean(l[-50:])
near_liquidity_high = abs(c[-1] - liquidity_high_zone) / c[-1] < 0.002
near_liquidity_low = abs(c[-1] - liquidity_low_zone) / c[-1] < 0.002
# --- 5. STRUCTURE ET RSI ---
range_start, range_end = np.mean(h[:20] - l[:20]), np.mean(h[-20:] - l[-20:])
squeeze = range_end < range_start * 0.65
slope_ema200 = (ema200.iloc[-1] / ema200.iloc[-50]) - 1
slope_ema50 = (ema50.iloc[-1] / ema50.iloc[-20]) - 1
trend_strength = abs(slope_ema200) + abs(slope_ema50)
trend_up, trend_down = (slope_ema200 > 0 and slope_ema50 > 0), (slope_ema200 < 0 and slope_ema50 < 0)
bos_up = np.max(h[-20:]) > np.max(h[-40:-20])
bos_down = np.min(l[-20:]) < np.min(l[-40:-20])
# ========================
# 🔥 FAKE BREAKOUT FILTER (Déplacé ici pour corriger le bug !)
# ========================
fake_breakout_up = bos_up and c[-1] < recent_high
fake_breakout_down = bos_down and c[-1] > recent_low
bullish_div = bearish_div = False
if 'RSI' in df.columns:
rsi = df['RSI'].values
bearish_div = np.max(h[-15:]) >= np.max(h[-30:-15]) and np.max(rsi[-15:]) < np.max(rsi[-30:-15])
bullish_div = np.min(l[-15:]) <= np.min(l[-30:-15]) and np.min(rsi[-15:]) > np.min(rsi[-30:-15])
vol_recent, vol_past = np.mean(v[-15:]), np.mean(v[-40:-15])
vol_inc = vol_recent > vol_past * 1.2
# 🔥 CONTINUATION PRO MAX
if squeeze and trend_strength > 0.004 and price_above_vwap and mtf_trend_up and not price_extended_high:
return 4
# 🔴 REVERSAL SMART MONEY
if (liquidity_sweep_high or bearish_ob or bearish_div or price_extended_high or near_liquidity_high) and trend_up:
return 5
if (liquidity_sweep_low or bullish_ob or bullish_div or price_extended_low or near_liquidity_low) and trend_down:
return 5
# 🟢 TREND INSTITUTIONNEL
if trend_strength > 0.006 and vol_inc and ob_strength > 0.5:
if price_above_vwap and mtf_trend_up and not fake_breakout_up:
return 0
if price_below_vwap and mtf_trend_down and not fake_breakout_down:
return 1
# 🟡 BREAKOUT PROPRE
if vol_inc and not fake_breakout_up and bos_up and price_above_vwap:
return 0
if vol_inc and not fake_breakout_down and bos_down and price_below_vwap:
return 1
return 3 # RANGE / CHAOS par défaut
except Exception as e:
print(f"⚠️ Erreur Detect Scenario: {e}")
return 3
async def predict_signal(symbol, timeframe="1h"):
try:
memory_guard()
symbol = str(symbol).strip().upper()
df = prepare_features_sync(symbol, timeframe)
if df.empty: return {"status": "error", "message": "Data insuffisante"}
last_row = df.iloc[[-1]]
prix, atr = float(last_row['close'].iloc[0]), float(last_row['ATR'].iloc[0])
vwap = float(last_row['VWAP'].iloc[0])
vol_spike = float(last_row['Vol_Spike'].iloc[0])
rsi_9 = float(last_row['RSI_9'].iloc[0])
# 🌊 1. COLLECTE DES DONNÉES (Flux, Sentiment, OI)
futures_data = await fetch_kucoin_futures_data(symbol)
oi, cvd = futures_data["oi"], futures_data["cvd"]
p_sent = await get_crypto_sentiment(symbol)
# 🏗️ 1.5 CHARGEMENT MULTI-TIMEFRAME MACRO
df_15m = prepare_features_sync(symbol, "15m", limit_bars=150)
df_1h = prepare_features_sync(symbol, "1h", limit_bars=150)
df_4h = prepare_features_sync(symbol, "4h", limit_bars=150)
# 🧠 2. DÉTECTION DU SCÉNARIO (L'œil + L'IA)
regime_scaled = regime_scaler.transform(last_row[["ATR_pct", "EMA200_slope", "Drawdown", "RSI_Macro"]])
regime_ml = int(regime_model.predict(regime_scaled)[0])
# On passe les dataframes MTF à la fonction
pattern_id = detect_chart_scenario(df, df_15m, df_1h, df_4h)
final_scenario = pattern_id if pattern_id in [4, 5] else regime_ml
# 🧠 3. CALCUL DES PROBABILITÉS IA
ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
ml_prob = float(ml_model.predict_proba(last_row[ml_cols])[0][1])
time_cols = ['return_1h', 'return_3h', 'return_12h', 'RSI_lag1', 'RSI_lag2', 'vol_lag1', 'VOL_RATIO']
time_prob = float(time_model.predict_proba(last_row[time_cols])[0][1])
# 🧠 SÉLECTION DU CERVEAU DINO (Schizophrénie)
sym_clean = symbol.replace("/", "").replace("USDT", "").replace("USD", "").upper()
if 'dino_brains' in globals() and sym_clean in dino_brains:
dino_prob = float(dino_brains[sym_clean].predict(last_row[ml_cols].values)[0])
elif 'dino_brain' in globals() and dino_brain: # Sécurité : Fallback sur l'ancien modèle si besoin
dino_prob = float(dino_brain.predict(last_row[ml_cols].values)[0])
else:
dino_prob = 0.5 # Si aucun cerveau n'est entraîné, on reste neutre
# ⚡ 4. ENSEMBLE V30
final_p, wt, wm, wl, ws = combine_scores(symbol, timeframe, time_prob, ml_prob, dino_prob, p_sent, final_scenario)
# 🐋 5. BOOSTS (Smart Money & Scalping)
smc_status = "AUCUN"
if int(last_row["Sweep_Low"].iloc[0]) == 1 and cvd > 0:
final_p = min(0.95, final_p + 0.20); smc_status = "LONG SWEEP + CVD 🐋"
elif int(last_row["Sweep_High"].iloc[0]) == 1 and cvd < 0:
final_p = max(0.05, final_p - 0.20); smc_status = "SHORT SWEEP + CVD 🐋"
if timeframe in ["1m", "5m"]:
if final_p > 0.5 and vol_spike > 1.5 and rsi_9 < 70: final_p = min(0.95, final_p + 0.10)
elif final_p < 0.5 and vol_spike > 1.5 and rsi_9 > 30: final_p = max(0.05, final_p - 0.10)
# 🛡️ 6. RÉCUPÉRATION ADN (Multi-Scénarios) + BOUCLIER V32
with sqlite3.connect(DB_NAME) as conn:
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()
if not res:
res_def = conn.execute("SELECT tp_mult, sl_mult, min_prob, min_tp_dist FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?", (timeframe,)).fetchone()
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)
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))
conn.commit()
else:
tp_m, sl_m, agent_min_prob, agent_min_tp_dist = res
tp_m, sl_m = safe_agent_values(tp_m, sl_m)
# 🔥 LE PATCH V32 (ANTI-CORRUPTION) EST LÀ :
if sl_m > 5.0 or tp_m > 5.0:
print(f"🚨 [V32] CORRUPTION DETECTÉE SUR {symbol} (SLx{round(sl_m,1)}) → NEUTRALISATION")
tp_m, sl_m = 1.5, 1.0 # On force le reset local
else:
# On bride les valeurs par sécurité extrême
tp_m = float(np.clip(tp_m, 0.5, 3.0))
sl_m = float(np.clip(sl_m, 0.5, 2.0))
# 📐 7. CALCUL DES NIVEAUX (TP/SL)
tp = prix + (atr * tp_m) if final_p > 0.5 else prix - (atr * tp_m)
sl = prix - (atr * sl_m) if final_p > 0.5 else prix + (atr * sl_m)
strength = abs(final_p - 0.5) * 2
conf_val = max(0, min(1, 1 - np.std([time_prob, ml_prob, dino_prob, p_sent])))
composite_score = max(0, min(100, (strength * 45) + (conf_val * 40) + (15 if final_scenario in [0, 1] else 5)))
# 🛑 8. SYSTÈME DE VÉTO
dist_tp_pct = abs(tp - prix) / prix
ema200_val = float(last_row["EMA200"].iloc[0])
mkt_trend = float(last_row["Market_Trend"].iloc[0])
print(f"🧠 [IA] {symbol} [{timeframe}] | Scénario: {final_scenario} | Proba: {round(final_p, 4)} | SMC: {smc_status}")
veto, veto_reason = False, ""
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)}"
elif dist_tp_pct < agent_min_tp_dist: veto, veto_reason = True, "Gain potentiel trop faible"
elif final_p < 0.5 and (prix > ema200_val or prix > vwap): veto, veto_reason = True, "Short Interdit (Prix > Bull Lines)"
elif final_p > 0.5 and (prix < ema200_val or prix < vwap): veto, veto_reason = True, "Long Interdit (Prix < Bull Lines)"
elif "BTC" not in symbol and "ETH" not in symbol and "SOL" not in symbol:
if final_p < 0.5 and mkt_trend > 0.002: veto, veto_reason = True, "Trend Global Haussier"
elif final_p > 0.5 and mkt_trend < -0.002: veto, veto_reason = True, "Trend Global Baissier"
if veto:
return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": veto_reason, "scenario": final_scenario}
# 🛑 8.5 ANTI-SPAM (On bloque ici si le signal a déjà été envoyé)
if not should_send_signal(symbol, timeframe, final_p):
return {"symbol": symbol, "timeframe": timeframe, "status": "veto", "message": "Anti-Spam Actif (Déjà envoyé)", "scenario": final_scenario}
# 💾 9. ENREGISTREMENT DB
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)
await save_to_db(db_task)
print(f"💾 [DB] Signal {symbol} [{timeframe}] sauvegardé. En attente du Bras...")
return {
"symbol": symbol, "timeframe": timeframe, "status": "success", "final_score": round(final_p, 4), "score": int(composite_score),
"smart_money": smc_status, "price": prix, "tp": round(tp, 6), "sl": round(sl, 6), "scenario": final_scenario, "confluence": round(conf_val * 100, 1)
}
except Exception as e: return {"status": "error", "message": str(e)}
# =============================
def mutate_agent(symbol, timeframe, regime, success=True):
import sqlite3, random, numpy as np, time
for tentative in range(5): # 🔄 Tente 5 fois si la base est verrouillée
try:
with sqlite3.connect(DB_NAME, timeout=20) as conn:
conn.row_factory = sqlite3.Row
row = conn.execute(
"SELECT * FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?",
(symbol, timeframe, regime)
).fetchone()
if not row:
res_def = conn.execute(
"SELECT * FROM agent_logic WHERE symbol = 'ALL' AND timeframe = ?",
(timeframe,)
).fetchone()
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)
else:
tp, sl, prob = row['tp_mult'], row['sl_mult'], row['min_prob']
# 🧠 MUTATION DOUCE
if success:
new_tp = tp * random.uniform(1.01, 1.05)
new_sl = sl * random.uniform(0.98, 1.00)
new_prob = max(0.55, prob - 0.003)
else:
new_tp = tp * random.uniform(0.95, 0.99)
new_sl = sl * random.uniform(1.01, 1.05)
new_prob = min(0.85, prob + 0.005)
# 🛡️ HARD CLAMP (ANTI-EXPLOSION)
new_tp = float(np.clip(new_tp, 0.5, 3.0))
new_sl = float(np.clip(new_sl, 0.5, 2.0))
conn.execute('''
INSERT OR REPLACE INTO agent_logic
(symbol, timeframe, regime, tp_mult, sl_mult, min_prob, min_tp_dist, generation)
VALUES (?, ?, ?, ?, ?, ?, ?,
COALESCE((SELECT generation FROM agent_logic WHERE symbol=? AND timeframe=? AND regime=?)+1, 1))
''', (
symbol, timeframe, regime,
new_tp, new_sl, new_prob, 0.001,
symbol, timeframe, regime
))
conn.commit()
return # ✅ Succès, on sort de la fonction
except sqlite3.OperationalError as e:
if "locked" in str(e):
time.sleep(1) # La porte est bloquée, on attend 1 seconde
else:
print(f"❌ [ERREUR DB] Mutation échouée : {e}")
break
except Exception as e:
print(f"🧬 Erreur Mutation V32: {e}")
break
# =============================
# 🛡️ 3. PROTECTION LECTURE DB
# =============================
def safe_agent_values(tp_m, sl_m):
import numpy as np
# 🔥 Anti corruption
if sl_m > 10 or tp_m > 10:
print("🚨 CORRUPTION DETECTÉE → RESET LOCAL")
return 1.5, 1.0
tp_m = float(np.clip(tp_m, 0.5, 3.0))
sl_m = float(np.clip(sl_m, 0.5, 2.0))
return tp_m, sl_m
# --- 🌙 DREAM LOOP V31 (CORRIGÉE) ---
async def dream_simulation_loop():
while True:
if DREAM_MODE_ACTIVE:
print("🌙 [DREAM MODE] Le bot rêve et s'entraîne à la salle... 🥊")
for symbol in AUTO_SYMBOLS:
for tf in AUTO_TIMEFRAMES:
try:
df = prepare_features_sync(symbol, timeframe=tf, limit_bars=200)
if df.empty or len(df) < 50: continue
# 1. On voyage dans le temps (une bougie au hasard)
idx = random.randint(10, len(df) - 30)
row_actuelle = df.iloc[idx]
prix = float(row_actuelle['close'])
atr = float(row_actuelle['ATR'])
# 2. On récupère le régime de marché (Scénario)
# 2. On calcule le VRAI scénario de cette époque !
# On coupe le graphique pile au moment du passé qu'on a choisi
df_past = df.iloc[:idx+1]
# Le bot analyse la structure du marché de l'époque
current_scenario = detect_chart_scenario(df_past, df_past, df_past, df_past, df_past)
# 3. On extrait l'ADN actuel (les multiplicateurs)
with sqlite3.connect(DB_NAME) as conn:
res = conn.execute("SELECT tp_mult, sl_mult FROM agent_logic WHERE symbol = ? AND timeframe = ? AND regime = ?", (symbol, tf, current_scenario)).fetchone()
tp_m, sl_m = res if res else (1.5, 1.0)
# 4. Simulation de combat (Long ou Short) depuis CETTE époque
is_long = random.choice([True, False])
tp = prix + (atr * tp_m) if is_long else prix - (atr * tp_m)
sl = prix - (atr * sl_m) if is_long else prix + (atr * sl_m)
# 5. On regarde les 24 bougies suivantes pour voir qui gagne
future_data = df.iloc[idx+1 : idx+25]
win = False
for _, row in future_data.iterrows():
if is_long:
if row['high'] >= tp: win = True; break
if row['low'] <= sl: win = False; break
else:
if row['low'] <= tp: win = True; break
if row['high'] >= sl: win = False; break
# 6. 🧬 ÉVOLUTION : On récompense ou on punit l'ADN !
mutate_agent(symbol, tf, current_scenario, success=win)
await asyncio.sleep(5)
except Exception as e:
pass # On reste silencieux pendant le sommeil
await asyncio.sleep(60)
def should_send_signal(symbol, timeframe, proba):
global last_signals_sent
key = (symbol, timeframe)
now = time.time()
# 🛡️ FIX : La prison est adaptée à la Timeframe
tf_blocks = {"1m": 60, "5m": 300, "15m": 900, "1h": 3600, "4h": 14400}
block_time = tf_blocks.get(timeframe, 3600) # Par défaut 1h si inconnu
if key in last_signals_sent:
last_proba, last_time = last_signals_sent[key]
# Tolérance de 5% d'écart max, et blocage selon le tf_block
if abs(last_proba - proba) < 0.05 and (now - last_time) < block_time:
return False
last_signals_sent[key] = (proba, now)
return True
# --- ⚖️ JUDGE API V31 (VERSION NETTOYÉE POUR SL PHYSIQUE) ---
def run_judge_api(live_prices_json="{}"):
try:
import json
try:
raw_prices = json.loads(live_prices_json) if isinstance(live_prices_json, str) else {}
live_prices = {k.lower(): v for k, v in raw_prices.items()}
except:
live_prices = {}
with sqlite3.connect(DB_NAME, timeout=10) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS'")
trades = cursor.fetchall()
closed_trades = []
watching_info = [] # 🎯 Contiendra uniquement des dicts pour le Bras
current_time = datetime.now(timezone.utc)
max_age_minutes = {"15m": 30, "1h": 60, "4h": 240} # Le 4h expire après 4 heures
for t in trades:
try:
sym_db = t['symbol']
epic = sym_db.replace("/", "").replace("USDT", "USD").lower()
if not epic.endswith("m"): epic += "m"
is_live = epic in live_prices
if is_live and t['confirmed'] == 0:
cursor.execute("UPDATE signals SET confirmed = 1 WHERE id = ?", (t['id'],))
print(f"✅ [AUTO-CONFIRM] ID: {t['id']}")
if not is_live:
date_str = t['date'].replace('Z', '+00:00')
signal_time = datetime.fromisoformat(date_str)
age_minutes = (current_time - signal_time).total_seconds() / 60.0
max_age = max_age_minutes.get(t['timeframe'], 60)
if t['confirmed'] == 1:
cursor.execute("UPDATE signals SET status = 'FERMÉ PAR MT5 🛑' WHERE id = ?", (t['id'],))
closed_trades.append({"symbol": t['symbol'], "id": t['id'], "direction": t['direction']})
elif age_minutes > max_age:
cursor.execute("UPDATE signals SET status = 'EXPIRÉ ⏰' WHERE id = ?", (t['id'],))
continue
# --- CALCUL DU TRAILING ---
current_price = float(live_prices[epic])
sl_dyn, peak = t['sl'], t['peak_price']
new_peak = max(peak, current_price) if t['direction'] == 'HAUSSIER' else min(peak, current_price)
dist_totale = abs(t['tp'] - t['price'])
if t['direction'] == 'HAUSSIER':
mouvement = current_price - t['price']
else:
mouvement = t['price'] - current_price
progression = mouvement / dist_totale if (dist_totale > 0 and mouvement > 0) else 0
nouveau_sl = sl_dyn
dist_sl_initial = abs(t['price'] - sl_dyn)
# LOGIQUE AGRESSIVE
if t['direction'] == 'HAUSSIER':
if progression >= 0.60: nouveau_sl = max(sl_dyn, t['price'] + (dist_totale * 0.50))
elif progression >= 0.40: nouveau_sl = max(sl_dyn, t['price'] + (dist_totale * 0.20))
elif progression >= 0.20: nouveau_sl = max(sl_dyn, t['price'] + (dist_totale * 0.02))
elif progression >= 0.10: nouveau_sl = max(sl_dyn, t['price'] - (dist_sl_initial * 0.50))
else:
if progression >= 0.60: nouveau_sl = min(sl_dyn, t['price'] - (dist_totale * 0.50))
elif progression >= 0.40: nouveau_sl = min(sl_dyn, t['price'] - (dist_totale * 0.20))
elif progression >= 0.20: nouveau_sl = min(sl_dyn, t['price'] - (dist_totale * 0.02))
elif progression >= 0.10: nouveau_sl = min(sl_dyn, t['price'] + (dist_sl_initial * 0.50))
cursor.execute("UPDATE signals SET peak_price = ?, sl = ? WHERE id = ?", (new_peak, nouveau_sl, t['id']))
# --- DÉCISION DE CLÔTURE ---
outcome, reward = None, 0
if t['direction'] == 'HAUSSIER':
if current_price >= t['tp']: outcome, reward = "GAGNÉ ✅", 3
elif current_price <= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl > t['price'] else ("PERDU ❌", -5)
else:
if current_price <= t['tp']: outcome, reward = "GAGNÉ ✅", 3
elif current_price >= nouveau_sl: outcome, reward = ("GAGNÉ (PARTIEL) 💸", 1) if nouveau_sl < t['price'] else ("PERDU ❌", -5)
if outcome:
mutate_agent(t['symbol'], t['timeframe'], t['regime'], success=(reward>0))
cursor.execute("UPDATE signals SET status=? WHERE id=?", (outcome, t['id']))
closed_trades.append({"symbol": t['symbol'], "id": t['id'], "direction": t['direction']})
else:
# 🎯 ON N'AJOUTE DANS WATCHING QUE SI LE TRADE EST ENCORE OUVERT
watching_info.append({
"id": t['id'],
"symbol": t['symbol'],
"sl": round(nouveau_sl, 6),
"prog": round(progression, 4)
})
except Exception as e:
print(f"⚠️ Erreur Trade ID {t['id']}: {e}")
conn.commit()
# Retour propre : data contient les fermetures, watching contient les mises à jour SL
return {
"status": "updates" if closed_trades else "waiting",
"data": closed_trades,
"watching": watching_info
}
except Exception as e: return {"status": "error", "message": str(e)}
# --- 📡 LECTURE RADAR OPTIMISÉE ---
def get_active_signals():
try:
with sqlite3.connect(DB_NAME, timeout=20) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# 🎯 On ne prend QUE les signaux NON CONFIRMÉS pour ne pas surcharger le Bras
cursor.execute("SELECT * FROM signals WHERE status = 'EN_COURS' AND confirmed = 0")
signaux = [dict(row) for row in cursor.fetchall()]
print(f"📡 [API RADAR] {len(signaux)} nouveaux signaux trouvés pour le Bras.")
return signaux
except Exception as e:
print(f"❌ [ERREUR CRITIQUE CLOUD] get_active_signals a planté : {e}")
return []
# --- 📊 GET BOT SKILLS (UI Dashboard) ---
def get_bot_skills():
try:
scenario_map = {0: "BULL RUN", 1: "BEAR RUN", 2: "PULLBACK", 3: "RANGE/CHAOS", 4: "SQUEEZE", 5: "REVERSAL"}
with sqlite3.connect(DB_NAME) as conn:
return [[r[0], r[1], scenario_map.get(r[2], "INCONNU"), f"x{round(r[3], 2)}", f"x{round(r[4], 2)}", f"{round(r[5]*100)}%", f"🧬 Gen {r[6]}"]
for r in conn.cursor().execute("SELECT symbol, timeframe, regime, tp_mult, sl_mult, min_prob, generation FROM agent_logic ORDER BY symbol, timeframe, regime").fetchall()]
except Exception as e: return [[f"Erreur: {str(e)}", "-", "-", "-", "-", "-", "-"]]
# --- 🧠 TRAINING ENGINE ---
def trigger_training(symbol="BTC/USD"):
try:
memory_guard()
# 🎯 1. Nettoyage du symbole pour le nom de fichier (ex: BTC/USDT -> BTC)
sym_clean = symbol.replace("/", "").replace("USDT", "").replace("USD", "").upper()
if not sym_clean: sym_clean = "DEFAULT"
# On crée un nom de fichier unique par crypto
model_filename = f"dino_lgbm_{sym_clean}.txt"
bars = exchange_sync.fetch_ohlcv(symbol, timeframe='1h', limit=1500)
df = pd.DataFrame(bars, columns=['ts', 'open', 'high', 'low', 'close', 'vol'])
if len(df) < 500: return f"❌ Historique insuffisant pour {symbol}."
df_final = prepare_features_sync(symbol, '1h', limit_bars=1000)
if df_final.empty or len(df_final) < 100: return f"❌ Données vides pour {symbol}."
if LGBM_AVAILABLE:
ml_cols = ["RSI", "Dist_High_24h", "Dist_Low_24h", "EMA_dist", "EMA_slope", "ATR_ratio", "VOL_ratio"]
df_final['Target'] = (df_final['close'].shift(-1) > df_final['close']).astype(int)
df_train = df_final.dropna(subset=ml_cols + ['Target'])
X, y = df_train[ml_cols], df_train['Target']
params = {'objective': 'binary', 'metric': 'binary_logloss', 'boosting_type': 'gbdt', 'learning_rate': 0.05, 'num_leaves': 31, 'verbose': -1}
model = lgb.train(params, lgb.Dataset(X, label=y), 100)
# 💾 2. Sauvegarde avec le nom unique
model.save_model(model_filename)
try:
api = HfApi()
api.upload_file(
path_or_fileobj=model_filename,
path_in_repo=model_filename,
repo_id=HF_REPO_ID,
repo_type="dataset",
token=HF_TOKEN
)
print(f"☁️ [BACKUP] {model_filename} sauvegardé sur le Cloud !")
except Exception as e:
print(f"⚠️ Erreur backup modèle {sym_clean} : {e}")
# 🧠 3. On met à jour le dictionnaire global des cerveaux DINO
global dino_brains
if 'dino_brains' not in globals():
dino_brains = {}
dino_brains[sym_clean] = lgb.Booster(model_file=model_filename)
print(f"🧠 [IA] Cerveau {sym_clean} chargé en mémoire vive.")
# Ces deux là restent uniques car ils sont sûrement génériques dans ton code
global ml_model, time_model
try:
ml_model, time_model = joblib.load("ml_model_v9.pkl"), joblib.load("time_model.pkl")
except: pass # Si les pkl n'existent pas, on ne crashe pas tout
gc.collect()
return f"✅ IA ré-entraînée et sauvegardée pour {symbol} ({model_filename})."
except Exception as e: return f"❌ Erreur Training {symbol} : {e}"
# --- 🚀 MOTEURS AUTO-PILOTE ---
AUTO_SYMBOLS = ["BTC/USD", "ETH/USD", "SOL/USD"]
AUTO_TIMEFRAMES = ["15m", "1h", "4h"]
def set_bot_mode(mode):
global DREAM_MODE_ACTIVE
target_mode = mode[0] if isinstance(mode, list) else str(mode)
if "LIVE" in target_mode.upper(): DREAM_MODE_ACTIVE, msg = False, "🛰️ Mode LIVE activé"
else: DREAM_MODE_ACTIVE, msg = True, "🌙 Mode DREAM activé"
return {"status": "success", "mode": "DREAM" if DREAM_MODE_ACTIVE else "LIVE", "message": msg}
async def universal_scanner_loop():
print("👁️ [SCANNER] Le Cerveau Global est en ligne H24...")
while True:
for symbol in AUTO_SYMBOLS:
for tf in AUTO_TIMEFRAMES:
try:
# 🧹 1. ON NETTOIE AVANT DE PRÉDIRE (On vire les vieux fantômes)
clean_old_db_signals(symbol, tf)
# 🧠 2. ON PRÉDIT ET ON SAUVEGARDE LE NOUVEAU SIGNAL
pred = await predict_signal(symbol, timeframe=tf)
if pred and pred.get("status") == "success":
print(f"✅ [SIGNAL VALIDÉ] {symbol} [{tf}] - Probabilité: {pred['final_score']}")
# Ligne clean_old supprimée d'ici pour ne pas tuer le trade !
active_signals_state[(symbol, tf)] = pred
if not DREAM_MODE_ACTIVE:
print(f"🎯 [LIVE] Action requise pour {symbol} [{tf}] : Passage d'ordre.")
await asyncio.sleep(5)
except Exception as e:
print(f"⚠️ Erreur Scanner {symbol} [{tf}]: {e}")
await asyncio.sleep(60)
# --- ⚖️ TOOLS ---
def keep_alive_ping(): return {"status": "awake", "time": datetime.now(timezone.utc).isoformat()}
def confirm_trade_api(trade_id, real_price):
try:
with sqlite3.connect(DB_NAME) as conn:
conn.row_factory, cursor = sqlite3.Row, conn.cursor()
cursor.execute("SELECT price, tp, sl FROM signals WHERE id = ?", (int(trade_id),))
t = cursor.fetchone()
if not t: return {"status": "error"}
ecart = float(real_price) - t['price']
cursor.execute("UPDATE signals SET confirmed = 1, price = ?, tp = ?, sl = ?, peak_price = ? WHERE id = ?", (float(real_price), t['tp'] + ecart, t['sl'] + ecart, float(real_price), int(trade_id)))
conn.commit()
print(f"🤝 [BRIDGE] Le Bras a exécuté l'ID {trade_id} à {real_price}$. Synchronisation OK.")
return {"status": "success"}
except: return {"status": "error"}
def cancel_trade_api(trade_id):
try:
with sqlite3.connect(DB_NAME) as conn: conn.execute("UPDATE signals SET status = 'ANNULÉ 🗑️', confirmed = 0 WHERE id = ?", (int(trade_id),)); conn.commit()
return {"status": "success"}
except: return {"status": "error"}
def reset_trade_history():
"""
Supprime UNIQUEMENT les trades (la table signals)
Garde intacte la mémoire génétique (la table agents)
"""
try:
with sqlite3.connect(DB_NAME, timeout=10) as conn:
cursor = conn.cursor()
# 1. On supprime tout l'historique des trades (brouillons, validés, expirés...)
cursor.execute("DELETE FROM signals")
# 2. (Optionnel mais propre) On remet le compteur d'ID à 1
cursor.execute("DELETE FROM sqlite_sequence WHERE name='signals'")
conn.commit()
return "✅ RESET RÉUSSI : Tous les trades ont été effacés. L'ADN est conservé !"
except Exception as e:
return f"❌ ERREUR RESET : {e}"
def training_wrapper(symbol, *args): return trigger_training(str(symbol).strip().upper() if isinstance(symbol, str) else "BTC/USD")
import atexit
@atexit.register
def close_session():
global global_aio_session
if global_aio_session and not global_aio_session.closed:
asyncio.run(global_aio_session.close())
# --- 🎨 INTERFACE GRADIO V30 ---
with gr.Blocks(theme=gr.themes.Monochrome()) as iface:
gr.Markdown("# 🦖 Alpha V30 Dinosaur Engine (Master Edition)")
with gr.Tab("Admin"):
gr.Button("Forcer Entraînement", variant="stop").click(fn=training_wrapper, inputs=gr.Textbox(label="Symbole à recalibrer", value="BTC/USDT"), outputs=gr.Textbox(), api_name="trigger_training")
gr.Markdown("---")
gr.Markdown("### ⚠️ Zone de Danger")
reset_btn = gr.Button("🗑️ Purger tous les trades (Garder l'ADN)", variant="primary")
reset_out = gr.Textbox(label="Résultat du nettoyage")
reset_btn.click(fn=reset_trade_history, inputs=[], outputs=reset_out, api_name="reset_trades")
with gr.Tab("🎯 Prédictions"):
gr.Button("Predict", variant="primary").click(fn=predict_signal, inputs=[gr.Textbox(label="Symbole", value="BTC/USD"), gr.Dropdown(choices=["15m", "1h", "4h"], value="1h")], outputs=gr.JSON())
with gr.Tab("⚖️ Système"):
gr.Button("Judge").click(fn=run_judge_api, inputs=gr.Textbox(value="{}", visible=False), outputs=gr.JSON(), api_name="run_judge_api")
gr.Button("Get Active", visible=False).click(fn=get_active_signals, outputs=gr.JSON(), api_name="get_active_signals")
gr.Button("Ping", visible=False).click(fn=keep_alive_ping, outputs=gr.JSON(), api_name="keep_alive_ping")
gr.Button("Set Mode", visible=False).click(fn=set_bot_mode, inputs=gr.Textbox(visible=False), outputs=gr.JSON(), api_name="set_bot_mode")
with gr.Tab("📊 Stats (ADN)"):
skills_table = gr.Dataframe(headers=["Marché", "Timeframe", "Scénario", "Cible (TP)", "Risque (SL)", "Peur Min.", "Génération"], value=get_bot_skills(), interactive=False)
with gr.Row():
gr.Button("🔄 Actualiser").click(get_bot_skills, outputs=skills_table)
gr.Button(visible=False).click(fn=confirm_trade_api, inputs=[gr.Textbox(visible=False), gr.Textbox(visible=False)], outputs=gr.JSON(), api_name="confirm_trade_api")
gr.Button(visible=False).click(fn=cancel_trade_api, inputs=gr.Textbox(visible=False), outputs=gr.JSON(), api_name="cancel_trade_api")
iface.load(get_bot_skills, outputs=skills_table)
# --- ☁️ SAUVEGARDE CLOUD SILENCIEUSE ---
async def hf_backup_loop():
while True:
await asyncio.sleep(1800) # Attendre 30 minutes (1800 secondes)
print("☁️ [SYSTEM] Lancement du backup planifié...")
backup_db_to_hf()
import threading
_auto_pilot_started = False
def run_auto_pilot():
global _auto_pilot_started
if _auto_pilot_started: return
_auto_pilot_started = True
print("⏳ [SYSTEM] Attente 15s avant propulsion Master V31...")
time.sleep(15)
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.create_task(universal_scanner_loop())
loop.create_task(dream_simulation_loop())
loop.create_task(hf_backup_loop()) # ⬅️ LA SAUVEGARDE EST BIEN ICI
loop.run_forever()
except Exception as e: print(f"⚠️ Erreur Auto-Pilote : {e}")
if __name__ == "__main__":
try: threading.Thread(target=run_auto_pilot, daemon=True).start()
except Exception as e: print(f"⚠️ Erreur Thread : {e}")
# 🛑 LE LAUNCH DOIT TOUJOURS ÊTRE LA TOUTE DERNIÈRE LIGNE DU FICHIER !
iface.launch(server_name="0.0.0.0", server_port=7860, show_api=True) |