# 需求规格文档：本地数据源体系建设

**任务ID**: data-platform-20260502
**节点**: pangtong_requirements
**作者**: 庞统（副军师）
**日期**: 2026-05-02

---

## 一、项目背景与核心问题

### 1.1 现状

| 资产 | 状态 | 位置 |
|------|------|------|
| NAS日线Parquet | ✅ 2010-2026年全市场，按年分目录 | `/Volumes/stock/A股数据/日线数据/daily/{year}/sh{code}_daily.parquet` |
| NAS分钟线Parquet | ⚠️ 仅84只15分钟线 | `/Volumes/stock/minute_kline/15min/sz{code}_15min.parquet` |
| vnpy quant_trading.db | ❌ **空库（8KB，0张表）** | `/Volumes/stock/sanguo_vnpy/data/quant_trading.db` |
| 回测服务 | ✅ 运行中（http://192.168.2.154:8088） | Docker容器 |
| 本地数据适配器 | ⚠️ 已有但路径硬编码Mac本地 | `vnpy_local_data_adapter.py`（指向`/Users/chufeng/nas/stock/...`） |

### 1.2 核心问题

**vnpy回测服务的数据库是空的**，回测引擎 `engine.load_data()` 从数据库读取数据 → 无数据 → 所有回测任务必然失败。

回测服务executor.py关键代码（L171-175）：
```python
engine.load_data()  # 从vnpy SQLite数据库加载
```
如果没有数据，直接抛出 `ValueError("无法加载历史数据")`。

### 1.3 目标

打通 **NAS Parquet → vnpy SQLite DB → 回测引擎** 的数据通路，让回测服务可以正常执行回测任务。

---

## 二、功能需求

### P1：打通vnpy数据通路

#### P1-1：确认Docker volume映射路径

| 项 | 说明 |
|-----|------|
| 需求 | 确认Mac写入的文件，Docker容器内能读到 |
| 输入 | NAS目录结构、Docker容器配置 |
| 输出 | 明确的映射关系文档：Mac路径 ↔ 容器内路径 |
| 验证 | 在Mac写入测试文件，容器内能读到；反之亦然 |

**关键证据**：
- 回测服务配置 `base_dir = "/app/backtest_jobs"`
- 数据目录 `data_dir = settings.base_dir.replace("backtest_jobs", "data")` → `/app/data`
- quant_trading.db 位于 `/Volumes/stock/sanguo_vnpy/data/`
- 需确认Docker容器启动时是否挂载了 `/Volumes/stock/sanguo_vnpy/data` → `/app/data`

#### P1-2：编写vnpy DB导入脚本

| 项 | 说明 |
|-----|------|
| 需求 | 将NAS日线Parquet数据批量导入vnpy SQLite数据库 |
| 输入 | `/Volumes/stock/A股数据/日线数据/daily/{year}/sh{code}_daily.parquet` |
| 输出 | quant_trading.db 中有完整的日线bar数据 |
| 验证 | 回测引擎 `load_data()` 能读出数据 |
| 约束 | 幂等操作（INSERT OR REPLACE），可重复执行 |

**vnpy DB Schema要求**（待姜维确认）：
- vnpy 4.x的BacktestingEngine通过 `MainEngine` + `BaseDataManager` 加载数据
- 数据表名和字段名由vnpy内部定义
- 必须先搞清楚vnpy 4.x期望的数据库结构，再写导入脚本

**Parquet字段**：
```
date, open, high, low, close, volume, amount, outstanding_share, turnover, year
```

**导入脚本功能要求**：
1. 扫描 `/Volumes/stock/A股数据/日线数据/daily/` 下所有年份目录
2. 每个Parquet文件解析股票代码（从文件名提取，如 `sh600000` → `600000.SSE`）
3. 转换为vnpy DB格式并批量写入
4. 支持增量导入（只导入新增数据）
5. 支持断点续传（中断后可继续）
6. 记录导入日志（成功/失败数、耗时）

#### P1-3：全量导入日线

| 项 | 说明 |
|-----|------|
| 需求 | 运行导入脚本，将全市场2010-2026年日线数据全部导入 |
| 输入 | P1-2的导入脚本 + NAS日线Parquet |
| 输出 | quant_trading.db 填满日线数据 |
| 验证 | 统计导入记录数，抽查几只股票确认数据完整 |
| 风险 | 导入耗时长（预估2-4小时），需支持断点续传 |

#### P1-4：验证回测服务可用

| 项 | 说明 |
|-----|------|
| 需求 | 提交一个简单回测任务，确认回测引擎能加载数据并完成回测 |
| 输入 | 回测服务API + 简单策略代码 |
| 输出 | 回测成功返回统计结果 |
| 验证 | total_trades > 0 或 total_days > 0 |

---

### P2：数据基础设施

#### P2-1：多源降级管理器 `fallback.py`

| 项 | 说明 |
|-----|------|
| 需求 | 统一数据获取入口，支持多数据源顺序降级 |
| 降级链（日线） | akshare `stock_zh_a_hist` → 腾讯K线API |
| 降级链（实时） | 新浪实时 → 东方财富 → 腾讯 |
| 接口 | `get_daily(symbol, start, end)` / `get_realtime(symbol)` |
| 行为 | 第一个源失败自动切下一个，记录使用的源 |
| 产出 | ~150行 |

#### P2-2：数据校验层 `validator.py`

| 项 | 说明 |
|-----|------|
| 需求 | 入库前校验数据质量，fatal级拒绝入库 |
| V1规则（7条fatal） | D1: close/open/high/low > 0；D2: OHLC一致性(high≥max(open,close), low≤min(open,close))；D3: volume ≥ 0；D6: 同股同日不重复；D7: date ≤ 今天；R1: 实时价格 > 0；R7: 必须携带source+fetched_at |
| 接口 | `validate(df) → (passed: bool, errors: List[str])` |
| 产出 | ~150行 |

#### P2-3：实时行情三源降级 `realtime.py`

| 项 | 说明 |
|-----|------|
| 需求 | 获取实时行情，支持3个源降级 |
| 降级链 | 新浪实时 → 东方财富 → 腾讯 |
| 接口 | `get_realtime_quote(symbol) → dict` |
| 产出 | ~200行 |

#### P2-4：增量更新 `updater.py`

| 项 | 说明 |
|-----|------|
| 需求 | 每日增量更新，Parquet+vnpy DB双写 |
| 流程 | 1.获取最新日期 2.拉取增量数据 3.校验 4.写Parquet（原子：临时文件+rename） 5.写vnpy DB（INSERT OR REPLACE幂等） 6.一致性校验 |
| 约束 | Parquet是真相源；vnpy DB失败不影响Parquet |
| 接口 | `update_daily() → UpdateResult` |
| 产出 | ~150行 |

#### P2-5：cron定时任务

| 项 | 说明 |
|-----|------|
| 需求 | 每交易日15:30自动执行增量更新 |
| 配置 | Mac crontab（Mac已确认永不休眠） |
| 验证 | 下一个交易日检查是否自动执行 |

---

### P3：分钟线数据

#### P3-1：P0限频验证

| 项 | 说明 |
|-----|------|
| 需求 | 验证腾讯API限频阈值 |
| 测试1 | 100只股票15分钟线连续下载，是否成功 |
| 测试2 | 连续1小时请求，记录每分钟成功次数、封禁恢复时间 |
| 输出 | 限频验证报告（每分钟最大请求数、封禁时长、恢复策略） |
| 决策 | 报告决定P3-2/P3-3的实现策略（分批间隔、每批数量） |

#### P3-2/P3-3：分钟线全量下载

| 项 | 说明 |
|-----|------|
| 需求 | 下载HS300/全市场15分钟线 |
| 前置 | P3-1限频验证通过 |
| 数据源 | 腾讯mkline API（唯一可用源，akshare分钟线已失效） |
| 存储路径 | `/Volumes/stock/minute_kline/15min/` |
| 约束 | 15分钟线优先，1分钟线暂缓 |

#### P3-4：分钟线导入vnpy DB

| 项 | 说明 |
|-----|------|
| 需求 | 将分钟线Parquet导入vnpy DB |
| 前置 | P1-2已确认vnpy DB Schema + 分钟线Parquet已下载 |
| 不确定项 | vnpy 4.x如何区分不同周期（15min vs 1min）的分钟线 |

---

### P4：配套skill与自动化

#### P4-1/P4-2：更新skill文档

更新 `data-acquisition` 和 `quant-backtest` SKILL.md，补充vnpy数据通路说明。

#### P4-3：全量校验脚本

关羽设计的V2规则（14条），用于定期全量扫描。

#### P4-4：周维护cron

每周校验Parquet与vnpy DB一致性。

---

## 三、交付物清单

### 代码文件（放到 `~/.openclaw/sanguo_projects/sanguo_vnpy/data_platform/`）

| 文件 | 功能 | 阶段 | 预估行数 |
|------|------|------|---------|
| `import_vnpy.py` | Parquet → vnpy DB 导入 | P1 | ~200 |
| `fallback.py` | 多源降级管理器 | P2 | ~150 |
| `validator.py` | 数据校验（V1 7条fatal） | P2 | ~150 |
| `realtime.py` | 实时行情三源降级 | P2 | ~200 |
| `updater.py` | 增量更新（双写） | P2 | ~150 |
| `validate_full.py` | 全量校验（V2 14条） | P4 | ~100 |

### 文档文件

| 文件 | 内容 | 位置 |
|------|------|------|
| 需求规格文档 | 本文档 | `docs/data-platform/01-requirements.md` |
| 设计方案文档 | 接口设计、数据流、Schema映射 | `docs/data-platform/02-design.md` |
| 验证报告 | 限频验证、导入验证、回测验证 | `docs/data-platform/reports/` |

### 配置文件

| 文件 | 内容 |
|------|------|
| crontab配置 | 每日15:30增量更新 |
| vnpy DB路径映射 | Mac ↔ Docker |

---

## 四、假设与不确定项

| # | 假设/不确定项 | 影响范围 | 验证人 | 验证时机 |
|---|-------------|---------|--------|---------|
| 1 | **Docker volume映射**：Mac写入NAS的文件Docker容器能读到 | P1全部 | 姜维 | P1开始前 |
| 2 | **vnpy 4.x DB Schema**：回测引擎load_data()期望的表结构和字段 | P1-2, P3-4 | 姜维 | P1开始前 |
| 3 | **vnpy分钟线周期区分**：vnpy如何存储/区分不同粒度分钟线 | P3-4 | 姜维 | P3开始前 |
| 4 | **腾讯API限频**：连续请求的频率上限和封禁恢复时间 | P3全部 | 赵云 | P3开始前 |
| 5 | **全量导入耗时**：5500只×17年数据的导入时间 | P1-3 | 张飞/赵云 | P1-3执行时 |
| 6 | **SQLite并发**：cron写入+回测读取是否冲突 | P2-5 | 姜维 | P2-5配置时 |
| 7 | NAS存储空间充足（1.5TB可用，只需28GB） | 全局 | 已确认 | - |
| 8 | Mac永不休眠（cron可靠执行） | P2-5 | 已确认 | - |
| 9 | 不引新依赖（只用akshare+urllib+已有库） | 全局 | 约束 | - |

**关键阻塞项**：#1和#2如果不明确，P1无法开始。**建议姜维先验证这两项。**

---

## 五、约束

1. 所有产出放到 `~/.openclaw/sanguo_projects/sanguo_vnpy/` 目录下
2. 不引新依赖（只用akshare + urllib + 已有的库）
3. 不改Docker/NAS配置，数据通过volume映射
4. Parquet是唯一真相源，vnpy DB是可重建的派生缓存
5. 双写顺序：先Parquet（原子写入）→ 再vnpy DB（幂等写入）
6. 腾讯API是唯一可用的分钟线源
7. 15分钟线优先，1分钟线暂缓
8. 不确定项遇到阻塞时，用最大尝试轮数限制，不无限重试
9. 每个阶段先输出需求和设计方案，经评审再编码

---

## 六、成功标准

| # | 标准 | 验证方法 |
|---|------|---------|
| 1 | vnpy DB有全市场日线数据 | `SELECT count(*) FROM ...` > 0 |
| 2 | 回测服务能完成一次完整回测 | 提交回测任务返回成功 |
| 3 | 增量更新可自动执行 | crontab触发后日志显示成功 |
| 4 | 数据校验拦截bad data | 构造异常数据，校验返回fatal |
| 5 | 多源降级正常工作 | 关掉主源，自动切到备用源 |
| 6 | 分钟线P0验证有结论 | 限频报告有明确数字 |

---

## 七、数据流架构

```
Layer 1: 远程数据源
  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
  │   akshare    │  │  新浪实时     │  │  腾讯API     │
  │  (日线主源)   │  │ (实时主源)    │  │ (分钟线唯一源)│
  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘
         │                 │                 │
         └────────┬────────┴────────┬────────┘
                  │ fallback.py     │
                  │ 降级管理         │
                  ▼                 │
Layer 2: 校验层    │                 │
         validator.py               │
         (7条fatal规则)             │
                  │                 │
                  ▼                 ▼
Layer 3: NAS持久层 (唯一真相源)
  /Volumes/stock/A股数据/日线数据/daily/{year}/{code}_daily.parquet
  /Volumes/stock/minute_kline/15min/{code}_15min.parquet
                  │
                  │ import_vnpy.py / updater.py
                  ▼
Layer 4: vnpy SQLite DB (派生缓存)
  /Volumes/stock/sanguo_vnpy/data/quant_trading.db
                  │
                  │ engine.load_data()
                  ▼
Layer 5: 回测引擎
  BacktestingEngine → 回测结果
```
