# -*- coding: utf-8 -*- """Shared helpers for the QMT gate scripts (qmt_sentinel.py / qmt_probe_0915.py). Design: scripts judge AVAILABILITY (process + bridge RPC + counter behaviour), agents/humans judge CORRECTNESS (cash baseline drift is recorded, never red). Audit trail: C:\\sanguo_bigqmt\\gate\\qmt_gate_YYYYMMDD.log (append) + qmt_gate_latest.json (overwrite status light). """ import datetime import json import re import subprocess ACCOUNT = "66639661" GATE_DIR = r"C:\sanguo_bigqmt\gate" RELOGIN_TASK = "sanguo-qmt-relogin" QMT_PROCS = ("XtItClient.exe", "XtMiniQmt.exe", "miniquote.exe") def now_str(): return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def gate_log(kind, verdict, msg): line = "%s [%s] %s %s" % (now_str(), kind, verdict, msg) try: import os os.makedirs(GATE_DIR, exist_ok=True) path = r"%s\qmt_gate_%s.log" % ( GATE_DIR, datetime.datetime.now().strftime("%Y%m%d")) with open(path, "a", encoding="utf-8") as f: f.write(line + "\n") except Exception as exc: line += " (log-write-failed %r)" % exc print(line, flush=True) return line def gate_json(kind, verdict, **detail): import os payload = {"ts": now_str(), "kind": kind, "verdict": verdict} payload.update(detail) try: os.makedirs(GATE_DIR, exist_ok=True) with open(r"%s\qmt_gate_latest.json" % GATE_DIR, "w", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False, default=str, indent=1) except Exception as exc: print("GATE_JSON_WRITE_FAIL %r" % exc, flush=True) return payload def redis_client(): import redis with open(r"C:\redis\redis.conf") as f: pw = re.search(r"^requirepass (\S+)", f.read(), re.M).group(1) return redis.Redis(host="127.0.0.1", port=6379, db=5, password=pw, socket_connect_timeout=5, decode_responses=True) def bridge_ping(rc, timeout=10): """(up, detail) — up means the RPC server answered ping with ok+pong.""" from bigqmt_signal_trader.redis_rpc import call_redis_rpc try: r = call_redis_rpc(rc, ACCOUNT, "ping", {}, timeout_seconds=timeout) except Exception as exc: return False, "RPC_DOWN %s %s" % (type(exc).__name__, str(exc)[:120]) if not isinstance(r, dict): return False, "RPC_BAD %r" % (r,) if not r.get("ok"): return False, "RPC_ERR %s" % str(r.get("error") or r)[:150] d = r.get("data") or {} if not d.get("pong"): return False, "RPC_NO_PONG %s" % str(d)[:150] return True, "server_time=%s rev=%s allow_order=%s" % ( d.get("server_time"), d.get("rpc_revision"), d.get("allow_order_methods")) def procs_alive(): """QMT family process presence via tasklist (no psutil dependency).""" out = subprocess.run(["tasklist", "/FO", "CSV", "/NH"], capture_output=True, text=True, timeout=30).stdout or "" return {name: ('"%s"' % name) in out for name in QMT_PROCS} def relogin_running(): """True if the v2 relogin python is mid-flight (avoid double trigger).""" try: import psutil except ImportError: return False for p in psutil.process_iter(["pid", "name", "cmdline"]): try: if "python" not in (p.info["name"] or "").lower(): continue cmd = " ".join(p.info["cmdline"] or []) except Exception: continue if "qmt_relogin.py" in cmd: return True return False def trigger_relogin(): r = subprocess.run(["schtasks", "/run", "/tn", RELOGIN_TASK], capture_output=True, text=True, timeout=30) ok = r.returncode == 0 return ok, ((r.stdout or "") + (r.stderr or "")).strip()[:120] def wait_bridge(rc, timeout_sec, kind): """Poll bridge ping until it answers; every probe logged once at the end.""" import time t0 = time.time() last = "" while time.time() - t0 < timeout_sec: up, detail = bridge_ping(rc) if up: return True, int(time.time() - t0), detail last = detail time.sleep(5) return False, int(time.time() - t0), last