Files
sanguo_vnpy_v2/docs/portfolio_local_unified_provider.md
T
claude_dev 41cc6d13bf feat(portfolio): LocalUnifiedProvider spec §6 使用层落地 + VPS E2E(Task6)
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 脚本
2026-07-23 08:25:44 +08:00

8.8 KiB
Raw Blame History

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 才纳入的)。比纯当前快照好,但不如 baostock query_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.pytmp_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_factor
  • C:\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 unifiedlocal 是方案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