Files
sanguo_vnpy/docs/data-platform/daily-update-design.md
T
2026-05-03 12:39:56 +08:00

542 lines
22 KiB
Markdown
Raw 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.
# 数据平台每日增量更新 — 详细设计文档
**项目**: sanguo_vnpy 数据平台
**作者**: 赵云(数据总管)
**日期**: 2026-05-03
**版本**: v1.0-draft
**状态**: 待评审
---
## 一、背景与目标
### 1.1 现状
经过 P1(日线导入)和 P3(15分钟线下载导入),数据平台已建成:
| 数据类型 | 存储 | 覆盖范围 | 数据量 |
|---------|------|---------|--------|
| 日线行情 | NAS Parquet (`/Volumes/stock/A股数据/日线数据/daily/{year}/`) | 2010~2026 全市场 | ~5000只/年 |
| 15min分钟线 | NAS Parquet (`/Volumes/stock/minute_kline/15min/`) | 2025-09~2026-04 全市场 | 5193只 |
| vnpy主库 | NAS SQLite (`/Volumes/stock/sanguo_vnpy/data/quant_trading.db`) | 同上 | 1.4GB, 1281万行 |
| vnpy DB备份 | NAS (`.bak`) | 2026-05-02 | 330MB |
**问题**:数据是静态快照,没有自动更新机制。每次更新需手动执行脚本。
### 1.2 目标
1. **每日自动增量更新**:交易日收盘后自动更新日线+15min数据
2. **多数据源整合**:保留所有数据源访问方式,取各源最优数据合并
3. **数据最大化**:历史数据尽量完整,增量数据每日累积
4. **部署集成**:最终整合到 sanguo_vnpy 项目统一部署(待实现)
---
## 二、数据源调研
### 2.1 已验证的数据源
| 源 | 接口 | 可用性 | 历史深度 | 限频 | 适用场景 |
|---|---|---|---|---|---|
| **新浪财经** | `quotes.sina.cn/.../getKLineData` | ✅ Mac可用 | 15min: 800条(~3个月), 日线: 800条(~3年), 60min: 800条(~10月) | 0.3s/请求无封禁 | 15min增量、日线增量 |
| **腾讯财经** | `web.ifzq.gtimg.cn/.../fqkline` | ⚠️ 偶尔连接重置 | 日线: 按日期范围查询,可获取多年 | 无明显限制 | 日线增量(主源) |
| **东方财富** | `push2his.eastmoney.com/.../kline` | ❌ Mac直连被拒 | 理论上可指定任意日期范围 | 未知 | 历史回补(需Windows环境) |
| **akshare** | `stock_zh_a_hist_min_em` / `stock_zh_a_hist` | ⚠️ 走东方财富,受代理影响 | 理论完整 | 有代理污染问题 | 备用(需网络正常时) |
| **腾讯 minute/query** | `web.ifzq.gtimg.cn/.../minute/query` | ⚠️ 仅当天1min数据 | 仅当天 | 未知 | 当天1min→聚合15min(备源) |
### 2.2 数据源限制详情
**新浪财经 K线API**
- URL: `https://quotes.sina.cn/cn/api/jsonp_v2.php/var%20=min15_{symbol}=/CN_MarketDataService.getKLineData?symbol={symbol}&scale={period}&ma=no&datalen={count}`
- `datalen` 参数最大有效值: **800**(超过返回null
- `scale` 支持: 5, 15, 30, 60, 240(日线)
- 字段: day, open, high, low, close, volume, amount
- amount为真实成交额
- 时间戳为end-of-bar格式
- 返回JSONP,需正则提取JSON数组
**腾讯财经 fqkline API**
- URL: `https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param={symbol},{period},{start},,{days},`
- 支持按日期范围查询
- 返回格式: `[date, open, close, high, low, volume]` 或 7列含amount
- amount有时为0(不完整)
**东方财富 K线API**
- URL: `http://push2his.eastmoney.com/api/qt/stock/kline/get?secid={market}.{code}&klt={period}&fqt=1&beg={start}&end={end}`
- Mac环境直连被拒绝(Connection reset / 502
- 可能与IP/地区/UA有关
- **Windows Node192.168.2.33)待验证**Node当前离线
### 2.3 多数据源策略
```
数据源选择优先级(按数据质量排序):
日线增量更新:
主源: 腾讯 fqkline(支持日期范围,amount有时为0)
备源: 新浪 getKLineData scale=240(固定800条,amount真实)
15min增量更新:
主源: 新浪 getKLineData scale=15(固定800条,amount真实,稳定可靠)
备源: 腾讯 minute/query → 聚合15min(仅当天数据)
历史回补(15min更早的历史):
首选: 东方财富(需Windows环境,可指定日期范围)
备选: akshare stock_zh_a_hist_min_em(依赖东方财富,需网络正常)
```
---
## 三、系统设计
### 3.1 整体架构
```
┌──────────────────────────────────────────────────────┐
│ 定时调度层 (OpenClaw Cron) │
│ 每交易日 15:35 触发 daily_update_all.sh │
└───────────────────┬──────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ daily_all_update.py (主脚本) │
│ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ 日线增量更新 │ │ 15min增量更新 │ │
│ │ 腾讯fqkline(主) │ │ 新浪API(主) │ │
│ │ 新浪(备) │ │ 腾讯聚合(备) │ │
│ └────────┬────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 数据校验层 │ │
│ │ 价格>0 | OHLC一致性 | 去重 | 类型兼容 │ │
│ └─────────────────────┬───────────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ Parquet写入 │ │ vnpy DB写入 │ │
│ │ 原子写入(.tmp) │ │ 本地tmp→ATTACH导入 │ │
│ │ 增量合并 │ │ NAS SQLite │ │
│ └─────────────────┘ └──────────────────────┘ │
└──────────────────────────────────────────────────────┘
```
### 3.2 文件结构
```
~/.openclaw/sanguo_projects/sanguo_vnpy/data_platform/
├── daily_all_update.py # 主脚本:全市场增量更新(日线+15min)
├── daily_update_all.sh # Shell wrapper,由cron调用
├── download_minute.py # 15min全量/批量下载脚本(保留)
├── import_vnpy_minute.py # 分钟线导入vnpy DB(保留)
├── import_vnpy_daily_fast.py # 日线全量导入脚本(保留,首次用)
├── updater.py # 旧版日线更新脚本(保留)
├── daily_update.sh # 旧版wrapper(保留)
├── fallback.py # 降级工具(保留)
├── validator.py # 数据验证工具(保留)
└── logs/ # 日志目录
```
### 3.3 核心流程
#### 3.3.1 日线增量更新
```
1. 扫描全市场股票列表(从 stock_basic_info CSV
2. 对每只股票:
a. 获取Parquet中最后日期
b. 计算需要补充的日期范围(last_date+1 ~ today
c. 如果已是最新,跳过
d. 调用腾讯fqkline API获取增量数据
e. 数据校验(价格>0, 类型一致)
f. 增量合并到年度Parquet文件(原子写入)
g. 收集vnpy DB写入数据
3. 批量写入vnpy DB(本地tmp → ATTACH导入NAS DB
```
#### 3.3.2 15分钟线增量更新
```
1. 扫描全市场股票列表
2. 对每只股票:
a. 调用新浪API获取最近800条15min数据
b. 数据校验(价格>0, OHLC一致性)
c. 与已有Parquet增量合并(drop_duplicates keep='last'
d. 原子写入Parquet
e. 计算新增行数,收集vnpy DB写入数据
3. 批量写入vnpy DB
```
#### 3.3.3 vnpy DB写入策略(解决SMB性能问题)
**问题**:NAS通过SMB挂载在Mac上,直接对1.4GB SQLite文件进行频繁读写:
- 查询超时(>20秒无响应)
- 写入可能触发SIGKILL(进程被系统终止)
- SMB文件锁与SQLite锁冲突风险
**方案:本地临时DB → ATTACH导入**
```
1. 在 /tmp/ 创建本地SQLite DB,写入增量数据
2. ATTACH NAS DB
3. INSERT OR REPLACE ... SELECT 从本地导入NAS DB
4. 更新 dbbaroverview 表
5. DETACH,删除本地临时文件
```
**优点**
- 增量数据先在本地SSD写完,与NAS交互只有一次批量INSERT
- 减少SMB文件操作次数
- INSERT OR REPLACE保证幂等性
**风险与缓解**
| 风险 | 缓解措施 |
|------|---------|
| ATTACH时NAS DB被锁定 | timeout=120秒等待 |
| 中途失败导致DB不一致 | WAL模式 + INSERT OR REPLACE幂等 |
| overview表更新慢 | 只在全部数据导入后执行一次 |
### 3.4 数据校验规则
| 规则 | 说明 | 实现 |
|------|------|------|
| 价格>0 | close/open ≤ 0 的行丢弃 | `(df[["close","open"]] <= 0).any(axis=1)` |
| OHLC一致性 | high < max(open,close) 或 low > min(open,close) 的行丢弃 | 逐行比较 |
| 去重 | 相同day/date保留最新 | `drop_duplicates(subset=["day"], keep="last")` |
| 类型兼容 | volume/amount保持object与已有Parquet一致 | `.astype(str)` |
| NaN处理 | 价格NaN行丢弃,volume/amount NaN填0 | `fillna(0)` + `dropna` |
| 日期格式 | 日线: YYYY-MM-DD(str), 15min: YYYY-MM-DD HH:MM:SS(str) | 统一astype(str)避免混合类型 |
### 3.5 断点续传
- 15min更新:进度文件 `/Volumes/stock/logs/daily_update/progress/15min_progress.json`
- 记录已完成的股票代码列表
- 中断后重启自动跳过已完成的
- 日线更新:通过检查Parquet最后日期判断,天然幂等
- 日志:`/Volumes/stock/logs/daily_update/update_{timestamp}.log`
- 报告:`/Volumes/stock/logs/daily_update/report_{date}.json`
### 3.6 限频与容错
| 参数 | 值 | 说明 |
|------|-----|------|
| 请求间隔 | 0.3秒 | 避免触发源站限频 |
| 单股重试 | 3次 | 失败后重试,间隔1秒 |
| 连续失败暂停 | 10次连续失败后暂停60秒 | 防止批量封禁 |
| 超时 | 15秒/请求 | 单次请求超时 |
| DB写入超时 | 120秒 | SMB写入等待 |
---
## 四、vnpy DB Schema 参考
```sql
-- 主数据表
CREATE TABLE dbbardata (
symbol VARCHAR(32),
exchange VARCHAR(32),
datetime VARCHAR(64),
interval VARCHAR(8),
volume FLOAT,
turnover FLOAT,
open_interest FLOAT,
open_price FLOAT,
high_price FLOAT,
low_price FLOAT,
close_price FLOAT,
PRIMARY KEY (symbol, exchange, interval, datetime)
);
-- 概览表
CREATE TABLE dbbaroverview (
symbol VARCHAR(32),
exchange VARCHAR(32),
interval VARCHAR(8),
count INT,
start VARCHAR(64),
end VARCHAR(64),
PRIMARY KEY (symbol, exchange, interval)
);
```
**interval值说明**
- `d` = 日线
- `1m` = 分钟线(vnpy 4.x 原始枚举,15分钟线也用此值,与BacktestingEngine兼容)
> ⚠️ 注意:vnpy原始Interval枚举只有 `MINUTE="1m"`,没有 `MINUTE_15`。Docker回测服务用原始vnpy,加载分钟线时传 `interval="1m"`。因此DB中15分钟线也存储为 `interval="1m"`。如果未来引入真正的1分钟线,需重新设计。
---
## 五、多数据源保留策略
### 5.1 当前实现
| 数据源 | 代码文件 | 状态 |
|--------|---------|------|
| 新浪财经 | `daily_all_update.py` 中的 `try_sina_15min()` | ✅ 在用 |
| 腾讯fqkline | `daily_all_update.py` 中的 `fetch_tencent_daily()` | ✅ 在用 |
| 腾讯minute/query | `download_minute.py` 中的 `try_minute_query_aggregate()` | ✅ 已实现,作为备源 |
| 东方财富 | 未实现 | ❌ 待开发(需Windows环境) |
| akshare | `daily_all_update.py` 外部依赖 | ⚠️ 受代理影响 |
### 5.2 设计原则
1. **所有数据源接口统一保留**,不删除任何已有的数据源访问代码
2. **数据合并策略**:同一股票同一周期从多个源获取时,按优先级选择:
- amount(成交额):优先有真实值的源(新浪 > 腾讯)
- 数据长度:优先历史更长的源
- 数据时效:优先更新的源
3. **源降级链**:主源失败自动尝试备源,不丢数据
4. **源标记**Parquet文件可选增加 `_source` 列标记数据来源(待讨论)
### 5.3 未来扩展点
- 东方财富API集成(需Windows Node
- akshare作为备用日线源(网络恢复后)
- 1min/5min/30min/60min等其他周期
- 北交所920xxx数据(需新数据源)
---
## 六、SMB/NAS 性能问题与方案
### 6.1 已知问题
| 问题 | 现象 | 影响 |
|------|------|------|
| SMB读大文件慢 | 1.4GB SQLite查询超时(>20s) | 无法直接在Mac上操作NAS DB |
| SMB写大文件卡死 | 进程被SIGKILL | 全量导入必须用本地中转 |
| SMB文件锁冲突 | SQLite WAL模式可能异常 | 并发写入风险 |
| Parquet小文件延迟 | 5300个parquet文件,SMB逐个读写 | 全量更新约30分钟 |
### 6.2 当前方案
```
写入流程(NAS DB:
本地/tmp写SQLite → ATTACH NAS DB → INSERT OR REPLACE → DETACH → 删除临时文件
写入流程(Parquet:
内存中合并 → 写本地.tmp → rename到NAS路径
```
### 6.3 待讨论:是否直接在NAS本地执行
NAS (192.168.2.154) 上运行的是 Linux,如果能SSH执行Python脚本:
- SQLite直接本地读写,无SMB延迟
- Parquet直接本地写入
- 速度提升10倍以上
**方案A(当前)**Mac上跑脚本,SMB读写NAS
- 优点:无需SSH,利用Mac环境
- 缺点:SMB性能瓶颈
**方案B(建议)**:NAS上直接跑脚本(需姜维配合SSH/容器环境)
- 优点:无SMB瓶颈,速度快
- 缺点:需要NAS上有Python环境
**方案C(折中)**Parquet写NAS(小文件SMB可接受),SQLite写Docker容器内(通过HTTP API
- 优点:各取所长
- 缺点:需要开发写入API
> 📌 **待与司马懿讨论**:NAS性能问题的最终解决方案
---
## 七、定时任务配置
### 7.1 当前方案(OpenClaw Cron
| 配置项 | 值 |
|--------|-----|
| 调度 | 每交易日(周一到周五)15:35 |
| 时区 | Asia/Shanghai |
| 执行方式 | isolated session(不消耗主session token |
| 超时 | 3600秒(1小时) |
| 通知 | 完成后飞书通知 |
### 7.2 Cron表达式
```
35 15 * * 1-5 # 周一到周五 15:35
```
### 7.3 注意事项
- 非交易日也会触发,但脚本会检测无新数据后快速退出(所有股票都skipped)
- 未来可增加交易日历判断(如使用akshare获取交易日历)
---
## 八、部署方案(待实现)
### 8.1 当前部署状态
- 脚本路径:`~/.openclaw/sanguo_projects/sanguo_vnpy/data_platform/`
- 运行环境:Mac mini(楚锋的Mac mini),Python 3.9
- 调度:OpenClaw Cron
- 数据存储:NAS SMB挂载 `/Volumes/stock/`
### 8.2 目标部署(整合到sanguo_vnpy项目)
**待实现,设计如下**
```
sanguo_vnpy/
├── deploy/
│ ├── docker-compose.yml # 包含数据更新服务
│ └── data-updater/
│ ├── Dockerfile # 数据更新容器
│ ├── crontab # 容器内crontab
│ └── entrypoint.sh
├── src/
│ └── data_platform/ # 数据平台代码(从data_platform/迁移)
│ ├── daily_all_update.py
│ ├── download_minute.py
│ ├── import_vnpy_daily_fast.py
│ ├── import_vnpy_minute.py
│ └── ...
├── docs/
│ └── data-platform/
│ └── daily-update-design.md # 本文档
└── config/
└── data_platform.yaml # 配置文件(路径、限频参数等)
```
### 8.3 部署步骤(草案)
1. 代码从 `~/.openclaw/sanguo_projects/` 迁移到 `sanguo_vnpy/src/data_platform/`
2. 配置外置为YAML文件
3. Docker容器内置crontab + Python脚本
4. 容器挂载NAS数据目录
5. 与现有vnpy回测服务docker-compose整合
---
## 九、测试
### 9.1 已完成的测试
| 测试项 | 结果 | 日期 |
|--------|------|------|
| 日线增量更新3只(000001/600519/300750 | ✅ 3只skipped(已是最新) | 2026-05-03 |
| 15min增量更新3只 | ✅ 3只ok0 failed | 2026-05-03 |
| 全市场15min下载(5193只) | ✅ 完成,107只北交所失败(源不支持) | 2026-05-02 |
| vnpy DB日线全量导入(1281万行) | ✅ 回测验证通过 | 2026-05-02 |
| vnpy DB 15min导入(单只验证) | ✅ 1970行,16个时间点正确 | 2026-05-02 |
### 9.2 待测试项
| 测试项 | 方法 | 优先级 |
|--------|------|------|
| 全市场增量更新完整流程 | cron触发后检查report | P0 |
| NAS离线时脚本行为 | umount后运行,验证优雅退出 | P0 |
| DB写入并发安全 | 两个脚本同时写DB | P1 |
| 东方财富APIWindows | Windows Node上线后测试 | P2 |
| 非交易日执行 | 周末运行,验证全部skipped | P1 |
| 30天连续运行稳定性 | 观察一个月的report | P1 |
---
## 十、Q&A — 讨论过的问题汇总
### Q1: Parquet双写是什么意思?还需要吗?
**讨论**:原TODO #4提到Parquet作为真相源source of truth)与vnpy DB双写。
**结论**:当前架构中 Parquet 是下载的**原始产出**vnpy DB 是**导入产物**。Parquet本身就是备份。不需要额外的双写机制。真正需要的是**vnpy DB的定时备份**(当前.bak只备份一次)。
### Q2: 新浪API只能拿800条,怎么获取更长的历史?
**讨论**:新浪 `datalen=800` 是硬限制,超过800返回null。实测15min=3个月,日线=3年。
**结论**
- 增量更新场景:每日800条足够覆盖最新数据,历史在Parquet中累积
- 历史回补:需要东方财富API(可指定日期范围),但Mac被拒,需Windows环境
- 另一条路:如果之前有更长的CSV数据(如84只深市老数据有1970行),合并进Parquet
### Q3: vnpy DB的interval为什么是"1m"而不是"15m"
**讨论**vnpy 4.x的Interval枚举只有 `MINUTE="1m"`,没有 `MINUTE_15`。Docker用的是原始vnpy。
**结论**DB中15分钟线用 `interval="1m"` 存储,与BacktestingEngine `load_data(interval="1m")` 匹配。如果未来引入真正的1分钟线,需要重新设计interval值。
### Q4: 北交所107只股票怎么办?
**讨论**:新浪行情源不支持920xxx代码。
**结论**:当前不影响(HS300无北交所),后续如需支持需引入新数据源(如东方财富)。
### Q5: 为什么不直接在NAS上跑脚本?
**讨论**:Mac通过SMB访问NAS,大文件操作慢且不稳定(SIGKILL)。
**结论**:当前用本地tmp中转方案缓解。长期建议在NAS本地执行(需SSH/容器环境),或通过Docker容器HTTP API写入。
### Q6: amount(成交额)数据准确性?
**讨论**:腾讯fqkline的amount有时返回0,新浪API的amount是真实值。
**结论**
- 15min:用新浪(amount真实)
- 日线:用腾讯(amount可能为0,但支持日期范围查询更重要)
- 未来可考虑用新浪的amount覆盖腾讯的0值
### Q7: 每日增量更新多长时间?
**预估**
- 日线:5300只 × 0.3s ≈ 26分钟(大部分skipped更快)
- 15min5300只 × 0.3s ≈ 26分钟
- DB写入:取决于增量数据量,通常几百条
- **总计约30-50分钟**
### Q8: 如何处理节假日/非交易日?
**当前方案**:非交易日执行时,所有股票都检测到"已是最新"被skipped,快速退出(<1分钟)。
**改进方向**:可增加交易日历判断,非交易日直接不执行(节省一次扫描)。
### Q9: 数据更新和回测服务会冲突吗?
**风险**:更新脚本和回测服务同时读写同一个vnpy DB。
**缓解**:回测服务在Docker容器内操作自己的DB副本(`/home/vnpy/.vntrader/database.db`),与NAS上的DB是不同文件。NAS DB更新后需要同步到Docker(目前手动wget)。
**待改进**:自动化DB同步机制(cron或文件监控)。
### Q10: 代码部署为什么要和sanguo_vnpy整合?
**理由**
1. 数据平台是为vnpy回测服务的,放一起管理方便
2. Docker统一部署,减少环境依赖
3. 配置集中管理(NAS路径、限频参数等)
---
## 十一、文件清单
| 文件 | 路径 | 说明 |
|------|------|------|
| `daily_all_update.py` | `sanguo_vnpy/data_platform/` | 主脚本:全市场增量更新 |
| `daily_update_all.sh` | `sanguo_vnpy/data_platform/` | Shell wrapper |
| `download_minute.py` | `sanguo_vnpy/data_platform/` | 15min全量下载(保留) |
| `import_vnpy_minute.py` | `sanguo_vnpy/data_platform/` | 分钟线导入DB(保留) |
| `import_vnpy_daily_fast.py` | `sanguo_vnpy/data_platform/` | 日线全量导入(保留) |
| `updater.py` | `sanguo_vnpy/data_platform/` | 旧版日线更新(保留) |
| `daily_update.sh` | `sanguo_vnpy/data_platform/` | 旧版wrapper(保留) |
---
## 十二、变更记录
| 日期 | 版本 | 变更 | 作者 |
|------|------|------|------|
| 2026-05-03 | v1.0-draft | 初始版本 | 赵云 |
---
## 十三、待评审项
> 以下是需要与司马懿讨论确认的项目:
1. **SMB性能问题**:方案A/B/C选择,是否需要NAS本地执行?
2. **vnpy DB同步到Docker**:自动化方案?
3. **数据源保留粒度**:是否需要在Parquet中记录数据来源?
4. **amount为0的处理**:腾讯日线amount=0时是否用新浪补?
5. **interval="1m"设计**:未来1分钟线和15分钟线如何区分?
6. **部署整合**Docker容器化方案的优先级?
7. **DB备份策略**:是否需要轮转备份(如保留最近7天)?
8. **错误告警**:更新失败时如何通知?(当前只有飞书announce)