feat(data): 15min数据补全—路径参数化+cron入口+回填+设计文档

- download_minute.py: STOCK_ROOT环境变量参数化(Mac/NAS兼容); 修tencent备源amount聚合 last→sum
- refresh_15min_daily.py: 新建cron入口(交易日判断baostock/直连pop代理/日志)
- 回填470缺口(170 SH/SZ成功, 300 BSE源不支持)
- docs/data/15min-data-design.md: 15min数据层设计文档(源/覆盖/脚本/刷新/约束/路径映射)
This commit is contained in:
2026-07-12 08:26:30 +08:00
parent 10508db11d
commit cf4b3a2cb0
3 changed files with 332 additions and 4 deletions
+195
View File
@@ -0,0 +1,195 @@
# 15min 数据层设计文档
**项目**: sanguo_vnpy_v2 数据层
**日期**: 2026-07-122026-07-08~10 审计+回填后落地)
**范围**: A 股 15 分钟 K 线数据层(数据源、覆盖现状、脚本、刷新机制、硬约束、已知问题)
**相关文档**: [`docs/data-platform/daily-update-design.md`](../data-platform/daily-update-design.md)(日线+15min+vNpy DB 早期多源架构 v1~v3,本文件聚焦 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
```
增量刷新(每日 15:30 cron:
sina 15min(主,800 条/次)→ 腾讯 minute/query(备,仅当天)
历史回填(一次性):
baostock adjustflag=3(全量历史,2024-01-01 起,不含 BSE
```
---
## 二、覆盖现状(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-01sina 长期累积 + baostock 回填)|
| 新股(170 只) | = 上市日 | baostock 回填至各自上市日 |
| 5 年深度扩展(未来) | 2.5yr → 5yr | baostock 从 2020 起回填(大工程,未来阶段)|
### 2.2 BSE 缺口说明
- 300 只北交所股票(920xxxbaostock 和 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` 职责
1. pop 全部代理环境变量(`http_proxy/https_proxy/...`)保证直连
2. 交易日判断:周末短路;工作日用 baostock `query_trade_dates`,失败降级为"默认交易日"
3. 交易日 → `subprocess``download_minute.py --scope all --resume`,继承 `STOCK_ROOT`
4. 日志写 `$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`,路径在容器内不变:
```bash
docker exec sanguo_vnpy_v2 bash -c "cd /app && python3 scripts/data_platform/refresh_15min_daily.py"
```
### 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 |