Files
sanguo_vnpy_v2/docs/portfolio_backtest_result.md
T
claude_dev a68cf4905e feat(portfolio): sanguo_portfolio 组合策略框架(BulletTrade+miniQMT,不用jqdatasdk)
把聚宽"全天候轮动"(post48819)搬到 BulletTrade。融合=pip+扩展点注入
(SanguoMiniQmtProvider 继承 MiniQMTProvider 只 override get_fundamentals,
set_data_provider 公开 API 注入, BulletTrade 源码 0 改动)。

- providers: SanguoMiniQmtProvider 补 get_fundamentals(PershareIndex+自算PE/PS/PB/PCF/市值/ROIC)
- strategies/all_weather: 4选股函数+大小盘轮动+ETF兜底+涨停止损(聚宽风格翻译)
- factors(估值/ROIC自算) + filters(ST/涨跌停/次新/停牌)
- 88/88 测试 Mac+VPS 双过; VPS 回测 pipeline 跑通(修9bug:Capital单位/日期格式/百分数口径/11字段alias)
- 实盘 runner_live+runbook 就绪等交易日; DEFAULT_DATA_PROVIDER=miniqmt env 不装 jqdatasdk
- 文档: sanguo_portfolio_plan / portfolio_backtest_result / portfolio_live_runbook
2026-07-18 19:08:18 +08:00

187 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sanguo_portfolio 全天候策略 VPS 回测报告
> 生成日期:2026-07-18
> 环境:VPS49.232.102.198WindowsPython 3.10.11+ miniQMT 模拟端(userdata_mini
> 范围:沪深300 子集 39 只权重股,2025-04-17 → 2026-07-17(约 3 个月)
## 1. VPS pytest 结果
| 项目 | 值 |
|---|---|
| Python | CPython 3.10.11 (MSC v.1929 64 bit) @ C:\Python310\python.exe |
| pytest | 9.1.1VPS 预装) |
| bullet-trade | 0.9.2jqdatasdk 列为 required 但 env guard 跳过) |
| xtquant | 内置 xtdata,路径 C:\Python310\lib\site-packages\xtquant |
| miniQMT 数据路径 | C:\国金QMT交易端模拟\userdata_mini |
| **测试结果** | **88 passed, 1 warning in 1.24s** |
环境前置(**必须**,否则 `import bullet_trade` 报缺 jqdatasdk):
```cmd
set DEFAULT_DATA_PROVIDER=miniqmt
python -m pytest tests/portfolio -q
```
## 2. 字段校准前后对比(关键发现)
VPS 连 miniQMT 实测 600519.SH 茅台 PershareIndex/Balance/Capital/Income/CashFlow 实际字段名,
**发现 3 个严重不匹配**,全部修复。
### 2.1 修了哪些 alias
| 表 | sanguo 代码原用字段 | miniQMT 实际字段 | 修复方式 |
|---|---|---|---|
| PershareIndex | `roe` | `du_return_on_equity`(或 `equity_roe` | `_get_multi()` 多 alias 回退 |
| PershareIndex | `eps` | `s_fa_eps_basic` | 同上 |
| PershareIndex | `gross_profit_margin` | `sales_gross_profit`(或 `gross_profit` | 同上 |
| PershareIndex | `net_profit_margin` | `du_profit_rate`(或 `net_profit` | 同上 |
| PershareIndex | `inc_revenue_year_on_year` | `inc_revenue_rate` | 同上 |
| PershareIndex | `inc_operation_profit_year_on_year` | `inc_net_profit_rate` | 同上 |
| PershareIndex | `inc_total_revenue_year_on_year` | `inc_total_revenue_annual` | 同上 |
| Balance | `total_liability` | `tot_liab` | 同上 |
| Balance | `total_sheet_owner_equities` | `tot_shrhldr_eqy_excl_min_int`(或 `total_equity` | 同上 |
| Balance | `retained_profit` | `undistributed_profit` | 同上 |
| Balance | `short_loan` / `long_loan` | `shortterm_loan` / `long_term_loans` | 同上 |
### 2.2 三个严重 bug 修复
| bug | 修复前 | 修复后 |
|---|---|---|
| **Capital 单位** | `_to_float(...) * 10000.0`(按"万股"放大) | 直接用,实证单位 = 股(茅台 1,256,197,800 股 = 12.56 亿股,符合现实) |
| **日期格式** | `_to_date_str` 输出 `YYYY-MM-DD`xtdata `end_time` 报"结束时间错误" | 加 `_to_yyyymmdd()``YYYYMMDD` |
| **百分数口径** | miniQMT 返回 10.57=10.57%),策略阈值 `roe > 0.15`(=15%)按小数设计 → 全部误通过 | 加 `_pct_to_decimal()` 在 provider 输出归一到小数(0.1057),对齐聚宽 indicator 口径 |
### 2.3 其他口径偏差(已记录,未改)
| 项 | 现状 | 说明 |
|---|---|---|
| **ROE 口径** | miniQMT `du_return_on_equity` 是 YTD 累计(Q1=10.57%,年化约 30% | 策略阈值 `roe > 0.15` 是 TTM 年化口径,Q1 累计数据通过率低。**未自动年化**(季节性偏差大),策略层后续可改取 Q4 报告或自算 TTM |
| **PE 口径** | EPS 来自单季,×4 近似 TTM | 茅台 PE=14.4(实际 ~25),偏差源于 Q1 EPS × 4 不等于 TTM EPS(茅台 Q4 业绩最重) |
| **PS / PCF / ROIC** | Income/CashFlow trading hours 下载超时,oper_profit/cash_flow NaN | provider 加了 EPS × total_capital 兜底单季净利润,但 oper_profit/cash_flow 无替代源,PS/PCF/ROIC 实测 0% 非空 |
| **ROA 口径** | PershareIndex 无 roa 字段 | 用 ROE × (归母权益/总资产) 自算,茅台 0.0895(≈8.95% |
## 3. Provider 冒烟实证(600519.SH 茅台)
`provider.get_fundamentals_df(['600519.SH'], date='2026-07-17')` 返回:
| 字段 | 实测值 | 用户期望 | 验证 |
|---|---|---|---|
| roe(归一小数) | **0.1057** | ROE≈10% | ✅ |
| gross_profit_margin | **0.8976** | 毛利率≈92% | ✅(Q1 季节性略低) |
| eps(元) | 21.76 | 合理 | ✅ |
| market_cap(亿元) | **15663** | 1.5-2 万亿 | ✅(close=1253 |
| circulating_market_cap(亿元) | 15663 | 同上 | ✅ |
| pe_ratio | **14.4** | 实际 ~25,Q1×4 偏低 | ⚠️(口径偏差,见 2.3) |
| pb_ratio | 5.78 | 合理 | ✅ |
| roa(自算) | 0.0895 | 合理 | ✅ |
| total_liability(元) | 38.8B | 财报匹配 | ✅ |
| total_sheet_owner_equities(元) | 270.9B | 财报匹配 | ✅ |
| ps_ratio | 0.97 | Income 下载成功后能算 | ✅ |
| pcf_ratio | NaN | CashFlow 缺 | ❌ |
| roic | NaN | oper_profit 缺 | ❌ |
## 4. 短回测结果
**配置**39 只 HS300 权重股子集,2025-04-17 → 2026-07-1715 个月),单次选股快照,
等权持仓至期末。
### 4.1 字段非空率(39 只子集)
| 字段 | 非空数 | 占比 |
|---|---|---|
| roe / roa / market_cap / pb / net_profit_margin / inc_revenue_yoy | 38/39 | 97% |
| eps / pe_ratio | 37/39 | 95% |
| ps_ratio(依赖 Income | 38/39 | 97% |
| gross_profit_margin | 26/39 | 67%(银行/券商PershareIndex 该字段为 NaN |
| **pcf_ratio(依赖 CashFlow** | **0/39** | **0%** |
| **roic(依赖 oper_profit** | **0/39** | **0%** |
### 4.2 选股名单(4 个 filter 函数分别执行)
| 函数 | 选出 | 名单 |
|---|---|---|
| `small()` (roe>0.15, roa>0.10, market_cap asc) | 1 | 600585.XSHG 海螺水泥 |
| `big()` (pe∈0-30, ps∈0-8, pcf<10, eps>0.3, roe>0.1, npm>0.1, gpm>0.3, rev_yoy>0.25) | 0 | pcf NaN 被过滤掉,Q1 累计 roe 不达 0.1 年化阈值) |
| `bm()` (中市值价值股,pcf<4) | 0 | (同 pcf NaN 问题) |
| `roic_big()` (roic>0.08) | 0 | roic 全 NaN |
| **合并选股** | **1** | **600585.XSHG** |
**选股少的原因**
1. ROE 是 Q1 累计(10.57% 对茅台这种 TTM 30% 的股),归一到 0.1057 < 0.15 阈值,大部分被过滤
2. pcf_ratio 全 NaN,触发 `df["pcf_ratio"] < 10` 时 NaN 行被丢弃
3. roic 全 NaNroic_big 空产
### 4.3 收益曲线(等权持仓 2025-04-17 → 2026-07-17
| 项目 | 收益率 |
|---|---|
| 组合(600585 等权) | **-30.21%** |
| 基准 HS300 (000300.XSHG) | **+24.55%** |
| 超额收益 | -54.77% |
**说明**:单只选股 + 单期快照不构成有效策略回测,仅用于验证 pipeline 连通。
真实回测需要每月调仓 + 多期 + 完整 HS300 池 + 完整 TTM ROE/PCF/ROIC 数据。
## 5. 聚宽数值对账状态
| 项 | 状态 |
|---|---|
| **聚宽同期数值对账** | ❌ **缺基准**(用户不续费 jqdata,铁律不装 jqdatasdk |
| 自洽验证 | ✅ provider 连通 miniQMT,所有可计算字段(ROE/毛利率/PE/PB/PS/市值/负债/权益)数值合理 |
| 选股合理性 | ✅ 选股逻辑跑通,filter 函数无报错,每只股的财务指标符合行业常识 |
| 茅台 ROE/毛利率实证 | ✅ 10.57% / 89.76%Q1 累计),与公开财报一致 |
| 茅台 PE 实证 | ⚠️ 14.4Q1×4 近似 TTM 偏低,实际 ~25),口径差异已记录 |
## 6. 已修 / 待修清单
### ✅ 已修(本次提交)
1. provider 字段 alias11 个字段加 `_get_multi()` 多 alias 回退
2. Capital 单位 bug:移除 ×10000miniQMT 实际返回股数)
3. 日期格式:`_to_yyyymmdd()` 转 YYYYMMDD 给 xtdata `end_time`
4. 百分数归一:PershareIndex 的 ROE/ROA/毛利率/净利率/同比全部 ÷100 到小数口径
5. Income 空表兜底:EPS × total_capital 算单季净利润(calc_pe 内 ×4 近似 TTM
6. ROA 自算:ROE × (归母权益 / 总资产)
7. 收窄 `download_financial_data` 默认表清单到 `['PershareIndex', 'Balance', 'Capital']`trading hours Income/CashFlow 常超时)
8. conftest Capital mock 单位对齐(万股 → 股)
9. test_provider 过滤断言对齐归一后口径(`>30``>0.3`
### ⚠️ 待修(策略层,下个迭代)
1. **ROE TTM 化**:当前 Q1 累计导致 roe>0.15 过滤过严,应取 Q4 报告或自算滚 4 季度 TTM
2. **PCF / ROIC 数据源**CashFlow/oper_profit 全空,考虑:
- 盘后批量下载 CashFlow 表(trading hours 超时)
- 用 PershareIndex 的 `s_fa_cfps` × total_capital 兜底经营现金流
-`net_profit / (1 - tax_rate)` 兜底 oper_profit
3. **真实回测驱动**:当前 mini_backtest.py 是单期快照;接 bullet-trade BacktestEngine 跑月度调仓序列需另做(runner_backtest.py 已写框架,需对齐 BT 0.9.2 API
4. ** benchmark 沪深300 完整 300 只**:当前子集 39 只只验证 pipeline,扩到全 300 只再跑完整调仓
## 7. 复现命令(VPS
```cmd
:: 1. 同步代码(Mac 端)
cd ~/.openclaw/sanguo_projects/sanguo_vnpy_v2
tar -czf /tmp/sp.tar.gz --exclude='__pycache__' --exclude='*.pyc' sanguo_portfolio/ tests/portfolio/
scp /tmp/sp.tar.gz 49.232.102.198:C:/sanguo_vnpy_v2/sanguo_portfolio_sync.tar.gz
:: 2. VPS 端解压 + 测试
ssh 49.232.102.198
cd C:\sanguo_vnpy_v2
tar -xzf sanguo_portfolio_sync.tar.gz
set DEFAULT_DATA_PROVIDER=miniqmt
C:\Python310\python.exe -m pytest tests/portfolio -q
:: 3. provider 冒烟(茅台)
C:\Python310\python.exe -X utf8 _smoke_provider.py
:: 4. 预下载 HS300 子集 + 回测
C:\Python310\python.exe -X utf8 _predl.py
C:\Python310\python.exe -X utf8 _mini_backtest.py
```
## 8. 关键代码位置
- provider 主文件:`sanguo_portfolio/providers/sanguo_fundamentals.py`
- 策略层:`sanguo_portfolio/strategies/all_weather.py`
- 因子(ROIC/估值自算):`sanguo_portfolio/factors/{roic,valuation}.py`
- 过滤器:`sanguo_portfolio/filters.py`
- 回测入口(框架):`sanguo_portfolio/runner_backtest.py`(接 BacktestEngine 待迭代)
- 简化回测驱动(本次用):VPS `_mini_backtest.py`(探针脚本,未提交)