Files
ggx/config/strategy/high_dividend_v1.yml
T
simon 14ec0c6c86 修复:量价单位 / 未来函数守卫 / 实时画像闸门;行情回补到 2005;手册补全流程
本轮会话的三项正确性改造(均为「不报错、只让结果静默错」的类型):

1) 修复 stock_daily 量价单位前后不一致
   - 现象:2015-2019 存 Tushare 原始单位(手/千元),2020 起存(股/元),2019 同日混合;
     而流动性阈值按「元」配置 → 早年门槛实际是「日均成交额 ≥ 200 亿元」,
     把 2015-2019 的股票池整体清空(实测 2016/2017/2018 各选出 0 只)。
   - 修复:写入端 sync/price.py 统一换算;读取端 units.normalize_ohlcv_units
     按行判定并幂等换算(price_history / avg_amount 都走它);
     审计新增 UNIT-OHLCV 防回归。
   - 效果:2016/2017/2018 的股票池变为 7/11/13 只。

2) 未来函数守卫(单次回测)
   - 股票池自带 asof:若晚于回测起点即**拒绝执行**(原先静默冻结套用),
     与 walk-forward 已有的拒绝理由一致;确需复现加 --allow-lookahead-universe,
     偏差写入 unimplemented_json。

3) 新增实时(PIT)个股画像闸门
   - profile/pit.py:每个决策日按当时可见数据重算过去 5 年画像,
     惰性(仅买入条件已触发的标的)、面板按 asof 缓存、
     规则不含财务指标时不查财报表;被剔除时产出 REJECT + 逐规则留痕。
   - 指标定义复用 ProfileBuilder._profile_one(与批量画像逐值等价的回归测试)。
   - profile/coverage.py:窗口覆盖率(按交易日历的真实开市天数),
     策略新增 entry.profile_gate.min_window_coverage(默认 0,不改变既有行为)。
   - core/metrics.py:闸门可用指标的唯一定义(配置期即校验,避免写错指标名静默失效)。

4) 行情回补到 2005(使 5/8/10 年窗口真正完整)
   - stock_daily / adjust_factor / daily_basic 补到 2005-01-04;
     hd_suspend / hd_limit 补到 2010-01-04。
   - 5 年窗口覆盖率:2018-05-18 由 67.0% → 99.1%,2016-12-30 由 39.8% → 99.0%;
     残差经逐日与 hd_suspend 交叉核实为真实停牌(16/16 命中)。
   - 审计 G2/G3 与断点续传原先用固定阈值(2000 / 1500 只),
     会把 2005-2009 的正常数据误判为异常 —— 改为按「当年应有上市股票数」成比例判定。
   - 节流修正:daily/adj_factor/daily_basic 限频 480 → 170(实测该 token 约 196/min 即被拒)。

5) 自我声明如实化
   - 原先「约束未生效」由「过滤后集合为空」判定,会把「这批股票恰好没停牌」
     误报成「hd_suspend 无数据」;改为按表级判定。
   - 补齐此前静默的「配置承诺但未实现」项:suspended_rule/limit_up_down_rule 的 defer、
     cash_mode=reinvest/reinvest_rule、handle_rights_issue、signal_to_execution、
     max_volume_pct、liquidity_limit_pct_adv —— 全部写入 unimplemented_json。

6) 手册:新增 §0「全流程操作(选股 → 画像 → 回测)」置于最前
   - 逐步说明「命令做了什么、数据从哪来、落了哪些库、有哪些坑」;
     含实时画像闸门 9 问 9 答、未来函数守卫表、成交与成本口径、验证 SQL。
   - 修正旧 §2.4 漏传 --universe-run(选了池子却没用于回测);
     修正两处声称「停牌顺延」「分红再投资」已实现的相反表述。

测试:403 项全部通过(含新增 test_units.py、test_profile_pit.py、
未实现声明诚实性测试、行序无关性回归测试)。

注意:本提交中 docs/*、README.md、src/hdiv/web/service.py 除本轮修改外,
也含此前遗留的未提交改动(无法按文件切分)。
2026-10-04 12:47:17 +08:00

127 lines
5.2 KiB
YAML
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.
# ============================================================
# 高股息均值回归策略 v1.0(★ 可直接修改)
# 对应 plan.md §16–§21(买入/卖出/加仓/仓位/风控)
#
# 修改任一字段后 config_hash 变化,历史 run 仍可复现。
# status 生命周期:DRAFT → RESEARCH → BACKTEST → VALIDATED → PAPER → LIVE → ARCHIVED
# ============================================================
strategy:
id: HD_MR_V1
name: high_dividend_mean_reversion
version: "1.0"
status: DRAFT
description: >-
高股息 + 安全边际 + 估值均值回归。寻找长期稳定分红、财务质量较好、
规模较大、上市时间较长的公司,当其股息率进入历史相对高位时买入,
当股息率下降到历史相对低位时卖出或减仓。
# ------------------------------------------------------------
# 股票池:引用全局筛选配置,可就地覆盖
# ------------------------------------------------------------
universe:
ref: config/universe.yml
# 就地覆盖示例(留空则完全沿用 universe.yml):
# override:
# dividend:
# min_dividend_yield: 0.035
override: {}
# ------------------------------------------------------------
# 买入规则(plan.md §17)
# ------------------------------------------------------------
entry:
# 股息率 >= 历史 P75 —— plan.md §43 MVP 买入条件
yield_percentile: 75
# 必须通过基本面筛选
require_universe_pass: true
# 必须未进入风险状态
require_risk_pass: true
# ----------------------------------------------------------
# 实时(PIT)个股画像闸门(plan.md §14/§15 的落地形态)
#
# 语义:股息率分位触发买入之后,再用**当日可见的数据**重算一次画像,
# 不通过的票直接剔除(信号类型 REJECT,可在「未成交信号」里看到
# 每一条规则的实际值与阈值)。
#
# 代价:只在触发时计算;跨股票共享的面板按时点缓存,因此成本与
# 「触发次数」成正比,而不是「区间长度 × 股票数」。
#
# enabled=false 时整条链路不参与,回测行为与启用前完全一致。
# ----------------------------------------------------------
profile_gate:
enabled: true
# 画像统计窗口(年):0 = 全历史;其余必须是 config/profile.yml 的
# windows_years 之一(当前 [5, 8, 10])
window_years: 5
# 数据缺失/样本不足时:reject = 保守不买(默认);pass = 放行
on_unverifiable: reject
# 逐条规则:metric 的 stat 必须满足 op value
# stat: current_value(当日值)/ current_percentile(当日值在窗口分布中的分位)
# metric 只能是 hdiv/core/metrics.py 里声明的代码
rules:
# 分红必须可持续:连续分红年数与窗口内分红年数
- { metric: dividend_continuity_years, op: ">=", value: 5 }
# 支付率不得超过 100%(分红不能靠借钱或吃老本)
- { metric: payout_ratio, op: "<=", value: 1.0 }
# 自由现金流必须覆盖分红(与分红同一财年口径)
- { metric: fcf_dividend_cover, op: ">=", value: 1.0 }
# 5 年平均 ROE 下限(与 universe.quality.min_roe_5y_avg 同一口径)
- { metric: roe_avg, op: ">=", value: 0.08 }
# 分批建仓(plan.md §19)。留空 => 一次性建满。
scale_in:
- { percentile: 75, weight: 0.25 }
- { percentile: 80, weight: 0.50 }
- { percentile: 85, weight: 0.75 }
- { percentile: 90, weight: 1.00 }
# ------------------------------------------------------------
# 卖出规则(plan.md §18)
# ------------------------------------------------------------
exit:
# 股息率 <= 历史 P25 —— plan.md §43 MVP 卖出条件
yield_percentile: 25
# 分批减仓;weight 为该分位下的目标仓位比例
scale_out:
- { percentile: 50, weight: 0.50 }
- { percentile: 25, weight: 0.00 }
# 第一版不加止损(plan.md §21:止损易致过拟合)
stop_loss_pct: null
max_holding_days: null
# ------------------------------------------------------------
# 仓位控制(plan.md §20)
# ------------------------------------------------------------
position:
max_position: 0.10 # 单只股票最大仓位 10%
min_position: 0.01 # 低于该权重不建仓(避免碎股)
sector_max_position: 0.25 # 单行业最大仓位 25%
max_holdings: 20
# equal | score | inverse_vol
weight_scheme: equal
# ------------------------------------------------------------
# 风险控制(plan.md §21)
# ------------------------------------------------------------
risk:
max_portfolio_drawdown: 0.20
max_single_drawdown: 0.25
# 单笔成交不超过该股当日成交额的比例
liquidity_limit_pct_adv: 0.05
# ------------------------------------------------------------
# 撮合
# ------------------------------------------------------------
execution:
signal_to_execution: next_open # next_open | next_close | same_close
limit_up_down_rule: skip
suspended_rule: defer
# ------------------------------------------------------------
# 成本:引用全局成本模型
# ------------------------------------------------------------
cost:
ref: config/cost.yml