144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Experiment: graceful close -> exe relaunch -> auto-login/auto-run/bridge.
|
|
Read-only w.r.t. trading: no order methods sent anywhere. Exit codes:
|
|
0 all green | 10 polite close refused | 11 launch/ready failed
|
|
12 instance not auto-run | 13 bridge rpc dead."""
|
|
import datetime
|
|
import io
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
ACCOUNT = "66639661"
|
|
|
|
|
|
def log(m):
|
|
print("[%s] %s" % (datetime.datetime.now().strftime("%H:%M:%S"), m), flush=True)
|
|
|
|
|
|
def shot(name):
|
|
try:
|
|
import pyautogui
|
|
pyautogui.FAILSAFE = False
|
|
pyautogui.screenshot(r"C:\sanguo_bigqmt\%s" % name)
|
|
log("shot saved: %s" % name)
|
|
except Exception as e:
|
|
log("shot err %r" % e)
|
|
|
|
|
|
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
|
|
root = os.path.dirname(d)
|
|
formula_log = os.path.join(root, "userdata", "log", "XtClient_Formula_20260909.log")
|
|
|
|
from bigqmt_signal_trader import qmt_launcher as L # noqa: E402
|
|
from bigqmt_signal_trader.redis_rpc import call_redis_rpc # noqa: E402
|
|
import redis # noqa: E402
|
|
|
|
try:
|
|
log("session locked: %s" % L.session_is_locked())
|
|
except Exception as e:
|
|
log("session check err %r" % e)
|
|
|
|
# 1) flush stale queue (composition pre-verified: 100% read-only, 0 orders)
|
|
with open(r"C:\redis\redis.conf") as f:
|
|
pw = re.search(r"^requirepass (\S+)", f.read(), re.M).group(1)
|
|
rc = redis.Redis(host="127.0.0.1", port=6379, db=5, password=pw,
|
|
decode_responses=True)
|
|
Q = "bigqmt:rpc:queue:%s" % ACCOUNT
|
|
n = rc.llen(Q)
|
|
rc.delete(Q)
|
|
log("queue flushed: %d stale read-only items deleted" % n)
|
|
|
|
# 2) graceful close, polite ONLY
|
|
subprocess.run(["taskkill", "/IM", "XtItClient.exe"], capture_output=True)
|
|
deadline = time.time() + 20
|
|
alive = True
|
|
while time.time() < deadline:
|
|
out = subprocess.run(["tasklist", "/FI", "IMAGENAME eq XtItClient.exe"],
|
|
capture_output=True, text=True).stdout or ""
|
|
if "XtItClient.exe" not in out:
|
|
alive = False
|
|
break
|
|
time.sleep(1)
|
|
if alive:
|
|
log("ABORT: polite close refused (dialog up?); NOT forcing")
|
|
shot("relogin_abort.png")
|
|
sys.exit(10)
|
|
log("polite close ok")
|
|
time.sleep(5)
|
|
t_launch = datetime.datetime.now()
|
|
log("relaunching via open_qmt(mode=exe) at %s" % t_launch.strftime("%H:%M:%S"))
|
|
|
|
# 3) exe relaunch: remembered-session auto-login path (no typing)
|
|
try:
|
|
waited = L.open_qmt(d, mode="exe", ready_timeout_seconds=240)
|
|
log("open_qmt(exe) ready in %.1fs" % waited)
|
|
except Exception as e:
|
|
log("FATAL open_qmt(exe): %r" % e)
|
|
shot("relogin_launchfail.png")
|
|
sys.exit(11)
|
|
|
|
|
|
def formula_events(since):
|
|
"""Return (banner_seen, runonce_seen) for log lines timestamped >= since."""
|
|
banner = runonce = False
|
|
try:
|
|
with io.open(formula_log, "r", encoding="gbk", errors="replace") as f:
|
|
for ln in f:
|
|
try:
|
|
ts = datetime.datetime.strptime(ln[:19], "%Y-%m-%d %H:%M:%S")
|
|
except ValueError:
|
|
continue
|
|
if ts < since:
|
|
continue
|
|
if "XtItClient/tags" in ln:
|
|
banner = True
|
|
if "runonce" in ln or "PythonFormula::" in ln:
|
|
runonce = True
|
|
except OSError:
|
|
pass
|
|
return banner, runonce
|
|
|
|
|
|
# 4) instance auto-run detection
|
|
t0 = time.time()
|
|
banner = runonce = False
|
|
while time.time() - t0 < 150:
|
|
time.sleep(5)
|
|
banner, runonce = formula_events(t_launch)
|
|
if runonce:
|
|
break
|
|
log("formula after relaunch: banner=%s runonce=%s (waited %.0fs)"
|
|
% (banner, runonce, time.time() - t0))
|
|
if not runonce:
|
|
log("instance did NOT auto-run after graceful relaunch")
|
|
shot("relogin_norun.png")
|
|
sys.exit(12)
|
|
|
|
# 5) bridge probe (ORDER-class query, safe immediately after login)
|
|
t0 = time.time()
|
|
got = None
|
|
while time.time() - t0 < 150:
|
|
for method, params in (("ping", {}), ("query_stock_orders",
|
|
{"account_id": ACCOUNT})):
|
|
try:
|
|
r = call_redis_rpc(rc, ACCOUNT, method, params, timeout_seconds=10)
|
|
if isinstance(r, dict):
|
|
got = (method, r)
|
|
break
|
|
except Exception as e:
|
|
last = "%s -> %s" % (method, e)
|
|
if got:
|
|
break
|
|
time.sleep(5)
|
|
if not got:
|
|
log("FATAL bridge rpc no answer; last=%r" % last)
|
|
shot("relogin_norpc.png")
|
|
sys.exit(13)
|
|
log("bridge answering via %r ok=%s" % (got[0], got[1].get("ok")))
|
|
shot("relogin_green.png")
|
|
log("=== RELOGIN_TEST_ALL_GREEN ===")
|
|
sys.exit(0)
|