Files
sanguo_vnpy/docs/data-platform/daily-update-design.md
T
2026-05-06 09:04:12 +08:00

777 lines
31 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-06
**版本**: v2.0
**状态**: 待评审(重大架构变更)
---
## 一、背景与目标
### 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` = 日线
- `15m` = 15分钟线(v1.1修正:与司马懿确认,采用方案B)
**方案B实现**2026-05-03 司马懿评审确认):
1. vnpy Interval枚举加 `MINUTE_15 = "15m"`monkey patch方式注入,不依赖vnpy版本)
2. executor INTERVAL_MAP 改 `"15m" = Interval.MINUTE_15`
3. DB迁移:`UPDATE dbbardata SET interval="15m" WHERE interval="1m" AND ...`
4. DB中现有"1m"数据需要一次迁移(迁移前备份)
---
## 五、多数据源保留策略
### 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 | 初始版本 | 赵云 |
| 2026-05-03 | v1.1 | 司马懿评审后修改:interval→15m, 严格增量追加, 日线进度文件, 全局源检测, DB轮转备份, 失败率告警 | 赵云 |
| 2026-05-05 | v1.2 | 东方财富集成:日线主源切换为东方财富(amount真实,反爬策略4s/请求+随机抖动), 腾讯降为备源 | 赵云 |
| 2026-05-06 | v2.0 | **重大架构变更**:BaoStock替代所有主源(无反爬、全量历史、amount真实);15min interval改为1mvnpy DB写入改为本地构建+rsync;新浪API已挂移除;多源fallback机制重构 | 赵云 |
---
## 十三、评审结果(2026-05-03 司马懿评审)
### v1.1 评审结论:有条件通过(已完成)
**2个阻塞项(已解决)**
1. ✅ interval="1m" → "15m":采用方案Bvnpy加MINUTE_15枚举 + monkey patch
2. ✅ 15min增量合并:改为严格按日期追加,不再用drop_duplicates keep=last
**4个硬伤(已修复)**
1. ✅ 日线增加进度文件
2. ✅ 全局源不可用检测(连续30只首次失败→终止)
3. ✅ DB轮转备份(保留7天,`quant_trading_{YYYYMMDD}.db.bak`
4. ✅ 失败率>5%告警标记 + 源终止告警
---
## 十四、v2.0 重大架构变更(2026-05-06
### 14.1 变更背景
v1.2运行暴露了5个根本性问题:
| # | 问题 | 根因 | 影响 |
|---|------|------|------|
| 1 | vnpy DB写入报`no such table: dbbardata` | SMB文件锁与SQLite ATTACH不兼容 | 日线+15min数据不入DB |
| 2 | 新浪15min API已失效 | 返回Error 0,所有请求失败 | 15min无法增量更新 |
| 3 | BaoStock 15min回补已完成但未入库 | 原脚本interval=`15m`与vnpy不兼容 | 5193只×8992条数据闲置 |
| 4 | 日线跨年写入bug | `year=datetime.now().year`硬编码 | 年初数据会写错目录 |
| 5 | overview全表聚合 | 1.4G DB上GROUP BY全表扫描 | NAS上可能超时/锁死 |
### 14.2 数据源重新调研
#### 数据源实测对比
| 数据源 | 15min可获取量 | 日线可获取量 | amount | 反爬 | 频率 | 当前状态 |
|--------|-------------|-------------|--------|------|------|----------|
| **BaoStock** | 无限制(按日期) | 无限制(按日期) | 真实(5.54亿) | **无** | 0.12s/只, 100只0错误 | ✅ 稳定 |
| **东方财富** | ~496条(7周) | 多年(~1046条) | 真实(5.54亿) | 4-5s/请求+UA+Referer | 中 | ✅ 可用 |
| **腾讯** | Connection reset | 按日期范围 | 有时为0 | 无 | 快 | ⚠️ 不稳定 |
| **新浪** | Error/2条 | Error/2条 | - | - | - | ❌ 已挂 |
**关键结论**:BaoStock在所有维度都最优(无反爬、全量历史、amount真实、速度快),应作为首选源。
#### v1.2 BaoStock压力测试
```
15min: 100只连续请求, 总耗时11.9s, 平均0.12s/只, 0错误
日线: 10只连续请求, 总耗时1.6s, 平均0.16s/只
全历史: sh.600000 2010-2026日线 3963条, 0.68s
```
#### v1.2 SQLite本地写入性能
```
100万条INSERT OR REPLACE: 2.0s
预估4600万条(15min全量): ~91s ≈ 1.5分钟
```
### 14.3 v2.0 核心架构变更
#### 变更1:数据源降级链重构
**设计原则**:按数据质量排序,质量最好的源排第一。每个源封装独立函数,统一返回DataFrame。主循环挨个尝试,成功即用,失败试下一个。
```
v1.x(旧):
日线: 腾讯(主) → 新浪(备)
15min: 新浪(主) → 无备源
v2.0(新):
日线: BaoStock(主) → 东方财富(备) → 腾讯(三备)
15min: BaoStock(主) → 东方财富(备) → 新浪(三备,当前已挂)
```
**Fallback机制**
```python
SOURCES_DAILY = [
("baostock", fetch_baostock_daily), # 最优:全量历史+无反爬+amount真实
("eastmoney", fetch_eastmoney_daily), # 备用:多年历史+amount真实+4s限频
("tencent", fetch_tencent_daily), # 三备:amount有时为0
]
SOURCES_15MIN = [
("baostock", fetch_baostock_15min), # 最优
("eastmoney", fetch_eastmoney_15min), # 备用:7周
("sina", try_sina_15min), # 三备:当前已挂
]
def fetch_with_fallback(sources, code, start, end):
for name, fetch_fn in sources:
try:
data = fetch_fn(code, start, end)
if data is not None and len(data) > 0:
return data, name
except Exception:
continue
return None, None
```
#### 变更2:vnpy DB写入策略改为本地构建+rsync
**v1.x方案(ATTACH via SMB**:直接在Mac上ATTACH NAS DB → SMB文件锁导致失败
**v2.0方案(本地构建+rsync**
1. 每日更新时,从NAS cp当前DB到本地`/tmp/`
2. 所有增量数据写入本地DB
3. 验证完整性后,rsync覆盖NAS DB
4. 备份旧DB(轮转7天)
```python
def sync_db_to_nas():
# 备份
backup = f"quant_trading_{today}.db.bak"
shutil.copy2(str(VNPY_DB_PATH), str(VNPY_DB_PATH.parent / backup))
# rsync本地→NAS
os.system(f"rsync -av --progress {LOCAL_DB_PATH} {VNPY_DB_PATH}")
```
**性能预估**
- cp NAS DB到本地:~15秒(1.4G
- 增量写入本地DB:<1秒(日线)
- rsync覆盖NAS~15秒
- 全量15min导入(首次):~1.5分钟(4600万条)
#### 变更315min interval统一用`1m`
**v1.x**interval=`15m`(与vnpy 4.x不兼容)
**v2.0**interval=`1m`(姜维确认vnpy 4.x Interval.MINUTE.value=`1m`
**数据格式(姜维确认)**
- symbol: `000001`(纯代码)
- exchange: `SSE` / `SZSE`
- interval日线: `d`
- interval分钟线: `1m`
#### 变更4:日线跨年写入修复
**v1.x bug**`year = datetime.now().year`,年初数据写错目录
**v2.0**:按数据日期分目录
```python
def update_daily_parquet(code, new_data):
for yr in new_data["date"].str[:4].unique():
year_data = new_data[new_data["date"].str[:4] == yr]
parquet_path = DAILY_DIR / yr / f"{prefix}{clean}_daily.parquet"
# 合并写入...
```
#### 变更5overview增量更新
**v1.x**`SELECT ... FROM dbbardata GROUP BY` 全表扫描(1.4G DB上很慢)
**v2.0**:只更新本次涉及的symbol
```python
for sym, exc, ivl in affected_keys:
c.execute("""INSERT OR REPLACE INTO dbbaroverview
SELECT ?,?,?,COUNT(*),MIN(datetime),MAX(datetime)
FROM dbbardata WHERE symbol=? AND exchange=? AND interval=?""",
(sym, exc, ivl, sym, exc, ivl))
```
#### 变更6:进度文件加日期
**v1.x**:进度文件不区分日期,跨天可能跳过
**v2.0**`daily_20260506_progress.json`,每次运行独立进度
#### 变更7Cron fallback模型
**v1.x**:只用默认模型,配额用完则任务失败
**v2.0**:设置fallback模型(zhipu/glm-5.1),配额不足时自动降级
### 14.4 执行计划
#### 第1步:灌入现有数据到本地vnpy DB
```
1. cp NAS quant_trading.db → /tmp/quant_trading_import.db
2. import_vnpy_daily_fast.py --start-year 2026 # 补3/28~今天的日线增量
3. import_vnpy_minute.py --scope all # 全量导入5193只15min
4. 验证数据完整性
5. rsync本地DB → NAS
```
#### 第2步:重构daily_all_update.py
按14.3的7个变更点重构代码。
#### 第3步:Cron更新+测试
- 更新cron任务配置
- 手动触发一次全量更新验证
- 确认日志无错误
### 14.5 与v1.x的兼容性
| 变更 | 向后兼容 | 影响 |
|------|---------|------|
| interval 15m→1m | ❌ 需要DB迁移 | 现有15m数据需UPDATE为1m |
| DB写入策略 | ✅ 无影响 | Parquet不受影响 |
| 数据源顺序 | ✅ 无影响 | 只是重试顺序变化 |
| 跨年写入 | ✅ 修正bug | 未来数据不再错 |
| overview增量 | ✅ 无影响 | 只是优化 |
> ⚠️ **DB迁移注意**v1.x如果有`interval='15m'`的记录,需要一次性UPDATE为`'1m'`。当前DB中实际无15min数据(v1.x的写入全部失败),所以无需迁移。
---
## 十五、v2.0 评审待确认项
| # | 问题 | 建议方案 | 待确认 |
|---|------|---------|--------|
| 1 | BaoStock作为全主源是否合适? | 无反爬+全量+amount真实,建议通过 | 司马懿 |
| 2 | 本地构建+rsync替代ATTACH | 姜维确认推荐,比ATTACH稳定 | 司马懿 |
| 3 | interval=1m而非15m | 姜维确认vnpy 4.x规范 | 司马懿 |
| 4 | 是否需要DB迁移脚本? | 当前无15m数据,无需迁移 | 司马懿 |
| 5 | Fallback顺序是否合理? | BaoStock→东方财富→腾讯/新浪 | 司马懿 |
| 6 | 日常更新全市场耗时预估? | BaoStock: ~10min(15min)+~8min(日线)+rsync | 司马懿 |
| 7 | 是否需要额外反爬措施? | BaoStock无需,备源保留原有措施 | 司马懿 |
### 15.6 v2.0 评审结论(2026-05-06 司马懿)
**结论:全部通过,可以部署**
C1 interval=1m:姜维翻源码确认vnpy硬约束,接受。**附加前提:代码里所有写interval='1m'的地方必须加注释,说明这是vnpy 4.x Interval.MINUTE硬约束,实际存储15分钟线。**
C2 rsync原子性:改为写新文件+mv原子重命名。
M1 BaoStock T+1延迟:已验证确认。日常增量改为东方财富(当天实时) → BaoStock(T+1补全) → 腾讯。
M2 失败暂停:改为失败率检测(最近100只>80%切换源)。
**最终Fallback顺序(含T+1调整):**
```
日常增量(当天15:35触发):
日线:东方财富(实时) → BaoStock(T+1) → 腾讯
15min:东方财富(实时7周) → BaoStock(T+1) → 新浪
历史回补:
日线+15minBaoStock(全量历史,无反爬)
```