diff --git a/docs/deployment/vps-unified-deploy.md b/docs/deployment/vps-unified-deploy.md new file mode 100644 index 0000000..03c925d --- /dev/null +++ b/docs/deployment/vps-unified-deploy.md @@ -0,0 +1,173 @@ +# VPS 统一部署(全量权威文档) + +> **状态:2026-07-17,端到端验证通过。** 本文档覆盖当前生产真实状态,**取代** `vps-native-brain.md`(07-15,架构已变)。 +> 所有服务统一到 VPS;NAS 降为备份;Mac mini 仅开发。 + +## 1. 三机角色(定稿) + +| 机器 | IP / 角色 | 跑什么 | +|------|-----------|--------| +| **北京 VPS**(生产) | `49.232.102.198` Windows Server 2022,4C16G/180G,腾讯云**境内** | sanguo_api + 前端 dist + miniQMT + 进程内 qmt_gateway_client + 权威 DB + 数据采集 schtasks | +| **首尔 VPS**(HTTPS 入口) | `43.133.235.218` Ubuntu,腾讯云**境外** | Caddy 签 Let's Encrypt 证书(境外免备案)+ 反代到北京:8000 | +| **NAS** | `192.168.2.154` Synology | 纯备份(xtdata zip 历史库);sanguo_api 容器**已停用** | +| **Mac mini** | 开发机 | 改代码、构建前端、rsync/scp 部署;采集/同步 cron **已停用** | + +> **为什么 HTTPS 入口在首尔**:北京(境内)用未备案域名 Host 走 80/443 会被腾讯**webblock**(见 §6)。首尔境外无需 ICP 备案,Caddy 自动签 LE 证书。北京只暴露 8000(API),由首尔反代。 + +## 2. 网络入口与域名 + +- **域名**:`vnpy.mysanguo.top` → A 记录指向**首尔** `43.133.235.218`(NameSilo/dnsowl 托管)。 +- **访问**:`https://vnpy.mysanguo.top`(Mac 浏览器;ClashX 关闭或在 `/etc/hosts` 钉 `43.133.235.218 vnpy.mysanguo.top` 绕 fake-ip)。 +- **北京防火墙**:已放行 22 / 80 / 443 / 3389 / 8000。 + +### 首尔 Caddyfile(vnpy block,关键:`header_up Host`) + +```caddyfile +vnpy.mysanguo.top { + reverse_proxy 49.232.102.198:8000 { + header_up Host 49.232.102.198:8000 + } +} +``` + +> **`header_up Host` 是 webblock 修复的核心**:不加它,Caddy 会透传 `Host: vnpy.mysanguo.top` 给北京:8000,腾讯基于该未备案域名 Host 对 **GET 请求** webblock(HEAD/POST 放行,故早先 login 能通、GET / 被拦)。改成北京 IP 后 Host 不带域名,北京不拦。备份在首尔 `/etc/caddy/Caddyfile.bak0716`。 +> 北京本机的 `sanguo-caddy` schtasks **已 Disabled**——入口是首尔,北京不直接serve web。 + +## 3. 北京 VPS 安装清单(`C:\sanguo_vnpy_v2\`) + +| 组件 | 来源 | 位置 | +|------|------|------| +| vnpy 4.4.0 源码 | 项目内 `vnpy_v4.4.0/`(纯 Python 零 C 扩展) | `C:\sanguo_vnpy_v2\vnpy_v4.4.0\`(`sys.path`/`PYTHONPATH` 引用,**不 pip install**)| +| sanguo_* 大脑包 | 项目 scp | `C:\sanguo_vnpy_v2\sanguo_*\` | +| Python 3.10 | 华为镜像 | `C:\Python310\python.exe` | +| vnpy-qmt 0.3.3(vendor 自维护)/ vnpy_ctastrategy 1.4.1 / vnpy_sqlite 1.1.3 / TA-Lib 0.6.8 / empyrical 0.5.5 / pandas / numpy / fastapi / uvicorn / apscheduler | pip(阿里镜像)| site-packages | +| 前端 dist | Mac 构建 scp | `C:\sanguo_vnpy_v2\frontend\dist\` | + +pip 镜像:`pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/`(写 `C:\Users\Administrator\AppData\Roaming\pip\pip.ini`)。 + +## 4. 数据(DB 为主,parquet 兜底) + +- **权威 DB**:`C:\sanguo_vnpy_v2\data\quant_trading.db`(vnpy sqlite,表 `dbbardata`/`dbbaroverview`)。 + - 个股日线:**13,484,621 行 / 5201 只 / 2010-01-04 ~ 2026-07-16**。 + - 指数:`000300`(沪深300)/`399001`(深证成指)/`399006`(创业板指)/`000905`(中证500) 全在 DB。 +- **回测结果库**:`C:\sanguo_vnpy_v2\data\backtest_results.db`(表 `backtest_stats`)。 +- **结果文件**:`C:\sanguo_vnpy_v2\data\{task_id}_{equity,trades,metrics}.json` + `{task_id}.log`(`file_dir = dirname(db_path)`)。 +- **读取口径**(`sanguo_data/datareader.py`): + - `read_db_daily`(个股)→ vnpy `load_bar_data`(`guess_exchange` 判交易所)。 + - `read_index_daily`(指数/基准)→ **从 DB 读**(前缀解析交易所:`sh→SSE`/`sz→SZSE`,避免 `guess_exchange` 把 000300 误判 SZSE)。返回 `pd.DataFrame`。parquet 仅备份不再读。 +- **灌库**:`scripts/data_platform/import_vnpy_daily_fast.py`(parquet → DB,向量化,`INSERT OR REPLACE`)。env `VNPY_DB_PATH`/`DAILY_DIR`(Windows 用正斜杠 `C:/...`)。日线增量走 `daily_update_xtdata.py`(xtdata 本地缓存,零漂移)。 +- **用户铁律**:下载质量不可控,**绝不直接写主库**——staging 隔离→验证→合并。 + +## 5. 后端服务 sanguo_api + +- **进程**:`schtasks sanguo-api`(SYSTEM,开机自启)→ `C:\sanguo_vnpy_v2\run_api.ps1`。 +- **端口**:8000(`/api/v1/*` + `/docs` + SPA `/`)。 +- **日志**:`C:\Users\Administrator\sanguo_api.log`(**找 worker 异常/traceback 看 here**)。 +- **回测执行**:`Orchestrator`(`sanguo_orchestrator/runner.py`)+ `ProcessPoolExecutor`(Windows spawn)。提交 → pool worker 跑 `run_cta_backtest` → 结果存 DB + JSON。状态走 WebSocket + 轮询 `/task/{id}`。 + +### env(`run_api.ps1`,保 git 真相,不改 yaml) + +```powershell +$env:PYTHONUTF8 = "1" +$env:PYTHONPATH = "C:\sanguo_vnpy_v2;C:\sanguo_vnpy_v2\vnpy_v4.4.0" +$env:SANGUO_DATA_ROOT = "C:\sanguo_vnpy_v2\data" # 重映射 daily/raw/qfq/15min/vnpy_db +$env:SANGUO_DB_PATH = "C:\sanguo_vnpy_v2\data\backtest_results.db" +$env:SANGUO_FILE_DIR = "C:\sanguo_vnpy_v2\data\backtest_files" +$env:SANGUO_LIVE_ENABLED = "true" +$env:SANGUO_BRIDGE_URL = "http://127.0.0.1:8765" # 兼容留,进程内不用 +$env:BRIDGE_TOKEN = "<见 windows-vps-access 记忆>" +$env:SANGUO_USE_QMT_GATEWAY = "1" # 进程内 qmt_gateway_client 直连 miniQMT +$env:SANGUO_QMT_ACCOUNT = "66639661" +# SPA_STATIC_DIR 未设 → 默认 repo/frontend/dist = C:\sanguo_vnpy_v2\frontend\dist +``` + +不设这些 env(NAS/Mac)则用 `config/backtest.yaml` 原值 → 三机共用同一份 git config。 + +## 6. 前端(Vue SPA) + +- **同源相对路径**:`frontend/src/api/client.ts` 用 `/api/v1`,WebSocket 自适配协议/host → Caddy 反代即打通,无需 build 时配 host。 +- **构建**:Mac 上 `cd frontend && npm run build` → `frontend/dist/`。 +- **部署**:`scp -i ~/.ssh/id_ed25519 -r frontend/dist/* Administrator@49.232.102.198:C:/sanguo_vnpy_v2/frontend/dist/`。 +- **挂载**:`sanguo_api/main.py::build_app` 把 `static_dir` 挂载在 `/`(history fallback,深链刷新可用)。 +- **结果页**:`Result.vue` 用 `Promise.allSettled` 隔离 7 个接口(benchmark/risk 缺数据不拖垮整页);时间范围筛选前端本地过滤。 + +## 7. 实盘链路(miniQMT,进程内直连) + +- miniQMT 模拟账户 `66639661`(`C:\国金QMT交易端模拟\`,XtMiniQmt.exe 常驻)。 +- **进程内 `qmt_gateway_client`**(commit 992f53d):sanguo_trader 同进程直连 miniQMT(`connect=0` 成功),替 HTTP bridge。 +- **bridge 已废弃**(`sanguo-bridge` schtasks = Plan B,仅 miniQMT 权限被收回/换券商/跨机时翻出,见记忆 `bigqmt-rpc-bridge-fallback`)。 +- 实盘走影子模式(sanguo 影子下单 + xtquant 独立脚本),详见记忆 `d-phase-mock-trading-link`。 + +## 8. 回测(CTA + 优化 + 历史) + +- **A股适配**:`sanguo_backtest/ashare_engine.py`(`AShareBacktestingEngine` 子类化修复 vnpy 失真:1股定寸 `size=N`/拒做空/费用)。记忆 `backtest-engine-ashare-adapter`。 +- **指标**:`sanguo_backtest/metrics.py::compute_metrics`(empyrical,聚宽同源)。**关键修复**:导入 empyrical 前补回 NumPy 2.0 移除的别名,否则 `sortino_ratio` 崩 `np.NINF`: + ```python + for _a,_v in (("NINF",-np.inf),("Inf",np.inf),("PINF",np.inf),("NaN",np.nan),("NAN",np.nan),("infty",np.inf)): + if not hasattr(np,_a): setattr(np,_a,_v) + ``` + > 不修则 compute_metrics 静默崩 → `_metrics.json` 不生成 → 结果页回退 vnpy 原始字段(单位混乱)→ 显示 3305% 收益 / -5000万% 回撤 / 无图表。记忆 `empyrical-numpy2-npinf-crash`。 +- **结果页 7 端点**:`/result`(statistics+relative_metrics)/`/benchmark-curve`/`/risk-series`/`/equity-curve`/`/daily-pnl`/`/trades`/`/log`。benchmark/risk 缺 metrics 文件时返空 200(不再 404 拖垮整页)。 +- **基准**:`hs300`(sh000300) / `zz500`(sz000905),从 DB 读。 +- **老任务回填**:`backfill_metrics.py`(本地 `/tmp`,按需移 `scripts/`)从 `_equity.json`+基准重算 compute_metrics,UPDATE statistics + 补 `_metrics.json`。 + +## 9. 部署流程(Mac 开发 → VPS 生产) + +```bash +# 1) 改代码(Mac ~/.openclaw/sanguo_projects/sanguo_vnpy_v2) +# 2) 前端构建 +cd frontend && npm run build +# 3) 同步后端 + 前端到 VPS +scp -i ~/.ssh/id_ed25519 -r frontend/dist/* Administrator@49.232.102.198:C:/sanguo_vnpy_v2/frontend/dist/ +scp -i ~/.ssh/id_ed25519 sanguo_backtest/metrics.py Administrator@49.232.102.198:C:/sanguo_vnpy_v2/sanguo_backtest/metrics.py +# (其余改动的 .py 同理 scp 到对应路径) +# 4) 重启 API(让新代码 + 新 worker 生效) +ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "schtasks /end /tn sanguo-api & schtasks /run /tn sanguo-api" +``` + +## 10. 运维速查 + +```bash +# SSH / scp / RDP +ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 +scp -i ~/.ssh/id_ed25519 本地文件 Administrator@49.232.102.198:C:/目标 +# RDP 49.232.102.198:3389 (Administrator + 腾讯云控制台密码) + +# 跑命令的坑(重要) +# - Python 一律 C:\Python310\python.exe -X utf8(emoji/中文不加 -X utf8 会 UnicodeEncodeError) +# - 复杂命令用 stdin 脚本:ssh ... "C:\Python310\python.exe -X utf8 -" < /tmp/script.py +# - Windows 路径在 ssh stdin 里用正斜杠 C:/...(\raw 等 \r 转义会炸) +# - .ps1 含中文路径用搜索代替字面量 + +# 服务管理 +ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "schtasks /end /tn sanguo-api & schtasks /run /tn sanguo-api" + +# API 日志(worker traceback / 启动报错) +ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 "powershell -Command \"Get-Content C:\Users\Administrator\sanguo_api.log -Tail 50\"" +``` + +## 11. 已知坑 + +| 坑 | 现象 | 解法 | +|----|------|------| +| **境内未备案 webblock** | GET vnpy.mysanguo.top 返腾讯备案页 | 入口走首尔境外 + Caddy `header_up Host <北京IP>` | +| **empyrical×numpy2.0** | 结果页垃圾值(3305%)+无图 | metrics.py 补回 np 别名(§8)| +| **SSH 密集连接触发限连/fail2ban** | `Connection closed by 49.232.102.198 port 22` | 等 ~30-60s 冷却;长脚本用 detached(`Start-Process`) + 轮询文件;或 Monitor 稀疏 20s 轮询 | +| **回测静默吞异常** | 指标崩被 `except` 吞,显示垃圾不报错 | cta_engine metrics 块已加 `traceback.format_exc()` 到 warning(见 sanguo_api.log)| +| **ProcessPool worker 缓存旧代码** | 改 .py 后 worker 仍旧行为 | `schtasks /end & /run sanguo-api` 重启(spawn 新 worker)| + +## 12. 验证记录(2026-07-17,浏览器端到端) + +- `https://vnpy.mysanguo.top` 登录(admin)→ 前端加载 ✓ +- CTA 回测 DoubleMa 600000:统计正确(33.06%/-29.58%/Alpha9.30%/Beta0.524/Sortino1.02) + 5 图表渲染 + 成交明细 ✓ +- zz500 基准切换:benchmark_return 80.3%(≠ hs300 36.7%)✓ +- 参数优化、BollChannel 策略:跑通 ✓ +- K线 / 每日收益(485点) / 日志:有数据 ✓ +- 老任务回填:8 个有交易的修复(含历史垃圾值任务)✓ +- 实盘链路:进程内 miniQMT connect=0 ✓(见 §7) + +## 13. 相关文档与记忆 + +- 旧版(已取代):`vps-native-brain.md`(07-15)、`nas-deploy-plan.md`、`docker-deployment.md`。 +- 数据:`data-download.md`、`env-version-matrix.md`。 +- 记忆:`windows-vps-access`、`empyrical-numpy2-npinf-crash`、`backtest-engine-ashare-adapter`、`db-primary-parquet-fallback`、`bigqmt-rpc-bridge-fallback`、`d-phase-mock-trading-link`、`dep-baseline-locked`。