Files
sanguo_vnpy/deploy/nas/deploy-plan.md
T
2026-04-29 20:14:54 +08:00

14 KiB
Raw Blame History

群晖NAS部署sanguo_vnpy回测服务方案

编制人:姜维 伯约
日期2026-04-28(第3轮修订)
状态:调研完成,待实施


一、问题复现与根因分析

1.1 现象

从局域网任意机器访问:

  • curl http://192.168.2.154:8888/ 302Jupyter正常)
  • curl http://192.168.2.154:8088/api/backtest/health Connection refused

1.2 根因(5重问题)

# 问题 严重度 影响
1 entrypoint.sh 未启动 backtest-service 🔴 阻断 容器内8088端口根本无进程监听
2 目录名 backtest-service 含连字符,不能做Python包 🔴 阻断 无法用 python -muvicorn backtest_service.main:app 启动
3 models.py 使用 ApiResponse[T](BaseModel) 语法 🔴 阻断 Python 3.12+语法,容器内Python 3.10不支持
4 executor.py 使用 vnpy 3.x API,与容器内vnpy 4.3.0不兼容 🔴 阻断 3个import失败 + Interval枚举值缺失 + API签名变化
5 main.py 的 uvicorn.run("main:app") 路径错误 🟡 修复 uvicorn重导入时相对导入失败

1.3 根因4详情:vnpy 3.x → 4.x API变更

executor.py 是vnpy深度集成代码,以下import在vnpy 4.3.0下全部失败:

executor.py (3.x写法) 4.x正确写法 状态
from vnpy.trader.event import EventEngine from vnpy.event import EventEngine 可修复
from vnpy.trader.backtesting import BacktestingEngine from vnpy_ctastrategy.backtesting import BacktestingEngine 可修复(需安装包)
from vnpy.trader.database import database_manager from vnpy.trader.database import get_database 可修复(需安装vnpy_sqlite)
from vnpy.trader.strategy import Strategy from vnpy_ctastrategy import CtaStrategyApp ⚠️ 需确认
Interval.FIVE_MINUTE 等细分枚举 4.x只有 MINUTE/HOUR/DAILY/WEEKLY/TICK ⚠️ API设计需调整
BacktestingEngine.set_parameters(data=...) 4.x API签名可能变化 ⚠️ 需逐个验证

结论executor.py与vnpy 4.x的API差异过大,不适合逐行修补,建议重写executor.py适配vnpy 4.x

1.4 已排除项

  • 端口映射问题:iptables DNAT 8088→172.17.0.2:8088 正确
  • DSM防火墙问题:Jupyter使用同模式可访问
  • Docker网络问题:bridge模式正常工作
  • 进程崩溃问题:服务从未启动过,不存在崩溃

二、现有资源盘点

2.1 NAS硬件

项目 规格
型号 DS216+II
CPU Intel Celeron N3060 @ 1.60GHz (2核)
内存 8GB (可用约6.4GB)
磁盘 3.5TB总量, 1.6TB可用
Docker 20.10.3 (需sudo)

2.2 容器内环境

容器ID:    8fc55af3d27d
镜像:      sanguo_vnpy:with-scripts
Python:    3.10.20
vnpy:      4.3.0(核心包)
vnpy相关:  vnpy_webtrader 1.1.0(已安装)
缺失包:    vnpy_ctastrategy, vnpy_sqlite(已手动验证可安装)
运行服务:  Jupyter Lab(8888), SSH(22) ✅
未运行:    backtest-service(8088) ❌

2.3 数据资源(NAS SMB共享)

/Volumes/stock/A股数据/日线数据/   — 日线历史行情
/Volumes/stock/A股数据/分钟线数据/ — 分钟线数据
/Volumes/stock/A股数据/财务数据/   — 财务数据

三、部署方案

3.1 架构图

┌─────────────────────────────────────────────────────────────────┐
│                    群晖 NAS 192.168.2.154                       │
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │           Docker Container: sanguo_vnpy                  │   │
│  │                                                          │   │
│  │  entrypoint.sh                                           │   │
│  │  ├── sshd -D                    (22)   ──→ 2222    ✅已有 │   │
│  │  ├── jupyter-lab               (8888) ──→ 8888    ✅已有 │   │
│  │  └── uvicorn backtest_service (8088) ──→ 8088    🆕新增  │   │
│  │                                                          │   │
│  │  /app/scripts/backtest_service/  (下划线目录名)          │   │
│  │  /app/backtest_jobs/  (回测结果)                          │   │
│  │  /app/data/  (volume → /volume1/stock/A股数据)            │   │
│  └─────────────────────────────────────────────────────────┘   │
│           ↑ Docker bridge (172.17.0.0/16)                      │
│           ↑ iptables DNAT: 8088→172.17.0.2:8088                │
└─────────────────────────────────────────────────────────────────┘
           ↑
    局域网 192.168.2.0/24
           ↑
┌─────────────────┐  ┌─────────────────┐
│   Mac mini      │  │  Windows Node   │
│  192.168.2.153  │  │  192.168.2.33   │
│  curl :8088     │  │  回测任务提交    │
└─────────────────┘  └─────────────────┘

3.2 修复清单(6处改动)

# 文件 改动 说明
1 Dockerfile COPY后 mv backtest-service backtest_service 目录名含连字符不能做Python包
2 entrypoint.sh 添加 uvicorn 启动命令 PYTHONPATH=/app/scripts uvicorn backtest_service.main:app
3 models.py ApiResponse[T](BaseModel)ApiResponse(BaseModel, Generic[T]) Python 3.10兼容
4 main.py uvicorn.run("main:app")uvicorn.run("backtest_service.main:app") 包路径启动
5 executor.py 重写,适配vnpy 4.x API 最核心的改动
6 requirements-base.txt 添加 vnpy_ctastrategyvnpy_sqlite 缺失的vnpy插件

关键决策:18处相对导入零改动(司马懿方案)。通过PYTHONPATH=/app/scripts uvicorn backtest_service.main:app包方式启动,所有from .xxx import yyy自然工作。

3.3 executor.py重写要点

executor.py是与vnpy深度集成的核心文件,需要适配4.x API变更:

# === vnpy 4.x import 路径 ===
from vnpy.event import EventEngine                          # 原: vnpy.trader.event
from vnpy_ctastrategy.backtesting import BacktestingEngine  # 原: vnpy.trader.backtesting
from vnpy.trader.constant import Interval                   # 不变
from vnpy.trader.database import get_database               # 原: database_manager

# === Interval 枚举变更 ===
# 3.x: FIVE_MINUTE, FIFTEEN_MINUTE, THIRTY_MINUTE, FOUR_HOUR
# 4.x: 只有 MINUTE, HOUR, DAILY, WEEKLY, TICK
INTERVAL_MAP = {
    "1m": Interval.MINUTE,
    "5m": Interval.MINUTE,   # 4.x不再细分,统一用MINUTE
    "15m": Interval.MINUTE,
    "30m": Interval.MINUTE,
    "1h": Interval.HOUR,
    "4h": Interval.HOUR,
    "1d": Interval.DAILY,
    "1w": Interval.WEEKLY,
}

# === 数据加载方式变更 ===
# 3.x: database_manager.query_history(req)
# 4.x: db = get_database(); data = db.query_bar_data(symbol, exchange, interval, start, end)
#      或者从CSV文件直接加载: engine.load_data(file_path)

重写策略:保持API接口不变(submit/status/result/health),只改内部vnpy调用逻辑。回测引擎调用改用 engine.load_data() 从CSV文件加载,绕过数据库兼容问题(NAS上数据本来就在CSV文件里)。

3.4 修改后的entrypoint.sh

#!/bin/bash
set -e

echo "=========================================="
echo "  sanguo_vnpy Docker 容器启动中..."
echo "=========================================="

# SSH服务
sudo /usr/sbin/sshd -D &

# Jupyter Lab
jupyter lab --ip=0.0.0.0 --port=8888 --no-browser \
    --NotebookApp.token='sanguo123' \
    --NotebookApp.password='' \
    --NotebookApp.allow_origin='*' &

# 自动化回测服务(uvicorn包方式启动)
mkdir -p /app/logs /app/backtest_jobs
# 兼容旧镜像:如果目录名含连字符,创建符号链接
if [ -d /app/scripts/backtest-service ] && [ ! -d /app/scripts/backtest_service ]; then
    ln -sf /app/scripts/backtest-service /app/scripts/backtest_service
fi
PYTHONPATH=/app/scripts uvicorn backtest_service.main:app \
    --host 0.0.0.0 --port 8088 \
    >> /app/logs/backtest-service.log 2>&1 &
BT_PID=$!
echo "回测服务已启动 (PID=$BT_PID, 端口8088)"

# code-server
code-server &

sleep 5

# 健康检查
if curl -sf http://localhost:8088/api/backtest/health > /dev/null 2>&1; then
    echo "✅ 回测服务健康检查通过"
else
    echo "⚠️  回测服务尚未就绪,检查日志: /app/logs/backtest-service.log"
fi

echo ""
echo "✅ sanguo_vnpy 环境启动成功!"
echo ""
echo "访问地址:"
echo "  Jupyter Lab:   http://localhost:8888 (token: sanguo123)"
echo "  回测服务:      http://localhost:8088/api/backtest/health"
echo "  回测API文档:   http://localhost:8088/docs"
echo "  SSH:           ssh -p 2222 vnpy@localhost (password: sanguo123)"
echo ""

tail -f /dev/null

3.5 Dockerfile增量修改

# 在 COPY scripts 行之后添加:
COPY --chown=vnpy:vnpy scripts /app/scripts
RUN find /app/scripts -name "*.sh" -type f -exec chmod +x {} \;
# 目录名含连字符不能做Python包,重命名为下划线
RUN if [ -d /app/scripts/backtest-service ]; then \
        mv /app/scripts/backtest-service /app/scripts/backtest_service; \
    fi
# 创建日志和回测结果目录
RUN mkdir -p /app/logs /app/backtest_jobs

# 在 requirements-base.txt 中添加:
# vnpy_ctastrategy>=1.0.0
# vnpy_sqlite>=1.0.0

3.6 docker run命令

sudo /var/packages/Docker/target/usr/bin/docker run -d \
  --name sanguo_vnpy \
  --restart unless-stopped \
  -m 4g --cpus=1.5 \
  -p 8888:8888 \
  -p 8088:8088 \
  -p 8000:8000 \
  -p 2222:22 \
  -v /volume1/stock/A股数据:/app/data:ro \
  -v /volume1/stock/回测结果:/app/backtest_jobs \
  sanguo_vnpy:latest

四、验证步骤

4.1 交付标准验证

# 标准一:Health端点返回200
curl -s -o /dev/null -w "%{http_code}" http://192.168.2.154:8088/api/backtest/health
# 预期: 200

# 标准二:提交回测任务并获取结果
TASK_ID=$(curl -s -X POST http://192.168.2.154:8088/api/backtest/submit \
  -H "Content-Type: application/json" \
  -d '{
    "strategy_name": "test_ma_cross",
    "strategy_code": "class TestStrategy(CtaTemplate): ...",
    "parameters": {"fast_window": 5, "slow_window": 20},
    "start_date": "2024-01-01",
    "end_date": "2024-12-31",
    "symbol": "000001.SZ",
    "interval": "d"
  }' | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['task_id'])")

# 查询状态
curl http://192.168.2.154:8088/api/backtest/status/$TASK_ID

# 获取结果
curl http://192.168.2.154:8088/api/backtest/result/$TASK_ID

# 标准三:自动恢复
sudo /var/packages/Docker/target/usr/bin/docker kill sanguo_vnpy
sleep 15
curl -s -o /dev/null -w "%{http_code}" http://192.168.2.154:8088/api/backtest/health
# 预期: 200

4.2 回滚方案

场景 操作
回测服务启动失败 容器仍可用(Jupyter/SSH正常),只影响8088
需要完全回滚 docker stop && docker rm,用旧镜像 sanguo_vnpy:with-scripts 重建
executor.py有bug docker cp 修复的文件进容器,不重启容器
代码改坏 git checkout恢复,重新docker cp

五、实施计划

Phase 操作 耗时 前置条件
1. 快速验证 容器内手动修复+启动backtest-service 10min
2. 代码修复 修复models.py/main.py/entrypoint.sh4处) 15min Phase 1通过
3. executor重写 适配vnpy 4.x API 30min Phase 2通过
4. 重建镜像 修改Dockerfile + docker build + 部署 30min Phase 3通过
5. 交付验证 执行4.1全部验证 10min Phase 4完成

Phase 1验证命令(在当前运行容器内,不重建镜像):

# SSH到NAS
ssh admin@192.168.2.154

# 进入容器
sudo /var/packages/Docker/target/usr/bin/docker exec -it sanguo_vnpy bash

# 容器内操作:
# 1. 安装缺失包
pip3 install vnpy_ctastrategy vnpy_sqlite

# 2. 创建修复后的目录
rm -rf /tmp/backtest_service
cp -r /app/scripts/backtest-service /tmp/backtest_service

# 3. 修复models.py泛型语法(sed替换,见3.2节#3)

# 4. 修复main.py的uvicorn路径(sed替换,见3.2节#4

# 5. 用uvicorn启动
PYTHONPATH=/tmp uvicorn backtest_service.main:app --host 0.0.0.0 --port 8088

# 6. 另一个终端验证
curl http://localhost:8088/api/backtest/health

六、关键技术决策

决策点 选择 理由
启动方式 PYTHONPATH=... uvicorn backtest_service.main:app 18处相对导入零改动(司马懿方案)
目录名 backtest-servicebacktest_service Python包名不允许连字符
数据加载 从CSV文件直接加载 绕过vnpy 4.x数据库API变更,NAS数据本来在CSV里
网络模式 bridge(默认) Jupyter已验证可用
重启策略 --restart unless-stopped 异常崩溃自动恢复
资源限制 4GB内存/1.5CPU 给NAS留足余量

附录:容器内实际验证日志

[2026-04-28 08:50] 容器内 pip list | grep vnpy → vnpy 4.3.0, vnpy_webtrader 1.1.0
[2026-04-28 08:51] vnpy 4.3.0 无 BacktestingEngine(拆分到vnpy_ctastrategy包)
[2026-04-28 08:52] pip install vnpy_ctastrategy vnpy_sqlite → 成功
[2026-04-28 08:53] from vnpy_ctastrategy.backtesting import BacktestingEngine → OK
[2026-04-28 08:53] from vnpy.event import EventEngine → OK
[2026-04-28 08:53] Interval枚举: 4.x只有MINUTE/HOUR/DAILY/WEEKLY/TICK
[2026-04-28 08:54] ApiResponse[T]语法 → Python 3.10不支持,需改Generic[T]