146 lines
5.5 KiB
Python
146 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""mofin_guard_monitor.py — 知微消息监控(2026-08-17 下午盘,老莫要求)
|
||
持续监控知微发给老莫的消息 + 内部流程,判断:
|
||
1. 推荐是否符合策略推荐规范(docs/strategy-recommendation-spec.md)
|
||
- 推荐必须带策略依据(版本/重评/止损/止盈/信号)
|
||
- 信号不充分/观望 不得推操作
|
||
- 候选股(非持仓)不推
|
||
2. 内部流程是否合规(executor/scanner/守卫/LLM)
|
||
后台 nohup 运行,写状态到 /tmp/mofin_monitor_status.json,不阻塞 TUI。
|
||
"""
|
||
import json, os, time, glob
|
||
from datetime import datetime, timedelta
|
||
|
||
STATUS = "/tmp/mofin_monitor_status.json"
|
||
XMPP_LOG = "/home/hmo/MoFin/gateway/logs/xmpp_messages.jsonl"
|
||
EXEC_LOG = "/home/hmo/MoFin/gateway/logs/strategy_executor.log"
|
||
GUARD_STATUS = "/home/hmo/MoFin/gateway/logs/deploy_guard_status.json"
|
||
GUARD_LOG = "/home/hmo/MoFin/gateway/logs/deploy_guard.log"
|
||
ERRORS_LOG = "/home/hmo/.hermes/profiles/position-analyst/logs/errors.log"
|
||
POOL_OUT = "/home/hmo/.hermes/profiles/position-analyst/cron/output/pool_news_collector_30min"
|
||
|
||
INTERVAL = 120 # 每2分钟一轮
|
||
|
||
|
||
def load_status():
|
||
try:
|
||
return json.load(open(STATUS, encoding="utf-8"))
|
||
except Exception:
|
||
return {"started": datetime.now().isoformat(), "checks": [], "alerts": []}
|
||
|
||
|
||
def save_status(s):
|
||
json.dump(s, open(STATUS, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||
|
||
|
||
def check_messages(s, last_check):
|
||
"""检查知微新消息:是否含操作推荐,是否带策略依据"""
|
||
alerts = []
|
||
try:
|
||
with open(XMPP_LOG, encoding="utf-8") as f:
|
||
lines = f.readlines()
|
||
new = []
|
||
for l in lines:
|
||
try:
|
||
d = json.loads(l)
|
||
ts = str(d.get("timestamp", ""))
|
||
if ts > last_check and d.get("direction") == "out" and "zhiwei" in str(d.get("from", "")):
|
||
new.append(d)
|
||
except Exception:
|
||
pass
|
||
for d in new:
|
||
body = str(d.get("body_preview", ""))
|
||
# 判断:是否含操作建议(买入/卖出/止损/建仓/清仓)
|
||
ops = ["买入", "卖出", "建仓", "清仓", "止损", "止盈", "加仓", "减仓"]
|
||
has_op = any(k in body for k in ops)
|
||
# 是否带策略依据(版本/重评/止损值/信号)
|
||
has_basis = any(k in body for k in ["依据", "重评", "止损", "信号", "策略", "RR"])
|
||
# 是否可能是"信号不充分/观望"却推了
|
||
weak_signal = any(k in body for k in ["信号不充分", "暂不买入", "不追", "等信号转买入"])
|
||
issue = None
|
||
if has_op and not has_basis:
|
||
issue = f"推荐无策略依据: {body[:100]}"
|
||
elif weak_signal and has_op:
|
||
issue = f"信号不充分却含操作建议: {body[:100]}"
|
||
if issue:
|
||
alerts.append({"ts": d.get("timestamp"), "issue": issue, "body": body[:150]})
|
||
except Exception as e:
|
||
alerts.append({"ts": datetime.now().isoformat(), "issue": f"消息读取失败: {e}"})
|
||
return alerts
|
||
|
||
|
||
def check_executor(s):
|
||
"""executor 是否正常(崩溃/超时)"""
|
||
alerts = []
|
||
try:
|
||
with open(EXEC_LOG, encoding="utf-8") as f:
|
||
lines = f.readlines()
|
||
for l in lines[-20:]:
|
||
if any(k in l for k in ["✗", "崩溃", "超时", "stderr:", "⚠️"]):
|
||
if "重评" not in l and "stderr(rc=0)" not in l:
|
||
alerts.append({"ts": l[:25], "issue": l.strip()[:120]})
|
||
except Exception:
|
||
pass
|
||
return alerts
|
||
|
||
|
||
def check_guard(s):
|
||
"""部署守卫是否有问题"""
|
||
alerts = []
|
||
try:
|
||
d = json.load(open(GUARD_STATUS, encoding="utf-8"))
|
||
probs = d.get("problems", []) or []
|
||
for p in probs:
|
||
alerts.append({"ts": datetime.now().isoformat(), "issue": f"守卫: {str(p)[:120]}"})
|
||
except Exception:
|
||
pass
|
||
return alerts
|
||
|
||
|
||
def check_llm(s, last_check):
|
||
"""LLM 是否连续失败"""
|
||
alerts = []
|
||
try:
|
||
with open(ERRORS_LOG, encoding="utf-8") as f:
|
||
for l in f.readlines()[-30:]:
|
||
if "API call failed" in l or "Stream stale" in l or "empty stream" in l:
|
||
ts = l[:19]
|
||
if ts > last_check:
|
||
alerts.append({"ts": ts, "issue": l.strip()[60:150]})
|
||
except Exception:
|
||
pass
|
||
return alerts
|
||
|
||
|
||
def main():
|
||
s = load_status()
|
||
last_msg = s.get("last_msg_check", "")
|
||
last_llm = s.get("last_llm_check", "")
|
||
while True:
|
||
now = datetime.now()
|
||
ts = now.strftime("%Y-%m-%d %H:%M:%S")
|
||
all_alerts = []
|
||
all_alerts += check_messages(s, last_msg)
|
||
all_alerts += check_executor(s)
|
||
all_alerts += check_guard(s)
|
||
all_alerts += check_llm(s, last_llm)
|
||
if all_alerts:
|
||
s["alerts"] = (s.get("alerts") or []) + [{"check_ts": ts, "alerts": all_alerts}]
|
||
s["alerts"] = s["alerts"][-30:]
|
||
s["last_check"] = ts
|
||
s["last_msg_check"] = now.strftime("%Y-%m-%d %H:%M:%S")
|
||
s["last_llm_check"] = now.strftime("%Y-%m-%d %H:%M:%S")
|
||
save_status(s)
|
||
# 简明写一行日志(不刷屏)
|
||
try:
|
||
with open("/tmp/mofin_monitor.log", "a") as f:
|
||
f.write(f"[{ts}] checks=ok alerts={len(all_alerts)}\n")
|
||
except Exception:
|
||
pass
|
||
time.sleep(INTERVAL)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|