This commit is contained in:
parent
999a73225a
commit
8ee1803327
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
if ok:
|
||||||
_cfg.MT5_LOGIN = login
|
_cfg.MT5_LOGIN = login
|
||||||
_cfg.MT5_PASSWORD = password
|
_cfg.MT5_PASSWORD = password
|
||||||
_cfg.MT5_SERVER = server
|
_cfg.MT5_SERVER = server
|
||||||
_mt5.shutdown()
|
_mt5.shutdown()
|
||||||
await websocket.send_text(json.dumps({"login_ok": True}))
|
await websocket.send_text(json.dumps({"login_ok": True}))
|
||||||
logger.info(f"Login MT5 OK — compte {login} sur {server}")
|
logger.info(f"✅ Login MT5 OK — compte {login} sur {server}")
|
||||||
else:
|
else:
|
||||||
error_code = _mt5.last_error()
|
error_code = _mt5.last_error()
|
||||||
error_msg = f"Erreur MT5: {error_code[0]} - {error_code[1]}" if error_code else "Identifiants invalides"
|
error_msg = f"Erreur MT5 ({error_code[0]}): {error_code[1]}" if error_code else "Identifiants invalides"
|
||||||
_mt5.shutdown()
|
_mt5.shutdown()
|
||||||
|
logger.error(f"❌ Login échoué: {error_msg}")
|
||||||
await websocket.send_text(json.dumps({"login_error": error_msg}))
|
await websocket.send_text(json.dumps({"login_error": error_msg}))
|
||||||
logger.warning(f"Login MT5 échoué pour {login}: {error_msg}")
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
logger.exception(f"Exception during MT5 login: {ex}")
|
||||||
|
try:
|
||||||
_mt5.shutdown()
|
_mt5.shutdown()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
await websocket.send_text(json.dumps({
|
await websocket.send_text(json.dumps({
|
||||||
"login_error": f"Erreur connexion: {str(ex)[:60]}"
|
"login_error": f"Erreur: {str(ex)[:80]}"
|
||||||
}))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"mt5_login error: {e}")
|
|
||||||
await websocket.send_text(json.dumps({
|
|
||||||
"login_error": f"Erreur: {str(e)[:60]}"
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Créer une boucle d'événements dédiée pour ce thread
|
||||||
self._loop = asyncio.new_event_loop()
|
self._loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(self._loop)
|
asyncio.set_event_loop(self._loop)
|
||||||
|
|
||||||
|
logger.info(f"🚀 Démarrage serveur FastAPI sur 0.0.0.0:{self.port}...")
|
||||||
config = uvicorn.Config(
|
config = uvicorn.Config(
|
||||||
app,
|
app,
|
||||||
host="127.0.0.1",
|
host="0.0.0.0", # Écouter sur toutes les interfaces
|
||||||
port=self.port,
|
port=self.port,
|
||||||
log_level="error",
|
log_level="warning",
|
||||||
|
access_log=False,
|
||||||
loop="asyncio",
|
loop="asyncio",
|
||||||
)
|
)
|
||||||
server = uvicorn.Server(config)
|
server = uvicorn.Server(config)
|
||||||
|
logger.info(f"✅ Serveur FastAPI lancé")
|
||||||
self._loop.run_until_complete(server.serve())
|
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."""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue