本轮会话的三项正确性改造(均为「不报错、只让结果静默错」的类型):
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 除本轮修改外,
也含此前遗留的未提交改动(无法按文件切分)。
127 lines
5.2 KiB
YAML
127 lines
5.2 KiB
YAML
# ============================================================
|
||
# 高股息均值回归策略 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
|