15min 数据层设计文档
项目: sanguo_vnpy_v2 数据层
日期: 2026-07-12(2026-07-0810 审计+回填后落地)
范围: A 股 15 分钟 K 线数据层(数据源、覆盖现状、脚本、刷新机制、硬约束、已知问题)
相关文档: docs/data-platform/daily-update-design.md(日线+15min+vNpy DB 早期多源架构 v1v3,本文件聚焦 15min 最终落地的实现)
一、数据源
| 源 |
用途 |
协议 |
特点 |
| 新浪财经 15min API |
主源·增量刷新 |
HTTP |
datalen=800(≈2.5 个月)/ 次,有真实 amount,不复权,无法回填历史 |
| 腾讯 minute/query + 聚合 |
备源·仅当天 |
HTTP |
sina 失败时用,拉 1min 聚合成 15min |
| baostock |
历史回填 |
TCP |
adjustflag=3 不复权,2024-01-01 起,0.4s/票,单连接,不支持 BSE |
1.1 新浪 15min API(主源)
- URL:
https://quotes.sina.cn/cn/api/jsonp_v2.php/.../CN_MarketDataService.getKLineData?symbol={symbol}&scale=15&ma=no&datalen=800
datalen 最大有效值 800(超过返回 null),即 15min ≈ 2.5 个月
- 字段:
day, open, high, low, close, volume, amount,amount 为真实成交额
- 时间戳为 end-of-bar 格式(09:45, 10:00 ...)
- 返回 JSONP,需正则提取 JSON 数组
- 不复权,无法指定起始日期 → 只能增量刷新最近 2.5 月,不能回填更早历史
1.2 腾讯 minute/query(备源)
- URL:
http://web.ifzq.gtimg.cn/appstock/app/minute/query?code={symbol}
- 仅返回当天 1min 数据,聚合为 15min(
_aggregate_1m_to_15m)
- 仅在 sina 主源失败时兜底
1.3 baostock(历史回填)
query_history_k_data_plus,adjustflag="3"(不复权,与 sina 主源一致)
- 起点 2024-01-01,可按日期范围全量拉取
- 0.4s/票,单连接(并发会崩),每 400 票 relogin 防断会话
- 不支持 BSE(北交所 920xxx)
1.4 源降级链(15min)
二、覆盖现状(2026-07-12 审计+回填后)
| 维度 |
数值 |
| 全市场 universe |
5493 |
| 主板 SH/SZ 覆盖 |
5193 = 5023 老股(≥2.5 年,sina 长期累积)+ 170 新股(baostock 回填至上市日) |
| BSE 北交所缺口 |
300(baostock + sina 均不支持,待 akshare/腾讯另接) |
| 15min 主目录文件数 |
5403 |
| 数据新鲜度 |
2026-07-08 ~ 2026-07-10 |
2.1 数据深度
| 股票类型 |
深度 |
起点 |
| 成熟股(5023 只) |
≈ 2.5 年 |
2024-01-01(sina 长期累积 + baostock 回填) |
| 新股(170 只) |
= 上市日 |
baostock 回填至各自上市日 |
| 5 年深度扩展(未来) |
2.5yr → 5yr |
baostock 从 2020 起回填(大工程,未来阶段) |
2.2 BSE 缺口说明
- 300 只北交所股票(920xxx)baostock 和 sina 均不支持
- 多为小盘新股,多数策略可剔除
- 待后续用 akshare / 腾讯另接(东财接口有封 IP 风险,建议按需)
三、脚本清单(scripts/data_platform/)
| 脚本 |
作用 |
关键点 |
download_minute.py |
sina 增量刷新 |
STOCK_ROOT 环境变量参数化(默认 /Volumes/stock,NAS 用 /volume1/stock);0.3s/票单线程;断点续传 download_progress.json;--scope all/hs300、--codes、--resume |
backfill_15min_baostock.py |
baostock 历史回填 |
全量重建 + 备份 backup_sina/;adjustflag=3;0.4s/票;marker 防重;每 400 票 relogin |
refresh_15min_daily.py(新) |
cron 入口 |
pop 代理直连;交易日判断(周末短路 + baostock query_trade_dates);调 download_minute --scope all --resume;日志 $STOCK_ROOT/logs/daily_update/ |
3.1 download_minute.py 关键参数
| 参数 |
值 / 说明 |
STOCK_ROOT |
os.environ.get("STOCK_ROOT", "/Volumes/stock")(line 47) |
OUTPUT_DIR |
$STOCK_ROOT/minute_kline/15min |
REQUEST_INTERVAL |
0.3s |
MAX_RETRIES |
3 |
| 连续失败暂停 |
5 次连续失败 → 暂停 60s |
| 写入策略 |
增量合并:concat + drop_duplicates(subset=["day"], keep="last") + 原子写 .tmp → rename |
3.2 backfill_15min_baostock.py 关键参数
| 参数 |
值 / 说明 |
NAS_ROOT |
硬编码 /Volumes/stock(line 43,待参数化) |
adjustflag |
"3"(不复权,line 131) |
RELOGIN_EVERY |
400 祒(line 268) |
| 防重 marker |
.{stem}.baostock 空文件(line 98/208) |
| 旧数据备份 |
$MINUTE_15_DIR/backup_sina/ |
3.3 refresh_15min_daily.py 职责
- pop 全部代理环境变量(
http_proxy/https_proxy/...)保证直连
- 交易日判断:周末短路;工作日用 baostock
query_trade_dates,失败降级为"默认交易日"
- 交易日 →
subprocess 调 download_minute.py --scope all --resume,继承 STOCK_ROOT
- 日志写
$STOCK_ROOT/logs/daily_update/refresh_15min_YYYYMMDD.log
四、刷新机制(本次新落地)
4.1 调度
- NAS Synology 任务计划,每交易日 15:30(A 股 15:00 收盘后半小时)
- 之前无任何自动刷新(crontab / Synology / 容器 cron 全空),7-08~10 的数据新鲜是手动跑的;本次补 cron
4.2 执行命令
容器 sanguo_vnpy_v2 bind-mount /volume1/stock,路径在容器内不变:
4.3 交易日判断
- 周末(weekday ≥ 5)→ 直接跳过
- 工作日 → baostock
query_trade_dates 查节假日
- baostock 不可用 → 降级为"工作日默认交易日"(非交易日跑也只是全部 skip,幂等)
五、硬约束
来源:CLAUDE.md 全局约定 + 数据下载经验(见 MEMORY.md)
| 约束 |
说明 |
实现 |
| 直连不走代理 |
避免被识别为异常流量 / akshare 代理污染 |
脚本入口 pop http_proxy/https_proxy/...;download_minute._make_opener() 用 ProxyHandler({}) |
| 单线程限速,0 并发 |
baostock 单连接并发会崩;新浪猛打封 IP |
sina 0.3s/票,baostock 0.4s/票,无并发 |
| 间隔别太大 |
baostock 长空闲断会话 |
sina 0.3s / baostock 0.4s |
| NAS 内存紧 |
swap 近满,分块+断点续传,别全市场并发 |
历史踩过 macOS Jetsam 崩溃(见 MEMORY 数据下载崩溃教训) |
| 见空就停 |
连续 5 空 = 会话掉了 |
MAX_CONSECUTIVE_FAILS=5 → 暂停 60s |
六、路径映射表(Mac / NAS / 容器 三端)
| 端 |
STOCK_ROOT |
15min 目录 |
| Mac 开发 |
/Volumes/stock(NAS 挂载) |
/Volumes/stock/minute_kline/15min |
| NAS host |
/volume1/stock |
/volume1/stock/minute_kline/15min |
容器 sanguo_vnpy_v2 |
/volume1/stock(bind-mount) |
同 NAS |
对应 config/data_platform.yaml 路径键:minute_15_dir: /volume1/stock/minute_kline/15min(容器/NAS 视角)。脚本通过 STOCK_ROOT 环境变量切换,不写死。
七、已知问题 / 后续
| 优先级 |
问题 |
说明 |
处理 |
| HIGH bug |
download_minute.py _aggregate_1m_to_15m 的 amount=("amount","last") 应为 "sum" |
腾讯备源路径 amount 聚合错误(sina 主源不受影响) |
待修(line 147) |
| MEDIUM |
BSE 920 缺口 300 只 |
baostock + sina 均不支持 |
待 akshare/腾讯另接(东财有封 IP 风险,建议按需) |
| LOW |
5 年深度扩展(5023 老股 2.5yr → 5yr) |
baostock 从 2020 起回填,大工程 |
未来阶段 |
| LOW |
backfill_15min_baostock.py 的 NAS_ROOT 仍硬编码 /Volumes/stock |
Mac 视角写死,NAS 跑需手改 |
建议后续也参数化为 STOCK_ROOT |
八、相关文件索引
| 文件 |
路径 |
说明 |
| 15min 主目录 |
$STOCK_ROOT/minute_kline/15min/ |
5403 个 _15min.parquet 文件 |
| 断点续传进度 |
$STOCK_ROOT/minute_kline/15min/download_progress.json |
download_minute.py --resume 用 |
| 审计清单 |
/volume1/stock/minute_kline/15min/backfill_target.json |
470 个回填目标 |
| 回填进度 |
/volume1/stock/minute_kline/15min/backfill_470_progress.json |
done=170, bse_unsupported=300 |
| 旧文件备份 |
/volume1/stock/minute_kline/15min/backup_sina/ |
baostock 全量重建前的 sina 旧数据 |
| 日刷新日志 |
$STOCK_ROOT/logs/daily_update/refresh_15min_YYYYMMDD.log |
cron 每日产出 |
| 配置 |
config/data_platform.yaml |
minute_15_dir 路径键 |
| 配置加载 |
sanguo_data/config.py |
DataConfig.data_paths["minute_15_dir"] |
变更记录
| 日期 |
变更 |
作者 |
| 2026-07-12 |
初始版本:15min 数据层落地后真实数据(5493 universe / 5193 覆盖 / 300 BSE 缺口 / cron 15:30 / sina主源+baostock回填) |
文档 Sub Agent |