12 KiB
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)
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-caddyschtasks 已 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(个股)→ vnpyload_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)。envVNPY_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)
$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-bridgeschtasks = 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: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 生产)
# 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. 运维速查
# 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。