Files
my-new-linux-mt5-bot/core/heartbeat.py
T
2026-07-11 02:42:55 +08:00

154 lines
4.9 KiB
Python

#!/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()