- 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确认后归档)
12 KiB
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);baostockquery_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);schtasksanguo-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:40和daily_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_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)→ stagingdata/delisted_kline/{code}.parquet - 单进程单登录,
time.sleep守预算,marker 断点续传(复用 Day1 模板),DAILY_LIMIT 计数器
- 遍历全 code(或
-
3.3
import_delisted_to_db.py:staging → INSERT OR REPLACEdaily_baostock_full(复用parse_baostock_codesh.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;schtasksanguo-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 REPLACEdaily_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 或复制;优先 frombaostock_daily_fullmarket_download import ...,注意QUERY_COUNTglobal 需在同进程)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);schtasksanguo-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_histschema 各源统一;daily_baostock_full18 列复用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。执行方式:
- Subagent-Driven(推荐):每 Task 派 fresh agent + task 间 review
- Inline:本 session 批量执行 + checkpoint