spec §6 使用层 provider — 读方案A 权威数据层, 零 online, 治幸存者偏差:
- get_price: dbbardata('d') raw + bs_adjust_factor 前复权(asof, qfq[t]=raw[t]*factor[t])
- get_index_stocks/get_constituent: constituent_unified 并集治偏差(300=940含被踢, 无date时点)
- get_fundamentals_df: baostock pe/pb/ps/pcf + akshare 市值 + 三表委托 LocalParquetProvider
- 辅助: trade_days/security_info/current_tick/split_dividend/all_securities
VPS E2E 实证修复(Mac fixture 盲区):
- dbbardata datetime 混合格式("2024-09-26" vs "2024-09-26 00:00:00")
→ pd.to_datetime format='mixed' + SQL substr(datetime,1,10) 比日期(字符串比漏边界)
- 补 TestMixedDatetimeFormat 单测覆盖
验证: VPS 真数据 E2E 全通过(600519在市raw/qfq复权/000005退市治偏差/510300ETF/
fundamentals市值+pe+eps全字段/辅助方法); Mac 37单测+149回归绿
交付: 使用说明 docs/portfolio_local_unified_provider.md(其他 session 直用)+
plan+probe+E2E 脚本
8.8 KiB
LocalUnifiedProvider 使用说明
spec §6 使用层 provider。读方案A 权威数据层,零 online,治幸存者偏差。方案A 数据层落地后的推荐 provider。 实现见
sanguo_portfolio/providers/local_unified_provider.py,测试tests/portfolio/test_local_unified_provider.py(36 用例)。
一句话定位
一个 provider,内部按数据类路由方案A 的权威表(dbbardata / constituent_unified / valuation_baostock / static akshare),零 online(不调 baostock HTTP,纯读本地 sqlite/parquet),治幸存者偏差(成份股并集含退市/被踢 + dbbardata 日线含退市),喂 all_weather 等策略。
快速使用
from sanguo_portfolio.providers import LocalUnifiedProvider
# VPS(默认路径 C:\sanguo_vnpy_v2\data)
p = LocalUnifiedProvider()
# Mac 测试 / 自定义路径
p = LocalUnifiedProvider({
"db_path": "/path/to/quant_trading.db",
"data_dir": "/path/to/data", # 含 valuation_baostock/ + static/
})
# 回测入口(runner)
# python -m sanguo_portfolio.runner_backtest --provider unified --start 2024-01-01 --end 2024-12-31
数据源映射(每接口 → 方案A 权威表)
| 方法 | 数据源 | 表 / 文件 | 归一化 |
|---|---|---|---|
get_price |
dbbardata('d') raw + bs_adjust_factor | quant_trading.db |
jq_code↔symbol+exchange; SSE→SH; raw 默认, fq='qfq' 按 foreAdjustFactor 算 |
get_index_stocks / get_constituent |
constituent_unified 并集 | quant_trading.db |
code(纯6位)→jq_code; 返回 in_current=1 ∪ was_removed=1 |
get_fundamentals_df |
pe/pb/ps/pcf ← valuation_baostock; 市值+三表 ← static akshare | <year>.parquet + static/{valuation,balance,income}/ |
对齐 _FUNDAMENTAL_COLUMNS; 市值元→亿 |
get_trade_days |
dbbardata('d') 600519 distinct datetime | quant_trading.db |
— |
get_all_securities |
dbbardata distinct symbol | quant_trading.db |
— |
get_security_info |
dbbardata min/max datetime + constituent_unified code_name | quant_trading.db |
— |
get_current_tick |
dbbardata 最近 close × 1.1/0.9 | quant_trading.db |
ST/创业/科创精确规则 v2 |
get_split_dividend |
bs_adjust_factor 除权事件 | quant_trading.db |
dividOperateDate + factor |
接口清单
# K 线(日线 raw 真实价,按需前复权)
get_price(security, start_date=None, end_date=None, frequency="daily",
fields=None, skip_paused=False, fq="raw", count=None,
panel=True, fill_paused=True) -> pd.DataFrame
# - frequency 非 daily/day/1d/d → 返空(1m 数据层无,day 频率回测降级)
# - panel=False → 长表含 time + code 列(供策略 pivot)
# - fq='qfq'/'pre' → 按 bs_adjust_factor 算前复权
# - fields 缺失列(如 high_limit)补 NaN(策略涨停识别降级)
# 成份股(spec §6 治偏差核心)
get_index_stocks(index_symbol, date=None) -> List[str] # date 忽略(并集模型)
get_constituent(index, date=None) -> List[str] # 语义别名
# 基本面(列对齐 _FUNDAMENTAL_COLUMNS,策略选股核心)
get_fundamentals_df(stocks, date=None) -> pd.DataFrame
# 辅助
get_trade_days(start_date=None, end_date=None, count=None) -> List[datetime]
get_all_securities(types=None) -> pd.DataFrame
get_security_info(security) -> Dict
get_current_tick(security) -> Optional[Dict] # 回测从 K 线推涨跌停
get_split_dividend(security, start_date=None, end_date=None) -> List[Dict]
复权(方案A §14.7 最终目标)
- dbbardata 存 raw 真实价(不复权)。
get_price默认fq='raw'返 raw。 - 前复权消费端算:
get_price(fq='qfq')按bs_adjust_factor.foreAdjustFactor算。 - asof 语义:每个日期找
≤ 该日的最大除权日的foreAdjustFactor;早于所有除权日用最早因子;晚于所有用最新(=1.0)。 - 公式:
qfq[t] = raw[t] × factor[t](open/high/low/close 同乘,volume/turnover 不乘)。 - 例:600519 最新除权 2026-06-26 factor=1.0;历史递减(2020-06-24=0.856)。
- 策略
_trend_mean算 N 日涨幅是比率,raw/qfq 等价(除权日 raw 跳水除外);要精确除权连续性用fq='qfq'。
幸存者偏差治理(关键!)
constituent_unified 是"全时期并集"模型(无 date 列):
- 9 指数分布:
000300=940只(300当前+640被踢) /000905=1803(500+1303) /000016=195(50+145) / 深证 399001=702,399005=145,399006=175,399330=150 - 治"纯当前幸存者"偏差:含已退市/被踢股票(如 000005 退市、600811 被踢都在 300 并集)
- 轻微前视:
get_index_stocks(date)的date参数被忽略(表无时点数据),回测 2020 年选股池 = 历史上所有曾在该指数的股票(含 2024 才纳入的)。比纯当前快照好,但不如 baostockquery_hs300_stocks(date)时点精确。 - 永久 gap:中证1000(
000852)/2000(932000)只当前快照(1000/2000 全当前,0 被踢),历史成份股不可补(csindex SPA 封/akshare 只快照)。 - dbbardata 日线也治偏差:含退市股 K 线(000005 退市到 2024-04-26,600811 等),回测能真实反映"当时买入现已退市"的标的。
Mac 测试(零 VPS 依赖)
tests/portfolio/test_local_unified_provider.py 用 tmp_path + sqlite3 + tmp parquet fixture,完全不依赖 VPS 数据:
def test_get_index_stocks_union(tmp_path):
db = tmp_path / "t.db"
c = sqlite3.connect(str(db))
c.execute("CREATE TABLE constituent_unified(...)")
# 造 in_current + was_removed 样本
...
p = LocalUnifiedProvider({"db_path": str(db), "data_dir": str(tmp_path)})
assert set(p.get_index_stocks("000300.XSHG")) == {...} # 含被踢
python3 -m pytest tests/portfolio/test_local_unified_provider.py -v # 36 passed
python3 -m pytest tests/portfolio/ -q # 全回归 149 passed
部署 / 运行
VPS 数据依赖(方案A 已落地,见 memory data-fusion-design-finalized / vps-local-data-layout):
C:\sanguo_vnpy_v2\data\quant_trading.db— 含 dbbardata / constituent_unified / bs_adjust_factorC:\sanguo_vnpy_v2\data\valuation_baostock\<year>.parquet— 1990-2026 全年份C:\sanguo_vnpy_v2\data\static\{valuation,balance,income,cashflow}\*.parquet— akshare 三表+市值- 日增量:
sanguo-bs-eod(18:05 baostock 日线+15min+pe/pb)+sanguo-xt-eod(18:40 ETF/基金)已部署
rsync 同步代码到 VPS:
rsync -avz -e ssh --exclude='.git' --exclude='vnpy_v4.4.0' --exclude='__pycache__' \
--exclude='.superpowers' --exclude='docs' --exclude='tests/data' \
./ 49.232.102.198:C:/sanguo_vnpy_v2/
⚠️ config 不在排除列表,会覆盖 VPS config(方案A §14.9 已知 TODO:部署前 --exclude config 或靠 SANGUO_DATA_ROOT)。
回测:
ssh 49.232.102.198 'cd C:\sanguo_vnpy_v2 && C:\Python310\python.exe -X utf8 -m sanguo_portfolio.runner_backtest --provider unified --start 2024-01-01 --end 2024-12-31 --cash 1000000 --max-pool 20'
已知限制(v1)
| 限制 | 影响 | 对策 |
|---|---|---|
high_limit 列 NaN |
策略 prepare_stock_list 昨日涨停识别降级(close==high_limit 不命中) |
dbbardata 不存涨跌停;get_current_tick 另算;v2 可从 valuation pctChg 推 |
| 1m 频率返空 | _intraday_high_low 降级 |
数据层无 1m;day 频率回测不触发;15m 在 dbbardata('15m') 可扩展支持 |
gross_profit_margin/roic NaN |
fundamentals 两字段空 | 委托 LocalParquetProvider 读 financial_abstract,fixture 未造则 NaN(非新缺口) |
| 成份股轻微前视 | 回测早期选股池含未来纳入股 | 方案A 既定取舍(并集模型);要精确时点需 baostock online(违反铁律) |
get_current_tick 涨跌停 ±10% 简化 |
ST/创业板/科创板精确涨跌停未区分 | v2 从 valuation isST + 代码段识别 |
与旧 provider 的关系
| provider | 数据源 | 用途 | 状态 |
|---|---|---|---|
LocalUnifiedProvider |
方案A 权威层(dbbardata/constituent_unified/valuation_baostock) | 方案A 后推荐 | ✅ 新增 |
LocalParquetProvider(--provider local) |
旧 parquet(qfq 日线/index_const 快照/akshare valuation) | MVP 验证遗留 | 保留(向后兼容,unittest 仍在) |
BaostockProvider(--provider baostock) |
baostock online HTTP | Mac 跨平台调试 | 保留(违反"读本地"铁律,非生产推荐) |
SanguoMiniQmtProvider(--provider miniqmt) |
miniQMT xtquant | VPS 实盘 | 保留(实盘 runner_live 用) |
迁移建议:新回测/策略用 --provider unified。local 是方案A 前的 MVP 链路(读旧 parquet, index_const 仅当前快照有幸存者偏差),unified 读方案A 权威层治偏差。
设计文档
- spec:
docs/superpowers/specs/2026-07-21-data-source-fusion-design.md§6(使用层)+ §14(方案A 数据层) - plan:
docs/superpowers/plans/2026-07-23-local-unified-provider.md(TDD 拆解) - 关联 memory:
data-fusion-design-finalized/vps-local-data-layout/provider-local-data-only/db-primary-parquet-fallback