From c4f0ddce08ec060eb022a8a3ee82c8aa53183b93 Mon Sep 17 00:00:00 2001 From: bast Date: Fri, 6 Nov 2026 09:21:21 +0000 Subject: [PATCH] bots/mcp_server.py aktualisiert --- bots/mcp_server.py | 96 ++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/bots/mcp_server.py b/bots/mcp_server.py index a79b9156..9f927096 100644 --- a/bots/mcp_server.py +++ b/bots/mcp_server.py @@ -257,7 +257,8 @@ class LiveBridge(MT5Bridge): self._rpyc_conn._config['sync_request_timeout'] = RPYC_TIMEOUT self._rpyc_conn.execute("import MetaTrader5 as mt5") self._rpyc_conn.execute("from datetime import datetime, timedelta") - self._rpyc_conn.execute("mt5.initialize()") + p = os.getenv("MT5_PATH") + self._rpyc_conn.execute(f"mt5.initialize(path={p!r})" if p else "mt5.initialize()") log.info("Direct rpyc connection established for tick data") except Exception as e: log.error(f"rpyc connection failed: {e}") @@ -272,19 +273,38 @@ class LiveBridge(MT5Bridge): # initialize() mit Login startet/verbindet das Terminal unter Wine und # dauert oft >30s (RPyC-Default) -> sonst "result expired" self.mt5._MetaTrader5__conn._config['sync_request_timeout'] = RPYC_TIMEOUT - # Broker-Login aus Umgebungsvariablen (bots.env) – leer = Terminal-Login verwenden - kwargs = {} - if os.getenv("MT5_LOGIN"): - kwargs = { - "login": int(os.getenv("MT5_LOGIN")), - "password": os.getenv("MT5_PASSWORD", ""), - "server": os.getenv("MT5_SERVER", ""), - } - log.info(f"Logging in to MT5 account {kwargs['login']} on {kwargs['server']}") - if not self.mt5.initialize(**kwargs): + # Schritt 1: Bridge – nur ans Terminal anhängen, noch KEIN Login. + # Pfad nötig bei Broker-Terminals (sonst IPC timeout -10005). + init_kwargs = {"timeout": int(os.getenv("MT5_INIT_TIMEOUT_MS", "90000"))} + if os.getenv("MT5_PATH"): + init_kwargs["path"] = os.getenv("MT5_PATH") + log.info(f"Connecting to terminal {init_kwargs.get('path', '(auto-detect)')}") + if not self.mt5.initialize(**init_kwargs): error = self.mt5.last_error() log.error(f"MT5 initialize failed: {error}") return False + log.info("Bridge OK – terminal attached") + + # Schritt 2: Login nur, wenn nicht schon das richtige Konto angemeldet ist + want = os.getenv("MT5_LOGIN") + current = self.mt5.account_info() + current_login = current.login if current is not None else None + if want and current_login != int(want): + log.info(f"Account in terminal: {current_login} – logging in to {want} " + f"on {os.getenv('MT5_SERVER', '')}") + ok = self.mt5.login(int(want), + password=os.getenv("MT5_PASSWORD", ""), + server=os.getenv("MT5_SERVER", ""), + timeout=int(os.getenv("MT5_LOGIN_TIMEOUT_MS", "60000"))) + if not ok: + log.error(f"MT5 login failed: {self.mt5.last_error()}") + return False + elif current_login is None: + log.error("Terminal has no logged-in account and MT5_LOGIN is not set") + return False + else: + log.info(f"Account {current_login} already logged in – no login needed") + info = self.mt5.account_info() log.info(f"LiveBridge connected: login={info.login}, server={info.server}") self.connected = True @@ -297,6 +317,22 @@ class LiveBridge(MT5Bridge): log.error(f"LiveBridge connection failed: {e}") return False + async def ensure(self): + """Prüft vor jedem Aufruf, ob die IPC-Verbindung zum Terminal noch steht, + und verbindet sonst neu (z.B. nach Terminal-Neustart im Web-UI oder wenn + ein anderer Client im selben Wine-Python mt5.initialize/shutdown aufruft).""" + try: + if self.mt5 is not None and self.mt5.terminal_info() is not None: + return + if self.mt5 is not None: + log.warning(f"IPC connection lost: {self.mt5.last_error()} – reconnecting") + except Exception as e: + log.warning(f"Bridge check failed ({e}) – reconnecting") + self.connected = False + self._rpyc_conn = None + if not await self.connect(): + raise HTTPException(503, "MT5 not connected (reconnect failed)") + async def disconnect(self): if self._rpyc_conn: try: @@ -312,8 +348,7 @@ class LiveBridge(MT5Bridge): self.connected = False async def account_info(self) -> Dict[str, Any]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() info = self.mt5.account_info() if info is None: raise HTTPException(500, f"account_info failed: {self.mt5.last_error()}") @@ -333,16 +368,14 @@ class LiveBridge(MT5Bridge): } async def symbols(self) -> List[str]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() syms = self.mt5.symbols_get() if syms is None: return [] return [s.name for s in syms if s.visible] async def symbol_info(self, symbol: str) -> Dict[str, Any]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() info = self.mt5.symbol_info(symbol) if info is None: raise HTTPException(404, f"Symbol {symbol} not found") @@ -361,8 +394,7 @@ class LiveBridge(MT5Bridge): } async def ohlc(self, symbol: str, timeframe: str, bars: int, start_pos: int = 0) -> List[Dict]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() import pandas as pd # Check if seconds timeframe @@ -407,8 +439,7 @@ class LiveBridge(MT5Bridge): from_ts: Unix timestamp Start to_ts: Unix timestamp Ende """ - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() import pandas as pd from datetime import datetime, timezone @@ -543,8 +574,7 @@ class LiveBridge(MT5Bridge): sl: float = 0, tp: float = 0, comment: str = "") -> Dict[str, Any]: """Open a market order.""" - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() # Get symbol info for filling info = self.mt5.symbol_info(symbol) @@ -588,8 +618,7 @@ class LiveBridge(MT5Bridge): async def order_modify(self, ticket: int, sl: float = None, tp: float = None) -> Dict[str, Any]: """Modify SL/TP of an existing position.""" - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() # Get current position positions = self.mt5.positions_get(ticket=ticket) @@ -619,8 +648,7 @@ class LiveBridge(MT5Bridge): async def order_close(self, ticket: int) -> Dict[str, Any]: """Close an existing position.""" - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() positions = self.mt5.positions_get(ticket=ticket) if positions is None or len(positions) == 0: @@ -659,8 +687,7 @@ class LiveBridge(MT5Bridge): async def tick(self, symbol: str) -> Dict[str, Any]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() tick = self.mt5.symbol_info_tick(symbol) if tick is None: raise HTTPException(404, f"No tick for {symbol}") @@ -674,8 +701,7 @@ class LiveBridge(MT5Bridge): } async def positions(self) -> List[Dict]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() pos = self.mt5.positions_get() if pos is None: return [] @@ -699,8 +725,7 @@ class LiveBridge(MT5Bridge): return result async def orders(self) -> List[Dict]: - if not self.mt5: - raise HTTPException(503, "MT5 not connected") + await self.ensure() ords = self.mt5.orders_get() if ords is None: return [] @@ -794,6 +819,11 @@ async def root(): @app.get("/health") async def health(): + if bridge.mode == "live": + try: + await bridge.ensure() + except HTTPException: + pass return { "status": "ok" if bridge.connected else "degraded", "mode": bridge.mode,