This commit is contained in:
YouDgn 2026-05-13 00:29:01 +02:00
parent 999a73225a
commit 8ee1803327
2 changed files with 101 additions and 42 deletions

View File

@ -16,7 +16,7 @@ MetaTrader5>=5.0.45
# --- Deep Learning & RL --- # --- Deep Learning & RL ---
torch>=2.0.0 torch>=2.0.0
torch-directml>=0.2.0 # torch-directml>=0.2.0 # À décommenter après upgrade à Python 3.12
numpy>=1.24.0 numpy>=1.24.0
gymnasium>=0.29.0 gymnasium>=0.29.0
@ -34,6 +34,10 @@ beautifulsoup4>=4.12.0
transformers>=4.35.0 transformers>=4.35.0
vaderSentiment>=3.3.2 vaderSentiment>=3.3.2
# --- Web Dashboard ---
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
# --- Visualisation Console --- # --- Visualisation Console ---
rich>=13.6.0 rich>=13.6.0
colorama>=0.4.6 colorama>=0.4.6

View File

@ -961,21 +961,30 @@ let pnlHistory = [];
let reconnectTimer = null; let reconnectTimer = null;
function connect() { function connect() {
ws = new WebSocket(`ws://${location.host}/ws`); const wsUrl = `ws://${location.host}/ws`;
console.log(`🔌 Tentative connexion WebSocket: ${wsUrl}`);
ws = new WebSocket(wsUrl);
ws.onopen = () => { ws.onopen = () => {
console.log('✅ WebSocket CONNECTÉ');
document.getElementById('wsDot').classList.add('connected'); document.getElementById('wsDot').classList.add('connected');
document.getElementById('wsLabel').textContent = 'Connecté'; document.getElementById('wsLabel').textContent = 'Connecté';
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
addLog('✅ Connecté au dashboard');
}; };
ws.onclose = () => { ws.onclose = () => {
console.log('❌ WebSocket FERMÉ');
document.getElementById('wsDot').classList.remove('connected'); document.getElementById('wsDot').classList.remove('connected');
document.getElementById('wsLabel').textContent = 'Déconnecté'; document.getElementById('wsLabel').textContent = 'Déconnecté';
reconnectTimer = setTimeout(connect, 3000); reconnectTimer = setTimeout(connect, 3000);
}; };
ws.onerror = () => ws.close(); ws.onerror = (event) => {
console.error('❌ WebSocket ERROR:', event);
addLog('⚠️ Erreur WebSocket - réessai dans 3s...');
ws.close();
};
ws.onmessage = (e) => { ws.onmessage = (e) => {
try { updateDashboard(JSON.parse(e.data)); } try { updateDashboard(JSON.parse(e.data)); }
@ -1312,7 +1321,11 @@ function saveTpanel() {
function sendRaw(obj) { function sendRaw(obj) {
if (ws && ws.readyState === WebSocket.OPEN) { if (ws && ws.readyState === WebSocket.OPEN) {
console.log('📤 Envoi:', JSON.stringify(obj));
ws.send(JSON.stringify(obj)); ws.send(JSON.stringify(obj));
} else {
console.error('❌ WebSocket non connecté (état:', ws?.readyState, ')');
addLog('⚠️ Erreur: WebSocket non connecté');
} }
} }
@ -1354,12 +1367,26 @@ class WebDashboardServer:
async def root(): async def root():
return DASHBOARD_HTML return DASHBOARD_HTML
@app.get("/settings")
async def get_settings():
"""Retourne les paramètres actuels."""
try:
import config as _cfg
return {
"capital": getattr(_cfg, "INITIAL_CAPITAL", 10000),
"rr": getattr(_cfg, "TAKE_PROFIT_ATR_MULT", 3.0) / max(getattr(_cfg, "STOP_LOSS_ATR_MULT", 1.0), 0.001),
"lot": getattr(_cfg, "MANUAL_LOT_SIZE", 0.05),
"protection": getattr(_cfg, "DAILY_MAX_LOSS", 0.05) * 100,
}
except Exception as e:
logger.error(f"get_settings error: {e}")
return {"capital": 10000, "rr": 3.0, "lot": 0.05, "protection": 5.0}
@app.websocket("/ws") @app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket): async def websocket_endpoint(websocket: WebSocket):
await websocket.accept() await websocket.accept()
self._clients.add(websocket) self._clients.add(websocket)
logger.info(f"Dashboard: nouveau client connecté ({len(self._clients)} total)") logger.info(f"Dashboard: nouveau client WebSocket connecté ({len(self._clients)} total)")
# Envoyer le statut actuel au nouveau client # Envoyer le statut actuel au nouveau client
bot = self.bot_ref bot = self.bot_ref
@ -1377,7 +1404,7 @@ class WebDashboardServer:
pass pass
finally: finally:
self._clients.discard(websocket) self._clients.discard(websocket)
logger.info(f"Dashboard: client déconnecté ({len(self._clients)} restants)") logger.info(f"Dashboard: client déconnecté ({len(self._clients)} restants)")
return app return app
@ -1385,7 +1412,7 @@ class WebDashboardServer:
"""Traite les commandes envoyées depuis le dashboard.""" """Traite les commandes envoyées depuis le dashboard."""
cmd = data.get("command") cmd = data.get("command")
bot = self.bot_ref bot = self.bot_ref
logger.info(f"Commande reçue : {cmd} | bot={bot is not None}") logger.info(f"🔹 COMMAND REÇUE: {cmd} | data={data} | bot={bot is not None}")
if cmd == "start": if cmd == "start":
is_running = bot and getattr(bot, "_run_event", None) and bot._run_event.is_set() is_running = bot and getattr(bot, "_run_event", None) and bot._run_event.is_set()
@ -1429,10 +1456,19 @@ class WebDashboardServer:
login = data.get("login", 0) login = data.get("login", 0)
password = data.get("password", "") password = data.get("password", "")
server = data.get("server", "") server = data.get("server", "")
logger.info(f"MT5 login tentative: {login} @ {server}")
try: try:
import config as _cfg import config as _cfg
import MetaTrader5 as _mt5 import MetaTrader5 as _mt5
except ImportError as e:
logger.error(f"MetaTrader5 non installé: {e}")
await websocket.send_text(json.dumps({
"login_error": "MetaTrader5 non installé"
}))
return
try:
# Nettoyer les données reçues # Nettoyer les données reçues
login = int(login) if login else 0 login = int(login) if login else 0
password = str(password).strip() if password else "" password = str(password).strip() if password else ""
@ -1445,36 +1481,41 @@ class WebDashboardServer:
return return
# Test connexion rapide # Test connexion rapide
logger.info(f"Initialisation MT5...")
if not _mt5.initialize(): if not _mt5.initialize():
error = _mt5.last_error()
msg = f"Init MT5 échouée: {error}" if error else "Impossible d'initialiser MT5"
logger.error(msg)
await websocket.send_text(json.dumps({ await websocket.send_text(json.dumps({
"login_error": "Impossible d'initialiser MT5" "login_error": msg
})) }))
return return
try: logger.info(f"Tentative login: {login}@{server}")
ok = _mt5.login(login=login, password=password, server=server) ok = _mt5.login(login=login, password=password, server=server)
if ok:
_cfg.MT5_LOGIN = login if ok:
_cfg.MT5_PASSWORD = password _cfg.MT5_LOGIN = login
_cfg.MT5_SERVER = server _cfg.MT5_PASSWORD = password
_mt5.shutdown() _cfg.MT5_SERVER = server
await websocket.send_text(json.dumps({"login_ok": True}))
logger.info(f"Login MT5 OK — compte {login} sur {server}")
else:
error_code = _mt5.last_error()
error_msg = f"Erreur MT5: {error_code[0]} - {error_code[1]}" if error_code else "Identifiants invalides"
_mt5.shutdown()
await websocket.send_text(json.dumps({"login_error": error_msg}))
logger.warning(f"Login MT5 échoué pour {login}: {error_msg}")
except Exception as ex:
_mt5.shutdown() _mt5.shutdown()
await websocket.send_text(json.dumps({ await websocket.send_text(json.dumps({"login_ok": True}))
"login_error": f"Erreur connexion: {str(ex)[:60]}" logger.info(f"✅ Login MT5 OK — compte {login} sur {server}")
})) else:
except Exception as e: error_code = _mt5.last_error()
logger.error(f"mt5_login error: {e}") error_msg = f"Erreur MT5 ({error_code[0]}): {error_code[1]}" if error_code else "Identifiants invalides"
_mt5.shutdown()
logger.error(f"❌ Login échoué: {error_msg}")
await websocket.send_text(json.dumps({"login_error": error_msg}))
except Exception as ex:
logger.exception(f"Exception during MT5 login: {ex}")
try:
_mt5.shutdown()
except:
pass
await websocket.send_text(json.dumps({ await websocket.send_text(json.dumps({
"login_error": f"Erreur: {str(e)[:60]}" "login_error": f"Erreur: {str(ex)[:80]}"
})) }))
elif cmd == "set_params": elif cmd == "set_params":
@ -1541,22 +1582,36 @@ class WebDashboardServer:
logger.error("uvicorn non installé. pip install uvicorn") logger.error("uvicorn non installé. pip install uvicorn")
return return
# Supprimer les logs asyncio bruyants sur Windows
import logging as _logging
_logging.getLogger("asyncio").setLevel(_logging.WARNING)
app = self._build_app() app = self._build_app()
if app is None: if app is None:
logger.error("Impossible de construire l'app FastAPI")
return return
self._loop = asyncio.new_event_loop() try:
asyncio.set_event_loop(self._loop) # Créer une boucle d'événements dédiée pour ce thread
self._loop = asyncio.new_event_loop()
asyncio.set_event_loop(self._loop)
config = uvicorn.Config( logger.info(f"🚀 Démarrage serveur FastAPI sur 0.0.0.0:{self.port}...")
app, config = uvicorn.Config(
host="127.0.0.1", app,
port=self.port, host="0.0.0.0", # Écouter sur toutes les interfaces
log_level="error", port=self.port,
loop="asyncio", log_level="warning",
) access_log=False,
server = uvicorn.Server(config) loop="asyncio",
self._loop.run_until_complete(server.serve()) )
server = uvicorn.Server(config)
logger.info(f"✅ Serveur FastAPI lancé")
self._loop.run_until_complete(server.serve())
except Exception as e:
logger.error(f"❌ Erreur démarrage serveur: {e}")
import traceback
traceback.print_exc()
def _open_browser(self): def _open_browser(self):
"""Ouvre le dashboard dans le navigateur par défaut.""" """Ouvre le dashboard dans le navigateur par défaut."""