Isolate CTP in worker process and improve strategy roll UX.

Split vn.py into qihuo-ctp worker with IPC client bridge, keep CTP connected during breaks with cached account fallback, speed up strategy page loads, and allow off-session breakout roll submissions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-01 12:35:47 +08:00
parent 08d55411aa
commit 9cd81a3ea7
17 changed files with 2214 additions and 227 deletions
+46
View File
@@ -0,0 +1,46 @@
"""Check qihuo web CTP status and qihuo-ctp worker health."""
from __future__ import annotations
import sys
import paramiko
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.8.21", username="root", password="woaini88", timeout=15)
cmds = [
'curl -s --max-time 8 -H "X-Qihuo-CTP-Token: qihuo-local-ctp" http://127.0.0.1:6601/health',
r'''python3 - <<'PY'
import http.cookiejar, json, sqlite3, urllib.parse, urllib.request
conn = sqlite3.connect("/opt/qihuo/futures.db")
row = conn.execute("SELECT value FROM settings WHERE key='admin_username'").fetchone()
conn.close()
user = row[0] if row else "admin"
pw = ""
for line in open("/opt/qihuo/.env", encoding="utf-8-sig", errors="replace"):
if line.startswith("ADMIN_PASSWORD="):
pw = line.split("=", 1)[1].strip().strip('"').strip("'")
jar = http.cookiejar.CookieJar()
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
op.open(urllib.request.Request(
"http://127.0.0.1:6600/login",
urllib.parse.urlencode({"username": user, "password": pw}).encode(),
), timeout=8)
raw = op.open("http://127.0.0.1:6600/api/ctp/status", timeout=8).read()
print(raw.decode("utf-8", "replace")[:2000])
PY''',
"pm2 jlist | python3 -c \"import sys,json; rows=json.load(sys.stdin); print([(r.get('name'), (r.get('pm2_env') or {}).get('status'), (r.get('pm2_env') or {}).get('restart_time')) for r in rows if r.get('name') in ('qihuo','qihuo-ctp')])\"",
]
try:
for cmd in cmds:
print("===", cmd)
_, o, e = c.exec_command(cmd, timeout=30)
out = o.read().decode("utf-8", "replace")
err = e.read().decode("utf-8", "replace")
print(out[:3000])
if err.strip():
print("ERR:", err[:1000])
finally:
c.close()
+112
View File
@@ -0,0 +1,112 @@
"""Verify qihuo web process survives an isolated qihuo-ctp restart."""
from __future__ import annotations
import json
import sys
import time
import paramiko
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
def _connect() -> paramiko.SSHClient:
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.8.21", username="root", password="woaini88", timeout=15)
return c
def _run(c: paramiko.SSHClient, cmd: str, timeout: int = 60) -> str:
_, o, e = c.exec_command(cmd, timeout=timeout)
out = o.read().decode("utf-8", "replace")
err = e.read().decode("utf-8", "replace")
return out + (("\nERR:\n" + err) if err.strip() else "")
def _pm2(c: paramiko.SSHClient) -> dict[str, dict]:
raw = _run(c, "pm2 jlist", timeout=30)
rows = json.loads(raw)
return {r.get("name"): r for r in rows}
def _restart_count(row: dict) -> int:
env = row.get("pm2_env") or {}
return int(env.get("restart_time") or 0)
def main() -> int:
c = _connect()
try:
before = _pm2(c)
for name in ("qihuo", "qihuo-ctp"):
row = before.get(name) or {}
env = row.get("pm2_env") or {}
print(
"before",
name,
"status",
env.get("status"),
"restarts",
_restart_count(row),
"pid",
row.get("pid"),
)
health = _run(
c,
'curl -s -H "X-Qihuo-CTP-Token: qihuo-local-ctp" '
"http://127.0.0.1:6601/health",
timeout=30,
)
print("worker_health", health[:1000])
web = _run(
c,
"curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:6600/login",
timeout=30,
)
print("web_login_before", web.strip())
print("restarting qihuo-ctp only")
print(_run(c, "pm2 restart qihuo-ctp --update-env", timeout=60))
time.sleep(8)
after = _pm2(c)
for name in ("qihuo", "qihuo-ctp"):
row = after.get(name) or {}
env = row.get("pm2_env") or {}
print(
"after",
name,
"status",
env.get("status"),
"restarts",
_restart_count(row),
"pid",
row.get("pid"),
)
web_after = _run(
c,
"curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:6600/login",
timeout=30,
)
print("web_login_after", web_after.strip())
qihuo_before = _restart_count(before.get("qihuo") or {})
qihuo_after = _restart_count(after.get("qihuo") or {})
ctp_before = _restart_count(before.get("qihuo-ctp") or {})
ctp_after = _restart_count(after.get("qihuo-ctp") or {})
ok = (
qihuo_after == qihuo_before
and ctp_after >= ctp_before + 1
and web_after.strip() == "200"
)
print("isolation_ok", ok)
return 0 if ok else 1
finally:
c.close()
if __name__ == "__main__":
raise SystemExit(main())
+72
View File
@@ -0,0 +1,72 @@
"""Deploy position display fix: stop ALTER lock + live rows from CTP."""
import sys
import time
from pathlib import Path
import paramiko
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
root = Path(__file__).resolve().parents[1]
files = [
"ctp_ipc_client.py",
"ctp_worker.py",
"ecosystem.config.cjs",
"market_sessions.py",
"trading_context.py",
"dashboard_lib.py",
"sl_tp_guard.py",
"install_trading.py",
"vnpy_bridge.py",
"static/js/trade.js",
"templates/strategy.html",
"templates/strategy_records.html",
"static/js/strategy.js",
"strategy/strategy_roll_lib.py",
"scripts/run_schema_migrate.py",
]
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.8.21", username="root", password="woaini88", timeout=15)
sftp = c.open_sftp()
for rel in files:
sftp.put(str(root / rel), f"/opt/qihuo/{rel}")
print("uploaded", rel)
sftp.close()
cmds = [
"cd /opt/qihuo && source venv/bin/activate && python3 scripts/run_schema_migrate.py",
"cd /opt/qihuo && source venv/bin/activate && python3 -m py_compile ctp_ipc_client.py ctp_worker.py vnpy_bridge.py sl_tp_guard.py install_trading.py",
"cd /opt/qihuo && (pm2 describe qihuo-ctp >/dev/null && pm2 restart qihuo-ctp --update-env || pm2 start ecosystem.config.cjs --only qihuo-ctp)",
"cd /opt/qihuo && (pm2 describe qihuo >/dev/null && pm2 restart qihuo --update-env || pm2 start ecosystem.config.cjs --only qihuo)",
"pm2 save",
]
for cmd in cmds:
print(">>>", cmd)
_, o, e = c.exec_command(cmd, timeout=120)
out = o.read().decode("utf-8", "replace")
err = e.read().decode("utf-8", "replace")
if out.strip():
print(out)
if err.strip():
print(err)
time.sleep(25)
_, o, _ = c.exec_command(
"curl -s -o /dev/null -w 'login:%{http_code}\\n' --max-time 10 http://127.0.0.1:6600/login",
timeout=30,
)
print(o.read().decode())
# verify live vs account_snapshot
poll = root / "scripts" / "_poll_loop.py"
if poll.exists():
import subprocess
r = subprocess.run([sys.executable, str(poll)], capture_output=True, text=True, timeout=120)
print(r.stdout)
if r.stderr.strip():
print(r.stderr)
c.close()
print("done")