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

23 KiB
Raw Blame History

数据平台每日增量更新 — 详细设计文档

项目: sanguo_vnpy 数据平台
作者: 赵云(数据总管)
日期: 2026-05-03
版本: v1.1
状态: 评审通过,代码已修改


一、背景与目标

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 参考

-- 主数据表
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-03 司马懿评审)

评审结论:有条件通过

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%告警标记 + 源终止告警

8个待评审项确认

  1. SMB性能:V1方案AMac→SMB),V1.1切方案BNAS本地执行)
  2. DB同步DockerV1手动,V2用cron job
  3. 数据源标记:V1不加
  4. amount为0:V1保留腾讯0值,记录告警
  5. interval:已改为"15m"
  6. 部署整合:P2不急
  7. DB备份:已实现轮转备份
  8. 错误告警:已实现失败率阈值