From 3ef8c617f07556134ad2aec15c82a6ac135e0192 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Wed, 15 Jul 2026 12:55:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20VPS=E5=8E=9F=E7=94=9F=E5=A4=A7?= =?UTF-8?q?=E8=84=91=E9=83=A8=E7=BD=B2=E2=80=94env=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E9=85=8D=E7=BD=AE+=E9=83=A8=E7=BD=B2=E6=96=87=E6=A1=A3(Option?= =?UTF-8?q?=20B=E8=90=BD=E5=9C=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sanguo_data/config.py: load_config 加 env 覆盖(SANGUO_DATA_ROOT 重映射 daily/raw/qfq/15min/vnpy_db;SANGUO_LIVE_ENABLED/SANGUO_BRIDGE_URL 覆盖 live 段), 三机共用一份 git config 零漂移 - sanguo_api/main.py: build_app 加 SANGUO_DB_PATH/SANGUO_FILE_PATH env 覆盖 - docs/deployment/vps-native-brain.md: VPS原生部署全记录(安装/配置/服务/验证/ 数据staging/Phase2暂缓理由) 实证:VPS vnpy4.4.0+vnpy_qmt0.3.3 连真实miniQMT读真实账户/持仓; live_step全链路+影子transport(本机bridge)通过。详见文档。 --- docs/deployment/vps-native-brain.md | 88 +++++++++++++++++++++++++++++ sanguo_api/main.py | 4 +- sanguo_data/config.py | 27 ++++++++- 3 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 docs/deployment/vps-native-brain.md diff --git a/docs/deployment/vps-native-brain.md b/docs/deployment/vps-native-brain.md new file mode 100644 index 0000000..8f199c6 --- /dev/null +++ b/docs/deployment/vps-native-brain.md @@ -0,0 +1,88 @@ +# VPS 原生大脑部署(Option B:vnpy native,无 docker) + +2026-07-15 落地。生产大脑从 NAS 迁到 VPS Windows 宿主**原生运行**(非容器), +vnpy 4.4.0 源码引用 + vnpy_qmt 进程内直连 miniQMT(同机同会话)。三机角色切分: +**VPS=实盘生产 / Mac=开发 / NAS=研究·回测·测试备份**(NAS `live.enabled=false`)。 + +## 头号风险已实证解除 + +vnpy_qmt 0.3.3 只测过 vnpy 3.5;本项目 vnpy 4.4.0。逐符号核对全部 vnpy import +(BaseGateway / OrderData / Status 枚举成员 / ZoneInfo 等)对 4.4.0 源码**零漂移**; +VPS 真机 `from vnpy_qmt import QmtGateway` import+实例化通过,**连真实 miniQMT 读到真实 +账户(66639661 余额 9999998.51)+ 持仓(600000.SSE 200股 / 000001.SZSE 100股,与 bridge +完全一致)+ 7551 合约**。无需改 gateway 代码。 + +## VPS 安装清单(C:\sanguo_vnpy_v2\) + +| 组件 | 来源 | 位置 | +|------|------|------| +| vnpy 4.4.0 源码 | 项目内 `vnpy_v4.4.0/`(纯 Python,零 C 扩展,scp) | `C:\sanguo_vnpy_v2\vnpy_v4.4.0\` | +| sanguo_* 大脑包 | 项目 tar(scp) | `C:\sanguo_vnpy_v2\sanguo_*\` | +| vnpy-qmt 0.3.3 | pip(阿里镜像) | site-packages | +| TA-Lib 0.6.8 | pip(self-contained wheel) | site-packages | +| vnpy_ctastrategy 1.4.1 / vnpy_sqlite 1.1.3 | pip | site-packages | +| pyarrow 24.0.0 / pandas / numpy / fastapi / uvicorn / sqlalchemy / apscheduler / auth(web) | pip | site-packages | +| 数据(策略标的 raw/qfq 日线 parquet) | NAS 同步→VPS | `C:\sanguo_vnpy_v2\data\{raw,qfq}\{year}\` | + +> **pip 镜像必配**:VPS 默认直连 pypi.org(从国内极慢/超时)。已配 +> `pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/`(写入 +> `C:\Users\Administrator\AppData\Roaming\pip\pip.ini`)。 + +## 配置本地化(env 覆盖,保 git 真相) + +不改部署态 yaml,全部走环境变量(代码见 `sanguo_data/config.py::load_config` + +`sanguo_api/main.py::build_app`): + +| env | 作用 | VPS 值 | +|-----|------|--------| +| `SANGUO_DATA_ROOT` | 重映射 daily/raw/qfq/15min*/vnpy_db 到该根下 | `C:\sanguo_vnpy_v2\data` | +| `SANGUO_DB_PATH` | API 的 db_path(paper_* 表+回测结果) | `C:\sanguo_vnpy_v2\data\backtest_results.db` | +| `SANGUO_LIVE_ENABLED` | live 总开关 | `true` | +| `SANGUO_BRIDGE_URL` | 影子下单 bridge 地址(**本机**,不再走 Mac tunnel) | `http://127.0.0.1:8765` | +| `BRIDGE_TOKEN` | bridge 鉴权(`live_orchestrator` 已有 env fallback) | `` | + +不设这些 env(NAS/Mac)则用 yaml 原值 → 三机共用同一份 git config,零漂移。 + +## 服务(schtasks,脱离 ssh 持久,开机自启) + +| 任务 | 命令 | 端口 | +|------|------|------| +| `sanguo-api` | `powershell -File C:\sanguo_vnpy_v2\run_api.ps1`(设 env + `run_web.py`) | 8000(`/docs`、`/api/v1/*`) | +| `sanguo-bridge` | `C:\run_bridge.ps1`(xtquant→miniQMT 执行通道) | 8765 | +| `sanguo-caddy` | `C:\run_caddy.ps1`(反代 bridge.mysanguo.top) | 80/443 | + +管理(同 bridge): +```bash +ssh -i ~/.ssh/id_ed25519 Administrator@49.232.102.198 \ + "schtasks /end /tn sanguo-api; schtasks /run /tn sanguo-api" +``` + +## 验证记录(2026-07-15,全部通过) + +1. `import sanguo_api.main` + `create_app()` — 大脑原生可导入。 +2. schtasks sanguo-api 常驻,`:8000/docs` → 200。 +3. DB 自动建库(init_db 幂等),6 张 paper_* 表齐。 +4. **live_step(2026-07-13)全链路**:warmup 重放 2024-01-01→07-12 → 读 raw/qfq bar + (pyarrow)→ PaperEngine.step → 存 daily_balance(cash=1M/equity=1M)。当日 DoubleMa + 无交叉信号→无交易(正常)。 +5. **影子 transport**:brain 的 `BridgeClient(localhost:8765).get_account/get_positions` + 读到真实 miniQMT 账户/持仓 → brain→本机 bridge→miniQMT transport 通(POST /order 同 + transport,策略出信号即镜像)。 + +## 数据 staging(NAS→VPS) + +NAS 是数据权威(baostock 5.5 年干净数据)。策略标的的 raw+qfq 日线 parquet 从 NAS +rsync 到 Mac 再 scp 到 VPS(`{raw,qfq}/{year}/sh{sym}_daily.parquet`,每股全历史 ~200KB)。 +当前已 staging:600000(2021-2026)。新增标的照此同步。 + +> 光猫拦 NAS→VPS,故走 NAS→Mac→VPS 两跳。Mac tunnel(旧 NAS→VPS bridge 通道)**已废弃** +> (brain 在 VPS 本机,不再需要)。 + +## Phase 2(vnpy_qmt 进程内换 bridge)—— 暂缓 + +用户初衷"vnpy_qmt 直接对接 miniQMT、bridge 废弃"针对的是 **bridge 跨机依赖**(Mac tunnel)。 +现在 brain+bridge **同机 VPS**(localhost),跨机问题已消除,bridge 退化为本机执行适配器 +(仍 vnpy 原生、wrap xtquant)。进程内换 vnpy_qmt 的收益仅是少一个本地进程 + 一个 localhost +HTTP 跳(延迟可忽略),代价是要写 async→sync 适配器 + 把 xtquant 生命周期塞进 brain 进程 +(xtquant 崩溃会拖垮 brain,反而损失进程隔离)。**故暂缓**,bridge 保留为本机执行通道。 +待 bridge 本机化成为实测瓶颈再重启 Phase 2。 diff --git a/sanguo_api/main.py b/sanguo_api/main.py index ba81c35..75a5129 100644 --- a/sanguo_api/main.py +++ b/sanguo_api/main.py @@ -42,8 +42,8 @@ def build_app(config_path: str = "config/backtest.yaml", static_dir: str | None auth = cfg.get("auth", {}) pool = cfg.get("pool", {}) - db_path = bt.get("db_path", "/tmp/backtest_results.db") - file_dir = bt.get("file_dir", "/tmp/backtest_files") + db_path = os.environ.get("SANGUO_DB_PATH") or bt.get("db_path", "/tmp/backtest_results.db") + file_dir = os.environ.get("SANGUO_FILE_DIR") or bt.get("file_dir", "/tmp/backtest_files") auth_config = None if auth: auth_config = { diff --git a/sanguo_data/config.py b/sanguo_data/config.py index 8f2b80c..4651788 100644 --- a/sanguo_data/config.py +++ b/sanguo_data/config.py @@ -24,13 +24,36 @@ def load_config(path: str) -> DataConfig: if not raw: raise ValueError(f"配置文件为空: {path}") + data_paths = dict(raw.get("data_paths", {})) + # 部署环境路径覆盖(VPS native 等):SANGUO_DATA_ROOT 设定时,*_dir 与 vnpy_db + # 重映射到该根下,保持 datareader 期望的叶子结构(daily/raw/qfq/15min*/quant_trading.db)。 + # 不设则用 yaml 原值(NAS/Mac)。避免改部署态配置文件,保 git 真相。 + root = os.environ.get("SANGUO_DATA_ROOT") + if root: + _leaf = {"daily_dir": "daily", "raw_dir": "raw", "qfq_dir": "qfq", + "minute_15_dir": "15min", "minute_15_qfq_dir": "15min_qfq", + "minute_15_raw_dir": "15min_raw"} + for _k, _suf in _leaf.items(): + if _k in data_paths: + data_paths[_k] = os.path.join(root, _suf) + if "vnpy_db" in data_paths: + data_paths["vnpy_db"] = os.path.join(root, "quant_trading.db") + + live = raw.get("live") + if isinstance(live, dict): + live = dict(live) + if os.environ.get("SANGUO_LIVE_ENABLED"): + live["enabled"] = os.environ["SANGUO_LIVE_ENABLED"].strip().lower() in ("1", "true", "yes", "on") + if os.environ.get("SANGUO_BRIDGE_URL"): + live["bridge_url"] = os.environ["SANGUO_BRIDGE_URL"].strip() + return DataConfig( - data_paths=raw.get("data_paths", {}), + data_paths=data_paths, data_sources=raw.get("data_sources", {}), validation=raw.get("validation", {}), performance=raw.get("performance", {}), risk_free_rate=float(raw.get("risk_free_rate", 0.02)), - live=raw.get("live"), + live=live, )