#!/usr/bin/env python3 """ GENESIS Heartbeat Monitor Runs every 60 minutes. Sends a system health report to Telegram. If this message stops appearing, the VPS is down. """ import os, requests, json, subprocess, sys from datetime import datetime, timezone from pathlib import Path try: from dotenv import load_dotenv load_dotenv() except: pass TG_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") TG_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "") sys.path.insert(0, str(Path(__file__).parent)) from mt5_bridge import bridge as _bridge default_journal = "/var/log/hermes/trade_journal.jsonl" try: Path(default_journal).parent.mkdir(parents=True, exist_ok=True) JOURNAL = Path(default_journal) except Exception: JOURNAL = Path(__file__).parents[1] / "logs" / "hermes" / "trade_journal.jsonl" JOURNAL.parent.mkdir(parents=True, exist_ok=True) default_ares_journal = "/var/log/ares/trade_journal.jsonl" try: Path(default_ares_journal).parent.mkdir(parents=True, exist_ok=True) ARES_JOURNAL = Path(default_ares_journal) except Exception: ARES_JOURNAL = Path(__file__).parents[1] / "logs" / "ares" / "trade_journal.jsonl" ARES_JOURNAL.parent.mkdir(parents=True, exist_ok=True) def tg(msg): try: requests.post(f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage", json={"chat_id": TG_CHAT_ID, "text": msg, "parse_mode": "Markdown"}, timeout=10) except: pass def check_bridge_health(): try: d = _bridge("/health") if isinstance(d, dict): s = d.get("status", "").lower() return s in ("ok", "healthy", "running", "alive") return True except: return False def check_llm_health(): try: import requests as _req base = os.getenv("OPENAI_BASE_URL", "") key = os.getenv("OPENAI_API_KEY", "") if not base or not key: return False r = _req.get(base.replace("/v1", ""), headers={"Authorization": f"Bearer {key}"}, timeout=10) return r.status_code == 200 except: return False def main(): now = datetime.now(timezone.utc) issues = [] balance_str = "N/A" equity_str = "N/A" pnl_str = "N/A" try: d = _bridge("/balance") balance_str = f"€{d.get('balance', 0):.2f}" equity_str = f"€{d.get('equity', 0):.2f}" pnl_str = f"€{d.get('profit', 0):.2f}" except: issues.append("🔴 Bridge DOWN") positions_str = "None" pos = [] try: pos = _bridge("/positions") if isinstance(pos, list) and len(pos) > 0: p = pos[0] positions_str = f"{p.get('symbol')} {p.get('orderType')} {p.get('lots')}lot | P&L: €{p.get('profit', 0):.2f}" except: issues.append("🔴 Cannot read positions") if not check_bridge_health(): issues.append("🔴 MT5 bridge not reachable") if not check_llm_health(): issues.append("🟡 LLM API not reachable") ares_balance_str = balance_str if equity_str != "N/A": ares_balance_str = f"{balance_str} (eq {equity_str})" ares_pos_str = "None" if isinstance(pos, list): ares_positions = [ f"{p.get('symbol')} {p.get('orderType')} {p.get('lots')}lot | P&L: €{p.get('profit', 0):.2f}" for p in pos if "ARES" in str(p.get("comment", "")).upper() ] if ares_positions: ares_pos_str = ares_positions[0] ares_wins = ares_losses = 0 if ARES_JOURNAL.exists(): for line in ARES_JOURNAL.read_text().strip().split("\n"): if not line: continue try: t = json.loads(line) if t.get("result") == "win": ares_wins += 1 if t.get("result") == "loss": ares_losses += 1 except: pass wins, losses = 0, 0 if JOURNAL.exists(): for line in JOURNAL.read_text().strip().split("\n"): if not line: continue try: t = json.loads(line) if t.get("result") == "win": wins += 1 if t.get("result") == "loss": losses += 1 except: pass status = "✅ ALL SYSTEMS NOMINAL" if not issues else "\n".join(issues) msg = ( f"🤖 *GENESIS HEARTBEAT*\n" f"🕐 {now.strftime('%Y-%m-%d %H:%M')} UTC\n\n" f"*System Status:* {status}\n\n" f"━━━ ⚡ HERMES (Account A) ━━━\n" f"💰 Balance: {balance_str}\n" f"📊 Equity: {equity_str}\n" f"📈 Open P&L: {pnl_str}\n" f"🔓 Position: {positions_str}\n" f"📒 History: {wins}W / {losses}L\n\n" f"━━━ ⚔️ ARES (Account B) ━━━\n" f"💰 Balance: {ares_balance_str}\n" f"🔓 Position: {ares_pos_str}\n" f"📒 History: {ares_wins}W / {ares_losses}L\n\n" f"_Next heartbeat in 60 minutes._" ) tg(msg) if __name__ == "__main__": main()