37 KiB
A 股多数据源融合层设计
日期:2026-07-21 创建 | 基于 brainstorming + 4 源全能力调查(akshare / baostock / miniQMT / csindex) 状态:活文档(唯一数据设计文档) —— 2026-09-02 起用户钦定:本文件是数据架构唯一权威设计, 每次数据架构变更必须同批更新到最新状态;演进采用「日期节追加,新节覆盖旧节相关决策」模式。 最后更新:2026-09-02(A 档截面数据域全量上生产,§15;使用层 provider 接口,§16;首夜巡查修正,§17)
1. 背景与痛点
多数据源(akshare / baostock / xtdata / miniQMT)各不全,整合时格式有偏差:
- symbol 格式:
600519vssh.600000vs600519.SH - exchange 命名:
SSEvsSH - 复权口径: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) | 1990–2026 | 待 #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 处)
- 日线 OHLCV:dbbardata ∩ daily_baostock_full ∩ parquet raw(三处)
- 估值 PE/PB:akshare valuation ∩ daily_baostock_full(peTTM/pbMRQ)
- 15min:dbbardata ∩ parquet minute_15
- 基本面:akshare 三表 ∩ miniQMT PershareIndex ∩ baostock 季频
- 成份股: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.600000→600000.SH;dbbardata 纯数字+exchange 字段 |
| exchange | SH/SZ |
dbbardata SSE/SZSE → SH/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 — 治幸存者偏差 + 策略核心缺口
- 历史成份股:深证/国证(
index_detail_hist_cni)+ 中证1000/2000(新浪vII_HistoryComponent)+ 300/500/50(baostock 已有) - ETF 全市场日线:xtdata universe 改
沪深A股∪沪深ETF∪沪深基金+dividend_type='front'(改build_daily_from_xtdata.py:40+daily_update_xtdata.py:114) - 退市股 K 线:baostock
query_all_stock筛 status=0 +query_stock_basic退市日期 + 逐只 K →daily_baostock_full
P1 — 策略增强
- 申万行业 SW1/2/3(xtdata
get_sector_list)+ 历史变动 - 龙虎榜(xtdata
get_longhubang) - 合约信息涨跌停/ST(xtdata
get_instrument_detail)全 A 入库 - 可转债(akshare
bond_zh_hs_cov_min+ 转股价调整) - 研报/一致预期 EPS(akshare
stock_research_info_em)
P2 — 按需
- 股东户数 / 除权明细 / 业绩快报 / 大宗交易 / 宏观 / 期货
融合层(贯穿)
- 归一化库(vt_symbol/exchange/factor/volume 映射)
LocalUnifiedProvider(读权威表 + 归一化 + 网络兜底)- 完整度监控报表(每类覆盖率/缺口,源退化早发现)
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 复权走 xtdatadividend_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+可回滚+审计日志)
- 存量垃圾清理(
_staging_xtdata14万文件 /_xtdata.tar1.4G / 归拢 cta_* dbg_* → backtest_files/) - config 统一 VPS 路径(NAS 仅备份)
- 成份股合并(bs_index_constituent + index_const_hist union → constituent_unified,按指数代码去重,新浪300/50丢弃)
- daily_baostock_full 拆分(OHLCV→dbbardata('d') 含退市;pe/pb→valuation_baostock/.parquet;旧表 rename _old)
- 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/ 分叉)。
15. 增补 2026-09-02:A 档截面数据域全量上生产(本节为最新,覆盖前文相关决策)
本节数据的使用层读取接口见 §16(get_event_panel / get_limit_pool,同日落地)。
背景:调研 daily_stock_analysis(Gitea 镜像
sanguo/daily_stock_analysis) 的数据采集面,双机实验(Mac + VPS 生产 IP 49.232.102.198)后落地。四 commit db789d6→020d7cd→00cfd45→46a0569,CI#303 NAS 验证绿 + vps-deploy#304 绿,tag=46a0569。
15.1 架构红线:单脚本注册表,零新采集脚本(用户钦定)
脚本按**数据源(栈)*分类恒定:akshare 栈唯一采集入口=scripts/data_platform/akshare_static_download.py,
baostock 栈=bs_ 系列,xtdata 栈=daily_update_xtdata,新浪指数=sina_index_eod。新数据类型=注册表添行
+一个 fetcher 函数+wrapper 加名字,禁止每类型开新脚本。akshare 本身是聚合库(东财/新浪/同花顺都在内),
新浪/同花顺数据进 akshare 脚本=按栈分类,不算混源。
15.2 新数据域清单(全部落 data/static//,parquet+marker)
| 数据域 | 类型 | 源/端点(集群) | 调度 | 行数 | 回补能力 |
|---|---|---|---|---|---|
| zt_pool 涨停池 | per-date | 东财 stock_zt_pool_em(push2ex) | ak-events 19:30 | ~83/日 | 30 交易日内可回补 |
| zt_pool_zbgc 炸板池 | per-date | 东财 zbgc_em(push2ex) | 同上 | ~6/日 | 同上 |
| zt_pool_dtgc 跌停池 | per-date | 东财 dtgc_em(push2ex) | 同上 | 0-2/日(空=当日真无跌停,写空+done) | 同上 |
| fund_flow_industry 行业资金流 | per-date 快照 | 新浪 stock_fund_flow_industry('即时') | 同上 | 90/日(净额/流入/流出/领涨股) | ❌无历史回补,洞即永久 |
| fund_flow_concept 概念资金流 | per-date 快照 | 新浪 fund_flow_concept | 同上 | ~387/日 | ❌ |
| ths_industry 行业榜 | per-date 快照 | 同花顺 board_industry_summary_ths | 同上 | 90×12列/日 | ❌ |
| ths_concept 概念名单 | per-date 快照 | 同花顺 board_concept_name_ths | 同上 | ~375/日 | ❌ |
| xueqiu_hot 雪球热度 | per-date 快照 | 雪球 stock_hot_follow_xq('最热门') | 同上 | ~5634/日 | ❌ |
| sina_sector 新浪行业 | per-date 快照 | 新浪 stock_sector_spot | 同上 | 49/日(ths_industry 对账源) | ❌ |
| hot_rank 东财人气榜 | per-date 快照 | 东财 stock_hot_rank_em(emappdata) | fetcher 就位未挂载,待墙测量拍板 | ~100/日 | ❌ |
| gdhs 股东户数 | per-period | 东财 stock_zh_a_gdhs(datacenter) | ak-weekly 周六 | ~5342/期 | marker 去重,无新期零网络 |
- ak-events 事件窗现状 13 类 = 上表前 9 + 原有 dragon_tiger/block_trade/margin_sse/restricted
- gdhs 形状特殊:
build_gdhs_units只枚举距今>90 天的季度末(披露守卫)——防未披露期拉空走「真空+done」 被 marker 锁死永不重拉;cyq 筹码判死后的筹码结构免费替代(策略侧共识) - 快照族节假日拉到上一交易日态(端点无 date 参数无自日期列),接受重复,消费方按交易日历过滤
15.3 东财墙模型(实测,勿再踩)
- 每 IP 每窗口(~30-60min)放 1 发:push2 clist(行业/概念榜/资金流榜)、push2his(kline/cyq/个股资金流)、 emappdata(人气榜)三集群同款;第二发必死、镜像(1./17./79.push2)共享、请求形状无关 (dsa 的 efinance 四差异 http+IE11UA+无ut+fid=f12 全复刻也死)、失败重试不重置窗口
- datacenter 集群(三报表/gdhs/top_holders)独立健康,不受 push2 惩罚波及;push2ex(涨停池)独立稳定连打全绿
- cyq 筹码全市场=数学死(5552×40min≈154 天);dsa 的答案是 tushare cyq_chips token 且免费档 15 次/天
- 行业/概念榜+资金流的免费答案=换域:同花顺(行业+概念榜)+新浪(资金流+行业)实测秒级零限流
15.4 语义与防线(全族统一)
- 失败→返 None 不写不标保旧数据(次日重试);真空 df→写空+done;列名零清洗 akshare 原样落盘
- 缺日防线(static_vintage_check.py 09:00):panel_gap_check 每工作日应有 1 个 parquet(节假日空文件也算有); 可回补族(zt_pool×3)洞≤42 日历日→告警+回补指引;快照族洞即永久→近窗洞告警查日志、旧洞只入 json 不刷告警
- vintage_status.json 的 panel 块=策略守卫层消费契约:
panel.<type>={n_files,first_date,holes_backfillable,holes_permanent}
15.5 消费契约(与策略 session 09-02 对齐)
- 炸板率 = 炸板池行数/(涨停池+炸板池行数):涨停池=收盘仍封(「炸板次数」>0 行=炸过又回封),炸板池=触板未封, 并集≈当日触板全集,分母无遗漏
- 涨停池 16 列:封板资金/首次封板时间/最后封板时间/炸板次数/涨停统计/连板数/所属行业( =免费当日热点行业分布信号,涨停行业集中度原料)/换手率/市值等
- 温度计三支柱=涨跌停(hot_rank 待批)+雪球;情绪周期/打板/行业轮动因子原料自 2026-08 起逐日积累
16. 增补 2026-09-02:使用层 provider 接口(A 档数据的读取出口)
§15 采集落盘的数据在本节获得 provider 出口。设计经业界对照定稿(2026-09-02 用户拍板): dsa
get_limit_up_pool/ tusharelimit_list_d(U/D/Z 参数化)/ 聚宽 per-domain 方法 / OpenBB TET ——共识三条:接口轴=数据域、列名在数据层边界标准化(无一家透传原始中文列)、 泛型传输+语义入口分层(tushare 底层也是pro.query(api_name, params)泛型通道)。 miniQMT/QMT/PTrade 均无涨停池/龙虎榜类接口(业界惯例=外部源补),实盘 provider 同样读本地文件。
16.1 两层出口(「泛型底座 + 域语义门面」)
| 接口 | 定位 | 返回列 |
|---|---|---|
get_event_panel(event_type, date / start+end, trading_days_only=True) |
保真底座:白名单 14 类统一通道(§15.2 全部+gdhs),研究/因子探索用 | akshare 原样中文列 + trade_date(YYYY-MM-DD,文件名合成);别把底座列名当生产契约 |
get_limit_pool(kind="zt"|"zbgc"|"dtgc", date / start+end, ...) |
涨停池门面:kind 三合一(← tushare limit_list_d),唯一有消费契约的域先建(温度计/炸板率/行业集中度) | 英文标准列:code/consecutive_boards/seal_amount/break_count/first_seal_time/industry/limit_stat…(键名对齐 dsa) |
- 实现=TET Fetcher(
fetchers/event_panel.py,FETCHERS 注册表新增event_panel/limit_pool), LocalUnifiedProvider(回测)与 SanguoMiniQmtProvider(实盘,委托self._unified)双侧同款 ——方法面钉死测试(issue #35)强制无缺口;双侧同 schema 支持副本对照验证 - 列名漂移吸收:akshare 改中文列名时,门面映射表(
_LIMIT_POOL_COLUMNS,基于 VPS 真实 parquet 实测 2026-09-02,dtgc 实测含 动态市盈率/封单资金/板上成交额/连续跌停/开板次数)吸收, 下游英文键不炸;有行但缺源列→DataSchemaError fail-fast - trading_days_only=True(默认):用
get_trade_days滤非交易日——治快照族「周中法定假日文件 =上一交易日态复制品」的区间双计数;=False按周一~五枚举(=采集侧口径) - 缺文件(洞/未来日/从未采集)=合法缺失→空 DataFrame;真空日(dtgc 0 跌停)→门面仍返标准列空表
- hot_rank 未挂载不在白名单(拍板后与采集注册表、PANEL_TYPES 三处同步加)
- 新数据类型接入=底座白名单加一行(零新方法);某域有消费契约后再升格门面(YAGNI,不加投机门面)
16.2 MCP 出口预留
FETCHERS 注册表新增两键即自动可被未来 provider MCP server 暴露
(Fetcher.fetch(ctx=provider, **kwargs) 直接调用,fetchers 包设计初衷)。
17. 增补 2026-09-02 深夜:首夜巡查修正(覆盖 §15 相关决策)
- 首夜实绩(09-02 19:30,13 类): 12/13 绿——快照族 6/6 行数与双机实验一致 (资金流 90/387、同花顺 90/375、雪球 5634、新浪行业 49);唯一红=margin_sse(下条)。
- margin_sse 结构性缺陷根治(用户批准): SSE 融资融券 T+1 供应(当晚拉当天→空响应
→akshare Length mismatch 崩),自 07-22 起慢性失血 43 天全空(旧码 empty+done 锁死,
缺日检查未覆盖老四类故无人发现)。修=①
--force回补 07220901(30/30 ok 59865 行, VPS+NAS 双端);②wrapper 拆 margin_sse 单独 **T-1T 两天窗**(564160d):每晚稳定写昨日, 当天失败不写不标、次晚同窗自愈。 - NAS 同步链扩容(
ab8f4e3):sync_parquet_daily.shTYPES 原写死老 4 类——扩到 13 类; gdhs=per-period 季度日期永不落 7 天滚动窗,单独全量拉(≤10 文件幂等)。⚠️NAS 坑:03:00 链 以 root 跑留 root 属主文件,admin scp 覆盖之=Permission denied,先 rm 再拉。 - 回灌纪律(zt_pool 0902 事故): 回灌窗口绝不含当日——盘前拉当日=拿到上一交易日态 复制品(0902 文件≡0901 逐值同)+marker 占位致当晚 19:30 跳过;已删+盘后重拉 52/15/8 真数据。
- VPS 部署终态: 使用层 3 commit(4966d30/7f01c5f/564160d)推 VPS(run#1043,tag=564160d, gap=0),VPS 端 8/8 功能验证过(双层接口/区间滤日/回补数据可读/白名单拒绝/实盘方法面)。
18. 增补 2026-09-06:基本面语料库(新闻+公告)采集设计——NAS 域(本节为最新,覆盖前文相关决策)
用户拍板(2026-09-06,经 factor session 转达):范围=六项全做(新闻标题+摘要级增量/新闻全文级/ 新闻历史回补/公告元数据全量回补 2000 起/公告每日增量/定期+业绩类 PDF),唯一不做=公告 PDF 全量 (520GB/年)。前置调研=
docs/fundamental_corpus_probes_20260906.md(三摸底实测)。 本节为 spec,验收通过后才实施;spec 阶段零建表零采集。
18.1 硬约束(用户原话级)
- 存储全部落 NAS、一律不上 VPS——「vps承载有压力,之前基本面都放在了nas上,没有放vps,本次 也一样」。VPS 零参与:零 schtask、零存储、零采集进程;repo 里进脚本仅为随 NAS 部署管道分发 (promote --target nas → /volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/,宿主机 直读该路径,不经容器),VPS 侧永远不会被调度。
- 采集进程=NAS 宿主机直跑,不绑定 docker(用户钦点:「不要和docker绑定,避免docker重启就
失败」)。运行时=/volume1/stock/corpus/venv(
python3 -m venv --copies自举,DSM 自带 Python 3.8.12/pip 已实测可用,x86_64;依赖锁 cp38 兼容版 pandas/pyarrow/requests)——容器 重启/升级/停用零影响,venv--copies连 DSM 换 Python 也不怕。家宽出口 IP ≠ VPS 公网 IP (49.232.102.198),与 VPS 侧 ak-daily/ak-events 的东财限流零冲突。09-06 实测佐证: np-listapi/巨潮/东财文章页 ~35 发连续探测自家宽 IP 全成功、零限流。 - 时间带:全部采集限于 00:30–17:30,避开 18:05–23:45 傍晚带(VPS 夜任务密集+家宽使用)。 唯一日窗=每日 06:00 起(见 18.3)。
- 慢爬纪律:单进程单线程、per-domain 限速+抖动、直连不走代理(Session.trust_env=False,防 Mac 系统代理同款坑)。新浪线已判不可依赖(09-06 实测空返),本设计不使用。
18.2 数据模型(NAS 落位:/volume1/stock/corpus/,与 A股数据/ 平级)
/volume1/stock/corpus/
news_meta/dt=YYYY-MM-DD/part-0.parquet # 东财个股新闻·标题+摘要级(仅新条目,append-only)
news_fulltext/dt=YYYY-MM-DD/part-0.parquet# 东财新闻正文(按 art_code 全局去重,4.1KB/条)
ann_meta/dt=YYYY-MM-DD/part-0.parquet # 巨潮公告元数据(仅新条目;回补的也落发现日分区)
ann_pdf/YYYY/<announcementId>.pdf # 定期+业绩类 PDF(文件名=唯一id,天然幂等)
state/ # 断点水位+unit marker(JSON/parquet)
logs/corpus_YYYYMMDD_HHMMSS.log
Schema(列名冻结,消费方=factor session 因子加工层,后续 provider fetcher 可包):
- news_meta:
art_code(pk),stock_code(6位),show_time,title,summary(可 null: np-listapi 回补段无摘要——search-api 只有最近100条,历史摘要物理不存在),media_name(同上可 null),url,first_seen_date。 - news_fulltext:
art_code(pk,跨股票全局一条——同一篇稿提及多股只在 meta 层多行),url,show_time,content_text(去标签正文),fetch_date。 - ann_meta:
announcement_id(pk),sec_code,sec_name,org_id,title,short_title,content(摘要原文),ann_time(epoch ms→ts),ann_type,ann_type_name,column_id,important,adjunct_url,adjunct_size,batch_num,first_seen_date。 - ann_pdf 索引(state/ann_pdf_index.parquet):
announcement_id,sec_code,ann_type_name,report_year(从报告类别+公告年推),local_path,bytes,fetch_date。
设计要点:id 键控、无「$today 文件名」语义——条目自带时间戳(show_time/ann_time),分区键是 发现日而非业务日,盘前跑/隔夜跑都无 0902/0904 式错标风险(与 A 档快照族的root差异);append-only 不重写(仅新条目入区,短窗 id 去重=读近 N 日分区),无 merge 改写类事故面。
18.3 采集设计:1 个脚本 + 1 个调度入口(脚本整合原则,用户追加指令②)
- 脚本:
scripts/data_platform/corpus_download.py(repo 内唯一新文件;单脚本红线同款—— corpus 栈一个入口,类型靠内部注册表,禁再开新脚本)。参数--lane daily|backfill、--until HH:MM(墙钟自停)、--limit N(手动冒烟)。 - 调度: DSM 任务计划 sanguo-corpus-daily,每日 06:00 一条,以 admin 身份跑(先例=现有
03:00 同步链 DSM 任务(id=2),宿主机直跑多年零 docker;本设计沿同模式但改 admin 跑,避该链
root 属主文件坑):
/volume1/stock/corpus/venv/bin/python -X utf8 /volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/scripts/data_platform/corpus_download.py --lane daily && 同解释器 --lane backfill --until 17:30(两 lane 串行=天然互斥;rc 聚合,任一 lane 失败 log 留痕)。全程零 docker 依赖。
断点续传(用户钦点加固,所有 lane 的第一设计原则):
- 粒度=unit(公告=(stock,year)/新闻=(stock,page)/全文/PDF=单 id),每 unit 一枚 marker(state/ 下原子写 tmp+rename);进程被杀/断电/重启后,重启即从「未标 done 的最老 unit」续跑,无任何 全局重启语义;17:30 墙钟=完成当前 unit 即 checkpoint 退出,次日 06:00 自动接续。
- daily lane 幂等可重跑(短窗 id 去重,重复执行零副作用);漏日自愈=公告 T-1~T 两天窗,新闻 recent-100 页面天然覆盖数日缺口(冷门股 100 条跨数月)——某天整个任务没跑,次日一次执行即可 补齐,不依赖专门回补。
daily lane(每日,总 ~4.5h,06:00–10:30 前后)(06:00 晨窗依据 09-06 实测:深市 600 条公告 发布时刻分布=盘中(09-15时)0%(3条)、盘后 15-20 时 46%、00 时档 54%(巨潮深夜批处理 披露)——公告主体在盘后+零点档,06:00 拉取 T+1 晨=前日公告 100% 入账,优于任何傍晚方案 (17:30 前收工的方案会漏 19-20 时 18%+00 时 54%);新闻当日条目盘中占 27%、盘后晚间 73%, 日频消费 T+1 晨拉零损失):
- 公告增量(巨潮,
5400 req @1/s≈1.5h):每股 seDate=T-1T 两天窗+announcement_id短窗去重 (同 margin_sse 两天窗自愈模式,漏一天次日自动补上); - 新闻增量(东财 search-api=akshare stock_news_em 同款端点,~5400 req @1/s≈1.5h):带摘要, 每股最近100条,新 art_code 入 news_meta;
- 新闻全文(东财文章页,@2/s,~1万 req≈1.5h):仅下载昨日新发现的 art_code 正文;
- 当日 PDF(定期+业绩类 category 过滤,数量级 ~100-400 份/日,峰值在 4 月年报季)。
backfill lane(每日 10:30–17:30 剩余时间,断点续跑,顺序=因子价值优先):
① ann_meta 全量回补(2000→今,unit=(stock,year),~19 万 req,预计 3–6 天跑完);
② news_meta 回补(np-listapi 标题级,unit=(stock,page),翻到空页或 page≥200 止,~11 万 req,
2–4 天)——历史段 summary=null 是物理上限(search-api 只有最近100条),spec 如实约定;
③ news_fulltext 回补(①②清单里的 url,@2/s,~1000万 req≈1–2 个月慢车道);
④ 定期+业绩类 PDF 回补(年份新→旧,2026→2000,~34 万份/~300GB,慢车道数周–数月)。
容量闸门: /volume1 实测空闲 645G(2026-09-06);PDF 回补每落 50GB 自查 df,空闲<200G 即
暂停 PDF 段并 log 告警升级用户重拍(其余段不受影响——非 PDF 段总盘子 <60G)。
失败语义(吸收 09-01/09-05 教训): fetch 失败→unit 不标 done 不写半截(parquet tmp+rename); 真空(该股该年 0 公告)→标 done 合法;确定性解析错→记日志跳过该 unit 绝不烧重试、绝不连坐后续 unit(northbound 断路器饿死教训);域名级连续传输错(403/429/超时×10)→该域本日冷却、它域继续、 rc=1 留痕,次日自然重试。
探测优先(用户追加指令①): 深度不预设上限——巨潮按年翻到空、东财按页翻到空/PAGE 硬顶为止, PDF 按可得性实下(404 标 missing 不重试);每源每股记录 first_date 入 state/depth_summary.json, 实施期边探边记,实际深度以落盘数据为准(三摸底测得:巨潮≈2000 起渐密/1993 有零星,东财≈最热股 3 年)。
18.4 预算汇总(实测口径)
| 层 | 增量/年 | 回补累计 | 请求量级 |
|---|---|---|---|
| news_meta(标题+摘要) | ~1.3GB | +3 年标题级 ~1.5GB | 日 ~5400;回补 ~11 万 |
| news_fulltext | ~10.6GB | 3 年 ~32GB | 日 ~1 万;回补 ~1000 万(慢) |
| ann_meta | ~1.3GB | 2000 起 ~15GB | 日 ~5400;回补 ~19 万 |
| ann_pdf(定期+业绩) | ~45GB | ~300GB(带容量闸门) | 日 ~100-400;回补 ~34 万 |
| 非 PDF 合计 | ~13GB/年 | <60GB | — |
18.5 验收标准(实施完成的定义)
- daily lane: T+1 晨抽查 5 股(超热/热/中/冷/ST)——show_time/ann_time∈[T-1,T] 的条目 全在且 id 无重复;日志限速合规(平均间隔≥设定值,零 429/403 连击)。
- ann_meta 回补: 600519 年报 2001 起逐年可查;announcement_id 全局无重;year×count 曲线 单调合理(早年稀疏→近年 ~130 万/年)。
- news 回补: 600519 最早条目落 2023-09±1 月(实测硬顶对齐);depth_summary.json 全量产出。
- PDF: 抽 20 份跨年份可打开(%PDF 魔数+size>0+meta 可反查路径);索引行数=文件数。
- 边界: /volume1/stock/corpus 之外零写入;VPS 零改动(git diff 触及面仅 scripts+tests+docs);
容量闸门演练一次(临时阈值)证明会停;docker 全停状态下手动触发 corpus-daily 照跑通
(不绑容器的验收实证)。
5b. 断点续传: 回补中途
kill -9采集进程→重启后从未完成 unit 续跑,已完成 unit 零重拉 (marker 核对);某日整个任务停跑一天→次日单次执行把漏日补齐(T-1~T 窗+recent-100 覆盖)。 - 测试: tests/data_platform/ 新增 CI 测试(fetch 全 mock 零网络)——失败语义/短窗去重/ 两天窗自愈/墙钟自停/unit marker,全套本地绿+CI 绿。
18.6 非目标(明确不做)
公告 PDF 全量(用户排除);因子加工层(factor session 职责,消费接口=18.2 的 parquet 契约); 新浪任何接口;VPS 侧任何东西;正文 NLP 解析/向量化;provider 出口(消费层稳定后再议,先直读 parquet);月度 compaction(数据量小,YAGNI)。
18.7 实施步骤概要(验收通过后,TDD)
T1 宿主机 venv 自举(--copies,锁 cp38 兼容版本 pandas/pyarrow/requests,清华镜像)+连通金丝雀
(每域 1 发)+目录骨架 → T2 corpus_download.py daily lane 四段+断点续传 state 机制+测试 →
T3 DSM 任务注册(admin 身份,零 docker)+首周人工盯跑 → T4 backfill lane ①② → T5 ③④+容量闸门
→ T6 验收清单执行+活文档本节补「实施终态」小节。每步一个 commit,标签 [nas](VPS 零触及)。
参考(调查来源)
- xtdata 官方:https://dict.thinktrader.net/nativeApi/xtdata.html
- akshare 指数:https://akshare.akfamily.xyz/data/index/index.html
- akshare 基金:https://akshare.akfamily.xyz/data/fund/fund_public.html
- akshare 债券:https://akshare.akfamily.xyz/data/bond/bond.html
- baostock API:https://www.baostock.com/mainContent?file=pythonAPI.md
- 国证指数网(深证历史):http://www.cnindex.com.cn/module/index-detail.html?indexCode=399001
- 新浪历史成份:http://vip.stock.finance.sina.com.cn/corp/go.php/vII_HistoryComponent/indexid/000852.phtml
- QLib 数据层:https://qlib.readthedocs.io/en/latest/component/data.html
- 现有缺口设计:
docs/static_data_gaps_design.md