Files
sanguo_vnpy_v2/scripts/qmt_relogin/qmt_gate_common.py
T
claude_dev 2b2ae3c4d8
CI/CD / test (push) Successful in 28s
CI/CD / nas-deploy (push) Successful in 3s
CI/CD / nas-verify (push) Successful in 11s
feat(ops): 大QMT哨兵+09:15盘前探针三层防御上线——30min巡检+必不成交单即撤+gate留痕 [vps]
- qmt_sentinel.py: sanguo-qmt-sentinel 每30min(进程+桥RPC ping判红,账户类仅记录);
  红->schtasks /run sanguo-qmt-relogin->轮询桥恢复<=10min->复检留痕;无退避(拍板#4)
- qmt_probe_0915.py: sanguo-qmt-probe0915 周一至五09:15; 510050.SH买100@0.01
  FIX_PRICE必不成交单(09-09 09:40实证参数形状)+即撤; status57废单=会话陈旧
  ->触发relogin赶开盘; 窗口硬卡09:15:00-09:19:30(09:20-09:25不可撤)
- qmt_gate_common.py: redis/ping/tasklist/触发/轮询/gate留痕共享库
- 留痕: C:\sanguo_bigqmt\gate\qmt_gate_YYYYMMDD.log + qmt_gate_latest.json
- ui_act.py 修复: activate仅contains时KeyError; credtype LocalFree类型错; 新增text op
  (typewrite连打丢数字/shift字符,验证码须key/hotkey逐字符打)
- 实测: 首夜14跳全绿cash零漂移; 下单RPC管线ok=True; SKIP/退出码/桥死分支全验
- schtask XML x2(SYSTEM+HighestAvailable,照sanguo-xt-daily模式)
2026-09-10 07:24:45 +08:00

123 lines
4.1 KiB
Python

# -*- 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