Files
sanguo_vnpy_v2/docs/archive/data/2026-07-21-data-fusion-p0.md
T
claude_dev c3e53fbef3 docs(data): 归档数据层验证产物 + 数据层总览README
- scripts/data_platform/_archive/legacy/: 归档20个独立探针/诊断/旧降级脚本(零引用验证)
- docs/archive/data/: 归档17个数据相关旧设计/plan/report(保留fusion spec作深读)
- docs/data-platform/README.md: 数据层单一权威记录(8节:架构/布局/源/管线/铁律/API/缺口/待办)
- 删除 _mootdx_depth_result.txt
- Phase2待办: 15m灌库链+旧回填import链(有测试/wrapper依赖,VPS schtask确认后归档)
2026-07-29 10:11:38 +08:00

222 lines
12 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.
# P0 数据补全实现计划(历史成份股 + ETF 全市场 + 退市 K 线)
> **For agentic workers:** 用 superpowers:subagent-driven-development 或 executing-plans 执行。Steps 用 `[ ]` 跟踪。
**Goal:** 补齐治幸存者偏差 + 策略核心缺口三类数据,落到 VPS 本地。
**Architecture:** 各源采集脚本 → staging parquet → 验证探针 → 合并主库;baostock 单登录守 48000/天;dbbardata 不动。
**Tech Stack:** python3.10 / akshare / baostock / xtquant(xtdata)/ pandas / pyarrow / sqlite3
---
## Global Constraints(所有 task 隐含)
- **baostock 单进程单登录**,不并发(防黑名单,日 ≤48000 query)
- **直连不走代理**:`$env:http_proxy=''; $env:https_proxy=''; $env:all_proxy=''`
- **dbbardata 不破坏**:只 INSERT OR REPLACE `daily_baostock_full` / 新表,不动 dbbardata 既有行
- **优先 baostock + miniQMT(xtdata)**
- **staging → 验证探针 → 合并主库**(用户铁律,不直接写主库)
- Windows VPS 49.232.102.198,`C:\Python310\python.exe -X utf8`,schtasks `/ru SYSTEM`
- 输出根:`C:\sanguo_vnpy_v2\data\`
---
## File Structure
| 文件 | 责任 |
|---|---|
| `scripts/data_platform/index_const_hist_download.py`(新) | 历史成份股采集(akshare 国证 + 新浪 + baostock 补时点) |
| `scripts/data_platform/build_daily_from_xtdata.py`(改 :40) | ETF universe 扩展(一次性全量) |
| `scripts/data_platform/daily_update_xtdata.py`(改 :114) | ETF 每日增量 universe |
| `scripts/data_platform/baostock_delisted_download.py`(新) | 退市股列表 + K 线采集 |
| `scripts/data_platform/import_delisted_to_db.py`(新) | 退市 K 线灌 `daily_baostock_full` |
| 各 `*_wrapper.ps1` + schtask | 部署 |
---
## Task 1: 历史成份股采集(治幸存者偏差)
**Files:** Create `scripts/data_platform/index_const_hist_download.py`;Output `data/index_const_hist/{code}.parquet`
**Interfaces:**
- Consumes: akshare `index_detail_hist_cni(symbol)` + `index_detail_hist_adjust_cni(symbol)`(国证源);新浪 `vII_HistoryComponent`(pandas.read_html, gb2312);baostock `query_hs300/zz500/sz50_stocks(date)`
- Produces: `data/index_const_hist/{code}.parquet`(列:`updateDate/index_code/code/code_name/adjust_type`);并集 = 曾经入选集
**指数清单:**
- 深证/国证(akshare 国证源):399001 / 399006 / 399101 / 399005 / 399330
- 中证(新浪):000852(中证1000)/ 932000(中证2000)/ 000300(交叉校验)/ 000016(上证50)
- baostock 已有(300/500/50 在 `bs_index_constituent`):Task1 补时点序列到同 schema
- [ ] **1.1 探针:akshare 国证源 hist 版**
```python
import akshare as ak
df = ak.index_detail_hist_cni(symbol="399101") # 历史样本(日期/样本代码/权重)
print(df.columns.tolist(), len(df), df.head(3))
adj = ak.index_detail_hist_adjust_cni(symbol="399101") # 调样记录(调整类型 OLD/+/-)
print(adj.columns.tolist(), len(adj))
```
预期:hist 有日期+样本+权重;adjust 有调整类型。**陷阱:必须 hist 版**(`index_detail_cni` 非 hist 版 2025-11-25 起只近期);`ak.index_stock_hist` 已下线别用。
- [ ] **1.2 探针:新浪中证历史成份**
```python
import pandas as pd
url = "http://vip.stock.finance.sina.com.cn/corp/go.php/vII_HistoryComponent/indexid/000852.phtml"
df = pd.read_html(url, encoding="gb2312")[0]
print(df.columns.tolist(), len(df), df.head(3))
```
预期:品种代码/品种名称/纳入日期/剔除日期(空=至今在列),含 *ST/退市股。
- [ ] **1.3 实现 `index_const_hist_download.py`**:三路采集 → 统一 schema(`updateDate/index_code/code/code_name/adjust_type`)→ 写 `data/index_const_hist/{code}.parquet`。串行 `time.sleep(0.8)`(akshare/新浪防封),单进程。环境变量 `BS_INDEX_HIST_OUT_DIR` 覆盖默认 Mac 路径(同 Day1 wrapper 模式)。
- [ ] **1.4 验证探针**:每指数 parquet 行数 + 抽样 3 行;**幸存者偏差校验** = 并集 `distinct code` 数 > 当前成份股数(证明含被踢股,例如 399101 并集 > 958 当前)。
- [ ] **1.5 wrapper + schtask**:`index_const_hist_wrapper.ps1`(设 OUT_DIR + utf8 + unset proxy + log);schtask `sanguo-index-hist` `/sc monthly /mo 2`(半年度调样后,6/12 月)`/ru SYSTEM`
- [ ] **1.6 commit**:`git add scripts/data_platform/index_const_hist_download.py scripts/data_platform/index_const_hist_wrapper.ps1 && git commit -m "feat(data): 历史成份股采集(治幸存者偏差,国证+新浪+baostock)"`
---
## Task 2: ETF 全市场日线
**Files:** Modify `scripts/data_platform/build_daily_from_xtdata.py:40` + `daily_update_xtdata.py:114`
**Interfaces:**
- Consumes: xtdata `get_stock_list_in_sector('沪深A股'/'沪深ETF'/'沪深基金')` + `get_market_data_ex(dividend_type='front')`
- Produces: 全市场 ETF(~1000 只)日线**前复权**,落 parquet/dbbardata(复用现有 xtdata 管线)
- [ ] **2.1 探针:ETF universe + 1 只 K 线**
```python
from xtquant import xtdata as xd
etf = xd.get_stock_list_in_sector('沪深ETF') or []
fund = xd.get_stock_list_in_sector('沪深基金') or []
a = xd.get_stock_list_in_sector('沪深A股') or []
u = list(set(a + etf + fund))
print(f"A={len(a)} ETF={len(etf)} fund={len(fund)} union={len(u)}")
r = xd.get_market_data_ex([], ['510300.SH'], period='1d',
start_time='20240101', end_time='20260721', dividend_type='front')
df = r.get('510300.SH')
print('510300 bars:', 0 if df is None else len(df), '| tail close:', None if df is None else df['close'].iloc[-1])
```
预期:ETF ~1000,union > A 股数;510300 前复权日线有值,close 非 NaN。
- [ ] **2.2 改 universe**:`build_daily_from_xtdata.py:40``daily_update_xtdata.py:114`
```python
u = xd.get_stock_list_in_sector("沪深A股") or []
```
改为
```python
u = list(set(
(xd.get_stock_list_in_sector("沪深A股") or []) +
(xd.get_stock_list_in_sector("沪深ETF") or []) +
(xd.get_stock_list_in_sector("沪深基金") or [])
))
```
保留 `dividend_type='front'`(前复权,§13 默认)。
- [ ] **2.3 全量下载 ETF**:跑改后的 `build_daily_from_xtdata.py`(走现有 xtdata 管线,**无限流**)→ parquet。
- [ ] **2.4 验证**:ETF 数 + 抽样(510300/513050/159919)+ 前复权 close 非 NaN + 日期范围。
- [ ] **2.5 schtask**:复用 `sanguo-daily-update`(universe 扩展后自动含 ETF,无需新 schtask)。
- [ ] **2.6 commit**:`git commit -m "feat(data): ETF 全市场日线(xtdata universe 扩展+前复权)"`
---
## Task 3: 退市股 K 线(反幸存者偏差核心)
**Files:** Create `scripts/data_platform/baostock_delisted_download.py` + `import_delisted_to_db.py`;Output → `daily_baostock_full`
**Interfaces:**
- Consumes: baostock `query_all_stock(day)` + `query_stock_basic(code)`(status + 退市日期)+ `query_history_k_data_plus(code, fields, adjustflag=3)`
- Produces: 退市股 K 线 INSERT OR REPLACE `daily_baostock_full`(18 列,复用 `parse_baostock_code`)
**范围:** 近 5 年退市(退市日期 ≥ 2021;守 48000/天;退市股分天跑)
- [ ] **3.1 探针:退市股列表字段**
```python
import baostock as bs, pandas as pd
bs.login()
rs = bs.query_all_stock(day="2026-07-18")
rows = []
while (rs.error_code == '0') & rs.next():
rows.append(rs.get_row_data())
df = pd.DataFrame(rows, columns=rs.fields)
print('query_all_stock fields:', rs.fields, '| rows:', len(df))
rs2 = bs.query_stock_basic(code="sh.600000")
b = []
while (rs2.error_code == '0') & rs2.next():
b.append(rs2.get_row_data())
print('query_stock_basic fields:', rs2.fields, '| sample:', b[0] if b else None)
bs.logout()
```
预期:`query_stock_basic``type`(1股)/`status`(1上市 0退市)/`outDate`(退市日期)。筛 `status=0 & outDate>='2021-01-01'`
- [ ] **3.2 实现 `baostock_delisted_download.py`**:
- 遍历全 code(或 `query_all_stock` 多日并集)→ `query_stock_basic``status=0 & outDate>='2021-01-01'` → 退市股列表
- 逐只 `query_history_k_data_plus(code, start_date='1990-01-01', end_date=outDate, fields=18字段, adjustflag=3)` → staging `data/delisted_kline/{code}.parquet`
- 单进程单登录,`time.sleep` 守预算,marker 断点续传(复用 Day1 模板),DAILY_LIMIT 计数器
- [ ] **3.3 `import_delisted_to_db.py`**:staging → INSERT OR REPLACE `daily_baostock_full`(复用 `parse_baostock_code` sh.600000→600000+SH + `executemany`,WAL + busy_timeout=60000,同 `import_baostock_to_db.py`)。**dbbardata 不碰**。
- [ ] **3.4 验证探针**:退市股数 + 抽样(某退市股 K 线行数 + max(date) ≤ 退市日)+ `daily_baostock_full` 行数增量 + distinct symbol 增量。
- [ ] **3.5 wrapper + schtask**:`baostock_delisted_wrapper.ps1`;schtask `sanguo-delisted` `/sc monthly /ru SYSTEM`(月度,守 48000,错开 day2b 02:00 + bs-daily-increment 17:00)。
- [ ] **3.6 commit**:`git commit -m "feat(data): 退市股 K 线采集(baostock,反幸存者偏差)"`
---
## Task 4: baostock 日增量 → daily_baostock_full(#7 daily_update_static)
> **串行约束**:本 task 与 Task3 都用 baostock 长会话,**必须串行**(Task3 probe → Task3 执行 → Task4),不可并发(防黑名单)。
**Files:** Create `scripts/data_platform/daily_update_static.py` + `daily_update_static_wrapper.ps1`
**背景:** 现有 `daily_update_xtdata.py` 只产 parquet 不灌 `daily_baostock_full`(已知 gap,memory `db-primary-parquet-fallback` 记录)。本 task 补 baostock 日线的**每日增量灌库**。
**Interfaces:**
- Consumes: baostock `query_stock_basic`(全 A,type=1 含退市,复用 `baostock_daily_fullmarket_download.py:fetch_all_stocks`)+ `query_history_k_data_plus`(LOOKBACK 窗口,adjustflag=3 raw,18 字段同 `BS_FIELDS`)
- Produces: staging `data/daily_baostock_increment/{YYYYMMDD}/{code}.{exc}_daily.parquet`(审计)→ 同进程 INSERT OR REPLACE `daily_baostock_full`(复用 `parse_baostock_code`+executemany+WAL+busy_timeout,同 `import_baostock_to_db.py`)
**设计(LOOKBACK 窗口 + 幂等,不同于全量 marker 模式):**
- **不用 marker 断点续传**(全量才需要;增量每日全量重拉最近 N 天)
- `LOOKBACK_DAYS=7`(覆盖周末/节假日;baostock 日终更新,17:00 跑时当日 bar 已就绪)
- 每只 1 query → 5537 query/run ≪ 48000/天 ✅(留足余量给 day2b/Task3)
- `sleep 0.4s × 5537 ≈ 37min`(17:00 schtask 可接受)
- `QUERY_COUNT` 计数器 + `DAILY_LIMIT=40000` 防御(复用全量脚本模式)
- **一脚本贯通**:download LOOKBACK → staging parquet(审计)→ in-memory df → executemany INSERT OR REPLACE(幂等,重复跑同一天安全,`drop_duplicates keep last` 不需要因 PK+OR REPLACE 天然去重)
**Steps:**
- [ ] **4.1 探针(可选,Day1 已实证 query_history_k_data_plus 可用)**:ssh VPS 跑 1 只近 7 天确认接口 + 当日 bar 就绪
- [ ] **4.2 写 `daily_update_static.py`**:自包含,结构
- `unset proxy` + `socket.setdefaulttimeout(30)`(同全量脚本,baostock 坑)
- `_login_once`/`_relogin`/`fetch_all_stocks`/`fetch_one_daily`/`parse_baostock_code` 复用(可 import 或复制;优先 from `baostock_daily_fullmarket_download import ...`,注意 `QUERY_COUNT` global 需在同进程)
- `LOOKBACK` 窗口:`start=today-7, end=today`
- 主循环:逐只 `fetch_one_daily` → staging parquet → 累积 df → 每 100 只 `executemany INSERT OR REPLACE`(WAL+busy_timeout=60000)
- `QUERY_COUNT`/`DAILY_LIMIT`/断路器/定期重登 复用
- 结束 verify:抽样 3 只 `max(date) ≈ today`、当日新增行数
- 环境变量 `BS_INCREMENT_OUT_DIR`/`DB_PATH` 覆盖默认(同 Day1 wrapper 模式适配 Win)
- [ ] **4.3 小样本**:`--limit 10` 跑 10 只,确认 staging 有行 + DB 抽样 max(date)≈today
- [ ] **4.4 全量跑**:5537 只,守预算
- [ ] **4.5 wrapper + schtask**:`daily_update_static_wrapper.ps1`(unset proxy+utf8+OUT_DIR+log);schtask `sanguo-bs-daily-increment` `/sc daily /st 17:00 /ru SYSTEM`(错开 daily-update 16:30 + day2b 02:00 + Task3 月度)
- [ ] **4.6 commit**:`git commit -m "feat(data): baostock 日增量灌库 daily_update_static(#7 gap 补)"`
---
## Self-Review
- **Spec 覆盖**:Task1→spec §4 成份股行 + §8 P0.1;Task2→§4 ETF 行 + §8 P0.2;Task3→§4 退市行 + §8 P0.3 ✅
- **Placeholder 扫描**:无 TBD/TODO;采集脚本给接口+探针+schema,实现者按骨架写完整(采集脚本完整代码由执行 agent 基于 接口/schema/陷阱 产出)✅
- **类型一致**:`index_const_hist` schema 各源统一;`daily_baostock_full` 18 列复用 `import_baostock_to_db.py``parse_baostock_code`+executemany ✅
- **陷阱纳入**:`ak.index_stock_hist` 下线(1.1 标注)/ csindex SPA 无历史(用国证+新浪)/ 新浪 gb2312(1.2)/ hist 版必须(1.1)✅
---
## Execution Handoff
计划存 `docs/superpowers/plans/2026-07-21-data-fusion-p0.md`。执行方式:
1. **Subagent-Driven**(推荐):每 Task 派 fresh agent + task 间 review
2. **Inline**:本 session 批量执行 + checkpoint