feat(deploy): VPS原生大脑部署—env覆盖配置+部署文档(Option B落地)
- 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)通过。详见文档。
This commit is contained in:
@@ -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) | `<BRIDGE_TOKEN>` |
|
||||
|
||||
不设这些 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。
|
||||
+2
-2
@@ -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 = {
|
||||
|
||||
+25
-2
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user