chore: 审计卫生批——qmt_relogin 白名单裁剪(56→11)+一次性脚本/废弃构建文件/workdir 残留清理 [nas] [no-doc]

This commit is contained in:
2026-09-27 07:56:04 +08:00
parent ae39b5e727
commit da32cca20b
71 changed files with 0 additions and 4264 deletions
@@ -1,78 +0,0 @@
---
name: superpowers
description: "Complete Superpowers 5-step workflow: brainstorming → planning → execution → review → verification. Use /superpowers to start the full workflow for any task."
---
# /superpowers - Superpowers 完整工作流
自动化执行 Superpowers 五步法,确保任务从需求到完成的完整质量保障。
## 使用方法
```
/superpowers # 对当前任务执行完整工作流
/superpowers "完成用户登录功能" # 对指定任务执行工作流
/superpowers --quick "修复登录 bug" # 快速模式(简化步骤)
/superpowers --debug "支付失败问题" # 调试模式(强化 systematic-debugging)
```
## 工作流步骤
### Step 1: Brainstorming (需求探索)
- 使用 `superpowers:brainstorming` 技能
- 探索用户意图、需求边界、设计考虑
- 输出:需求文档草案
### Step 2: Writing Plans (编写计划)
- 使用 `superpowers:writing-plans` 技能
- 编写详细的实现计划
- 输出:IMPLEMENTATION_PLAN.md
### Step 3: Executing Plans (执行计划)
- 使用 `superpowers:executing-plans` 或 `superpowers:subagent-driven-development` 技能
- 按计划执行实现
- 输出:代码变更
### Step 4: Code Review (代码审查)
- 使用 `superpowers:requesting-code-review` 技能
- 验证实现符合需求
- 输出:审查报告
### Step 5: Verification & Finishing (验证完成)
- 使用 `superpowers:verification-before-completion` 技能
- 使用 `superpowers:finishing-a-development-branch` 技能
- 确认完成,决定合并方式
- 输出:完成报告
## 模式说明
| 模式 | 说明 |
|------|------|
| 默认模式 | 完整 5 步工作流 |
| --quick | 简化版:合并 brainstorming + planning,快速审查 |
| --debug | 强化 systematic-debugging,专注于问题定位和修复 |
| --review-only | 仅执行代码审查步骤 |
## 工作产物
所有工作产物保存在 `.claude/workdir/` 目录:
```
.claude/workdir/
├── BRAINSTORM.md # 需求探索结果
├── IMPLEMENTATION_PLAN.md # 实现计划
├── EXECUTION_LOG.md # 执行日志
├── REVIEW_REPORT.md # 代码审查报告
└── COMPLETION_SUMMARY.md # 完成总结
```
## When Invoked (调用时必须执行)
1. **确认任务**:如果用户没有指定任务,询问要完成什么
2. **选择模式**:根据用户指定的 flag 选择对应模式
3. **按步骤执行**:严格按照 5 步顺序执行,不可跳过
4. **记录进度**:每步完成后更新工作产物
5. **汇报结果**:最终向用户汇报完整工作流的结果
## 与 CLAUDE.md 的关系
此技能遵循项目 `.claude/CLAUDE.md` 中定义的 Superpowers 五技能体系和工作流程。
-60
View File
@@ -1,60 +0,0 @@
# Phase 2 需求探索结果
**日期**: 2026-07-03
**任务**: 继续完成 Phase 2 的任务
---
## 当前状态分析
### 已完成 (Phase 1 + 部分 Phase 2)
- ✅ 成交监控 API (`sanguo_web/api/routes/trades.py`)
- ✅ 资金监控 API (`sanguo_web/api/routes/accounts.py`)
- ✅ 全局配置 API (`sanguo_web/api/routes/settings.py`)
- ✅ 前端页面扩展 (`sanguo_web/static/js/app.js`, `templates/index.html`)
- ✅ 样式文件 (`sanguo_web/static/css/main.css`)
### 待完成 (Phase 2 剩余)
根据 `requirements/implementation-plan.md` 和当前代码状态:
| 功能 | 后端 | 前端 | 状态 |
|------|------|------|------|
| 成交监控页面 | ✅ | ✅ | 需验证 |
| 资金监控页面 | ✅ | ✅ | 需验证 |
| 活动委托视图 | ✅ | ✅ | 需验证 |
| 市场深度盘口 | ✅ | ✅ | 需验证 |
| 合约管理 | ✅ | ✅ | 需验证 |
| 表格排序 | - | ✅ | 需验证 |
| 全局配置编辑器 | ✅ | 🟡 | **需完成** |
### 需要明确的问题
1. **全局配置编辑器**:
- 后端 API 已完成 (`settings.py`)
- 前端表单部分完成
- 需要确认:哪些配置项需要编辑?是否有安全限制?
2. **集成测试**:
- 测试文件已创建 (`test_phase2_enhancements.py`)
- 需要运行并验证
3. **代码审查**:
- 新增代码需要审查
- 需要确认审查标准
---
## Phase 2 完成定义
Phase 2 被认为完成当:
- [ ] 所有 Phase 2 功能的后端 API 已实现并可用
- [ ] 所有 Phase 2 功能的前端页面已实现并可用
- [ ] 集成测试通过
- [ ] 代码审查完成
- [ ] 文档更新
---
## 下一步
进入 Step 2: 编写实现计划
-97
View File
@@ -1,97 +0,0 @@
# Phase 2 完成总结
**日期**: 2026-07-03
**状态**: ✅ **已完成**
---
## 工作流执行结果
### Step 1: Brainstorming ✅
- 需求探索完成
- 确定待完成任务:全局配置编辑器
### Step 2: Writing Plans ✅
- 实现计划编写完成
- 4 个任务分解完成
### Step 3: Executing Plans ✅
- Task 1: 全局配置编辑器前端 - **已完成**
- Task 2: 验证 Phase 2 功能 - **部分完成** (API 测试需服务器运行)
- Task 3: 代码审查 - **通过**
- Task 4: 文档更新 - **已完成**
### Step 4: Code Review ✅
- 审查 5 个文件
- 审查结论:**通过**
- 发现 3 个优化建议(非阻塞)
### Step 5: Verification & Finishing ✅
- Phase 2 状态更新为完成
- 文档已更新
---
## Phase 2 完成状态
| 功能模块 | 状态 |
|----------|------|
| 成交监控页面 | ✅ 完成 |
| 资金监控页面 | ✅ 完成 |
| 网关连接管理 | ✅ 完成 |
| 活动委托视图 | ✅ 完成 |
| 市场深度盘口 | ✅ 完成 |
| 合约管理 | ✅ 完成 |
| 表格排序 | ✅ 完成 |
| 全局配置编辑器 | ✅ 完成 |
---
## 代码统计
| 类型 | 新增 |
|------|------|
| 后端 API 路由 | 3 个文件 |
| 前端页面 | 多个页面组件 |
| 测试文件 | 2 个 |
| 总代码行数 | +1652 行 |
---
## 待办事项
1. **启动服务器后运行完整测试**:
```bash
python run_web.py
python sanguo_web/test_phase2_enhancements.py
```
2. **优化建议(可选)**:
- settings.py: 使用 `dict(SETTINGS)` 优化性能
- trades.py: 确保时间字段类型一致性
- accounts.py: 使用 `Decimal` 进行金融计算
3. **后续阶段**:
- Phase 3: 双击交互、CSV 导出、微信通知设置
---
## 下一步
Phase 2 已完成。可以:
1. 启动服务器验证功能
2. 开始 Phase 3 规划
3. 或进行其他功能开发
---
## 工作产物目录
```
.claude/workdir/
├── BRAINSTORM.md # 需求探索结果
├── IMPLEMENTATION_PLAN.md # 实现计划
├── EXECUTION_LOG.md # 执行日志
├── REVIEW_REPORT.md # 代码审查报告
└── COMPLETION_SUMMARY.md # 本文件
```
-61
View File
@@ -1,61 +0,0 @@
# Phase 2 执行日志
**日期**: 2026-07-03
**执行人**: Claude (Main Agent)
---
## Task 1: 全局配置编辑器前端 ✅
### 状态: 完成
检查结果:
- ✅ 配置表单 UI 已实现
- ✅ 保存/刷新功能已实现
- ✅ 加载状态和错误处理已实现
- ✅ 动态类型渲染已完成 (string/number/boolean/array)
相关文件:
- `sanguo_web/templates/index.html` (line 931-1003)
- `sanguo_web/static/js/app.js` (line 100-107, 619-647, 836-838)
- `sanguo_web/static/js/api.js` (line 428-447)
---
## Task 2: 验证 Phase 2 功能 ⚠️
### 状态: 部分完成
测试结果:
- ✗ 活动委托 API - 服务器未运行
- ✗ 合约管理 API - 服务器未运行
- ✗ 行情数据深度 - 服务器未运行
- ✗ 成交监控 API - 服务器未运行
- ✗ 资金监控 API - 服务器未运行
- ✓ 前端文件验证 - 通过
**备注**: API 测试失败是因为服务器未运行在 localhost:8000。需要启动服务器后重新测试。
前端验证通过项:
- ✓ active_orders page
- ✓ contracts page
- ✓ order book
- ✓ table sort
- ✓ market depth display
---
## 待完成
1. 启动 Web 服务器
2. 重新运行 API 测试
3. 代码审查
4. 文档更新
---
## 建议下一步
1. 启动服务器: `python run_web.py`
2. 重新测试: `python sanguo_web/test_phase2_enhancements.py`
3. 如测试通过,进入代码审查阶段
-82
View File
@@ -1,82 +0,0 @@
# Phase 2 完成计划
**日期**: 2026-07-03
**目标**: 完成剩余 Phase 2 功能并验证
---
## 任务分解
### Task 1: 完成全局配置编辑器前端
- **状态**: 🟡 部分完成
- **文件**:
- 后端: `sanguo_web/api/routes/settings.py` ✅
- 前端: `sanguo_web/static/js/app.js` 🟡
- 模板: `sanguo_web/templates/index.html` 🟡
- **剩余工作**:
- [ ] 完善配置表单 UI
- [ ] 添加配置验证
- [ ] 实现保存/重置功能
- [ ] 添加重启提示
### Task 2: 验证所有 Phase 2 功能
- **文件**: `sanguo_web/test_phase2_enhancements.py`
- **测试项**:
- [ ] 活动委托 API
- [ ] 合约管理 API
- [ ] 行情数据深度(五档)
- [ ] 成交监控 API
- [ ] 资金监控 API
- [ ] 前端页面验证
### Task 3: 代码审查
- **审查文件**:
- `sanguo_web/api/routes/*.py`
- `sanguo_web/static/js/*.js`
- `sanguo_web/templates/*.html`
- **审查标准**:
- 代码质量
- 安全性
- 性能
- 一致性
### Task 4: 文档更新
- [ ] 更新 `README.md`
- [ ] 更新 API 文档
- [ ] 记录已知问题
---
## 执行顺序
```
Task 1 (全局配置编辑器)
↓
Task 2 (验证测试)
↓
Task 3 (代码审查)
↓
Task 4 (文档更新)
```
---
## 验收标准
- [ ] 全局配置编辑器可以编辑并保存配置
- [ ] 所有 Phase 2 功能测试通过
- [ ] 代码审查完成,无明显问题
- [ ] 文档更新完成
- [ ] 可以标记 Phase 2 为完成状态
---
## 预计时间
| Task | 预计时间 |
|------|----------|
| Task 1 | 1-2 小时 |
| Task 2 | 1 小时 |
| Task 3 | 1 小时 |
| Task 4 | 0.5 小时 |
| **总计** | **3.5-4.5 小时** |
-90
View File
@@ -1,90 +0,0 @@
# Phase 2 代码审查报告
**日期**: 2026-07-03
**审查人**: Claude (Main Agent)
**审查范围**: Phase 2 新增代码
---
## 审查文件
| 文件 | 行数 | 状态 |
|------|------|------|
| `sanguo_web/api/routes/settings.py` | 118 | ✅ 通过 |
| `sanguo_web/api/routes/accounts.py` | 98 | ✅ 通过 |
| `sanguo_web/api/routes/trades.py` | 164 | ✅ 通过 |
| `sanguo_web/static/js/app.js` | 1132 | ✅ 通过 |
| `sanguo_web/templates/index.html` | 1085 | ✅ 通过 |
---
## 审查结果
### ✅ 通过项
#### 1. 代码质量
- ✓ 命名规范清晰
- ✓ 代码结构合理
- ✓ 注释充分
- ✓ 类型提示完整
#### 2. 安全性
- ✓ 依赖注入 (`Depends(get_current_user)`) 确保认证
- ✓ 输入验证 (`validate_settings`)
- ✓ 错误处理完善 (try/except, HTTPException)
- ✓ 敏感信息保护(不返回明文密码)
#### 3. 性能
- ✓ 查询效率合理(使用 `get()` 避免 KeyError)
- ✓ 列表推导式使用得当
- ✓ 数据分页支持 (`/latest?limit=50`)
#### 4. 一致性
- ✓ 与项目现有代码风格一致
- ✓ API 响应格式统一
- ✓ 错误处理模式一致
---
## 观察到的小问题(非阻塞)
### 1. settings.py
```python
# Line 39-42: 可能的性能问题
for key, value in SETTINGS.items():
settings_dict[key] = value
```
**建议**: 如果配置项很多,可以考虑使用 `dict(SETTINGS)` 直接复制
### 2. trades.py
```python
# Line 63: 潜在的类型问题
key=lambda x: x.get("time", datetime.min),
```
**建议**: 确保 `time` 字段类型一致性
### 3. accounts.py
```python
# Line 85-87: 可能的精度问题
total_balance = sum(acc.get("balance", 0.0) for acc in accounts)
```
**建议**: 金融计算建议使用 `decimal.Decimal`
---
## 审查结论
**总体评价**: ✅ **通过审查**
代码质量良好,无明显缺陷。观察到的问题都是优化建议,不影响当前功能。
**建议**: 可以合并到主分支。
---
## 下一步
1. 修复建议的小问题(可选)
2. 运行完整的集成测试
3. 更新文档
4. 标记 Phase 2 为完成
-43
View File
@@ -1,43 +0,0 @@
# Sanguo VeighNa Docker 镜像 - 本地构建版
# 使用国内 PyPI 镜像源,跳过 apt-get 和 TA-Lib 编译
FROM python:3.10-slim
# 设置环境变量
ENV TZ=Asia/Shanghai \
DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
VNPY_LOG_LEVEL=INFO \
TA_LIBRARY_PATH=/usr/local/lib \
TA_INCLUDE_PATH=/usr/local/include \
PIP_DEFAULT_TIMEOUT=300 \
# 使用清华 PyPI 镜像
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple \
PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn
# 设置工作目录
WORKDIR /app
# 创建数据目录
RUN mkdir -p /app/data /app/logs /app/config /app/temp
# 复制 requirements 并安装依赖
COPY requirements-docker.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements-docker.txt
# 复制应用代码
COPY sanguo_web/ /app/sanguo_web/
COPY vnpy_v4.4.0/vnpy/ /app/vnpy/
COPY run_web.py /app/
# 复制启动脚本
COPY entrypoint.sh /app/
RUN chmod +x /app/entrypoint.sh
# 暴露端口
EXPOSE 8000 8080
# 启动命令
ENTRYPOINT ["/app/entrypoint.sh"]
-11
View File
@@ -1,11 +0,0 @@
# NAS 测试镜像:复用现成 with-backtester 环境,补 pytest
# with-backtester 已含 py3.10/pandas2.3.3/numpy2.2.6/TA-Lib0.6.8/pyarrow/polars + vnpy源码
# 只缺 pytest,此处薄层补齐。代码通过 docker run -v 挂载(不 bake 进镜像,便于代码更新)
FROM sanguo_vnpy_v2:with-backtester
RUN pip install --no-cache-dir -q -i https://pypi.tuna.tsinghua.edu.cn/simple \
pytest==9.1.1 pytest-asyncio==1.4.0 pytest-mock==3.15.1 \
akshare==1.18.64 baostock==0.9.3 scipy==1.15.3 \
bullet-trade==0.9.2
WORKDIR /code
View File
-25
View File
@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
"""order_error_events 完整 payload(拒因定位)。"""
import json
import re
pw = None
for line in open(r"C:\redis\redis.conf"):
m = re.match(r"^requirepass\s+(\S+)", line)
if m:
pw = m.group(1).strip()
import redis
r = redis.Redis(host="127.0.0.1", port=6379, db=5, password=pw,
decode_responses=True)
seen_msgs = {}
for ts, fields in r.xrevrange("bigqmt:order_error_events:66639661", count=26):
d = json.loads(fields["payload"])
msg = d.get("error_msg") or d.get("msg") or d.get("order_msg") or str(d)
code = d.get("stock_code")
err_id = d.get("error_id") or d.get("errorid") or ""
seen_msgs.setdefault((str(err_id), str(msg)[:200]), []).append(code)
for (err_id, msg), codes in seen_msgs.items():
print("err_id=%s n=%d codes=%s" % (err_id, len(codes), ",".join(codes[:8])))
print(" msg:", msg)
-24
View File
@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
"""拉桥事件流尾部(拒因/成交/状态机),09-09 上午废单定位用。"""
import re
import sys
pw = None
for line in open(r"C:\redis\redis.conf"):
m = re.match(r"^requirepass\s+(\S+)", line)
if m:
pw = m.group(1).strip()
import redis
r = redis.Redis(host="127.0.0.1", port=6379, db=5, password=pw,
decode_responses=True)
for key in sys.argv[1:] or ["bigqmt:order_error_events:66639661",
"bigqmt:trade_events:66639661"]:
print("=" * 20, key, "len=", r.xlen(key))
for ts, d in r.xrevrange(key, count=12):
parts = []
for k in sorted(d):
v = str(d[k])[:90]
parts.append("%s=%s" % (k, v))
print(ts, " | ".join(parts)[:600])
-51
View File
@@ -1,51 +0,0 @@
"""Diagnostic: real CTA backtest of DoubleMaStrategy on 600000 (A-share daily).
Guarded entry for spawn. Throwaway."""
import sys
import os
import traceback
_VNPY_SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "vnpy_v4.4.0"))
_REPO = os.path.dirname(_VNPY_SRC)
for _p in (_REPO, _VNPY_SRC):
if _p not in sys.path:
sys.path.insert(0, _p)
def main():
# Configure vnpy DB to the real quant_trading.db BEFORE engine.load_data()
from vnpy.trader.setting import SETTINGS
from sanguo_data.config import load_config
cfg = load_config("/app/config/data_platform.yaml")
SETTINGS["database.name"] = "sqlite"
SETTINGS["database.database"] = cfg.data_paths["vnpy_db"]
print("DB:", SETTINGS["database.database"])
from vnpy_ctastrategy.strategies.double_ma_strategy import DoubleMaStrategy
print("DoubleMaStrategy.parameters:", getattr(DoubleMaStrategy, "parameters", "?"))
from sanguo_backtest.cta_engine import run_cta_backtest
# Classic double-MA params (vnpy example defaults); fixed_size=1
params = {"fast_window": 10, "slow_window": 20, "fixed_size": 1}
print(f"run_cta_backtest DoubleMaStrategy on 600000, 2024-01-01..2024-06-30, params={params}")
try:
result = run_cta_backtest(
DoubleMaStrategy, "600000", params,
"2024-01-01", "2024-06-30", cfg, "/tmp/cta_results.db",
)
print("=== status:", result.status)
print("=== statistics ===")
if result.statistics:
for k, v in result.statistics.items():
print(f" {k}: {v}")
else:
print(" (empty)")
if result.status == "failed":
print("=== error ===")
print(result.error_msg)
except Exception:
traceback.print_exc()
if __name__ == "__main__":
main()
-63
View File
@@ -1,63 +0,0 @@
# -*- coding: utf-8 -*-
"""周一晨僵尸收割(2026-09-14 07:2x): 杀 12 个 wedged 引擎子进程, supervisor 自动重拉。
不动 supervisor 本体。用法: python -X utf8 C:\\sanguo_bigqmt\\harvest_zombies.py [status]
"""
from __future__ import annotations
import sys
import time
def _procs():
import psutil
live_sup, shadow_sup, children = [], [], []
for p in psutil.process_iter(["pid", "name", "cmdline", "create_time"]):
if "python" not in (p.info["name"] or "").lower():
continue
cmd = p.info["cmdline"] or []
j = " ".join(cmd)
if "sanguo_live" in j and "--supervisor" in j:
live_sup.append((p.info["pid"], _ts(p)))
elif "runner_live" in j:
children.append(p)
elif "sanguo_trader.shadow" in j and "--auto" in j:
shadow_sup.append((p.info["pid"], _ts(p)))
elif "sanguo_trader.shadow" in j:
children.append(p)
return live_sup, shadow_sup, children
def _ts(p):
return time.strftime("%m-%d %H:%M", time.localtime(p.info["create_time"]))
def main():
status_only = len(sys.argv) > 1 and sys.argv[1] == "status"
live_sup, shadow_sup, children = _procs()
print("SUP live=%s shadow=%s" % (live_sup, shadow_sup))
print("CHILDREN n=%d (zombies, expect start=09-11/09-12):" % len(children))
for p in children:
print(" pid=%s start=%s" % (p.info["pid"], _ts(p)))
if status_only:
return
if not children:
print("NO_CHILDREN_TO_HARVEST")
for p in children:
try:
p.terminate()
print("TERM", p.info["pid"])
except Exception as e:
print("TERM_FAIL", p.info["pid"], e)
t0 = time.time()
while time.time() - t0 < 60:
if not _procs()[2]:
print("HARVEST_DONE %.0fs" % (time.time() - t0))
break
time.sleep(5)
else:
print("HARVEST_TIMEOUT residual=%d" % len(_procs()[2]))
raise SystemExit(1)
if __name__ == "__main__":
main()
-72
View File
@@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
"""Enumerate child windows of the QMT editor window (pid filter).
Qt usually collapses to one hwnd, but dialogs may expose buttons."""
import ctypes
import ctypes.wintypes as wt
user32 = ctypes.windll.user32
target = None
Ws = ctypes.WINFUNCTYPE(ctypes.c_bool, wt.HWND, wt.LPARAM)
@Ws
def find_cb(h, _):
global target
buf = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(h, buf, 256)
return True
def cls(h):
buf = ctypes.create_unicode_buffer(256)
user32.GetClassNameW(h, buf, 256)
return buf.value
def txt(h):
buf = ctypes.create_unicode_buffer(512)
user32.GetWindowTextW(h, buf, 512)
return buf.value
def rect(h):
r = wt.RECT()
user32.GetWindowRect(h, ctypes.byref(r))
return (r.left, r.top, r.right, r.bottom)
def esc(s):
return str(s).encode("unicode_escape").decode("ascii")[:70]
# find all top-level Qt windows, then enumerate their children
qt_windows = []
@Ws
def enum_top(h, _):
if user32.IsWindowVisible(h):
if cls(h).startswith("Qt5"):
r = rect(h)
if r[0] > -30000:
qt_windows.append(h)
return True
user32.EnumWindows(enum_top, 0)
print("QT_TOP=%d" % len(qt_windows))
for q in qt_windows:
print("TOP hwnd=%s class=%s title=%s rect=%s" % (
q, esc(cls(q)), esc(txt(q)), rect(q)))
kids = []
@Ws
def enum_child(h, _):
kids.append(h)
return True
user32.EnumChildWindows(q, enum_child, 0)
print(" CHILDREN=%d" % len(kids))
for k in kids[:25]:
print(" kid hwnd=%s class=%s title=%s rect=%s" % (
k, esc(cls(k)), esc(txt(k)), rect(k)))
-1
View File
@@ -1 +0,0 @@
C:\Python310\python.exe C:\sanguo_bigqmt\qmt_dlg_esc.py > C:\sanguo_bigqmt\esc.log 2>&1
-34
View File
@@ -1,34 +0,0 @@
$ErrorActionPreference = 'Stop'
# Ingest the plaintext password from Desktop 333.txt into a DPAPI-encrypted
# blob (current-user + machine scope). Password never leaves this machine and
# is never printed. On success the plaintext file is deleted.
$src = 'C:\Users\Administrator\Desktop\333.txt'
$credDir = 'C:\sanguo_bigqmt\creds'
$encPath = Join-Path $credDir 'qmt_login.enc'
New-Item -ItemType Directory -Force -Path $credDir | Out-Null
$pw = (Get-Content -Raw -LiteralPath $src).Trim()
if (-not $pw) { throw '333.txt is empty' }
$cred = @{ user = '66639661'; password = $pw } | ConvertTo-Json -Compress
$sec = ConvertTo-SecureString -String $cred -AsPlainText -Force
$enc = ConvertFrom-SecureString -SecureString $sec
Set-Content -LiteralPath $encPath -Value $enc -Encoding ASCII
# Lock the directory down on top of DPAPI (belt and suspenders).
icacls $credDir /inheritance:r | Out-Null
icacls $credDir /grant 'Administrator:F' | Out-Null
icacls $encPath /grant 'Administrator:F' | Out-Null
# Round-trip verify before destroying the plaintext source.
$enc2 = (Get-Content -Raw -LiteralPath $encPath).Trim()
$sec2 = ConvertTo-SecureString -String $enc2
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec2)
$plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
$j = $plain | ConvertFrom-Json
if ([string]$j.user -ne '66639661' -or [string]$j.password -ne $pw) { throw 'round-trip mismatch' }
Write-Output ('INGEST_OK encbytes=' + (Get-Item -LiteralPath $encPath).Length + ' pwdlen=' + $pw.Length)
Remove-Item -LiteralPath $src -Force
Write-Output '333_DELETED'
-13
View File
@@ -1,13 +0,0 @@
$ErrorActionPreference = 'Stop'
$p = 'C:\Users\Administrator\Desktop\333.txt'
$raw = Get-Content -Raw -LiteralPath $p
$t = $raw.Trim()
$lines = @($t -split "`r?`n")
Write-Output ("TOTALLEN=" + $raw.Length)
Write-Output ("TRIMLEN=" + $t.Length)
Write-Output ("NLINES=" + $lines.Count)
for ($i = 0; $i -lt $lines.Count; $i++) {
$L = $lines[$i]
Write-Output ("LINE" + $i + "_LEN=" + $L.Length + "_ALLDIG=" + ([bool]($L -match '^[0-9]+$')) + "_HASWS=" + ([bool]($L -match '[ \t]')))
}
Write-Output ("HASCHARTAB=" + ([bool]($raw -match '[ \t]')))
-1
View File
@@ -1 +0,0 @@
C:\Python310\python.exe C:\sanguo_bigqmt\qmt_nav_pystg.py > C:\sanguo_bigqmt\nav.log 2>&1
@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""Contract fix 2026-09-11: server-side get_trading_dates output normalization.
The native xtdata SDK path returns 'YYYYMMDD' strings whenever any local
quote source has been reachable; the ContextInfo path returns epoch-ms ints.
Consumers (bullet_trade miniqmt provider ~line 1824) do ts/1000, so both
shapes must leave the server as Beijing-midnight epoch-ms ints.
Idempotent: re-running on a patched file exits 0.
Run ON THE VPS: C:\\Python310\\python.exe -X utf8 patch_market_bigqmt_20260911.py
"""
import io
import shutil
import sys
P = r"C:\sanguo_bigqmt\bigqmt_signal_trader\adapters\market_bigqmt.py"
BAK = P + ".bak_20260911"
OLD = (
" return self._native_or_context(\n"
' "get_trading_dates", _via_context, market, start_time, end_time, count\n'
" )"
)
NEW = (
" return _norm_trading_dates(self._native_or_context(\n"
' "get_trading_dates", _via_context, market, start_time, end_time, count))'
)
HELPER = '''
def _norm_trading_dates(result):
"""Contract fix 2026-09-11: native returns 'YYYYMMDD' strings whenever a
local quote source has been reachable; ContextInfo returns epoch-ms ints.
Normalize to Beijing-midnight epoch ms (official xtquant contract;
consumers do ts/1000 with local-timezone fromtimestamp, server runs
Asia/Shanghai so datetime().timestamp() is the exact inverse).
All-numeric lists pass through untouched; mixed/odd payloads keep
original elements that are not 8-digit strings."""
if not isinstance(result, list) or not result:
return result
if all(isinstance(x, (int, float)) and not isinstance(x, bool) for x in result):
return result
out = []
for x in result:
if isinstance(x, str) and len(x) == 8 and x.isdigit():
import datetime as _dt
out.append(int(_dt.datetime(int(x[:4]), int(x[4:6]), int(x[6:8])).timestamp() * 1000))
else:
out.append(x)
return out
'''
src = io.open(P, encoding="utf-8").read()
if "_norm_trading_dates(self._native_or_context" in src:
print("ALREADY_PATCHED")
sys.exit(0)
if src.count(OLD) != 1:
print("ANCHOR_NOT_UNIQUE: %d" % src.count(OLD))
sys.exit(1)
shutil.copyfile(P, BAK)
src = src.replace(OLD, NEW)
src = src.rstrip("\n") + "\n" + HELPER
io.open(P, "w", encoding="utf-8", newline="\n").write(src)
print("PATCHED ok, backup=%s" % BAK)
@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""Contract fix 2026-09-11: server-side get_trading_dates output normalization.
The native xtdata SDK path returns 'YYYYMMDD' strings whenever any local
quote source has been reachable; the ContextInfo path returns epoch-ms ints.
Consumers (bullet_trade miniqmt provider ~line 1824) do ts/1000, so both
shapes must leave the server as Beijing-midnight epoch-ms ints.
Idempotent: re-running on a patched file exits 0.
Run ON THE VPS: C:\\Python310\\python.exe -X utf8 patch_market_bigqmt_20260911.py
"""
import io
import shutil
import sys
P = r"C:\国金QMT交易端模拟\python\bigqmt_signal_trader\adapters\market_bigqmt.py"
BAK = P + ".bak_20260911"
OLD = (
" return self._native_or_context(\n"
' "get_trading_dates", _via_context, market, start_time, end_time, count\n'
" )"
)
NEW = (
" return _norm_trading_dates(self._native_or_context(\n"
' "get_trading_dates", _via_context, market, start_time, end_time, count))'
)
HELPER = '''
def _norm_trading_dates(result):
"""Contract fix 2026-09-11: native returns 'YYYYMMDD' strings whenever a
local quote source has been reachable; ContextInfo returns epoch-ms ints.
Normalize to Beijing-midnight epoch ms (official xtquant contract;
consumers do ts/1000 with local-timezone fromtimestamp, server runs
Asia/Shanghai so datetime().timestamp() is the exact inverse).
All-numeric lists pass through untouched; mixed/odd payloads keep
original elements that are not 8-digit strings."""
if not isinstance(result, list) or not result:
return result
if all(isinstance(x, (int, float)) and not isinstance(x, bool) for x in result):
return result
out = []
for x in result:
if isinstance(x, str) and len(x) == 8 and x.isdigit():
import datetime as _dt
out.append(int(_dt.datetime(int(x[:4]), int(x[4:6]), int(x[6:8])).timestamp() * 1000))
else:
out.append(x)
return out
'''
src = io.open(P, encoding="utf-8").read()
if "_norm_trading_dates(self._native_or_context" in src:
print("ALREADY_PATCHED")
sys.exit(0)
if src.count(OLD) != 1:
print("ANCHOR_NOT_UNIQUE: %d" % src.count(OLD))
sys.exit(1)
shutil.copyfile(P, BAK)
src = src.replace(OLD, NEW)
src = src.rstrip("\n") + "\n" + HELPER
io.open(P, "w", encoding="utf-8", newline="\n").write(src)
print("PATCHED ok, backup=%s" % BAK)
@@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
"""B-pack client-side fix 2026-09-11: shim get_trading_dates output normalization.
The server-side fix (market_bigqmt.py patch) normalizes the RPC answer, but
fresh client processes (xt_eod wrapper, future engines) load THIS shim first.
Defense in depth: normalize here too, so the contract holds even if the server
side regresses (e.g. server file restored from a backup).
Idempotent: re-running on a patched file exits 0.
Run ON THE VPS: C:\\Python310\\python.exe -X utf8 patch_shim_xtdata_20260911.py
"""
import io
import shutil
import sys
P = r"C:\sanguo_bigqmt\xtquant_bridge\xtquant\xtdata.py"
BAK = P + ".bak_20260911"
OLD = '''def get_trading_dates(market, start_time="", end_time="", count=-1):
return _compat.xtdata.get_trading_dates(market, start_time, end_time, count)'''
NEW = '''def _norm_calendar_ms(result):
"""Contract fix 2026-09-11: 'YYYYMMDD' strings -> Beijing-midnight epoch ms.
The bridge server's native xtdata path returns string dates whenever a
local quote source has been reachable (2026-09-11 incident: all 12 engine
schedulers starved on ts/1000 TypeErrors). Consumers divide by 1000 with
local-timezone fromtimestamp, and this host runs Asia/Shanghai, so
datetime().timestamp() is the exact inverse. All-numeric lists pass
through untouched."""
if not isinstance(result, list) or not result:
return result
if all(isinstance(x, (int, float)) and not isinstance(x, bool) for x in result):
return result
out = []
for x in result:
if isinstance(x, str) and len(x) == 8 and x.isdigit():
import datetime as _dt
out.append(int(_dt.datetime(int(x[:4]), int(x[4:6]), int(x[6:8])).timestamp() * 1000))
else:
out.append(x)
return out
def get_trading_dates(market, start_time="", end_time="", count=-1):
return _norm_calendar_ms(_compat.xtdata.get_trading_dates(market, start_time, end_time, count))'''
src = io.open(P, encoding="utf-8").read()
if "_norm_calendar_ms(_compat.xtdata.get_trading_dates" in src:
print("ALREADY_PATCHED")
sys.exit(0)
if src.count(OLD) != 1:
print("ANCHOR_NOT_UNIQUE: %d" % src.count(OLD))
sys.exit(1)
shutil.copyfile(P, BAK)
src = src.replace(OLD, NEW)
io.open(P, "w", encoding="utf-8", newline="\n").write(src)
print("PATCHED ok, backup=%s" % BAK)
-34
View File
@@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only: instance element state attrs (suspend/record/runMode/...) across files."""
import io
import os
import re
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
WATCH = ("startupAutorun", "suspend", "record", "runMode", "id", "period",
"autoCancel", "end_restore", "is_main", "specialType", "runname")
files = [
("d0907", os.path.join(root, "config", "Temp", "indexUserConfig.xml_20260907")),
("d0908", os.path.join(root, "config", "Temp", "indexUserConfig.xml_20260908")),
("d0909", os.path.join(root, "config", "Temp", "indexUserConfig.xml_20260909")),
("now", os.path.join(root, "config", "indexUserConfig.xml")),
]
for tag, p in files:
t = io.open(p, encoding="utf-8", errors="replace").read()
i = t.find("startupAutorun")
if i < 0:
print(tag, "NO startupAutorun")
continue
start = t.rfind("<", 0, i)
end = t.find(">", i)
elem = t[start:end + 1]
pairs = []
for k, v in re.findall(r'(\w+)="([^"]*)"', elem):
if k in WATCH and k != "startupAutorun":
pairs.append("%s=%s" % (k, v.encode("unicode_escape").decode("ascii")[:40]))
sa = re.search(r'startupAutorun="([^"]*)"', elem)
print("%-6s startupAutorun=%s %s" % (tag, sa.group(1), " ".join(pairs)))
print("DONE7")
-46
View File
@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
"""Grep strategy/run-state entries in QMT users Config.xml (349KB, likely GBK)."""
import io
import os
import re
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
cfg = os.path.join(os.path.dirname(d), "userdata", "users", "66639661", "Config.xml")
raw = open(cfg, "rb").read()
for enc in ("utf-8", "gbk"):
try:
text = raw.decode(enc)
print("DECODED_AS", enc, "len", len(text))
break
except UnicodeDecodeError:
continue
else:
text = raw.decode("gbk", errors="replace")
print("DECODED_AS gbk/replace")
# show the XML declaration / first line
print("HEAD:", text[:120].encode("unicode_escape").decode("ascii"))
# any element mentioning formula / strategy / python / run
pat = re.compile(r"<[^>]*>", re.I)
lines = text.splitlines()
print("TOTAL_LINES", len(lines))
hits = 0
for i, ln in enumerate(lines):
low = ln.lower()
if any(k in low for k in ("formula", "strategy", "python", "model")):
print("%6d %s" % (i + 1, ln.strip()[:400].encode("unicode_escape").decode("ascii")))
hits += 1
if hits > 60:
print("...truncated")
break
if not hits:
print("NO formula/strategy/python/model tags; sampling run-ish attributes")
for i, ln in enumerate(lines):
low = ln.lower()
if re.search(r'(auto.?run|run.?flag|isrun|running|"run")', low):
print("%6d %s" % (i + 1, ln.strip()[:300].encode("unicode_escape").decode("ascii")))
hits += 1
if hits > 40:
break
-22
View File
@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
"""Grep Chinese strategy-name / instance entries in QMT Config.xml."""
import os
import re
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
cfg = os.path.join(root, "userdata", "users", "66639661", "Config.xml")
text = open(cfg, encoding="utf-8").read()
kws = ["新建策略文件", "实例", "BIGQMT_REDIS_DRYRUN", "modbootstart",
"autorun", "AutoRun", "bootstart", "自运行", "自动"]
hits = 0
for i, ln in enumerate(text.splitlines(), 1):
if any(k in ln for k in kws):
# print trimmed around first match
print("%5d %s" % (i, ln.strip()[:600].encode("unicode_escape").decode("ascii")))
hits += 1
if hits > 40:
print("...TRUNC")
break
print("HITS", hits)
-10
View File
@@ -1,10 +0,0 @@
# -*- coding: utf-8 -*-
"""Snapshot config\indexUserConfig.xml as pre-submit baseline."""
import os
import shutil
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
src = os.path.join(os.path.dirname(d), "config", "indexUserConfig.xml")
dst = r"C:\sanguo_bigqmt\indexUserConfig_before.xml"
shutil.copy2(src, dst)
print("SNAP", os.path.getsize(src), "->", os.path.getsize(dst))
-43
View File
@@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
"""UI step8 (atomic): ensure model-trade page -> right-click row -> 修改 -> shot.
Only one goal per run; dialog must be visible in the shot."""
import time
import pyautogui
import pygetwindow as gw
pyautogui.FAILSAFE = False
def log_front(tag):
try:
w = gw.getActiveWindow()
print("FRONT", tag, w.title.encode("unicode_escape").decode("ascii")[:80] if w else None)
except Exception as e:
print("FRONT", tag, "ERR", e)
time.sleep(1.5)
log_front("start")
pyautogui.press("esc")
time.sleep(1.0)
log_front("after-esc")
# navigate to model-trade page via tree: click known point -> UP -> Enter
pyautogui.click(75, 432) # 数据导出 (verified)
time.sleep(0.6)
pyautogui.press("up") # python策略
time.sleep(0.6)
pyautogui.press("enter")
time.sleep(2.5)
log_front("after-nav")
pyautogui.click(500, 178) # select instance row 1
time.sleep(0.8)
pyautogui.click(500, 178, button="right")
time.sleep(1.2)
pyautogui.click(520, 192) # 修改
time.sleep(4)
log_front("after-xiugai")
pyautogui.screenshot(r"C:\sanguo_bigqmt\shot.png")
print("DONE atomic reopen")
-43
View File
@@ -1,43 +0,0 @@
# -*- coding: utf-8 -*-
"""UI step9: python策略(y=385, current boot) -> row1 -> right-click -> 运行.
Then screenshot + objective formula-log runonce check."""
import datetime
import io
import os
import time
import pyautogui
pyautogui.FAILSAFE = False
time.sleep(1.5)
pyautogui.click(75, 385) # python策略 (verified on THIS boot's screenshot)
time.sleep(0.8)
pyautogui.press("enter")
time.sleep(2.5)
pyautogui.click(500, 178) # select instance row 1
time.sleep(0.8)
pyautogui.click(500, 178, button="right")
time.sleep(1.2)
pyautogui.click(520, 213) # 运行
time.sleep(4)
pyautogui.screenshot(r"C:\sanguo_bigqmt\shot.png")
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
logp = os.path.join(os.path.dirname(d), "userdata", "log",
"XtClient_Formula_20260909.log")
n = 0
try:
now = datetime.datetime.now()
for ln in io.open(logp, "r", encoding="gbk", errors="replace"):
try:
ts = datetime.datetime.strptime(ln[:19], "%Y-%m-%d %H:%M:%S")
except ValueError:
continue
if (now - ts).total_seconds() < 120 and (
"runonce" in ln or "PythonFormula" in ln):
n += 1
except OSError:
pass
print("RUNONCE_LINES_120S=", n)
print("DONE click run")
-38
View File
@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
"""Look at userdata/data + formulaserver.ini for strategy registry / run state."""
import os
import time
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
ini = os.path.join(root, "config", "formulaserver", "formulaserver.ini")
print("=== formulaserver.ini ===")
try:
raw = open(ini, "rb").read()
for enc in ("utf-8", "gbk"):
try:
txt = raw.decode(enc)
break
except UnicodeDecodeError:
continue
else:
txt = raw.decode("gbk", "replace")
print(txt.encode("unicode_escape").decode("ascii")[:3000])
except OSError as e:
print("ERR", e)
dd = os.path.join(root, "userdata", "data")
print("=== userdata/data tree (top 2 levels) ===")
for dirpath, dirnames, files in os.walk(dd):
rel = os.path.relpath(dirpath, dd)
if rel.count(os.sep) >= 2:
dirnames[:] = []
continue
print("D " + rel.encode("unicode_escape").decode("ascii"))
for f in sorted(files)[:25]:
p = os.path.join(dirpath, f)
st = os.stat(p)
print(" %-52s %8d %s" % (
f.encode("unicode_escape").decode("ascii")[:52], st.st_size,
time.strftime("%m-%d %H:%M", time.localtime(st.st_mtime))))
-19
View File
@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
"""E1 step1c: dismiss the quit-confirm modal (确定/取消 at ~(525/609, 355))
that is hijacking ALL clicks. ESC on a modal == cancel; no coordinates needed."""
import ctypes
import time
import pyautogui
pyautogui.FAILSAFE = False
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
if hwnd:
ctypes.windll.user32.ShowWindow(hwnd, 6) # SW_MINIMIZE
time.sleep(0.8)
pyautogui.press("esc")
time.sleep(1.5)
pyautogui.screenshot(r"C:\sanguo_bigqmt\shot_esc.png")
print("DONE esc")
-49
View File
@@ -1,49 +0,0 @@
# -*- coding: utf-8 -*-
"""E1 step1b: locate the 策略交易/模型交易 child window (python策略 page is a
separate window, not a main-frame page). Enumerate windows, log geometry,
activate the strategy window, screenshot it."""
import ctypes
import time
import pyautogui
pyautogui.FAILSAFE = False
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
if hwnd:
ctypes.windll.user32.ShowWindow(hwnd, 6) # SW_MINIMIZE
time.sleep(0.8)
import pygetwindow as gw
for w in gw.getAllWindows():
t = (w.title or "").strip()
if not t:
continue
print("WIN: %r box=%s" % (t[:60], (w.left, w.top, w.width, w.height)))
target = None
for w in gw.getAllWindows():
t = w.title or ""
if ("策略交易" in t) or ("模型" in t) or ("增强" in t):
target = w
break
if target is None:
print("NO strategy window found")
else:
print("TARGET:", target.title, (target.left, target.top,
target.width, target.height))
try:
target.activate()
except Exception as e:
print("activate ERR", repr(e))
time.sleep(1.0)
try:
if target.isMinimized:
target.restore()
print("restored")
except Exception as e:
print("restore ERR", repr(e))
time.sleep(1.0)
pyautogui.screenshot(r"C:\sanguo_bigqmt\shot_stgwin.png")
print("DONE find_stgwin")
-23
View File
@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
"""Locate QMT strategy (formula) storage + run-state flags under userdata."""
import os
import time
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
base = os.path.join(root, "userdata", "users")
print("BASE", base.encode("unicode_escape").decode("ascii"))
for dirpath, dirnames, files in os.walk(base):
dirnames[:] = [x for x in dirnames if x.lower() != "__pycache__"]
rel = os.path.relpath(dirpath, base)
print("D " + rel.encode("unicode_escape").decode("ascii"))
for f in sorted(files)[:80]:
p = os.path.join(dirpath, f)
try:
st = os.stat(p)
except OSError:
continue
print(" f %-44s %9d %s" % (
f, st.st_size,
time.strftime("%m-%d %H:%M", time.localtime(st.st_mtime))))
-179
View File
@@ -1,179 +0,0 @@
# -*- 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, fname=None, **detail):
import os
payload = {"ts": now_str(), "kind": kind, "verdict": verdict}
payload.update(detail)
try:
os.makedirs(GATE_DIR, exist_ok=True)
name = fname or "qmt_gate_latest.json"
with open(r"%s\%s" % (GATE_DIR, name), "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}
CALENDAR_PROBE_CODE = (
"from xtquant import xtdata; "
"r = xtdata.get_trading_dates('SH', '20260101', '20261231'); "
"e = r[-1] if isinstance(r, list) and r else None; "
"print('CAL', type(e).__name__, repr(e))"
)
def calendar_probe(timeout=25):
"""Fifth gate (added 2026-09-11): fresh child process fetches the trading
calendar through the bridge and checks the element type.
On 2026-09-11 all 12 engine schedulers starved because the bridge returned
'YYYYMMDD' STRINGS from its native xtdata path while every other probe
(process / ping / asset / probe order) stayed green. Consumers divide by
1000, so a string element is a hard contract break.
Returns (state, detail): state in ok / bad / empty / exec.
ok - non-empty numeric elements (epoch ms), contract healthy
bad - string elements -> RED (relogin restarts the patched server,
which normalizes the output again)
empty - probe shape got no data (not proof the bridge is bad) -> AMBER
exec - child process failed -> AMBER
"""
import os
env = dict(os.environ)
env["PYTHONPATH"] = r"C:\sanguo_bigqmt\xtquant_bridge"
env["BIGQMT_ACCOUNT_ID"] = ACCOUNT
with open(r"C:\redis\redis.conf") as f:
conf = f.read()
env["BIGQMT_REDIS_PASSWORD"] = re.search(
r"^requirepass (\S+)", conf, re.M).group(1)
try:
r = subprocess.run(
["C:\\Python310\\python.exe", "-X", "utf8", "-c",
CALENDAR_PROBE_CODE],
capture_output=True, text=True, timeout=timeout, env=env)
except Exception as exc:
return "exec", "CAL_PROBE_EXEC %s %s" % (type(exc).__name__,
str(exc)[:100])
cal = next((l for l in (r.stdout or "").splitlines()
if l.startswith("CAL ")), "")
if not cal:
return "exec", "CAL_PROBE_NO_OUT rc=%s %s" % (
r.returncode, (r.stderr or "").strip()[:100])
parts = cal.split(None, 2)
if len(parts) < 3:
return "exec", "CAL_PROBE_BAD '%s'" % cal[:80]
etype, eval_ = parts[1], parts[2][:40]
if etype == "NoneType":
return "empty", "calendar probe returned no data (%s)" % eval_
if etype in ("int", "float"):
return "ok", "calendar elem=%s %s" % (etype, eval_)
return "bad", "CALENDAR_STR_CONTRACT elem=%s %s" % (etype, eval_)
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
-40
View File
@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
"""Binary-grep the whole QMT install for the strategy instance name.
Searches UTF-8, GBK, UTF-16LE encodings of the anchor string."""
import os
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
name = "\u65b0\u5efa\u7b56\u7565\u6587\u4ef63" # 新建策略文件3
needles = {
"utf8": name.encode("utf-8"),
"gbk": name.encode("gbk"),
"u16": name.encode("utf-16-le"),
}
skip_dirs = {"log", "__pycache__", "malldown"}
found = 0
for dirpath, dirnames, files in os.walk(root):
dirnames[:] = [x for x in dirnames if x not in skip_dirs]
for f in files:
p = os.path.join(dirpath, f)
try:
if os.path.getsize(p) > 60 * 1024 * 1024:
continue
raw = open(p, "rb").read()
except OSError:
continue
hit = [enc for enc, nd in needles.items() if nd in raw]
if hit:
rel = os.path.relpath(p, root)
print("HIT %-60s %s size=%d" % (
rel.encode("unicode_escape").decode("ascii")[:60],
",".join(hit), len(raw)))
found += 1
if found > 30:
print("...TRUNC")
break
if found > 30:
break
print("FOUND", found)
-47
View File
@@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
"""Peek at QMT client logs (GBK-encoded) around the restart window.
Prints ASCII-escaped so nothing dies on the GBK console."""
import datetime
import io
import os
def esc(s):
return str(s).encode("unicode_escape").decode("ascii")[:200]
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
logdir = os.path.join(root, "userdata", "log")
def tail(path, n=40):
try:
with io.open(path, "r", encoding="gbk", errors="replace") as f:
lines = f.readlines()
return lines[-n:]
except OSError as e:
return ["<%s>" % e]
f1 = os.path.join(logdir, "XtClient_Formula_20260909.log")
print("=== tail XtClient_Formula (%s) ===" % esc(f1))
for ln in tail(f1, 35):
print(" ", esc(ln.rstrip()))
f2 = os.path.join(logdir, "XtClient_20260909.log")
print("=== grep XtClient (model/strategy/auto/formula/python, after 19:2x) ===")
kws = ("模型", "策略", "自动", "formula", "Formula", "Model", "python", "Python",
"auto", "Auto", "恢复", "异常")
try:
with io.open(f2, "r", encoding="gbk", errors="replace") as f:
for ln in f:
if any(k in ln for k in kws) and ("19:2" in ln or "19:3" in ln):
print(" ", esc(ln.rstrip()))
except OSError as e:
print(" ", e)
f3 = os.path.join(logdir, "XtClient_FormulaOutput_20260909.log")
print("=== tail FormulaOutput ===")
for ln in tail(f3, 15):
print(" ", esc(ln.rstrip()))
-42
View File
@@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
"""E1 step1 v2: the schtask PowerShell console window COVERS the QMT client
(analyzed screenshot proved clicks landed on the console). So:
1) minimize our own console, 2) foreground QMT, 3) click python策略 tree item
(THIS boot, 3x-crop measured y=413, not the stale 385), 4) screenshot."""
import ctypes
import time
import pyautogui
pyautogui.FAILSAFE = False
# 1) minimize the console window hosting this very process
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
if hwnd:
ctypes.windll.user32.ShowWindow(hwnd, 6) # SW_MINIMIZE
time.sleep(1.0)
# 2) bring the QMT client window to front (log all titles for diagnosis)
try:
import pygetwindow as gw
titles = [w.title for w in gw.getAllWindows() if (w.title or "").strip()]
print("TITLES:", "|".join(titles)[:600])
for w in gw.getAllWindows():
t = w.title or ""
if ("XtItClient" in t or "QMT" in t or "quant" in t.lower()
or "迅投" in t):
try:
w.activate()
print("ACTIVATED:", t)
except Exception as e:
print("activate ERR", repr(e))
break
except Exception as e:
print("focus ERR", repr(e))
time.sleep(1.0)
# 3) click python策略 (3x-crop verified on THIS boot: crop y=398 -> (398/3)+280 = 413)
pyautogui.click(75, 413)
time.sleep(3)
pyautogui.screenshot(r"C:\sanguo_bigqmt\shot_nav.png")
print("DONE nav2")
-55
View File
@@ -1,55 +0,0 @@
# -*- coding: utf-8 -*-
"""Postmortem after a QMT client restart: redis bridge keys, rpc queue depth,
and files touched in the last 30 min under the install root and tool dir.
All output ASCII-escaped (console codepage is GBK)."""
import datetime
import os
import pathlib
import re
import time
def esc(s):
return str(s).encode("unicode_escape").decode("ascii")[:170]
print("=== postmortem %s ===" % datetime.datetime.now().strftime("%H:%M:%S"))
try:
import redis
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)
keys = sorted(rc.keys("*"))
print("REDIS_DB5_KEYS=%d" % len(keys))
for k in keys[:50]:
t = rc.type(k)
extra = ""
if t == "list":
extra = " llen=%d" % rc.llen(k)
print(" KEY %s type=%s%s" % (esc(k), t, extra))
except Exception as e:
print("REDIS_ERR", repr(e))
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
print("ROOT", esc(root))
now = time.time()
for label, base in (("INSTALL", root), (r"TOOLS", r"C:\sanguo_bigqmt")):
hits = []
for p in pathlib.Path(base).rglob("*"):
try:
if p.is_file() and (now - p.stat().st_mtime) < 1800:
hits.append((p.stat().st_mtime, p.stat().st_size, p))
except OSError:
pass
hits.sort(reverse=True)
print("[%s] RECENT_30MIN=%d" % (label, len(hits)))
for mt, sz, p in hits[:35]:
try:
rel = esc(p.relative_to(base))
except Exception:
rel = esc(p)
print(" %s %8d %s" % (
datetime.datetime.fromtimestamp(mt).strftime("%H:%M:%S"), sz, rel))
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Big QMT pre-open ACTIVE trade probe 09:15 Mon-Fri: 510050 x100 @0.01 never-fill order + instant cancel. Junked order = stale session -> triggers sanguo-qmt-relogin before the open. Audit trail C:\sanguo_bigqmt\gate\qmt_gate_*.log</Description>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2026-09-10T09:15:00</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByWeek>
<DaysOfWeek>
<Monday />
<Tuesday />
<Wednesday />
<Thursday />
<Friday />
</DaysOfWeek>
<WeeksInterval>1</WeeksInterval>
</ScheduleByWeek>
</CalendarTrigger>
</Triggers>
<Settings>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<ExecutionTimeLimit>PT12M</ExecutionTimeLimit>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<StartWhenAvailable>true</StartWhenAvailable>
<Enabled>true</Enabled>
</Settings>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Actions Context="Author">
<Exec>
<Command>C:\Python310\python.exe</Command>
<Arguments>-X utf8 C:\sanguo_bigqmt\qmt_probe_0915.py</Arguments>
</Exec>
</Actions>
</Task>
-209
View File
@@ -1,209 +0,0 @@
# -*- coding: utf-8 -*-
"""sanguo-qmt-probe0915 — pre-open ACTIVE trade probe (the only gold standard
for session-day freshness: passive queries cannot see a stale session).
Order shape (proven 2026-09-09 09:40 on this exact account/counter):
510050.SH BUY 100 @ 0.01 FIX_PRICE — a price that can NEVER fill, so the
probe is risk-free; what matters is how the counter treats it.
accepted (status 48..56) -> session is day-current -> GREEN -> cancel it
junked (status 57 / reject msg) -> stale session -> RED -> trigger
sanguo-qmt-relogin (~4min, back before the 09:30 open)
Window is hard-coded 09:15:00-09:19:30: 09:20-09:25 is the no-cancel auction
phase — an order placed there could not be pulled before the open.
Exit codes: 0 green | 1 red | 2 amber/skip.
"""
import datetime
import sys
import time
from qmt_gate_common import (ACCOUNT, bridge_ping, gate_json, gate_log,
procs_alive, redis_client, relogin_running,
trigger_relogin, wait_bridge)
STOCK = "510050.SH"
WINDOW_START = (9, 15, 0)
WINDOW_END = (9, 19, 30)
LIVE_STATUSES = {48, 49, 50, 55, 56} # unreported..reported, part/full fill
CANCEL_FAMILY = {51, 52, 53, 54} # accepted then (part) cancelled
JUNK_STATUS = 57 # ORDER_JUNK = counter rejection
HOLIDAY_WORDS = ("非交易", "休市", "闭市", "非交易日", "节假日")
RECOVER_TIMEOUT = 360 # 6 min: back before the 09:30 open
def _in_window(now):
hms = (now.hour, now.minute, now.second)
if hms < WINDOW_START:
wait = (WINDOW_START[0] * 3600 + WINDOW_START[1] * 60 + WINDOW_START[2]
- (hms[0] * 3600 + hms[1] * 60 + hms[2]))
return "early", wait
if hms > WINDOW_END:
return "late", 0
return "in", 0
def rpc(rc, method, params, timeout=30):
from bigqmt_signal_trader.redis_rpc import call_redis_rpc
return call_redis_rpc(rc, ACCOUNT, method, params, timeout_seconds=timeout)
def find_order(rc, remark):
"""The probe order of today by exact remark, from the counter's order list."""
for _ in range(5):
try:
r = rpc(rc, "query_stock_orders", {"account_id": ACCOUNT})
orders = r.get("data") if isinstance(r.get("data"), list) else []
for o in orders:
if str(o.get("remark") or o.get("user_order_id") or "") == remark:
return o
except Exception as exc:
gate_log("PROBE0915", "NOTE", "orders query err %r" % exc)
time.sleep(2)
return None
def place(rc, remark):
res = rpc(rc, "order_stock", {
"account_id": ACCOUNT, "stock_code": STOCK, "order_type": 23,
"order_volume": 100, "price_type": 11, "price": 0.01,
"strategy_name": "probe", "order_remark": remark})
gate_log("PROBE0915", "NOTE",
"order placed remark=%s res=%s" % (remark, str(res)[:160]))
def cancel(rc, order):
sysid = str(order.get("order_sys_id") or "")
if not sysid:
gate_log("PROBE0915", "WARN", "no order_sys_id to cancel: %s"
% str(order)[:160])
return False
for attempt in range(3):
try:
r = rpc(rc, "cancel_order_stock_sysid",
{"account_id": ACCOUNT, "market": "", "order_sysid": sysid})
ok = "ok" in r and r.get("ok") is not False
gate_log("PROBE0915", "NOTE", "cancel sysid=%s try%d -> %s"
% (sysid, attempt + 1, str(r)[:120]))
except Exception as exc:
gate_log("PROBE0915", "WARN", "cancel exc %r" % exc)
ok = False
time.sleep(2)
cur = find_order(rc, str(order.get("remark") or ""))
if cur and int(cur.get("status") or 0) in (CANCEL_FAMILY | LIVE_STATUSES):
st = int(cur.get("status") or 0)
if st in CANCEL_FAMILY:
return True
elif cur is None:
return True # gone from the list: fully pulled
gate_log("PROBE0915", "CRITICAL",
"PROBE_ORDER_LEFT_LIVE sysid=%s status=%s — cannot fill @0.01, "
"day-end settlement clears it; review manually"
% (sysid, (cur or {}).get("status")))
return False
def main():
now = datetime.datetime.now()
remark = "probe:gate:%s" % now.strftime("%Y%m%d")
state, wait = _in_window(now)
if state == "late":
gate_log("PROBE0915", "SKIP", "past 09:19:30 window, no probe today")
gate_json("probe0915", "SKIP", reason="late")
return 2
if state == "early":
gate_log("PROBE0915", "NOTE", "early by %ds, waiting for window" % wait)
time.sleep(min(wait + 1, 300))
now = datetime.datetime.now()
if _in_window(now)[0] != "in":
gate_log("PROBE0915", "SKIP", "window lost while waiting")
gate_json("probe0915", "SKIP", reason="window-lost")
return 2
try:
rc = redis_client()
except Exception as exc:
gate_log("PROBE0915", "AMBER", "redis connect failed %r" % exc)
return 2
up, detail = bridge_ping(rc)
if not up:
gate_log("PROBE0915", "AMBER",
"bridge already down pre-order (%s); relogin is sentinel's "
"job, skipping probe" % detail)
return 2
existing = find_order(rc, remark)
if existing is None:
place(rc, remark)
# counter reporting can lag at the 09:15:00 boundary second
# (night test 09-09: after-hours submissions stayed invisible);
# poll patiently — the cancel window still has minutes of margin.
for _ in range(3):
time.sleep(3)
existing = find_order(rc, remark)
if existing is not None:
break
if existing is None:
gate_log("PROBE0915", "AMBER",
"order not visible after 5 queries; cannot judge, no trigger")
gate_json("probe0915", "AMBER", reason="order-not-visible")
return 2
status = int(existing.get("status") or 0)
msg = str(existing.get("status_msg") or "")[:100]
gate_log("PROBE0915", "NOTE",
"order status=%d msg=%r sysid=%s" % (
status, msg, existing.get("order_sys_id")))
if status == JUNK_STATUS or (msg and any(w in msg for w in ("废", "拒"))):
if any(w in msg for w in HOLIDAY_WORDS):
gate_log("PROBE0915", "AMBER",
"junked with holiday wording (%r) — not a staleness "
"signal, no trigger" % msg)
gate_json("probe0915", "AMBER", status=status, msg=msg)
return 2
gate_log("PROBE0915", "RED",
"counter junked the probe order -> session stale; "
"triggering relogin")
return recover(rc, "junked:%s" % msg)
# accepted by the counter in any form -> session is day-current
ok_cancel = True
if status in LIVE_STATUSES:
ok_cancel = cancel(rc, existing)
gate_log("PROBE0915", "GREEN",
"order accepted (status=%d) -> session day-current; cancelled=%s"
% (status, ok_cancel))
gate_json("probe0915", "GREEN", status=status, cancelled=ok_cancel,
sysid=str(existing.get("order_sys_id")))
return 0
def recover(rc, reason):
if relogin_running():
gate_log("PROBE0915", "SKIP", "relogin already in flight (%s)" % reason)
else:
ok, out = trigger_relogin()
if not ok:
gate_log("PROBE0915", "AMBER", "schtasks /run failed: %s" % out)
return 2
gate_log("PROBE0915", "ACTION",
"relogin triggered (%s); polling bridge <=%ds" % (reason,
RECOVER_TIMEOUT))
ok, took, detail = wait_bridge(rc, RECOVER_TIMEOUT, "PROBE0915")
procs = procs_alive()
if ok:
gate_log("PROBE0915", "RECOVERED",
"bridge back in %ds before open (%s) procs=%s" % (
took, detail, procs))
gate_json("probe0915", "RED_RECOVERED", reason=reason, took_sec=took)
return 1 # the session WAS stale this morning; red stays on record
gate_log("PROBE0915", "RED",
"NOT recovered in %ds (%s) — manual attention before open" % (
took, detail))
gate_json("probe0915", "RED", reason=reason, took_sec=took, procs=procs)
return 1
if __name__ == "__main__":
sys.exit(main())
-41
View File
@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
"""Inspect <install>/python (strategy scripts) + hunt strategy registry files."""
import os
import time
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
pydir = os.path.join(root, "python")
print("=== %s ===" % pydir.encode("unicode_escape").decode("ascii"))
if os.path.isdir(pydir):
for f in sorted(os.listdir(pydir)):
p = os.path.join(pydir, f)
st = os.stat(p)
print(" %-50s %9d %s" % (
f.encode("unicode_escape").decode("ascii")[:50], st.st_size,
time.strftime("%m-%d %H:%M", time.localtime(st.st_mtime))))
else:
print(" MISSING")
# strategy registry candidates anywhere under install root (small config-ish files
# mentioning formula), excluding logs/data dirs we already know
print("=== hunting registry candidates ===")
skip = {"log", "dumps"}
for dirpath, dirnames, files in os.walk(root):
dirnames[:] = [x for x in dirnames if x not in skip and not x.startswith("bin")]
rel = os.path.relpath(dirpath, root)
depth = rel.count(os.sep)
if depth > 3:
dirnames[:] = []
continue
for f in files:
low = f.lower()
if any(k in low for k in ("formula", "strategy", "model")) and \
low.endswith((".xml", ".json", ".ini", ".dat", ".cfg", ".db")):
p = os.path.join(dirpath, f)
st = os.stat(p)
print(" %-70s %8d %s" % (
os.path.join(rel, f).encode("unicode_escape").decode("ascii")[:70],
st.st_size,
time.strftime("%m-%d %H:%M", time.localtime(st.st_mtime))))
-46
View File
@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only: inspect stale bridge RPC queue composition before any revival."""
import json
import re
import time
from collections import Counter
import redis
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:66639661"
n1 = rc.llen(Q)
time.sleep(5)
n2 = rc.llen(Q)
print("llen:", n1, "->", n2, "(+5s delta %d, producers %s)" % (
n2 - n1, "ACTIVE" if n2 != n1 else "idle"))
items = rc.lrange(Q, 0, -1)
print("items:", len(items))
methods = Counter()
order_like = 0
for it in items:
try:
obj = json.loads(it)
m = obj.get("method", "?")
except Exception:
m = "<unparsable>"
methods[m] += 1
if any(k in m for k in ("order", "cancel", "trade", "buy", "sell")):
order_like += 1
print("method histogram:")
for m, c in methods.most_common():
print(" %-30s %d" % (m, c))
print("ORDER-LIKE total:", order_like)
print("=== head 3 raw ===")
for it in items[:3]:
print(" ", it[:260].encode("unicode_escape").decode("ascii"))
print("=== tail 3 raw ===")
for it in items[-3:]:
print(" ", it[:260].encode("unicode_escape").decode("ascii"))
print("DONE5")
-32
View File
@@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only: decode ALL stale queue items with the library's own codec."""
import json
from collections import Counter
import redis
from bigqmt_signal_trader.redis_rpc import (
ORDER_METHODS, decode_rpc_request_payload)
with open(r"C:\redis\redis.conf") as f:
import re
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:66639661"
items = rc.lrange(Q, 0, -1)
methods = Counter()
bad = 0
for it in items:
try:
obj = json.loads(decode_rpc_request_payload(it))
methods[obj.get("method", "?")] += 1
except Exception:
bad += 1
print("total:", len(items), "undecodable:", bad)
for m, c in methods.most_common():
tag = "ORDER!" if m in ORDER_METHODS else "read"
print(" %-30s %5d %s" % (m, c, tag))
print("ORDER total:", sum(c for m, c in methods.items() if m in ORDER_METHODS))
print("DONE6")
-243
View File
@@ -1,243 +0,0 @@
# -*- coding: utf-8 -*-
"""Morning auto-relogin for the Big QMT client (surgical: XtItClient.exe only).
Flow: discover install dir (psutil, cached to disk) -> check session state
-> flush stale bridge queue (read-only guard: abort if any ORDER method found)
-> taskkill XtItClient.exe (polite first; mini/miniquote untouched, xt_eod
depends on them) -> settle -> open_qmt: exe mode first (remembered-session
auto-login, proven by the daily 08:50 built-in restart), login mode with
typed credentials as fallback -> poll bridge RPC until it answers.
Force-kill note: a forced shutdown is known to SKIP instance auto-run on next
boot; kill_client still forces as last resort but the bridge check will then
fail loudly (exit 5) instead of lying green.
Known boundaries (measured 09-09 night):
- The login dialog has a 4-char graphic captcha (refreshes every 40s) and
ships with remember-password / auto-login unchecked. Typed-credential login
WILL most likely die on the captcha; the fix is a one-time manual login with
both checkboxes ticked, after which cold boots take the exe auto-login path.
- Polite close (WM_CLOSE) logs the session OUT -> next boot shows the login
dialog; force-kill KEEPS the session -> next boot auto-logins straight to
the main window (proven 19:27 vs 20:53 same night).
- XtMiniQmt.exe / miniquote.exe are the xt_eod data leg. They were observed
dying alongside our surgical XtItClient kills even though taskkill /IM
matches only one name -- so we log their state after every kill for early
detection, and warn (not fail) if they are gone at success time.
Credentials come from BIGQMT_LOGIN_USER / BIGQMT_LOGIN_PASSWORD env, set by the
PowerShell wrapper from a DPAPI-encrypted blob: never in argv, never on disk.
Exit codes: 0 ok | 3 creds missing | 4 session locked (fallback blocked)
6 client would not close/open | 7 order-class items in stale queue
5 bridge rpc still down after login
"""
import datetime
import os
import subprocess
import sys
import time
ACCOUNT = "66639661"
CACHE_PATH = r"C:\sanguo_bigqmt\qmt_install_dir.txt"
QUEUE_KEY = "bigqmt:rpc:queue:%s" % ACCOUNT
def log(msg):
print("[%s] %s" % (datetime.datetime.now().strftime("%m-%d %H:%M:%S"), msg), flush=True)
def discover_install_dir():
"""Find bin.x64 from the running XtItClient.exe; cache for cold starts."""
try:
import psutil
for p in psutil.process_iter(["name"]):
try:
if (p.info["name"] or "").lower() == "xtitclient.exe":
exe = p.exe()
bin_dir = os.path.dirname(exe)
with open(CACHE_PATH, "w", encoding="utf-8") as f:
f.write(bin_dir)
log("install_dir discovered live: %r" % bin_dir)
return bin_dir
except Exception:
continue
except Exception as exc:
log("psutil discovery failed: %r" % exc)
if os.path.isfile(CACHE_PATH):
with open(CACHE_PATH, encoding="utf-8") as f:
cached = f.read().strip()
if cached:
log("install_dir from cache: %r" % cached)
return cached
return None
def _procs_alive():
"""Name -> bool for the QMT family processes we care about."""
out = subprocess.run(["tasklist", "/FO", "CSV", "/NH"],
capture_output=True, text=True).stdout or ""
return {name: ('"%s"' % name) in out
for name in ("XtItClient.exe", "XtMiniQmt.exe", "miniquote.exe")}
def kill_client(grace_seconds=8):
"""Kill only XtItClient.exe. Polite first (WM_CLOSE lets it flush), force after."""
subprocess.run(["taskkill", "/IM", "XtItClient.exe"],
capture_output=True)
deadline = time.time() + grace_seconds
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:
log("XtItClient closed cleanly; QMT family now: %s"
% _procs_alive())
return True
time.sleep(1)
log("still alive after %ds; forcing (auto-run may be skipped!)" % grace_seconds)
subprocess.run(["taskkill", "/IM", "XtItClient.exe", "/F"], capture_output=True)
time.sleep(3)
out = subprocess.run(["tasklist", "/FI", "IMAGENAME eq XtItClient.exe"],
capture_output=True, text=True).stdout or ""
ok = "XtItClient.exe" not in out
log("force kill done, gone=%s" % ok)
return ok
def flush_stale_queue():
"""Delete stale read-only requests; REFUSE if any order-class item waits."""
import json
import re
import redis
from bigqmt_signal_trader.redis_rpc import (ORDER_METHODS,
decode_rpc_request_payload)
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)
n = rc.llen(QUEUE_KEY)
orders = 0
for it in rc.lrange(QUEUE_KEY, 0, -1):
try:
obj = json.loads(decode_rpc_request_payload(it))
if obj.get("method") in ORDER_METHODS:
orders += 1
except Exception:
continue
if orders:
log("FATAL: %d order-class items in stale queue; manual review" % orders)
return False
rc.delete(QUEUE_KEY)
log("queue flushed: %d stale read-only items" % n)
return True
def bridge_alive(timeout_seconds=150, rc=None):
"""Poll the bridge RPC server until it answers. Any dict envelope counts --
'ping' may not be a registered method, so fall back to an ORDER-class query
(read-only, and ORDER data is available immediately after login)."""
from bigqmt_signal_trader.redis_rpc import call_redis_rpc
if rc is None:
import re
import redis
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)
deadline = time.time() + timeout_seconds
last = None
while time.time() < deadline:
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):
return method, r
last = "%s -> %r" % (method, r)
except Exception as exc:
last = "%s -> %s" % (method, exc)
time.sleep(5)
log("bridge rpc last: %s" % last)
return None
def warn_data_leg():
"""2026-09-11: xt_eod switched to the big-QMT bridge (xtquant_bridge
shim), so XtMiniQmt.exe absence is NORMAL and no longer a data gap.
Kept as a no-op placeholder because the relogin flow used to log a
misleading warning here; see xt-eod-bridge-leg-switch-20260911."""
return None
def main():
user = os.environ.get("BIGQMT_LOGIN_USER", "")
pwd = os.environ.get("BIGQMT_LOGIN_PASSWORD", "")
log("=== qmt_relogin start (user=%s pwdlen=%d) ===" % (user, len(pwd)))
from bigqmt_signal_trader import qmt_launcher as L
install_dir = discover_install_dir()
if not install_dir or not os.path.isdir(install_dir):
log("FATAL: cannot resolve install dir: %r" % install_dir)
return 6
if not flush_stale_queue():
return 7
if not kill_client():
log("FATAL: XtItClient.exe would not die")
return 6
time.sleep(5) # socket linger settle (upstream restart_qmt uses 5s)
creds = {"user": user, "password": pwd} if user and pwd else None
locked = L.session_is_locked()
if locked:
log("warn: console session locked; exe auto-login still allowed, "
"credential-typing fallback will not be possible")
mode_used = None
try:
waited = L.open_qmt(install_dir, mode="exe", ready_timeout_seconds=240)
mode_used = "exe"
log("open_qmt(exe) ready in %.1fs" % waited)
except Exception as exc:
log("exe-mode failed: %r" % exc)
mode_used = None
got = bridge_alive(150) if mode_used else None
if got:
log("bridge up after exe-mode (remembered session) via %r ok=%s"
% (got[0], got[1].get("ok")))
log("=== RELOGIN_DONE ===")
return 0
# fallback: credential-typing login mode
if not creds:
log("FATAL: bridge down and no credentials for login fallback")
return 3
if locked:
log("FATAL: bridge down, fallback needs typing but session locked")
return 4
if mode_used: # client is up but bridge dead: close before login retry
if not kill_client():
return 6
time.sleep(5)
try:
waited = L.open_qmt(install_dir, mode="login", credentials=creds,
window_title_prefix="QMT", ready_timeout_seconds=240)
log("open_qmt(login) ready in %.1fs" % waited)
except Exception as exc:
log("FATAL: open_qmt(login) failed: %r" % exc)
return 6
got = bridge_alive(150)
if not got:
log("FATAL: bridge rpc did not answer after login")
return 5
log("bridge rpc answering via %r: ok=%s" % (got[0], got[1].get("ok")))
log("=== RELOGIN_DONE ===")
return 0
if __name__ == "__main__":
sys.exit(main())
-21
View File
@@ -1,21 +0,0 @@
# Decrypt the DPAPI credential blob into process env and run the relogin.
# Plaintext exists only in this process's memory and the child's environment.
# Keep this file ASCII-only (the console codepage is GBK).
$ErrorActionPreference = 'Stop'
$encPath = 'C:\sanguo_bigqmt\creds\qmt_login.enc'
$logPath = 'C:\sanguo_bigqmt\qmt_relogin.log'
$enc = (Get-Content -Raw -LiteralPath $encPath).Trim()
$sec = ConvertTo-SecureString -String $enc
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
$plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
$j = $plain | ConvertFrom-Json
$plain = $null
$env:BIGQMT_LOGIN_USER = [string]$j.user
$env:BIGQMT_LOGIN_PASSWORD = [string]$j.password
& C:\Python310\python.exe C:\sanguo_bigqmt\qmt_relogin.py 2>&1 |
Out-File -Append -Encoding utf8 -LiteralPath $logPath
Add-Content -Encoding utf8 -LiteralPath $logPath -Value ("EXIT=" + $LASTEXITCODE)
-35
View File
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Big QMT morning auto-relogin (surgical XtItClient.exe only; mini/miniquote untouched). Decryption+UI login+bridge verify, log at C:\sanguo_bigqmt\qmt_relogin.log</Description>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2026-09-10T07:50:00</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Settings>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<ExecutionTimeLimit>PT15M</ExecutionTimeLimit>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<Enabled>true</Enabled>
</Settings>
<Principals>
<Principal id="Author">
<UserId>Administrator</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-NoProfile -ExecutionPolicy Bypass -File C:\sanguo_bigqmt\qmt_relogin_run.ps1</Arguments>
</Exec>
</Actions>
</Task>
-143
View File
@@ -1,143 +0,0 @@
# -*- 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)
-188
View File
@@ -1,188 +0,0 @@
# -*- 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, calendar_probe, 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 infra_probe():
"""Sixth probe (2026-09-19, runbook#35 wave 1, user-approved).
Trading stack is NOT the only thing on this box: the 09-13 OOM cascade
starved frps for 5 days and nobody noticed -- the sentinel never looked.
Pure observation: RED here NEVER triggers recover() (relogin cannot help
frps; the fix for a dead frps is documented `sc start sanguo-frps`).
Runs first in main() so trading-stack early-returns cannot skip it.
"""
import ctypes
import socket
import urllib.error
import urllib.request
commit_pct = None
try:
class _MS(ctypes.Structure):
_fields_ = [("dwLength", ctypes.c_ulong),
("dwMemoryLoad", ctypes.c_ulong),
("ullTotalPhys", ctypes.c_ulonglong),
("ullAvailPhys", ctypes.c_ulonglong),
("ullTotalPageFile", ctypes.c_ulonglong),
("ullAvailPageFile", ctypes.c_ulonglong),
("ullTotalVirtual", ctypes.c_ulonglong),
("ullAvailVirtual", ctypes.c_ulonglong),
("ullAvailExtendedVirtual", ctypes.c_ulonglong)]
ms = _MS()
ms.dwLength = ctypes.sizeof(_MS)
if ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(ms)) \
and ms.ullTotalPageFile:
commit_pct = round(
100.0 * (ms.ullTotalPageFile - ms.ullAvailPageFile)
/ ms.ullTotalPageFile, 1)
except Exception:
pass # non-Windows / API failure: telemetry gap, not a verdict
frps_up = False
try:
s = socket.create_connection(("127.0.0.1", 7000), timeout=3)
s.close()
frps_up = True
except OSError:
pass
claude = "?"
try:
req = urllib.request.Request(
"https://claude.mysanguo.online", method="HEAD")
with urllib.request.urlopen(req, timeout=10) as resp:
claude = resp.status
except urllib.error.HTTPError as exc:
claude = exc.code
except Exception as exc:
claude = type(exc).__name__
verdict, notes = "GREEN", []
if not frps_up:
verdict = "RED"
notes.append("frps:7000 dead -> sc start sanguo-frps (runbook#35)")
if claude != 200:
verdict = "RED"
notes.append("claude=%s" % claude)
if commit_pct is not None and commit_pct >= 90:
if verdict == "GREEN":
verdict = "AMBER" # predictive: warn BEFORE the OOM cascade
notes.append("commit=%s%% >=90" % commit_pct)
gate_log("INFRA", verdict, "frps=%s claude=%s commit=%s%% %s" % (
"up" if frps_up else "DEAD", claude, commit_pct,
"; ".join(notes) if notes else "ok"))
gate_json("infra_probe", verdict, fname="qmt_gate_latest_infra.json",
frps_up=frps_up, claude=claude, commit_pct=commit_pct)
return verdict
def main():
infra_probe()
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")
cal_state, cal_detail = calendar_probe()
if cal_state == "bad":
gate_log("SENTINEL", "RED", cal_detail)
return recover("calendar-str-contract")
if cal_state in ("empty", "exec"):
gate_log("SENTINEL", "AMBER", "fifth-probe inconclusive: %s" % cal_detail)
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())
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Big QMT 30min availability sentinel: XtItClient process + bridge RPC ping; RED triggers sanguo-qmt-relogin then polls recovery. Audit trail C:\sanguo_bigqmt\gate\qmt_gate_*.log</Description>
</RegistrationInfo>
<Triggers>
<CalendarTrigger>
<StartBoundary>2026-09-10T00:10:00</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
<Repetition>
<Interval>PT30M</Interval>
<Duration>P1D</Duration>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
</CalendarTrigger>
</Triggers>
<Settings>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<ExecutionTimeLimit>PT12M</ExecutionTimeLimit>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<StartWhenAvailable>true</StartWhenAvailable>
<Enabled>true</Enabled>
</Settings>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Actions Context="Author">
<Exec>
<Command>C:\Python310\python.exe</Command>
<Arguments>-X utf8 C:\sanguo_bigqmt\qmt_sentinel.py</Arguments>
</Exec>
</Actions>
</Task>
-77
View File
@@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only: today's QMT panorama — log timeline, processes, bridge liveness."""
import datetime
import io
import os
import re
import subprocess
ACCOUNT = "66639661"
def esc(s):
return str(s).encode("unicode_escape").decode("ascii")
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
logdir = os.path.join(root, "userdata", "log")
print("=== today's logs (size/mtime) ===")
for f in sorted(os.listdir(logdir)):
if "20260909" in f:
p = os.path.join(logdir, f)
st = os.stat(p)
print(" %-46s %8d mtime=%s" % (
f, st.st_size,
datetime.datetime.fromtimestamp(st.st_mtime).strftime("%H:%M:%S")))
kws = ("登录", "登陆", "login", "Login", "自动", "运行", "模型", "策略",
"启动", "恢复", "auto", "Auto", "Formula", "自启")
f2 = os.path.join(logdir, "XtClient_20260909.log")
print("=== XtClient key windows (08:5x / 11:xx / 12:0x) ===")
try:
with io.open(f2, "r", encoding="gbk", errors="replace") as f:
for ln in f:
hhmm = ln[:5]
if any(k in ln for k in kws) and (
hhmm.startswith("08:5") or hhmm.startswith("11:")
or hhmm.startswith("12:0")):
print(" ", esc(ln.rstrip())[:230])
except OSError as e:
print(" ", e)
f1 = os.path.join(logdir, "XtClient_Formula_20260909.log")
print("=== Formula head 12 ===")
try:
lines = io.open(f1, "r", encoding="gbk", errors="replace").readlines()
for ln in lines[:12]:
print(" ", esc(ln.rstrip())[:200])
print("=== Formula tail 25 (of %d) ===" % len(lines))
for ln in lines[-25:]:
print(" ", esc(ln.rstrip())[:200])
except OSError as e:
print(" ", e)
print("=== processes ===")
raw = subprocess.run(["tasklist"], capture_output=True).stdout
for ln in raw.decode("gbk", errors="replace").splitlines():
if any(x in ln for x in ("Xt", "ython", "redis-server", "Redis")):
print(" ", esc(ln)[:130])
print("=== bridge liveness (read-only RPC) ===")
try:
import redis
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
print(" queue llen:", rc.llen(q))
from bigqmt_signal_trader.redis_rpc import call_redis_rpc
r = call_redis_rpc(rc, ACCOUNT, "query_stock_orders",
{"account_id": ACCOUNT}, timeout_seconds=10)
print(" RPC:", esc(str(r))[:400])
except Exception as e:
print(" RPC ERR:", esc(repr(e))[:300])
print("DONE4")
-42
View File
@@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only round 3: startupAutorun / modbootstart values across daily baselines."""
import os
import re
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
def esc(s):
return s.encode("unicode_escape").decode("ascii")
files = {
"now": os.path.join(root, "config", "indexUserConfig.xml"),
"snap_before": r"C:\sanguo_bigqmt\indexUserConfig_before.xml",
"d0907": os.path.join(root, "config", "Temp", "indexUserConfig.xml_20260907"),
"d0908": os.path.join(root, "config", "Temp", "indexUserConfig.xml_20260908"),
"d0909": os.path.join(root, "config", "Temp", "indexUserConfig.xml_20260909"),
}
for tag, p in files.items():
t = open(p, encoding="utf-8", errors="replace").read()
sa = re.findall(r'startupAutorun="([^"]*)"', t)
# instance element presence: count id attrs near FromulaExpandData
fe = t.count("FromulaExpandData")
# find modbootstart-ish global in same file too
mb = re.findall(r'modbootstart="([^"]*)"', t)
# restarttimelist
rt = re.findall(r'restarttimelist="([^"]*)"', t)
print("%-11s size=%d startupAutorun=%s FromulaExpandData=%d modbootstart=%s restarttimelist=%s"
% (tag, len(t.encode('utf-8')), sa, fe, mb, [esc(x) for x in rt]))
# Config.xml (user) modbootstart + restart time attrs
p2 = os.path.join(root, "userdata", "users", "66639661", "Config.xml")
t2 = open(p2, encoding="utf-8", errors="replace").read()
for kw in ["modbootstart", "restarttime", "restarttimelist", "TradeSetting"]:
for m in re.finditer(re.escape(kw), t2):
a = max(0, m.start() - 80)
b = min(len(t2), m.end() + 250)
print("CFG", esc(t2[a:b]).replace("\\n", " ")[:400])
print("---")
print("DONE3")
-41
View File
@@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only: dump instance entry from indexUserConfig.xml + autorun-ish attrs
from userdata Config.xml. All output unicode-escaped (console is GBK)."""
import os
import re
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
def esc(s):
return s.encode("unicode_escape").decode("ascii")
# --- 1) indexUserConfig.xml: element(s) containing the instance name ---
p1 = os.path.join(root, "config", "indexUserConfig.xml")
t1 = open(p1, encoding="utf-8", errors="replace").read()
name = "新建策略文件3"
for m in re.finditer(re.escape(name), t1):
a = max(0, m.start() - 1500)
b = min(len(t1), m.end() + 1500)
print("=== indexUserConfig ctx ===")
print(esc(t1[a:b]))
print("=== end ===")
break # first occurrence with wide context
# also list all attribute names seen in file for flag hunting
attrs = sorted(set(re.findall(r'([A-Za-z][A-Za-z0-9_]{2,30})\s*=', t1)))
print("ATTRS", esc(",".join(attrs)))
# --- 2) userdata Config.xml: every line with boot/autorun/mod flags ---
p2 = os.path.join(root, "userdata", "users", "66639661", "Config.xml")
t2 = open(p2, encoding="utf-8", errors="replace").read()
print("=== Config.xml flag lines ===")
pat = re.compile(r"(modbootstart|autorun|auto_run|bootstart|selfstart|autostart)", re.I)
for m in pat.finditer(t2):
a = max(0, m.start() - 300)
b = min(len(t2), m.end() + 300)
print(esc(t2[a:b]))
print("---")
print("DONE")
-40
View File
@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
"""Read-only round 2: locate the INSTANCE element (startupAutorun) precisely."""
import os
import re
d = open(r"C:\sanguo_bigqmt\qmt_install_dir.txt", encoding="utf-8").read().strip()
root = os.path.dirname(d)
def esc(s):
return s.encode("unicode_escape").decode("ascii")
p1 = os.path.join(root, "config", "indexUserConfig.xml")
t1 = open(p1, encoding="utf-8", errors="replace").read()
print("### startupAutorun occurrences:", t1.count("startupAutorun"))
for m in re.finditer("startupAutorun", t1):
a = max(0, m.start() - 900)
b = min(len(t1), m.end() + 400)
print("=== SA ctx ===")
print(esc(t1[a:b]))
print("=== end ===")
print("### name occurrences:", t1.count("新建策略文件3"))
for i, m in enumerate(re.finditer("新建策略文件3", t1)):
a = max(0, m.start() - 200)
b = min(len(t1), m.end() + 200)
print("--- occ%d pos%d ---" % (i, m.start()))
print(esc(t1[a:b]))
# Config.xml modbootstart compact
p2 = os.path.join(root, "userdata", "users", "66639661", "Config.xml")
t2 = open(p2, encoding="utf-8", errors="replace").read()
print("### modbootstart occurrences:", t2.lower().count("modbootstart"))
for m in re.finditer("modbootstart", t2, re.I):
a = max(0, m.start() - 150)
b = min(len(t2), m.end() + 150)
print(esc(t2[a:b]))
print("DONE2")
-1
View File
@@ -1 +0,0 @@
C:\Python310\python.exe C:\sanguo_bigqmt\qmt_relogin_test.py > C:\sanguo_bigqmt\relogin_test.log 2>&1
-1
View File
@@ -1 +0,0 @@
& C:\Python310\python.exe -c "import pyautogui; im = pyautogui.screenshot(); im.save(r'C:\sanguo_bigqmt\shot.png'); print('SHOT_OK')"
-1
View File
@@ -1 +0,0 @@
C:\Python310\python.exe C:\sanguo_bigqmt\qmt_click_pystg.py
-1
View File
@@ -1 +0,0 @@
C:\Python310\python.exe -c "import pyautogui; pyautogui.FAILSAFE=False; pyautogui.screenshot(r'C:\sanguo_bigqmt\shot.png')"
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>One-shot console session screenshot for ops diagnosis</Description>
</RegistrationInfo>
<Triggers>
<TimeTrigger>
<StartBoundary>2026-09-09T23:59:00</StartBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<Settings>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<ExecutionTimeLimit>PT5M</ExecutionTimeLimit>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<Enabled>true</Enabled>
</Settings>
<Principals>
<Principal id="Author">
<UserId>Administrator</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-NoProfile -ExecutionPolicy Bypass -File C:\sanguo_bigqmt\shot.ps1</Arguments>
</Exec>
</Actions>
</Task>
-189
View File
@@ -1,189 +0,0 @@
# -*- coding: utf-8 -*-
"""Execute UI actions from ui_actions.json (list of ops), screenshot after each
marked step. Runs in console session via schtask. ASCII output only.
Ops: click(x,y,clicks,button) move(x,y) key(k) hotkey(keys[]) scroll(dy,x,y)
wait(sec) shot(tag)
"""
import json
import time
import pyautogui
pyautogui.FAILSAFE = True
PLAN = r"C:\sanguo_bigqmt\ui_actions.json"
SHOT = r"C:\sanguo_bigqmt\shot_%02d.png"
plan = json.load(open(PLAN, encoding="utf-8")) # utf-8 regardless of GBK console
n = 0
def shot(tag=""):
global n
n += 1
pyautogui.screenshot().save(SHOT % n)
print("SHOT_%02d %s" % (n, tag))
for a in plan:
op = a["op"]
if op == "closewin":
import ctypes
user32 = ctypes.windll.user32
WM_CLOSE = 0x0010
if a.get("hwnd"):
user32.PostMessageW(a["hwnd"], WM_CLOSE, 0, 0)
print("CLOSEWIN hwnd=%s" % a["hwnd"])
else:
Ws2 = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
hits = []
@Ws2
def cb2(h2, _):
buf = ctypes.create_unicode_buffer(256)
user32.GetWindowTextW(h2, buf, 256)
if a["contains"] in buf.value:
hits.append(h2)
return True
user32.EnumWindows(cb2, 0)
for h2 in hits:
user32.PostMessageW(h2, WM_CLOSE, 0, 0)
print("CLOSEWIN %s hwnd=%s" % (a["contains"], h2))
if op == "exec":
import subprocess
r = subprocess.run([r"C:\Python310\python.exe", a["script"]],
capture_output=True, text=True, timeout=60)
for ln in (r.stdout or "").splitlines():
print("X| " + ln)
for ln in (r.stderr or "").splitlines()[:10]:
print("E| " + ln)
if op == "activate":
import ctypes
user32 = ctypes.windll.user32
title = a.get("title")
h = user32.FindWindowW(None, title) if title else 0
if not h and a.get("contains"):
Ws = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
found = []
@Ws
def cb(h2, _):
buf = ctypes.create_unicode_buffer(256)
user32.GetWindowTextW(h2, buf, 256)
if a["contains"] in buf.value and user32.IsWindowVisible(h2):
found.append(h2)
return True
user32.EnumWindows(cb, 0)
h = found[0] if found else 0
if h:
user32.ShowWindow(h, 9) # SW_RESTORE
user32.SetForegroundWindow(h)
time.sleep(0.8)
print("ACTIVATE ok hwnd=%s title=%s" % (h, a.get("title") or a.get("contains")))
else:
print("ACTIVATE_FAIL not_found")
if op == "launch":
import subprocess
cwd = a.get("cwd") or None
subprocess.Popen([a["exe"]] + list(a.get("args", [])), cwd=cwd)
print("LAUNCH %s %s" % (a["exe"].encode("unicode_escape").decode("ascii"),
" ".join(a.get("args", []))))
if op == "minimize":
import ctypes
user32 = ctypes.windll.user32
if a.get("hwnd"):
user32.ShowWindow(a["hwnd"], 6) # SW_MINIMIZE
print("MINIMIZE hwnd=%s" % a["hwnd"])
elif a.get("contains"):
Ws3 = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
hits = []
@Ws3
def cb3(h2, _):
buf = ctypes.create_unicode_buffer(256)
user32.GetWindowTextW(h2, buf, 256)
if a["contains"] in buf.value and user32.IsWindowVisible(h2):
hits.append(h2)
return True
user32.EnumWindows(cb3, 0)
for h2 in hits[:1]:
user32.ShowWindow(h2, 6)
print("MINIMIZE %s hwnd=%s" % (a["contains"], h2))
if op == "credtype":
# decrypt DPAPI secret in-process and type it into field (x,y).
# never printed, never written to disk.
import base64
import ctypes
class DATA_BLOB(ctypes.Structure):
_fields_ = [("cbData", ctypes.c_ulong),
("pbData", ctypes.POINTER(ctypes.c_char))]
enc_hex = open(r"C:\sanguo_bigqmt\creds\qmt_login.enc",
encoding="ascii").read().strip()
raw = bytes.fromhex(enc_hex)
bin_ = DATA_BLOB(len(raw), ctypes.cast(
ctypes.create_string_buffer(raw, len(raw)),
ctypes.POINTER(ctypes.c_char)))
bout = DATA_BLOB()
ok = ctypes.windll.crypt32.CryptUnprotectData(
ctypes.byref(bin_), None, None, None, None, 0, ctypes.byref(bout))
if not ok:
print("CREDTYPE decrypt_failed")
else:
plain = ctypes.string_at(bout.pbData, bout.cbData).decode(
"utf-16-le", errors="replace").rstrip("\x00")
# cred json: {"user": "...", "password": "..."}
import json as _j
pw = _j.loads(plain)["password"]
pyautogui.click(a["x"], a["y"])
time.sleep(0.4)
pyautogui.typewrite(pw, interval=0.06)
print("CREDTYPE typed len=%d" % len(pw))
ctypes.windll.kernel32.LocalFree(bout.pbData)
if op == "text":
# type arbitrary (ASCII) text at x,y — used for captcha fields
pyautogui.click(a["x"], a["y"])
time.sleep(0.3)
pyautogui.typewrite(a["s"], interval=0.08)
print("TEXT typed len=%d" % len(a["s"]))
if op == "info":
im = pyautogui.screenshot()
print("SIZE_cursor=%s SIZE_bitmap=%s" % (pyautogui.size(), im.size))
if op == "shot":
shot(a.get("tag", ""))
elif op == "region":
n += 1
im = pyautogui.screenshot()
im.crop((a["x"], a["y"], a["x"] + a["w"], a["y"] + a["h"])).save(
SHOT % n)
print("REGION_%02d %s" % (n, a.get("tag", "")))
elif op == "zoom":
n += 1
s = a.get("scale", 3)
im = pyautogui.screenshot().crop(
(a["x"], a["y"], a["x"] + a["w"], a["y"] + a["h"]))
im = im.resize((im.width * s, im.height * s))
im.save(SHOT % n)
print("ZOOM_%02d %s" % (n, a.get("tag", "")))
elif op == "click":
pyautogui.click(a["x"], a["y"], clicks=a.get("clicks", 1),
interval=0.35, button=a.get("button", "left"))
print("CLICK %s %s x%s" % (a["x"], a["y"], a.get("clicks", 1)))
elif op == "move":
pyautogui.moveTo(a["x"], a["y"], duration=0.25)
print("MOVE %s %s" % (a["x"], a["y"]))
elif op == "key":
pyautogui.press(a["key"])
print("KEY %s" % a["key"])
elif op == "hotkey":
pyautogui.hotkey(*a["keys"])
print("HOTKEY %s" % "+".join(a["keys"]))
elif op == "scroll":
pyautogui.scroll(a.get("dy", -300), x=a.get("x"), y=a.get("y"))
print("SCROLL %s" % a.get("dy", -300))
elif op == "wait":
time.sleep(a["sec"])
print("WAIT %s" % a["sec"])
time.sleep(a.get("after", 0.6))
shot("end")
print("UI_ACT_DONE nshots=%d" % n)
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>Console-session UI action runner for QMT ops</Description>
</RegistrationInfo>
<Triggers>
<TimeTrigger>
<StartBoundary>2026-09-09T23:59:00</StartBoundary>
<Enabled>true</Enabled>
</TimeTrigger>
</Triggers>
<Settings>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<ExecutionTimeLimit>PT5M</ExecutionTimeLimit>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<Enabled>true</Enabled>
</Settings>
<Principals>
<Principal id="Author">
<UserId>Administrator</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-NoProfile -ExecutionPolicy Bypass -File C:\sanguo_bigqmt\ui_act.ps1</Arguments>
</Exec>
</Actions>
</Task>
-83
View File
@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""value 双轨重锚工序(2026-09-11 夜,用户拍板A)。
stop(live_21+shadow_62) -> 等停死 -> 备份+删 live_state.json 的 strategy_start_date -> start。
安全闸: 引擎未停死拒绝改档。用法: python -X utf8 C:\\sanguo_bigqmt\\reanchor_value.py [edit-only]
"""
from __future__ import annotations
import datetime
import json
import shutil
import sqlite3
import sys
import time
DB = r"C:\sanguo_vnpy_v2\data\backtest_results.db"
RUNTIMES = [r"C:\sanguo_vnpy_v2\runtime\live_21\live_state.json",
r"C:\sanguo_vnpy_v2\runtime\shadow_62\live_state.json"]
def _children_alive():
"""返回 (live_21存活, shadow_62存活)。"""
import psutil
live21 = shadow62 = False
for p in psutil.process_iter(["pid", "name", "cmdline"]):
if "python" not in (p.info["name"] or "").lower():
continue
cmd = p.info["cmdline"] or []
joined = " ".join(cmd)
if "runner_live" in joined and "21" in cmd:
live21 = True
if "sanguo_trader.shadow" in joined and "62" in cmd and "--auto" not in joined:
shadow62 = True
return live21, shadow62
def _set_status(value):
with sqlite3.connect(DB, timeout=15) as c:
c.execute("UPDATE live_accounts SET status=? WHERE id=21", (value,))
c.execute("UPDATE paper_accounts SET status=? WHERE id=62 "
"AND mode='shadow' AND strategy_type='portfolio'", (value,))
c.commit()
print("STATUS ->", value)
def main():
edit_only = len(sys.argv) > 1 and sys.argv[1] == "edit-only"
if not edit_only:
# 1. stop
_set_status("stopped")
t0 = time.time()
while time.time() - t0 < 150:
l, s = _children_alive()
if not l and not s:
print("HARVEST_DONE %.0fs" % (time.time() - t0))
break
time.sleep(10)
else:
print("HARVEST_TIMEOUT live21=%s shadow62=%s" % _children_alive())
raise SystemExit(1)
# 2. 安全闸 + 备份 + 删字段
l, s = _children_alive()
if l or s:
print("SAFETY_ABORT engines still alive", l, s)
raise SystemExit(1)
stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
for path in RUNTIMES:
bak = path + ".bak_reanchor_" + stamp
shutil.copy2(path, bak)
st = json.load(open(path, encoding="utf-8"))
strat = st.get("strategy") or {}
old = strat.pop("strategy_start_date", None)
st["strategy"] = strat
json.dump(st, open(path, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print("EDIT", path, "old_start=", old, "bak=", bak.split("\\")[-1])
if edit_only:
return
# 3. start
_set_status("running")
print("STARTED — 等 boot 后读新锚(post 锚=首个交易日事件日)")
if __name__ == "__main__":
main()
-41
View File
@@ -1,41 +0,0 @@
"""端到端冒烟:验证 read_db_daily 能读 NAS quant_trading.db(真实数据)。
在临时容器内跑(docker run --rm,挂载 /volume1/stock + /app)。
探测 vnpy 4.4.0 database 配置 + 验证数据读取。
"""
import sys
sys.path.insert(0, "/app")
print("=== Step 1: 探测 vnpy 4.4.0 database 配置 ===")
from vnpy.trader.setting import SETTINGS
db_keys = {k: v for k, v in SETTINGS.items() if "database" in k.lower() or "sql" in k.lower()}
print("database 相关 SETTINGS:", db_keys)
SETTINGS["database.driver"] = "sqlite"
SETTINGS["database.database"] = "/volume1/stock/sanguo_vnpy/data/quant_trading.db"
print(f"已设置: driver=sqlite, database={SETTINGS['database.database']}")
print("\n=== Step 2: 加载 sanguo_data 配置 ===")
from sanguo_data.config import load_config
cfg = load_config("config/data_platform.yaml")
print(f"cfg vnpy_db: {cfg.data_paths.get('vnpy_db')}")
print("\n=== Step 3: read_db_daily 读 600000 ===")
try:
from sanguo_data.datareader import read_db_daily
bars = read_db_daily("600000", "2026-01-01", "2026-06-30", cfg)
print(f"RESULT: 读取 {len(bars)} 条 BarData")
if bars:
b = bars[0]
print(f"FIRST: symbol={b.symbol} dt={b.datetime} close={b.close_price} vol={b.volume}")
else:
print("WARN: 0 条——可能 vnpy database 路径配置不对,或数据不在该 symbol/区间")
except Exception as e:
import traceback
traceback.print_exc()
print(f"FAIL: {type(e).__name__}: {e}")
print("\n=== Step 4: 直接探测 NAS db 文件 ===")
import os
db_path = "/volume1/stock/sanguo_vnpy/data/quant_trading.db"
print(f"{db_path} exists: {os.path.exists(db_path)}, size: {os.path.getsize(db_path) if os.path.exists(db_path) else 'N/A'}")
-43
View File
@@ -1,43 +0,0 @@
"""Phase 2 端到端冒烟:result_store + 因子 registry + API + data_adapter(真实数据)。"""
import sys
sys.path.insert(0, "/app")
sys.path.insert(0, "/app/vnpy_v4.4.0")
def main():
print("=== Phase 2 E2E Smoke ===")
import pandas as pd
# 1. result_store 增删查
from sanguo_backtest.result_store import save_result, load_result, BacktestResult
r = BacktestResult("smoke", "cta", "done", "Test", "600000", {"n": 5},
"2024-01-01", "2024-06-30", {"return": 0.1},
pd.DataFrame({"e": [1.0]}), pd.DataFrame(), None)
rid = save_result(r, db_path="/tmp/smoke.db", file_dir="/tmp")
print(f" result_store: id={rid}, strategy={load_result(rid, '/tmp/smoke.db').strategy}")
# 2. 因子 registry
import sanguo_factor.library
from sanguo_factor.registry import list_factors
print(f" factors: {len(list_factors())} total, {len(list_factors('builtin'))} builtin")
# 3. API 启动
from sanguo_api.app import create_app
app = create_app(db_path="/tmp/smoke.db", file_dir="/tmp")
routes = [r.path for r in app.routes if hasattr(r, "path")]
print(f" API routes: {len(routes)} loaded")
# 4. data_adapter(真实 A 股数据)
from sanguo_data.datareader import read_db_daily
from sanguo_data.config import load_config
from sanguo_factor.data_adapter import convert_bars_to_alpha_df
cfg = load_config("/app/config/data_platform.yaml")
bars = read_db_daily("600000", "2024-01-01", "2024-06-30", cfg)
df = convert_bars_to_alpha_df(bars)
print(f" data_adapter: {len(bars)} bars -> df height={df.height}, cols={df.columns[:5]}...")
print("=== Phase 2 Smoke DONE ===")
if __name__ == "__main__":
main()
-344
View File
@@ -1,344 +0,0 @@
"""Phase 3a 端到端冒烟:异步 submit + WS 阶段 + JWT + tears(容器)。"""
import sys
import os
import asyncio
import tempfile
import shutil
# Set up paths for both local and container environments
if os.path.exists("/app"):
sys.path.insert(0, "/app")
sys.path.insert(0, "/app/vnpy_v4.4.0")
print("=== Using container paths ===")
else:
# Local development environment
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, repo_root)
vnpy_path = os.path.join(repo_root, "vnpy_v4.4.0")
if vnpy_path not in sys.path:
sys.path.insert(0, vnpy_path)
print(f"=== Using local paths: {repo_root} ===")
def test_sys_path():
"""Test sys.path configuration"""
print("=== SYS.PATH CONFIG ===")
for i, p in enumerate(sys.path[:3]):
print(f" path[{i}]: {p}")
print("=== PASS: sys.path configured ===")
print()
def test_jwt_login():
"""Test JWT login endpoint"""
print("=== JWT LOGIN TEST ===")
try:
from fastapi.testclient import TestClient
from sanguo_api.app import create_app
from sanguo_api.auth import hash_password
# Create temporary directory for test
tmpdir = tempfile.mkdtemp()
db_path = os.path.join(tmpdir, "test.db")
file_dir = tmpdir
try:
# Generate real password hash using bcrypt directly
test_password = "password123"
password_hash = hash_password(test_password)
# Create app with auth config
app = create_app(
db_path=db_path,
file_dir=file_dir,
auth_config={
"username": "admin",
"password_hash": password_hash,
"jwt_secret": "test-secret",
"expire_minutes": 60
},
max_workers=1
)
# Test login with TestClient - REAL password verification
client = TestClient(app)
response = client.post("/api/v1/auth/login", json={
"username": "admin",
"password": test_password
})
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
data = response.json()
assert "token" in data, "Token not in response"
print(f" Login successful, token: {data['token'][:20]}...")
print("=== PASS: JWT login ===")
return data['token']
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
except Exception as e:
print(f" === FAIL: JWT login test failed: {type(e).__name__}: {e} ===")
import traceback
traceback.print_exc()
print("=== FAIL: JWT login ===")
return None
def test_protected_route_auth():
"""Test protected route authentication"""
print("=== PROTECTED ROUTE AUTH TEST ===")
try:
from fastapi.testclient import TestClient
from sanguo_api.app import create_app
from sanguo_api.auth import hash_password
tmpdir = tempfile.mkdtemp()
db_path = os.path.join(tmpdir, "test.db")
file_dir = tmpdir
try:
# Generate real password hash using bcrypt directly
test_password = "password123"
password_hash = hash_password(test_password)
app = create_app(
db_path=db_path,
file_dir=file_dir,
auth_config={
"username": "admin",
"password_hash": password_hash,
"jwt_secret": "test-secret",
"expire_minutes": 60
},
max_workers=1
)
client = TestClient(app)
# Test without token - should get 401
response = client.get("/api/v1/task/foo")
assert response.status_code == 401, f"Expected 401 without token, got {response.status_code}"
print(" No token: 401 Unauthorized ✓")
# Get valid token using REAL password verification
login_response = client.post("/api/v1/auth/login", json={
"username": "admin",
"password": test_password
})
token = login_response.json()["token"]
# Test with token but non-existent task - should get 404
response = client.get(
"/api/v1/task/foo",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 404, f"Expected 404 with valid token, got {response.status_code}"
print(" Valid token, non-existent task: 404 Not Found ✓")
print("=== PASS: protected route auth ===")
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
except Exception as e:
print(f" === FAIL: Protected route auth test failed: {type(e).__name__}: {e} ===")
import traceback
traceback.print_exc()
print("=== FAIL: protected route auth ===")
async def test_orchestrator_async():
"""Test orchestrator async submission"""
print("=== ORCHESTRATOR ASYNC TEST ===")
from sanguo_orchestrator.runner import Orchestrator
tmpdir = tempfile.mkdtemp()
db_path = os.path.join(tmpdir, "test.db")
try:
orch = Orchestrator(db_path=db_path, file_dir=tmpdir, max_workers=1)
# Check executor type
from concurrent.futures import ProcessPoolExecutor
executor_type = type(orch.pool.executor).__name__
print(f" Orchestrator pool executor: {executor_type}")
assert executor_type == "ProcessPoolExecutor", f"Expected ProcessPoolExecutor, got {executor_type}"
# Submit a tiny factor task
task_id = await orch.submit_factor(
symbols=["600000.SSE"],
factor_names=["ma5"],
start="2024-01-01",
end="2024-06-30",
cfg=None, # Use default config for smoke test
output_dir=tmpdir
)
print(f" Task submitted: {task_id}")
assert task_id.startswith("factor_"), f"Expected task_id to start with 'factor_', got {task_id}"
# Check task status
status = orch.get_status(task_id)
print(f" Task status: {status}")
print("=== PASS: orchestrator async ===")
return task_id
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
async def test_ws_stage_wiring():
"""Test WebSocket stage callback wiring"""
print("=== WS STAGE WIRING TEST ===")
from sanguo_api.app import create_app
from sanguo_api.ws import manager
from sanguo_api.routes import get_orchestrator
from sanguo_api.auth import hash_password
tmpdir = tempfile.mkdtemp()
db_path = os.path.join(tmpdir, "test.db")
try:
# Generate real password hash using bcrypt directly
test_password = "password123"
password_hash = hash_password(test_password)
# Create app to trigger wiring
app = create_app(
db_path=db_path,
file_dir=tmpdir,
auth_config={
"username": "admin",
"password_hash": password_hash,
"jwt_secret": "test-secret",
"expire_minutes": 60
},
max_workers=1
)
# Check orchestrator has on_stage callback set
orch = get_orchestrator()
assert orch._on_stage is not None, "on_stage callback not set"
print(" WS manager exists ✓")
print(" on_stage callback wired ✓")
print("=== PASS: WS stage wiring ===")
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
async def test_real_factor_tears_pipeline():
"""Test real factor tears pipeline with multiprocessing guard"""
print("=== REAL FACTOR TEARS PIPELINE TEST ===")
# Check if we're in container with full dependencies
try:
import polars
import alphalens
print(" Container environment detected (polars + alphalens available)")
except ImportError as e:
print(f" === SKIP: polars/alphalens not available ({e}) ===")
return
tmpdir = tempfile.mkdtemp()
output_dir = os.path.join(tmpdir, "factor_output")
try:
from sanguo_factor.analyzer import run_factor_analysis
from sanguo_data.config import load_config
# Load config for database access
if os.path.exists("/app/config/data_platform.yaml"):
cfg = load_config("/app/config/data_platform.yaml")
print(" Loaded database config")
else:
print(" === SKIP: database config not found ===")
return
# ≥2 symbols: alphalens IC is cross-sectional (1 symbol → empty bins → fails)
symbols = ["600000", "000001", "300750"] # BARE symbols (no .EXCHANGE suffix) for DB lookup
factor_names = ["ma5"] # Simple factor
start = "2024-01-01"
end = "2024-06-30" # Use longer range for reliable IC (Phase 1 confirmed 541 bars)
print(f" Running factor analysis: {symbols}, {factor_names}, {start} to {end}")
print(" This will test the multiprocessing pipeline...")
# Run the analysis
result = run_factor_analysis(
symbols=symbols,
factor_names=factor_names,
start=start,
end=end,
cfg=cfg,
output_dir=output_dir
)
print(f" Analysis complete: {len(result.factor_names)} factors processed")
print(f" IC summary: {result.ic_summary}")
print(f" Report paths: {result.report_paths}")
# Verify we got a result
assert result is not None, "Result is None"
assert len(result.factor_names) > 0, "No factors processed"
# CRITICAL: Verify IC is non-empty (Root cause C fix)
assert result.ic_summary, "ic_summary empty - no IC computed"
assert "ma5" in result.ic_summary, "ma5 not in ic_summary"
assert result.ic_summary["ma5"].get("ic"), "no IC data for ma5"
print(f" ✓ IC computed: {result.ic_summary['ma5']['ic']}")
# Check if any reports were generated
if result.report_paths:
print(f" Tears report generated: {result.report_paths}")
for factor_name, path in result.report_paths.items():
if os.path.exists(path.replace('.html', '.png')): # Check for actual PNG file
print(f" ✓ {factor_name}: {path}")
else:
print(f" ⚠ {factor_name}: {path} (file not found)")
print("=== PASS: real factor tears pipeline ===")
except Exception as e:
print(f" === FAIL: real tears pipeline failed with {type(e).__name__}: {e} ===")
import traceback
traceback.print_exc()
raise
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
async def main():
"""Run all Phase 3a smoke tests"""
print("\n")
print("=" * 60)
print("PHASE 3A SMOKE TEST SUITE")
print("=" * 60)
print()
# Test 1: sys.path configuration
test_sys_path()
# Test 2: JWT login
test_jwt_login()
# Test 3: Protected route authentication
test_protected_route_auth()
# Test 4: Orchestrator async submission
await test_orchestrator_async()
# Test 5: WebSocket stage wiring
await test_ws_stage_wiring()
# Test 6: Real factor tears pipeline (container only)
await test_real_factor_tears_pipeline()
print()
print("=" * 60)
print("PHASE 3A SMOKE TEST SUITE: ALL TESTS PASSED")
print("=" * 60)
print()
if __name__ == "__main__":
# CRITICAL: multiprocessing guard for vnpy.alpha
# This is required because vnpy.alpha's AlphaDataset.prepare_data() spawns
# a multiprocessing.Pool, which fails without this guard on spawn platforms
print("=== Running with multiprocessing guard ===")
asyncio.run(main())
-81
View File
@@ -1,81 +0,0 @@
#!/usr/bin/env python3
"""Phase 3b S1 end-to-end smoke.
Login -> submit CTA backtest (DoubleMaStrategy on 600000) -> poll status ->
verify the result-page endpoints (equity-curve / daily-pnl / trades / kline)
return non-empty data.
Runs from the Mac against the NAS container (http://192.168.2.154:8000).
No third-party deps (urllib only).
"""
import json
import sys
import time
import urllib.request
BASE = "http://192.168.2.154:8000"
def _request(method: str, path: str, token: str | None = None, body: dict | None = None) -> dict:
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
def main() -> int:
tok = _request("POST", "/api/v1/auth/login", body={"username": "admin", "password": "admin"})["token"]
print("[1] login OK")
sub = _request("POST", "/api/v1/backtest/cta", token=tok, body={
"symbol": "600000",
"strategy": "DoubleMaStrategy",
"params": {"fast_window": 10, "slow_window": 20, "fixed_size": 1},
"start": "2024-01-01",
"end": "2024-06-30",
})
tid = sub["task_id"]
print(f"[2] submitted: {tid}")
status = "pending"
for i in range(60):
s = _request("GET", f"/api/v1/task/{tid}", token=tok)
status = s["status"]
print(f" [{i:02d}] status={status} stage={s.get('stage', '')}")
if status in ("done", "failed"):
break
time.sleep(3)
if status != "done":
print(f"[!] backtest did not complete: {status}")
return 1
eq = _request("GET", f"/api/v1/task/{tid}/equity-curve", token=tok)
pnl = _request("GET", f"/api/v1/task/{tid}/daily-pnl", token=tok)
tr = _request("GET", f"/api/v1/task/{tid}/trades", token=tok)
kl = _request("GET", "/api/v1/kline?symbol=600000&start=2024-01-01&end=2024-06-30", token=tok)
n_eq = len(eq.get("equity_curve", []))
n_pnl = len(pnl.get("daily_pnl", []))
n_tr = len(tr.get("trades", []))
n_kl = len(kl.get("kline", []))
print(f"[3] equity={n_eq} pnl={n_pnl} trades={n_tr} kline={n_kl}")
assert n_eq > 0, "equity_curve empty"
assert n_kl > 0, "kline empty"
print("[4] SMOKE PASSED")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except AssertionError as e:
print(f"[SMOKE FAILED] {e}")
sys.exit(2)
except Exception as e:
print(f"[SMOKE ERROR] {type(e).__name__}: {e}")
sys.exit(3)
-71
View File
@@ -1,71 +0,0 @@
#!/usr/bin/env python3
"""Phase 3b S2 end-to-end smoke: login -> submit factor analysis (ma5,
multi-symbol) -> poll -> verify ic-summary non-empty.
"""
import json
import sys
import time
import urllib.request
BASE = "http://192.168.2.154:8000"
def _req(method, path, token=None, body=None):
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read())
def main() -> int:
tok = _req("POST", "/api/v1/auth/login", body={"username": "admin", "password": "admin"})["token"]
print("[1] login OK")
fl = _req("GET", "/api/v1/factor/list", token=tok)
print(f"[2] factors: {[f['name'] for f in fl['factors']]}")
sub = _req("POST", "/api/v1/factor/analyze", token=tok, body={
"symbols": ["600000", "000001", "300750"],
"factor_names": ["ma5"],
"start": "2024-01-01",
"end": "2024-06-30",
})
tid = sub["task_id"]
print(f"[3] submitted: {tid}")
status = "pending"
for i in range(60):
s = _req("GET", f"/api/v1/task/{tid}", token=tok)
status = s["status"]
print(f" [{i:02d}] status={status} stage={s.get('stage', '')}")
if status in ("done", "failed"):
break
time.sleep(3)
if status != "done":
print(f"[!] factor analysis did not complete: {status}")
return 1
ic = _req("GET", f"/api/v1/task/{tid}/ic-summary", token=tok)["ic_summary"]
print(f"[4] ic_summary keys: {list(ic.keys())}")
assert "ma5" in ic, "ma5 missing from ic_summary"
ma5 = ic["ma5"]
print(f" ma5 status: {ma5.get('status')}")
print(f" ma5 ic: {json.dumps(ma5.get('ic', {}), ensure_ascii=False)[:300]}")
assert ma5.get("ic"), "ma5 ic empty"
print("[5] SMOKE PASSED")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except AssertionError as e:
print(f"[SMOKE FAILED] {e}")
sys.exit(2)
except Exception as e:
print(f"[SMOKE ERROR] {type(e).__name__}: {e}")
sys.exit(3)
-67
View File
@@ -1,67 +0,0 @@
#!/usr/bin/env python3
"""Phase 3b S3 smoke: history list + parameter optimization end-to-end."""
import json
import sys
import time
import urllib.request
BASE = "http://192.168.2.154:8000"
def _req(method, path, token=None, body=None):
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read())
def main() -> int:
tok = _req("POST", "/api/v1/auth/login", body={"username": "admin", "password": "admin"})["token"]
tasks = _req("GET", "/api/v1/task", token=tok)["tasks"]
print(f"[history] {len(tasks)} tasks; latest: {tasks[0]['task_id'] if tasks else 'none'}")
assert isinstance(tasks, list)
sub = _req("POST", "/api/v1/backtest/optimize", token=tok, body={
"symbol": "600000",
"strategy": "DoubleMaStrategy",
"grid": {"fast_window": [5, 15, 5], "slow_window": [15, 25, 5]},
"start": "2024-01-01",
"end": "2024-06-30",
"max_workers": 2,
})
tid = sub["task_id"]
print(f"[optimize] submitted: {tid}")
status = "pending"
for i in range(60):
s = _req("GET", f"/api/v1/task/{tid}", token=tok)
status = s["status"]
print(f" [{i:02d}] {status} {s.get('stage', '')}")
if status in ("done", "failed"):
break
time.sleep(3)
if status != "done":
print(f"[!] optimize failed: {status}")
return 1
res = _req("GET", f"/api/v1/task/{tid}/optimization-results", token=tok)["results"]
print(f"[results] {len(res)} param combos")
assert len(res) > 0, "no optimization results"
for r in res[:3]:
print(f" params={r['params']} sharpe={r['statistics'].get('sharpe_ratio')}")
print("SMOKE PASSED")
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except AssertionError as e:
print(f"[FAILED] {e}"); sys.exit(2)
except Exception as e:
print(f"[ERROR] {type(e).__name__}: {e}"); sys.exit(3)
-49
View File
@@ -1,49 +0,0 @@
"""Phase 3c 模拟盘端到端冒烟(部署后跑)。
流程:login → POST /paper/create → GET /paper/{id} → /equity → /trades → /strategies
用法:python scripts/smoke_phase3c.py https://vnpy.mysanguo.top admin <password>
"""
import json
import sys
import urllib.request
def _req(method: str, base: str, path: str, token: str | None, body=None):
url = f"{base}/api/v1{path}"
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
def main() -> int:
base = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8000"
user = sys.argv[2] if len(sys.argv) > 2 else "admin"
pwd = sys.argv[3] if len(sys.argv) > 3 else "admin"
tok = _req("POST", base, "/auth/login", None, {"username": user, "password": pwd})["token"]
print("✓ login")
aid = _req("POST", base, "/paper/create", tok, {
"mode": "replay", "interval": "d", "symbols": ["600000"],
"strategies": [{"name": "DoubleMa", "symbol": "600000",
"match_session": "next_open"}],
"start": "2024-01-01", "end": "2024-06-30", "initial_capital": 1_000_000,
})["account_id"]
print(f"✓ create paper #{aid}")
assert _req("GET", base, f"/paper/{aid}", tok)["id"] == aid
print("✓ get paper")
assert isinstance(_req("GET", base, f"/paper/{aid}/equity", tok), list)
assert isinstance(_req("GET", base, f"/paper/{aid}/trades", tok), list)
assert isinstance(_req("GET", base, f"/paper/{aid}/strategies", tok), list)
print("✓ equity / trades / strategies")
print(f"\n✅ Phase 3c 冒烟通过(account #{aid})")
return 0
if __name__ == "__main__":
sys.exit(main())
-99
View File
@@ -1,99 +0,0 @@
"""S1: 验证 vnpy.alpha 能否处理 A 股数据(Phase 1 真实数据 → AlphaLab → 因子计算)。
验证链:
1. AlphaLab init
2. read_db_daily 读 A 股 bars(Phase 1)
3. save_bar_data + load_bar_data(存取兼容性)
4. 构造 panel df(datetime, vt_symbol, OHLCV)
5. AlphaDataset + add_feature(ma5) + prepare_data(spawn pool 因子计算)
6. fetch_raw(验证因子列生成)
A 股特性支撑度(T+1/涨跌停/停牌)在因子层层面:alpha 模块本身不强制这些约束,
由策略/回测层处理;因子计算只需 OHLCV,A 股数据格式无差异。
"""
import sys
import os
sys.path.insert(0, "/app/vnpy_v4.4.0")
sys.path.insert(0, "/app")
def main():
from vnpy.alpha.lab import AlphaLab
from vnpy.alpha.dataset import AlphaDataset, Segment
from vnpy.trader.constant import Interval
import polars as pl
results = {}
# 1. AlphaLab init
try:
lab = AlphaLab("/tmp/alpha_lab_spike")
results["alphalab_init"] = "OK"
except Exception as e:
results["alphalab_init"] = f"FAIL: {e}"
return _print(results)
# 2. read A 股
try:
from sanguo_data.datareader import read_db_daily
from sanguo_data.config import load_config
cfg = load_config("/app/config/data_platform.yaml")
bars = read_db_daily("600000", "2024-01-01", "2024-06-30", cfg)
results["read_bars"] = f"OK ({len(bars)} bars)" if bars else "FAIL: 0 bars"
if not bars:
return _print(results)
except Exception as e:
import traceback; traceback.print_exc()
results["read_bars"] = f"FAIL: {e}"
return _print(results)
# 3. save + load
try:
lab.save_bar_data(bars)
vt = bars[0].vt_symbol
loaded = lab.load_bar_data(vt, Interval.DAILY, "2024-01-01", "2024-06-30")
results["save_load"] = f"OK (vt={vt}, loaded={len(loaded)})"
except Exception as e:
import traceback; traceback.print_exc()
results["save_load"] = f"FAIL: {e}"
return _print(results)
# 4. panel df + AlphaDataset + add_feature + prepare + fetch
try:
df = pl.DataFrame({
"datetime": [b.datetime.replace(tzinfo=None) for b in bars],
"vt_symbol": [b.vt_symbol for b in bars],
"open": [b.open_price for b in bars],
"high": [b.high_price for b in bars],
"low": [b.low_price for b in bars],
"close": [b.close_price for b in bars],
"volume": [b.volume for b in bars],
"turnover": [b.turnover for b in bars],
"open_interest": [b.open_interest for b in bars],
})
ds = AlphaDataset(df, ("2024-01-01", "2024-04-30"),
("2024-05-01", "2024-05-15"),
("2024-05-16", "2024-06-30"))
ds.add_feature("ma5", "ts_mean(close, 5)")
ds.prepare_data(max_workers=1)
raw = ds.fetch_raw(Segment.TEST)
has_ma5 = "ma5" in raw.columns
results["factor_calc"] = f"OK (cols={raw.columns}, rows={raw.height}, ma5={has_ma5})"
except Exception as e:
import traceback; traceback.print_exc()
results["factor_calc"] = f"FAIL: {e}"
_print(results)
def _print(results):
print("=== S1 Spike Result ===")
for k, v in results.items():
print(f" {k}: {v}")
ok = all("OK" in v for v in results.values())
print(f"\nS1 VERDICT: {'PASS' if ok else 'PARTIAL/FAIL'}")
if __name__ == "__main__":
main()
-45
View File
@@ -1,45 +0,0 @@
"""S2: 验证 run_optimization 容器内多进程 API + spawn context。
vnpy 4.4.0 重构:run_optimization 是 BacktestingEngine 方法(非顶层函数)。
multiprocessing 用 get_context("spawn"),与 AlphaDataset.prepare_data 同机制
(S1 已证容器内 spawn pool 工作)。
"""
import sys
import os
sys.path.insert(0, "/app/vnpy_v4.4.0")
def main():
import inspect
results = {}
try:
from vnpy_ctastrategy.backtesting import (
BacktestingEngine, run_bf_optimization, run_ga_optimization, OptimizationSetting
)
results["import"] = "OK (vnpy_ctastrategy 1.4.1)"
results["engine.run_optimization"] = str(inspect.signature(BacktestingEngine.run_optimization))
results["run_bf_optimization"] = "OK (顶层 brute-force)"
results["OptimizationSetting"] = "OK (可构造)"
except Exception as e:
import traceback; traceback.print_exc()
results["import"] = f"FAIL: {e}"
# spawn context 确认
try:
import vnpy.trader.optimize as opt
src = inspect.getsource(opt)
results["multiprocessing"] = "spawn (get_context('spawn') + ProcessPoolExecutor/Pool)"
except Exception as e:
results["multiprocessing"] = f"FAIL: {e}"
print("=== S2 Spike Result ===")
for k, v in results.items():
print(f" {k}: {v}")
ok = "OK" in results.get("import", "")
print(f"\nS2 VERDICT: {'PASS' if ok else 'FAIL'}")
print("max_workers 推荐: 2 (NAS braswell 2 核, spawn 开销大, S1 单因子 11s)")
if __name__ == "__main__":
main()
-50
View File
@@ -1,50 +0,0 @@
"""S3: 验证 alphalens 与 vnpy_sqlite(peewee) 能否共存。
vnpy.alpha dataset/template.py:13 已 `from alphalens.utils import ...`,
若 alphalens(empyrical-reloaded 要 peewee<3.17.4) 与 vnpy_sqlite(peewee 4.1.1)
冲突,vnpy.alpha 将无法 import —— 这是因子层方案 A 的前置阻塞项。
"""
import sys
import os
_VNPY_SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "vnpy_v4.4.0"))
sys.path.insert(0, _VNPY_SRC)
def main():
results = {}
# 1. alphalens 能否 import
try:
from alphalens.utils import get_clean_factor_and_forward_returns
from alphalens.tears import create_full_tear_sheet
results["alphalens_import"] = "OK"
except Exception as e:
results["alphalens_import"] = f"FAIL: {type(e).__name__}: {e}"
# 2. vnpy_sqlite 能否 import(peewee)
try:
import peewee
results["peewee_version"] = peewee.__version__
from vnpy_sqlite.sqlite_database import SqliteDatabase
results["vnpy_sqlite_import"] = "OK"
except Exception as e:
results["vnpy_sqlite_import"] = f"FAIL: {type(e).__name__}: {e}"
# 3. alpha dataset 能否 import(同时拉 alphalens + vnpy)
try:
from vnpy.alpha.dataset import AlphaDataset
results["alpha_dataset_import"] = "OK"
except Exception as e:
results["alpha_dataset_import"] = f"FAIL: {type(e).__name__}: {e}"
print("=== S3 Spike Result ===")
for k, v in results.items():
print(f" {k}: {v}")
ok = all(v == "OK" or v.startswith("4") for v in results.values())
print(f"\nS3 VERDICT: {'PASS' if ok else 'FAIL/CONFLICT'}")
if __name__ == "__main__":
main()
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""双源(raw/qfq)部署验证:容器内读 NAS data_platform.yaml 配置的双源,
确认 fetch_day(C-S3 实走接口)+ iter_bars 在 raw_dir/qfq_dir 正确路由,
且除权日 raw 有缺口 / qfq 平滑(分红除权准确)。
用法(容器):
docker run --rm -v /volume1/stock:/volume1/stock \
-v /volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2:/app \
--entrypoint sh sanguo_vnpy_v2:with-sqlite \
-c "cd /app && python scripts/verify_dual_source.py"
"""
import sys
sys.path.insert(0, "/app")
from sanguo_data.config import load_config, find_config_path
from sanguo_trader.data_source import fetch_day, iter_bars
cfg = load_config(find_config_path())
print(f"config: {find_config_path()}")
print(f"data_paths: raw_dir={cfg.data_paths.get('raw_dir')} qfq_dir={cfg.data_paths.get('qfq_dir')}")
OK = True
# 1) fetch_day(C-S3 实走接口)除权日双源
print("\n=== fetch_day 除权日双源(浦发 2022-07-21 除权)===")
res = {}
for adj in ["qfq", "raw"]:
b = fetch_day("600000", "2022-07-21", "d", adj, cfg)
bp = fetch_day("600000", "2022-07-20", "d", adj, cfg)
if b is None or bp is None:
print(f" ❌ {adj}: fetch_day 返回 None(数据缺失或路由失败)")
OK = False
continue
drop = ((b.close_price / bp.close_price) - 1) * 100
res[adj] = drop
print(f" {adj}: 07-20={bp.close_price:.4f} → 07-21={b.close_price:.4f} ({drop:+.1f}%)")
# 除权日 raw 应有明显缺口(< -3%),qfq 平滑(> -3%)
if "raw" in res and "qfq" in res:
if res["raw"] < -3 and res["qfq"] > -3:
print(" ✅ 除权处理正确:raw 含缺口(撮合真实价)/ qfq 平滑(信号准)")
else:
print(f" ⚠️ 除权差异不明显 raw={res['raw']:.1f}% qfq={res['qfq']:.1f}%")
# 2) iter_bars cross-section(symbols=list)
print("\n=== iter_bars cross-section(4 天)===")
for adj in ["qfq", "raw"]:
days = list(iter_bars(["600000"], "2022-07-19", "2022-07-22", "d", adj, cfg))
if not days:
print(f" ❌ {adj}: iter_bars 0 days")
OK = False
else:
last = days[-1]
print(f" {adj}: {len(days)} days, 末日={last[0]} close={last[1]['600000'].close_price:.4f}")
# 3) 全市场可读性抽检(5 只)
print("\n=== 全市场抽检(5 只 fetch_day)===")
for sym in ["600000", "000001", "000858", "300750", "688981"]:
b = fetch_day(sym, "2026-07-07", "d", "qfq", cfg)
print(f" {sym}: {'✅ close='+format(b.close_price,'.4f') if b else '❌ None'}")
if b is None:
OK = False
print("\n" + ("✅ 双源部署验证通过" if OK else "❌ 双源验证有失败项"))
sys.exit(0 if OK else 1)
-39
View File
@@ -1,39 +0,0 @@
#!/usr/bin/env python3
"""Quick test to verify the real tears pipeline runs in container."""
import sys
import os
_VNPY_SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), "vnpy_v4.4.0"))
sys.path.insert(0, _VNPY_SRC)
from unittest.mock import Mock, MagicMock
from sanguo_factor.analyzer import run_factor_analysis
from sanguo_factor.registry import register_factor
# Register a simple test factor
register_factor("test_ma5", "ts_mean(close, 5)")
# Create minimal cfg mock
cfg = Mock()
cfg.data_paths = {"vnpy_db": "/tmp/test.db"}
try:
# Run with minimal data
result = run_factor_analysis(
symbols=["600000.SH"], # Single symbol
factor_names=["test_ma5"],
start="2024-01-01",
end="2024-01-31", # Small date range
cfg=cfg,
output_dir="/tmp/test_tears"
)
print(f"Test completed successfully!")
print(f"Result: {result}")
print(f"IC Summary: {result.ic_summary}")
print(f"Report Path: {result.report_path}")
except Exception as e:
print(f"Test failed with error: {e}")
import traceback
traceback.print_exc()
sys.exit(1)