Files
2026-04-29 20:14:54 +08:00

367 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 群晖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 -m``uvicorn 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_ctastrategy``vnpy_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变更:
```python
# === 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
```bash
#!/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增量修改
```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命令
```bash
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 交付标准验证
```bash
# 标准一: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验证命令**(在当前运行容器内,不重建镜像):
```bash
# 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-service``backtest_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]
```