# 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`。