Files
sanguo_vnpy_v2/docs/superpowers/specs/2026-07-21-data-source-fusion-design.md
T
claude_dev c2a89d01a4 feat(data): 方案A 数据层落地(spec §14)— DB唯一表+治幸存者偏差+权威源
spec §14 方案A 数据层迁移完成 + E2E 验证(read_db_daily: 在市/退市治偏差/ETF 全OK):
- dbbardata('d') 1826万含退市(治回测幸存者偏差, INSERT OR REPLACE staging迁移, WHERE OHLC NOT NULL+COALESCE)
- constituent_unified 7110行/9指数(300/500/50 baostock全集 + 深证4指 akshare cni union + 中证1000/2000 snapshot)
- pe/pb 不进DB -> valuation_baostock/<year>.parquet 按年宽表(2003-2026)
- 废弃 daily_baostock_full/bs_index_constituent(rename _old 保留); 旧4 schtask disabled
- 新 schtask sanguo-bs-eod 18:05(baostock个股日线+15min+拆pe/pb DAILY_LIMIT 48000) + sanguo-xt-eod 18:40(ETF/基金xtata)
- 权威源: baostock个股日线+估值+15min+复权+300/500/50 / xtata ETF+基金+当天实时 / akshare三表+事件+深证中证成份股
- 全程备份+staging+_old保留可回滚; 脚本 audit/probe/migrate/merge/cleanup/fix_config/verify/bs_eod/xt_eod/wrapper/register_schtasks
- 待办(spec §6 使用层): LocalParquetProvider 接 constituent_unified+valuation_baostock + 实时拼接
2026-07-23 07:20:34 +08:00

17 KiB
Raw Blame History

A 股多数据源融合层设计

日期:2026-07-21 | 基于 brainstorming + 4 源全能力调查(akshare / baostock / miniQMT / csindex) 状态:设计草案,待用户评审 → writing-plans


1. 背景与痛点

多数据源(akshare / baostock / xtdata / miniQMT)各不全,整合时格式有偏差:

  • symbol 格式:600519 vs sh.600000 vs 600519.SH
  • exchange 命名:SSE vs SH
  • 复权口径:raw vs qfq
  • volume 单位:xtdata÷100 vs 原值
  • 价格源间漂移:同股同日不同源 close 微差

用户约束(明确)

  • 不要查询时网络源切换(源变化/限流不可控)
  • VPS 本地一份稳定数据,日常只读本地
  • 网络源只用于「采集时拼凑完整本地」
  • 使用层无感(本地缺才网络兜底,罕见)
  • 优先 miniQMT + baostock

2. 设计原则(三层)

职责 原则
采集层 多网络源 → 拼凑完整本地 各源 adapter + 定时 schtask;源不可控隔离在采集(失败重试,不影响使用层)
数据层 整理(重叠定权威,特定保留) 不强合物理表(vnpy 回归风险);每类定权威源(优先 baostock+miniqmt)
使用层 LocalUnifiedProvider 逻辑融合 读权威表 + 归一化 + 本地缺网络兜底;策略无感

核心:网络源不稳的风险只影响采集层(定时跑、可重试),使用层永远读本地 —— 风险隔离。


3. 数据源全能力盘点(综合调查)

3.1 已下(稳定源)

表/源 内容 范围 增量
dbbardata 日线+5m/15m(xtdata/akshare/baostock) 日线2010+/分钟2020+ 16:30
daily_baostock_full 日线 18 字段(pe/pb/turn/pctChg) 19902026 待 #7
bs_index_constituent 成份股 300/500/50(含退市) 2006+ 待增量
bs_adjust_factor 复权因子 全史 待增量
akshare 静态表 三表/估值/北向/龙虎榜/融资融券/股本/解禁/业绩预告 - 部分增量
miniQMT xtdata 日线+5m/15m 全周期(实时 T+0,零漂移) 16年 按需
miniQMT PershareIndex ROE/毛利率/EPS 季频 按需
parquet data/raw、daily_baostock、minute_5/15、static/index_const、qfq+raw ETF(5只) - 部分

3.2 重叠(5 处)

  1. 日线 OHLCV:dbbardata ∩ daily_baostock_full ∩ parquet raw(三处)
  2. 估值 PE/PB:akshare valuation ∩ daily_baostock_full(peTTM/pbMRQ)
  3. 15min:dbbardata ∩ parquet minute_15
  4. 基本面:akshare 三表 ∩ miniQMT PershareIndex ∩ baostock 季频
  5. 成份股:bs_index_constituent ∩ parquet index_const

3.3 新发现缺口(本次调查)

  • ETF 全市场日线(现仅 5 只,策略资产类别缺口)
  • 深证/中证历史成份股(治幸存者偏差,当前仅最新快照 = latent bug)
  • 退市股 K 线(baostock 有,从未提取;反幸存者偏差核心)
  • 申万行业 SW1/2/3 + 历史变动
  • 龙虎榜 / 合约信息(涨跌停/ST)/ 可转债 / 研报一致预期 / 股东户数 / 除权明细

4. 权威源地图(数据层整理)

数据类 权威源 物理存储 重叠处理 / 备注
日线 OHLCV(个股,历史) baostock daily_baostock_full dbbardata 日线保留(vnpy 回测硬依赖);parquet raw 冗余
日线(盘中实时) miniQMT xtdata xtdata API 独有(T+0 实时)
日线 ETF(全市场) miniQMT xtdata parquet/dbbardata universe 加 沪深ETF∪沪深基金,dividend_type='front' 自动复权
估值 PE/PB/turn baostock daily_baostock_full akshare valuation 兜底/校验
基本面指标 ROE/毛利率 miniQMT PershareIndex miniQMT API akshare 三表补原始报表
基本面三表(原始) akshare akshare 表 miniQMT Balance/Income/CashFlow + baostock 季频交叉
15min baostock dbbardata parquet minute_15 冗余可清
成份股 300/500/50(含退市) baostock bs_index_constituent query_*_stocks(date) 任意时点
成份股 深证/国证(399xxx) akshare(国证源) parquet index_detail_hist_cni + index_detail_hist_adjust_cni
成份股 中证1000/2000(000852/932000) 新浪 parquet vII_HistoryComponent(gb2312,含退市)
退市股 K 线 baostock daily_baostock_full query_all_stock status=0 + query_stock_basic 退市日期 + 逐只 K
复权因子 baostock bs_adjust_factor 独有
申万行业 SW1/2/3 miniQMT xtdata parquet get_sector_list + get_stock_list_in_sector;akshare 补历史变动
龙虎榜 miniQMT xtdata parquet get_longhubang;akshare 兜底
合约信息(涨跌停/ST/上市日) miniQMT xtdata parquet get_instrument_detail 全 A 一入库
可转债 akshare parquet bond_zh_hs_cov_min + bond_cb_adj_logs_jsl(转股价)
研报/一致预期 EPS akshare parquet stock_research_info_em
股东户数 miniQMT/akshare parquet Holdernum 表 / stock_zh_a_gdhs_detail
除权明细 miniQMT xtdata parquet get_divid_factors
龙虎榜/北向/融资融券/解禁 akshare akshare 表 独有(保留)

5. 归一化规则

维度 统一标准 源映射
symbol 600519.SH(数字+交易所后缀) baostock sh.600000600000.SH;dbbardata 纯数字+exchange 字段
exchange SH/SZ dbbardata SSE/SZSESH/SZ
日期 ISO 2026-07-21 各源统一
复权 raw 存储 + factor 字段(QLib:factor=adj/raw) 查询时按需 qfq($close/$factor);治 raw/qfq 冲突
volume 原值(股) xtdata ÷100 还原
停牌 OHLCV 全 NaN QLib 约定
溯源 source 字段 每行标来源 + 主源/补丁标记

6. 使用层:LocalUnifiedProvider(逻辑融合)

接口:

get_daily(symbol, start, end, adjust='raw')          # 个股日线
get_etf_daily(symbol, ...)                            # ETF 日线
get_fundamentals(symbol, fields, date)                # 财务指标/三表
get_constituent(index, date)                          # 成份股(含历史,治幸存者偏差)
get_industry(symbol, date)                            # 申万行业(含历史变动)
get_longhubang(symbol, start, end)                    # 龙虎榜
get_instrument_detail(symbol)                         # 涨跌停/ST
get_delisted_kline(...)                               # 退市股 K 线

职责:

  • 按数据类路由到权威表(§4 地图)
  • 归一化(§5 规则):源格式 → 统一 vt_symbol/exchange/复权/单位
  • 本地缺 → network_fetcher 透明兜底(罕见,如新股未及采集)→ 写本地 → 返回
  • source 字段溯源;可选多源交叉校验
  • 使用层 API 不变,不知数据来自哪个源

实现:扩展现有 sanguo_portfolio/providers/DataProvider 接口(BaostockProvider/LocalParquetProvider 已有)。


7. 增量 schtask 清单

schtask 数据 时间
sanguo-daily-update(已有) 日线+分钟→dbbardata xtdata 16:30
sanguo-bs-daily-increment(#7 待建) 日线→daily_baostock_full baostock 17:00
sanguo-etf-daily-increment(新) ETF 全市场日线 xtdata(universe 沪深ETF) 17:30
sanguo-akshare-static-increment(新) 估值/龙虎榜/三表/北向 akshare 18:00
sanguo-index-hist(新,半年度) 历史成份股调样 akshare 国证 + 新浪 调样后(6/12 月)
sanguo-delisted(新,月度) 退市股列表+K 线 baostock 月初

query 预算守 48000/天/IP(baostock 硬限):各 baostock schtask 错开 + 日计数器。


8. 分阶段实现

P0 — 治幸存者偏差 + 策略核心缺口

  1. 历史成份股:深证/国证(index_detail_hist_cni)+ 中证1000/2000(新浪 vII_HistoryComponent)+ 300/500/50(baostock 已有)
  2. ETF 全市场日线:xtdata universe 改 沪深A股∪沪深ETF∪沪深基金 + dividend_type='front'(改 build_daily_from_xtdata.py:40 + daily_update_xtdata.py:114)
  3. 退市股 K 线:baostock query_all_stock 筛 status=0 + query_stock_basic 退市日期 + 逐只 K → daily_baostock_full

P1 — 策略增强

  1. 申万行业 SW1/2/3(xtdata get_sector_list)+ 历史变动
  2. 龙虎榜(xtdata get_longhubang)
  3. 合约信息涨跌停/ST(xtdata get_instrument_detail)全 A 入库
  4. 可转债(akshare bond_zh_hs_cov_min + 转股价调整)
  5. 研报/一致预期 EPS(akshare stock_research_info_em)

P2 — 按需

  1. 股东户数 / 除权明细 / 业绩快报 / 大宗交易 / 宏观 / 期货

融合层(贯穿)

  1. 归一化库(vt_symbol/exchange/factor/volume 映射)
  2. LocalUnifiedProvider(读权威表 + 归一化 + 网络兜底)
  3. 完整度监控报表(每类覆盖率/缺口,源退化早发现)

9. 陷阱清单(实证)

  • ak.index_stock_hist 已下线(akshare 1.10.37,2024 初)—— 别抄 2022-23 旧博客
  • ak.index_stock_cons 的"纳入日期"字段有迷惑性 —— 只是当前 300 只各自最初纳入日,不含被剔除,治不了幸存者偏差
  • csindex.com.cn 是 Vue SPA —— requests.get 拿空壳,官网只当前 Excel 无历史
  • 新浪 fund_etf_hist_sina 不复权 —— 不适合回测;ETF 复权走 xtdata dividend_type='front'
  • 东财 fund_etf_hist_em 封 IP —— 单线程限速或避用
  • baostock query_all_stock 不列退市日期 —— 配合 query_stock_basic
  • 申万历史板块有变更 —— 回测用当时分类
  • ak.index_detail_cni(非 hist 版)2025-11-25 起只近期 —— 必须用 hist 版

10. YAGNI(不做)

  • 不强合物理表(冲突解决/历史一致性/vnpy 回归风险,代价大)
  • 不引 QLib/OpenBB 框架(几百 MB,只摘模式:factor/source/归一)
  • CS 截面归一(中期按需,先解决不全+格式)
  • 实时 tick/盘口(非日终策略才需)

11. 风险与对策

风险 对策
vnpy 回测硬读 dbbardata 不动 dbbardata,provider 层 SSE↔SH 映射
baostock query 预算 48000/天 增量 schtask 错开 + 日计数器(#7+day2b 同天 44296<48000)
东财封 IP 避用东财,优先 baostock/xtdata/新浪/国证
接口下线(如 index_stock_hist) 调查实证,不抄旧文
数据源漂移/幽灵尖峰 source 溯源 + 涨跌停/量异常校验
历史成份股缺口致回测幸存者偏差 P0 优先补(深证+中证1000/2000+退市)

12. 实现路径(writing-plans 拆)

  • Phase 1(P0 数据补全):历史成份股 + ETF 全市场 + 退市 K 线(3 个采集脚本 + 灌库)
  • Phase 2(融合层):归一化库 + LocalUnifiedProvider(读现有+新表)
  • Phase 3(P1 增强):板块/龙虎榜/合约/可转债/研报
  • Phase 4(运维):增量 schtask 全套 + 完整度监控

13. 开放问题与默认决策(自行决策,你可推翻)

# 问题 默认(我定) 备选 理由
1 exchange 统一格式 SH/SZ(provider 映射 dbbardata SSE→SH) 保留 SSE/SZSE baostock/xtdata 都用 SH/SZ,主流;vnpy SSE 在 provider 层映射
2 P0 三项优先级 全做(历史成份股 + ETF + 退市 K 线) 先 ETF(策略即用) 三项都治幸存者偏差/核心缺口,并行不冲突
3 ETF 复权方式 xtdata dividend_type='front'(前复权) raw + factor(精确还原) 前复权够策略用;raw+factor 中期按需
4 退市股 K 线范围 近 5 年退市(守 baostock 48000/天预算) 全退市(几千只,慢) 近 5 年覆盖绝大多数回测;全量可后补
5 LocalUnifiedProvider 接口 §6 签名(get_daily/get_etf/get_fundamentals/get_constituent/get_industry/...) 精简 覆盖全天候 + CTA 需求
6 增量 schtask 时间 17:00(baostock 日线)/ 17:30(ETF)/ 18:00(akshare 静态) 调整 错开 sanguo-daily-update 16:30 + day2b 02:00
7 物理表 不新建统一表,provider 读现有(dbbardata/daily_baostock_full/各 parquet) 建 daily_unified 避免 vnpy 回归 + 数据迁移(§10 YAGNI)

默认推进路径:按以上默认 → spec 定稿 → 转 writing-plans(P0 拆 3 个采集脚本实现计划)。你审 spec 时可推翻任一项,我改。


14. 方案 A 定稿(2026-07-22 讨论收敛 — 本节为最新,覆盖前文相关决策)

14.1 背景

P0 补全 + schtask 部署完成后,审计(audit_data_layout.py + dbbardata_probe.py 实证)发现「同类多源、多份落地、DB 多表」混乱。讨论收敛为方案 A:每类数据唯一权威源(允许互补 fallback,禁止并行)、DB 表唯一、估值/三表/事件不进 DB、DB 只放高频随机读取的 K 线+成份股+复权

14.2 权威源最终分工

数据类 权威源 备注
个股日线 OHLCV(含退市) baostock raw 真实价 → dbbardata('d')
ETF/基金日线 xtata baostock 盲区(只取 type=1)
个股当天实时(盘后 baostock 未更窗口/盘中) xtata 拼 baostock 历史,同为 raw
15min baostock 历史+增量同源
5min/1min baostock(暂停) 战略占位,size 扩容续
估值 pe/pb/ps/pcf/turn/pctChg/isST baostock 日线18字段拆出
基本面三表 akshare
成份股 300/500/50 baostock(历史)
成份股 深证 399xxx akshare cni(历史 union)
成份股 中证1000/2000 akshare csindex(当前快照) 历史不可补=永久 gap
复权因子 baostock 全链路复权基准
事件类(龙虎榜/大宗/北向/两融/解禁/预告) akshare

三大源:xtata(ETF+实时) / baostock(个股日线+估值+15min+复权+300.500.50) / akshare(三表+事件+深证中证成份股)。同类不并行,按标的/字段互补。

14.3 DB 边界(只放)

  • dbbardata:日线('d')+15min('15m')+5min('5m'占位)— 唯一行情表
  • constituent_unified:成份股合并唯一表(date,index_code,code,code_name,source)
  • bs_adjust_factor:复权
  • backtest_stats:回测(已有)
  • 不进 DB:基本面三表、估值 pe/pb、事件类、回测明细(独立 db)
  • 废弃 daily_baostock_full(OHLCV→dbbardata,pe/pb→parquet)、bs_index_constituent(合入 constituent_unified)

14.4 pe/pb 落地:parquet 按年宽表

data/valuation_baostock/<year>.parquet,列 date,symbol,pe_ttm,pb_mrq,ps_ttm,pcf_ncf_ttm,turn,pct_chg,is_st。选股排序(全市场某日)列存宽表秒级。baostock 日线增量同源拆出。

14.5 增量 schtask(4 个)

schtask 时间 内容 落点 预算
sanguo-bs-eod 18:05 baostock 个股日线+15min 增量+拆 pe/pb dbbardata('d'/'15m')+valuation_baostock/ ~11000/48000
sanguo-xt-eod 18:40 xtata ETF/基金日线+个股当天实时补 dbbardata('d') 无限流
sanguo-bs-akshare 19:00 akshare 三表+事件类 parquet 限频单线程
sanguo-index 月度 19:50 baostock+akshare 成份股合并 constituent_unified

baostock 单进程单登录,DAILY_LIMIT=48000,sleep 限速,login 探针 graceful skip,直连不走代理。5min/1min 不设 schtask。

14.6 dbbardata 补退市(本次审计发现,顺带治回测幸存者偏差)

实证(dbbardata_probe):退市股(000005/000023/600811)在 dbbardata 只有 15m,无日线 → 回测读 dbbardata 日线天然幸存者偏差。迁移:daily_baostock_full OHLCV 全量(含退市)→ dbbardata('d') INSERT OR REPLACE(本地 DB 迁移,无网络)。ETF 已在 dbbardata(不碰)。

14.7 复权统一

全链路 raw 真实价存储(dbbardata 存 raw),前复权由消费端按 bs_adjust_factor(baostock)统一算。xtata 当天实时同为 raw,可拼接。禁止混 xtata dividend_type=front。

14.8 数据迁移(5 单元,风险升序,备份+staging+可回滚+审计日志)

  1. 存量垃圾清理(_staging_xtdata 14万文件 / _xtdata.tar 1.4G / 归拢 cta_* dbg_* → backtest_files/)
  2. config 统一 VPS 路径(NAS 仅备份)
  3. 成份股合并(bs_index_constituent + index_const_hist union → constituent_unified,按指数代码去重,新浪300/50丢弃)
  4. daily_baostock_full 拆分(OHLCV→dbbardata('d') 含退市;pe/pb→valuation_baostock/.parquet;旧表 rename _old)
  5. dbbardata 个股日线切 baostock 源(单元4已覆盖:daily_baostock_full 含全量,在市股 REPLACE 覆盖 xtata,数值一致)

每单元:全库 sqlite3 .backup + rsync NAS → 脚本写 staging(新表/新目录)→ 验证探针(行数/distinct symbol/抽样价格/成功率)→ 用户确认合并 → 旧数据 rename _old 保留 7 天。全程 nohup + 审计日志 data/migration_logs/

14.9 config 清理

VPS config 的 daily_dir/raw_dir/qfq_dir/minute_15_dir 改 C:\sanguo_vnpy_v2\data\...(当前 yaml 指 NAS /volume1,容器版遗留)。NAS config 保留作备份。read_parquet_daily 的 daily_dir 统一指向 qfq(消除 daily/ vs qfq/ 分叉)。

参考(调查来源)