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

12 KiB
Raw Blame History

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 版

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 探针:新浪中证历史成份
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 线

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:40daily_update_xtdata.py:114
u = xd.get_stock_list_in_sector("沪深A股") or []

改为

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 探针:退市股列表字段
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_basictype(1股)/status(1上市 0退市)/outDate(退市日期)。筛 status=0 & outDate>='2021-01-01'

  • 3.2 实现 baostock_delisted_download.py:

    • 遍历全 code(或 query_all_stock 多日并集)→ query_stock_basicstatus=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.pyparse_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