# 自动化回测服务设计方案 ## 需求背景 三国量化团队需要支持**自动化批量回测**场景: - 将军们提交回测脚本和参数 - 系统自动运行回测 - 完成后返回结果给将军 - 不需要手动干预 ## 现有环境基础 当前 Docker 容器环境已经提供: - ✅ Python 3.10 - ✅ 完整 vnpy 框架 - ✅ `vnpy.trader.backtesting.BacktestingEngine` 原生回测引擎 - ✅ FastAPI(已被 vnpy_webtrader 依赖) - ✅ SSH 远程访问 - ✅ Jupyter Lab 结果分析 ## 设计原则 1. **尽量基于vnpy原生**:核心回测使用原生 `BacktestingEngine`,不重复造轮子 2. **轻量级实现**:不引入过重的第三方任务队列(如 Redis/RabbitMQ) 3. **保持简单**:够用就好,便于维护 4. **不强制自动启动**:作为可选服务,需要时手动启动 ## 架构设计 ``` ┌─────────────┐ │ 将军 │ └──────┬──────┘ │ POST /api/backtest/submit ↓ ┌──────────────────────────┐ │ FastAPI 回测服务 │ ← 监听 8088 端口 │ - 接收任务 │ │ - 任务排队 │ │ - 进程池执行 │ │ - 保存结果 │ └──────┬───────────────────┘ │ 调用 ↓ ┌──────────────────────────┐ │ vnpy 原生 BacktestingEngine ← 核心回测计算 │ - 加载历史数据 │ │ - 运行策略回测 │ │ - 计算统计结果 │ │ - 输出图表 │ └──────┬───────────────────┘ │ 保存结果 ↓ ┌──────────────────────────┐ │ 文件系统存储 │ │ - 任务元数据 JSON │ │ - 回测结果 CSV/JSON │ │ - 收益曲线图片 │ └──────┬───────────────────┘ │ GET /api/backtest/result/{task_id} ↓ ┌─────────────┐ │ 将军 │ ← 获取结果分析 └─────────────┘ ``` ## 端口分配 | 服务 | 端口 | 用途 | 状态 | |------|------|------|------| | SSH | 22 → 2222 | 远程登录 | ✅ 已映射 | | Jupyter Lab | 8888 | Notebook开发 | ✅ 已映射 | | code-server | 8080 | Web VS Code | ✅ 已映射 | | VNPY Web Trader | 8000 | 交易Web API | ✅ 已映射 | | VNPY RPC 交易核心 | 2018 | RPC 请求 | ✅ 已映射 | | VNPY RPC 交易核心 | 4102 | RPC 订阅 | ✅ 已映射 | | **自动化回测服务** | **8088** | **回测API** | ✅ 已映射 | ## API 接口设计 ### 1. 提交回测任务 **接口:** `POST /api/backtest/submit` **请求体:** ```json { "strategy_name": "double_ma", "strategy_code": "from vnpy.trader.strategy import ...", "parameters": { "fast_window": 5, "slow_window": 20 }, "start_date": "2020-01-01", "end_date": "2024-01-01", "symbol": "IF888", "interval": "1h" } ``` **响应:** ```json { "code": 0, "msg": "success", "data": { "task_id": "a1b2c3d4-1234-5678-90ef-ghijklmnopqr", "status": "pending" } } ``` ### 2. 查询任务状态 **接口:** `GET /api/backtest/status/{task_id}` **响应:** ```json { "code": 0, "msg": "success", "data": { "task_id": "a1b2c3d4-1234-5678-90ef-ghijklmnopqr", "status": "running|completed|failed", "progress": 75, "message": "正在计算统计结果" } } ``` ### 3. 获取回测结果 **接口:** `GET /api/backtest/result/{task_id}` **响应:** ```json { "code": 0, "msg": "success", "data": { "task_id": "...", "status": "completed", "statistics": { "total_trades": 100, "win_rate": 0.6, "sharpe_ratio": 1.8, "max_drawdown": 0.15, "total_return": 0.35, "annual_return": 0.08 }, "result_file": "/app/backtest_results/.../result.csv", "chart_file": "/app/backtest_results/.../equity_curve.png", "completed_at": "2026-04-11T22:00:00Z" } } ``` ### 4. 列出所有任务 **接口:** `GET /api/backtest/list` **响应:** ```json { "code": 0, "msg": "success", "data": { "tasks": [ { "task_id": "...", "strategy_name": "...", "status": "...", "created_at": "..." } ] } } ``` ## 目录结构 ``` /app/ ├── scripts/ │ └── backtest-service/ │ ├── main.py # FastAPI 入口 │ ├── task_queue.py # 任务队列实现 │ ├── executor.py # 任务执行器 │ ├── models.py # 数据模型 │ └── README.md # 使用说明 └── backtest_jobs/ ├── pending/ # 等待执行 ├── running/ # 执行中 ├── completed/ # 已完成 └── failed/ # 执行失败 ``` ## 任务队列实现 使用 Python 内置 `multiprocessing.Pool` 做进程池,限制并发回测数量,避免资源耗尽: ```python # 配置示例 MAX_WORKERS = 2 # 最大并发回测数,根据CPU核数调整 ``` 每个回测任务在独立进程中运行,互不干扰,一个失败不影响其他任务。 ## 启动方式 ### 手动启动(开发环境) ```bash ssh -p 2222 vnpy@192.168.2.153 cd /app/scripts/backtest-service python main.py ``` ### 后台运行 ```bash nohup python main.py > backtest-service.log 2>&1 & ``` ## 后续实现步骤 1. [ ] 创建目录结构 2. [ ] 编写 Pydantic 数据模型 3. [ ] 编写任务队列 4. [ ] 编写任务执行器 5. [ ] 编写 FastAPI 路由 6. [ ] 编写 README 使用说明 7. [ ] 测试验证 ## 参考文档 - [vnpy-webtrader-research-report.md](./../task-20260331-vnpy-webtrader-research/final/vnpy-webtrader-research-report.md) - [docker-dependency-cache-scheme.md](./docker-dependency-cache-scheme.md) --- **文档记录时间:** 2026-04-11 22:21 GMT+8 **记录人:** 姜维 伯约 **状态:** 设计完成,待实现 --- ## 成功部署记录 - 2026-04-12 ### 部署信息 - **部署目标:** Synology NAS @ 192.168.2.154 - **镜像名称:** `sanguo_vnpy:latest` - **镜像ID:** `a77f153853b9` - **容器ID:** `8fccf1eb15ae` - **基础镜像:** `python:3.10-slim` ### 成功部署的服务 | 服务 | 地址 | 状态 | 验证结果 | |------|------|------|----------| | Jupyter Lab | http://192.168.2.154:8888/lab?token=sanguo123 | ✅ 运行正常 | **已验证可访问** | | SSH | `ssh vnpy@192.168.2.154 -p 2222` (密码: sanguo123) | ✅ 运行正常 | 已修复 hostkey 问题 | | VNPY Web Trader | http://192.168.2.154:8000 | ⚪ 待手动启动 | 端口已映射 | | code-server | http://192.168.2.154:8080 | ⚪ 待手动安装 | 配置已预生成 | | VNPY RPC 请求 | 192.168.2.154:2018 | ✅ 端口就绪 | 已映射 | | VNPY RPC 订阅 | 192.168.2.154:4102 | ✅ 端口就绪 | 已映射 | | 自动化回测服务 | http://192.168.2.154:8088 | ✅ 端口就绪 | 已映射,预留 | ### 关键修复 1. **openssh-server 配置文件缺失问题** 问题:`openssh-server` 因依赖 systemd 安装报错,`/etc/ssh/sshd_config` 缺失 解决:在 Dockerfile 添加判断,如果配置文件不存在则生成默认配置 2. **sshd hostkey 缺失问题** 问题:容器启动时 `sshd: no hostkeys available -- exiting` 解决:容器启动后在容器内执行 `ssh-keygen -A` 生成 hostkeys,重启后正常 3. **code-server 网络安装失败问题** 问题:GitHub 访问不稳定,二进制下载失败 解决:跳过预安装,保留配置,由用户启动容器后手动安装 ### 容器启动命令 ```bash # 停止并删除旧容器 echo "Ccf7561523" | sudo -S docker stop sanguo_vnpy echo "Ccf7561523" | sudo -S docker rm sanguo_vnpy # 启动新容器 echo "Ccf7561523" | sudo -S docker run -d \ -p 8888:8888 \ -p 8000:8000 \ -p 8080:8080 \ -p 8088:8088 \ -p 2018:2018 \ -p 4102:4102 \ -p 2222:22 \ --name sanguo_vnpy \ sanguo_vnpy:latest ``` ### 账号信息 | 账号 | 密码 | 用途 | |------|------|------| | vnpy (container) | sanguo123 | SSH 登录容器 | | Jupyter token | sanguo123 | Jupyter Lab 访问 | | code-server | sanguo123 | code-server 访问(预配置) | ### 验证结果 - ✅ Jupyter Lab 已验证可正常访问 - ✅ 所有端口映射正确配置 - ✅ SSH 已修复可正常连接 - ✅ 完整 vnpy 开发环境已就绪 - ✅ 所有依赖包安装成功 ### 记录人 姜维 伯约 2026-04-12 09:29 GMT+8 **状态:✅ 部署成功,Jupyter Lab 可访问** --- ## 实施完成记录 - 2026-04-12 ### 已完成工作 #### 1. Dockerfile 更新 - ✅ code-server 改为预装,不再跳过 - ✅ 添加 vnpy_webtrader 到 requirements-base.txt - ✅ 更新 EXPOSE 暴露所有需要的端口(22 8000 8080 8888 2018 4102 8088) #### 2. 自动化回测服务代码实现 完整目录结构: ``` /app/scripts/backtest-service/ ├── config.py # 配置 ├── models.py # Pydantic 数据模型 ├── task_queue.py # 任务队列管理器 ├── executor.py # 回测执行器(调用 vnpy 原生 BacktestingEngine) ├── result_storage.py # 结果存储 ├── api.py # FastAPI 路由 ├── main.py # 服务入口 └── README.md # 使用说明 ``` 所有代码按照设计方案实现完成: - ✅ 严格遵循 vnpy 原生设计,不修改核心架构 - ✅ 只做外层封装,完全复用原生 BacktestingEngine - ✅ multiprocessing.Pool 做并发控制,可配置 MAX_WORKERS - ✅ 每个回测独立进程,隔离性好,一个失败不影响其他 - ✅ 完整的 CRUD API,支持提交/查询/结果获取 #### 3. 准备好各个服务启动脚本 ``` /app/scripts/ ├── start_rpc_server.py # VNPY 交易核心 RPC 服务启动 ├── start_web_trader.py # VNPY Web Trader 服务启动 └── start_backtest_service.py # 自动化回测服务启动 ``` 按照 vnpy 官方双进程架构: 1. 第一步:启动 `start_rpc_server.py` → RPC 服务端监听 2018/4102 2. 第二步:启动 `start_web_trader.py` → Web 服务监听 8000 3. 需要回测时:启动 `start_backtest_service.py` → API 服务监听 8088 ### 当前代码结构确认 | 文件路径 | 功能 | 状态 | |----------|------|------| | `scripts/backtest-service/config.py` | 配置 | ✅ 完成 | | `scripts/backtest-service/models.py` | 数据模型 | ✅ 完成 | | `scripts/backtest-service/task_queue.py` | 任务队列 | ✅ 完成 | | `scripts/backtest-service/executor.py` | 回测执行器 | ✅ 完成 | | `scripts/backtest-service/result_storage.py` | 结果存储 | ✅ 完成 | | `scripts/backtest-service/api.py` | API 路由 | ✅ 完成 | | `scripts/backtest-service/main.py` | 服务入口 | ✅ 完成 | | `scripts/backtest-service/README.md` | 使用说明 | ✅ 完成 | | `scripts/start_rpc_server.py` | VNPY RPC 启动 | ✅ 完成 | | `scripts/start_web_trader.py` | VNPY Web Trader 启动 | ✅ 完成 | | `scripts/start_backtest_service.py` | 自动化回测服务启动 | ✅ 完成 | ### 待执行 - [ ] 重新构建 Docker 镜像 - [ ] 测试验证各个服务启动正常 - [ ] 运行一个示例回测验证功能 **记录人:** 姜维 伯约 2026-04-12 10:17 GMT+8 **状态:✅ 设计实施完成,等待重新构建镜像**