Files
sanguo_vnpy_v2/audit/20261005_qmt_identity_env_plan.md

8.5 KiB
Raw Permalink Blame History

QMT 部署身份单一权威源方案(P2-18 根治)——66639661 硬编码调研与实施

2026-10-05 傍晚用户拍板(⚖️-2):确认 66639661 永为模拟柜台账号;同时指出「硬编码本身是问题」, 令详调根因并给出硬编码以外的方案。infra 派单(025b4e6):本档落 audit/ + repo 侧实施归 stratergy, VPS 班车 = 10-08 复市验证窗口之后(护 10-08 07:50 relogin 首班纯净)。

一、全量枚举(git grep 实证)

库内 32 文件 88 处 + 生产侧库外若干。按形态分五类:

形态 位置 处置
纯常量 qmt_gate_common.py:14、qmt_relogin.py:41、qmt_bridge_probe.py:9、setclip_acc.py:8、check_xtquant.py:17 → 读身份文件
env 带硬编码默认 xt_gateway.py:22、qmt_gateway_client.py:76、is_price_source_vps.py:24 → 删默认值,env > 身份文件
wrapper 注入字面量 bridge_switch_ops.py:119/276/322/331、xt_eod_wrapper.ps1:10、生产 run_shadow_bridge.ps1:7(C:\sanguo_bigqmt\,不在 git) 库内 → 读身份文件;生产 wrapper 10-08 后按非代码资产流程
配置文件(正确形态,保留) config/live.yaml:9、config/data_platform.yaml:63 保留,CI 同步测钉一致
文档/注释/测试 engine.py:41、runner.py:12、runner_live.py:12/294、live.yaml:4 注释、README、7 份 docs、9 个 test 码内注释改占位符;docs 归档不动;测试保留

换号真实成本(改前):库内 14 个生产文件 + 生产侧 ≥4 个 wrapper ps1;漏一处 = 静默盯错账号。

二、根因(三层)

  1. 机制层:Windows schtask 无 per-task env(sentinel_task.xml 实证无 env 节);VPS 机器级 env 未设 (HKLM\...\Session Manager\Environment reg query 实证空)→ 每个计划任务被迫 wrapper 注入,字面量顺手写进 wrapper。
  2. 结构层:「部署身份」(这台机器驱动哪个 QMT 账号)无单一权威源。三代各自发明: 07 月 live 腿 = env SANGUO_QMT_ACCOUNT > live.yaml > fail(正规,96b1924); 09 月大QMT 灾备腿 = 纯常量(stdlib 纯净 + 图省事,2b2ae3c/4444848); wrapper 层 = 注入字面量。env 名分裂三个:ACCOUNT_ID / SANGUO_QMT_ACCOUNT / BIGQMT_ACCOUNT_ID。
  3. 氛围层:模拟号泄漏无害 → 零安全压力(铁证 run_shadow_bridge.ps1 同文件:redis 密码运行时读 conf 纪律在线,上一行账号就是字面量)。09-14/19 ops 批次 copy-paste 传播主力。

三、方案比选(已报用户+infra)

方案 结论
A 全 env 化 ❌ 字面量只是从 py 搬家到 ps1(生产 wrapper 已是此形态);灾备腿 fail-loud = 机器最坏时 gate 起不来
B 机器级 env(setx /M) ⭐ 优秀传输层非完整方案;机器事实≠版本管理事实,重建 VPS 丢(本批不采用,留作可选增强)
C 单一配置文件权威 ✅ 采纳:config/qmt_identity.json + 各腿镜像读取器 + CI 同步测(休市表 7f9b413 先例)
D DB 注册表权威 ❌ 鸡生蛋:relogin/gate 恰在「别的都坏」时要跑,不能加 DB 依赖(runtime 腿的 sticky 行机制 B1 已在用)

C 的关键约束:灾备腿(gate/sentinel/relogin)stdlib 纯净 → 配置文件必须 JSON(json 是标准库, pyyaml 不是)——这也是不能并进现有 data_platform.yaml 的原因。

定位:本方案买的是可维护性(换号一处生效、杜绝静默盯错账号),不是保密——git 历史已含该号; 真上实盘那天的正确动作 = 开新实盘号 + 模拟号退役,而非改配置。

四、实施设计

身份文件 config/qmt_identity.json(随 promote 走,git 管理)

{
  "default_account": "66639661",
  "kind": "simulated",
  "mini_path": "C:\\国金QMT交易端模拟\\userdata_mini"
}

(mini_path 本批只存档不接线——账号才是 P2-18 的病灶;路径接线留后续按需。)

解析链(各腿一致)

env SANGUO_QMT_ACCOUNT > env BIGQMT_ACCOUNT_ID(兼容别名,存量 wrapper 设的就是它)
  > config/qmt_identity.json > RuntimeError fail-loud(不猜、不默认)

文件查找顺序(灾备腿专用后两条):env SANGUO_IDENTITY_JSON 显式指路(不存在=立即报错, 显式覆盖不静默回退)> <脚本>/../../config/(repo 布局)> C:\sanguo_vnpy_v2\config\ (生产副本位——gate/relogin 以 C:\sanguo_bigqmt 副本运行时 repo 相对路径失效)。

改动清单(repo 侧本批)

文件 改动
config/qmt_identity.json 新增
scripts/qmt_relogin/qmt_gate_common.py load_identity() 正典装载器 + ACCOUNT 改读它
scripts/qmt_relogin/qmt_relogin.py / qmt_bridge_probe.py / setclip_acc.py from qmt_gate_common import ACCOUNT(同目录,消重)
sanguo_qmt_bridge/xt_gateway.py / check_xtquant.py 包内镜像 _qmt_account()(env ACCOUNT_ID/SANGUO_QMT_ACCOUNT/BIGQMT_ACCOUNT_ID > json)
sanguo_trader/qmt_gateway_client.py _setting() 删默认字面量 → 镜像链
scripts/pipeline/is_price_source_vps.py _ACCOUNT 删 → _identity_default_account()
scripts/bridge_switch_ops.py 3 处 env 注入 + 1 处内嵌代码串 → _qmt_account()
scripts/data_platform/xt_eod_wrapper.ps1 Get-Content qmt_identity.json | ConvertFrom-Json(保持 ASCII)
sanguo_live/engine.py / runner.py、runner_live.py、config/live.yaml:4 注释 字面量 → <账号> 占位
sanguo_qmt_bridge/README.md 默认值表行 → 指向身份文件
tests/trader/test_qmt_identity.py 新增(见下)
docs/deployment/vps-production-runbook.md 身份文件条目(CI 基建门:qmt_relogin/bridge_switch_ops 触发)
docs/superpowers/specs/2026-10-03-monitoring-design.md gate 簇身份链一句话 + 坑台账

测试策略(tests/trader/test_qmt_identity.py 四组钉)

  1. 装载链语义:env 两名覆盖、SANGUO_IDENTITY_JSON 指空=RuntimeError、模块 ACCOUNT ≡ json。
  2. 单源一致性(anti-drift,休市表先例):identity ≡ live.yaml.account ≡ ∈ watch_accounts ≡ gate_common.ACCOUNT ≡ xt_gateway/qmt_gateway_client 镜像解析值(env 清空)。
  3. 字面量 grep-pin:66639661 只允许出现在 config 三件套 + tests/ + docs/ + audit/; 生产码面(scripts/、sanguo_*/)零字面量——任何新增扩散直接被单测抓住。
  4. 文件格式钉=纯 ASCII(10-07 补,🔴 生产事故反向钉):PS5.1 Get-Content 默认按 ANSI/GBK 解无 BOM 文件,文件含 UTF-8 中文原始字节 → ConvertFrom-Json FormatException → env 注入失败。10-06 21:00 xt_eod 实证全链: BIGQMT_ACCOUNT_ID 未注入 → 桥 shim import 期硬错 account_id is required → universe ETF/基金=0 → rc=1(对照 10-05 同假期班 rows=37301 正常)。 修=文件中文一律 \u 转义(ensure_ascii),双端等值:Python json.load 与 PS5.1 ConvertFrom-Json 均原生解转义。phase-③ 生产 wrapper 裸 Get-Content 读此文件的安全性由本钉担保(改文件加中文必红)。

五、分阶段上线

阶段 时点 内容
① repo 本班(10-05) 上表全部 + 测试 + 两档更新;push 后 CI test 绿即达(nas-deploy 楔死=找 infra 手动 promote,既定协议)
①′ ASCII 化修复 10-07(10-08 窗口前) json 改 \u 转义 + 第④钉;紧急性=10-08 复市 21:00 xt-daily 必须能用(10-06 起已断,30d 窗口拉取自愈无数据丢失);部署须在 10-08 21:00 前落地(理想=10-07 当天,当晚 21:00 班即真班验证)
② VPS 10-08 复市验证窗口后班车 promote 带上 json + 新脚本;10-08 07:50 relogin 首班跑旧码=纯净验证(注:①′ 若 10-07 发车则 ② 的 json 部分已随行,脚本部分仍候窗口后)
③ 生产 wrapper ②后 run_shadow_bridge.ps1 等库外资产改读 json(runbook 非代码资产流程报备 infra);裸 Get-Content 安全性由第④钉担保,也可加 -Encoding UTF8 双保险
④ 可选增强 按需 setx /M 机器级兜底;mini_path 接线;watch_accounts 多账号扩展

六、回滚

单 commit revert 即回旧形态(各文件改动均为等价替换,行为差异仅「默认值来源」一项); 身份文件缺失时 fail-loud(RuntimeError/启动非零)而非静默错号——方向保守。