2b2ae3c4d8
- 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模式)
108 lines
4.2 KiB
Python
108 lines
4.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""sanguo-qmt-sentinel — every-30min availability patrol for the Big QMT stack.
|
|
|
|
Verdict rules (2026-09-09 design, user-approved):
|
|
judge RED only on: XtItClient.exe gone OR bridge RPC ping dead.
|
|
account-class probes (asset/positions) and the data leg (XtMiniQmt/miniquote)
|
|
are recorded as telemetry only — post-close false-reds by design.
|
|
RED -> schtasks /run sanguo-qmt-relogin (reuse v2, never copy login code)
|
|
-> poll bridge <=10min -> re-check -> full audit trail in gate log.
|
|
No backoff on consecutive failures (user decision #4): every tick independent,
|
|
failures just stack in the log; the next tick retries naturally.
|
|
|
|
Exit codes: 0 green/recovered | 1 red-remains | 2 amber (cannot judge).
|
|
"""
|
|
import json
|
|
|
|
from qmt_gate_common import (ACCOUNT, bridge_ping, gate_json, gate_log,
|
|
procs_alive, redis_client, relogin_running,
|
|
trigger_relogin, wait_bridge)
|
|
|
|
RECOVER_TIMEOUT = 600 # 10 min budget for relogin round trip
|
|
|
|
|
|
def telemetry(rc):
|
|
"""Recorded, never judged: allow_order bit, asset cash, queue length."""
|
|
from bigqmt_signal_trader.redis_rpc import call_redis_rpc
|
|
t = {}
|
|
try:
|
|
r = call_redis_rpc(rc, ACCOUNT, "query_stock_asset",
|
|
{"account_id": ACCOUNT}, timeout_seconds=15)
|
|
d = r.get("data") or {}
|
|
t["cash"] = d.get("cash")
|
|
t["mktval"] = d.get("market_value")
|
|
except Exception as exc:
|
|
t["asset_err"] = "%s %s" % (type(exc).__name__, str(exc)[:80])
|
|
try:
|
|
t["queue_len"] = rc.llen("bigqmt:rpc:queue:%s" % ACCOUNT)
|
|
except Exception as exc:
|
|
t["queue_err"] = str(exc)[:60]
|
|
return t
|
|
|
|
|
|
def main():
|
|
procs = procs_alive()
|
|
if not procs.get("XtItClient.exe"):
|
|
msg = "XtItClient.exe DEAD family=%s" % procs
|
|
gate_log("SENTINEL", "RED", msg)
|
|
return recover("process-dead")
|
|
try:
|
|
rc = redis_client()
|
|
except Exception as exc:
|
|
gate_log("SENTINEL", "AMBER", "redis connect failed %r" % exc)
|
|
return 2
|
|
up, detail = bridge_ping(rc)
|
|
if not up:
|
|
gate_log("SENTINEL", "RED", "bridge ping dead (%s)" % detail)
|
|
return recover("bridge-dead")
|
|
t = telemetry(rc)
|
|
msg = "proc=1 bridge=up %s mini=%d miniquote=%d cash=%s mktval=%s q=%s" % (
|
|
detail, procs.get("XtMiniQmt.exe", 0), procs.get("miniquote.exe", 0),
|
|
t.get("cash"), t.get("mktval"), t.get("queue_len"))
|
|
gate_log("SENTINEL", "GREEN", msg)
|
|
gate_json("sentinel", "GREEN", bridge=detail, procs=procs, **t)
|
|
return 0
|
|
|
|
|
|
def recover(reason):
|
|
"""Trigger v2 relogin, wait for the bridge, re-check, log the outcome."""
|
|
try:
|
|
rc = redis_client()
|
|
except Exception as exc:
|
|
gate_log("SENTINEL", "AMBER", "redis failed pre-recovery %r" % exc)
|
|
return 2
|
|
if relogin_running():
|
|
gate_log("SENTINEL", "SKIP",
|
|
"relogin already in flight (%s); let it finish" % reason)
|
|
ok, took, detail = wait_bridge(rc, RECOVER_TIMEOUT, "SENTINEL")
|
|
return 0 if ok else 1
|
|
ok, out = trigger_relogin()
|
|
if not ok:
|
|
gate_log("SENTINEL", "AMBER",
|
|
"schtasks /run %s failed: %s" % (reason, out))
|
|
return 2
|
|
gate_log("SENTINEL", "ACTION",
|
|
"relogin triggered (%s); polling bridge <=%ds" % (reason,
|
|
RECOVER_TIMEOUT))
|
|
ok, took, detail = wait_bridge(rc, RECOVER_TIMEOUT, "SENTINEL")
|
|
procs = procs_alive()
|
|
if ok and procs.get("XtItClient.exe"):
|
|
t = telemetry(rc)
|
|
gate_log("SENTINEL", "RECOVERED",
|
|
"bridge back in %ds (%s) procs=%s cash=%s" % (
|
|
took, detail, procs, t.get("cash")))
|
|
gate_json("sentinel", "RECOVERED", reason=reason, took_sec=took,
|
|
bridge=detail, procs=procs, **t)
|
|
return 0
|
|
gate_log("SENTINEL", "RED",
|
|
"NOT recovered in %ds (bridge=%s procs=%s) — no backoff by "
|
|
"design, next tick retries" % (took, detail, procs))
|
|
gate_json("sentinel", "RED", reason=reason, took_sec=took, bridge=detail,
|
|
procs=procs)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
sys.exit(main())
|