bots/mcp_server.py aktualisiert
All checks were successful
MT5 Helm GitOps Deploy / build-and-deploy (push) Has been skipped
All checks were successful
MT5 Helm GitOps Deploy / build-and-deploy (push) Has been skipped
This commit is contained in:
parent
c5c0d42eac
commit
c4f0ddce08
1 changed files with 63 additions and 33 deletions
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue