31 KiB
数据平台每日增量更新 — 详细设计文档
项目: 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 目标
- 每日自动增量更新:交易日收盘后自动更新日线+15min数据
- 多数据源整合:保留所有数据源访问方式,取各源最优数据合并
- 数据最大化:历史数据尽量完整,增量数据每日累积
- 部署集成:最终整合到 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 Node(192.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 参考
-- 主数据表
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 司马懿评审确认):
- vnpy Interval枚举加
MINUTE_15 = "15m"(monkey patch方式注入,不依赖vnpy版本) - executor INTERVAL_MAP 改
"15m" = Interval.MINUTE_15 - DB迁移:
UPDATE dbbardata SET interval="15m" WHERE interval="1m" AND ... - 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 设计原则
- 所有数据源接口统一保留,不删除任何已有的数据源访问代码
- 数据合并策略:同一股票同一周期从多个源获取时,按优先级选择:
- amount(成交额):优先有真实值的源(新浪 > 腾讯)
- 数据长度:优先历史更长的源
- 数据时效:优先更新的源
- 源降级链:主源失败自动尝试备源,不丢数据
- 源标记: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 部署步骤(草案)
- 代码从
~/.openclaw/sanguo_projects/迁移到sanguo_vnpy/src/data_platform/ - 配置外置为YAML文件
- Docker容器内置crontab + Python脚本
- 容器挂载NAS数据目录
- 与现有vnpy回测服务docker-compose整合
九、测试
9.1 已完成的测试
| 测试项 | 结果 | 日期 |
|---|---|---|
| 日线增量更新3只(000001/600519/300750) | ✅ 3只skipped(已是最新) | 2026-05-03 |
| 15min增量更新3只 | ✅ 3只ok,0 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 |
| 东方财富API(Windows) | 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更快)
- 15min:5300只 × 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整合?
理由:
- 数据平台是为vnpy回测服务的,放一起管理方便
- Docker统一部署,减少环境依赖
- 配置集中管理(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改为1m;vnpy DB写入改为本地构建+rsync;新浪API已挂移除;多源fallback机制重构 | 赵云 |
十三、评审结果(2026-05-03 司马懿评审)
v1.1 评审结论:有条件通过(已完成)
2个阻塞项(已解决):
- ✅ interval="1m" → "15m":采用方案B(vnpy加MINUTE_15枚举 + monkey patch)
- ✅ 15min增量合并:改为严格按日期追加,不再用drop_duplicates keep=last
4个硬伤(已修复):
- ✅ 日线增加进度文件
- ✅ 全局源不可用检测(连续30只首次失败→终止)
- ✅ DB轮转备份(保留7天,
quant_trading_{YYYYMMDD}.db.bak) - ✅ 失败率>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机制:
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):
- 每日更新时,从NAS cp当前DB到本地
/tmp/ - 所有增量数据写入本地DB
- 验证完整性后,rsync覆盖NAS DB
- 备份旧DB(轮转7天)
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万条)
变更3:15min 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:按数据日期分目录
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"
# 合并写入...
变更5:overview增量更新
v1.x:SELECT ... FROM dbbardata GROUP BY 全表扫描(1.4G DB上很慢)
v2.0:只更新本次涉及的symbol
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,每次运行独立进度
变更7:Cron 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) → 新浪
历史回补:
日线+15min:BaoStock(全量历史,无反爬)