初始提交:高股息策略研究与回测系统

从 Point-in-Time 股票筛选到统一 Web 前端的完整链路:
筛选 → 画像 → 策略 → 回测 → Walk-forward → 绩效分析 → 报告/前端。

架构
- 数据层与策略层分离;策略代码不写 SQL,只经 data/repo.py 取数
- 所有业务阈值集中在 config/*.yml,代码零硬编码(字段写错直接报错)
- 报告只做「run_id → SQL → 渲染」,不做任何计算,数字可追溯
- 前后端分离:output/ 静态站点 + hdiv web 提供的 REST API

数据安全
- 只增不删:SQL 钩子拦截 DELETE/DROP/TRUNCATE,并有源码扫描测试守护
- qlib 原有表只读,本项目数据写入 hd_ 前缀表
- 回补使用 INSERT IGNORE,保证既有行零改动
- .env 存密钥且已 gitignore;output/、logs/、.venv/ 不入库

交付物
- 30 张 hd_* 表、7 个 YAML 配置、283 项自动化测试
- 统一 Web 前端(hash 路由 SPA)+ nginx 部署配置与 launchd 托管脚本

如实声明的限制
- 策略缺少稳定的样本外超额收益(Walk-forward 7 窗口均值 -0.95%,
  基准 +2.29%);其价值体现在回撤控制,而非超额收益
- 涨跌停/停牌约束仅覆盖 2019 年起;index_weight 尚未填充
- AI Agent 层(plan.md 第四版 P8)未实现

详见 docs/user-guide.md 与 docs/implementation-status.md。
This commit is contained in:
2026-10-03 13:54:56 +08:00
commit fce725e13c
127 changed files with 28001 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# ============================================================
# 回测配置(★ 可直接修改)
# 对应 plan.md §22–§27(Walk-forward / 过拟合控制)与 §29–§31
# ============================================================
version: 1
capital:
initial: 1000000 # 元
currency: CNY
period:
# P0b 已完成 2015-2018 行情回补,行情/每日指标均覆盖 2015-01-05 起,
# 因此按 plan.md §22「历史数据 10 年」的要求使用完整窗口。
start: 2015-01-01
end: latest
# ------------------------------------------------------------
# 调度:多久评估一次信号、多久重建一次股票池
# ------------------------------------------------------------
schedule:
# 信号评估频率(月):1 = 每月末评估一次买卖信号
signal_frequency_months: 1
# 股票池重建频率(月):高股息池成员(大市值/长上市/连续分红)非常稳定,
# 每年重建一次即可;调小更严格但显著更慢。
universe_refresh_months: 12
# 信号与成交的时间关系由 fill.price 决定(next_open = 次日开盘成交)
# ------------------------------------------------------------
# 分位阈值口径
# ------------------------------------------------------------
percentile_reference:
# rolling —— 用截至评估日的滚动窗口分布(PIT 自适应,单次回测默认)
# frozen —— 冻结训练窗口分布(walk-forward 测试段必须用这个,plan.md §25)
mode: rolling
# rolling 模式下的回看年数
lookback_years: 5
# ------------------------------------------------------------
# Walk-forward(plan.md §23/§24/§25)
# ------------------------------------------------------------
walk_forward:
enabled: true
# rolling —— 固定长度训练窗(plan.md §23 的示例)
# expanding —— 训练窗递增
scheme: rolling
train_years: 5 # plan.md §43: Training = 5~8 年
test_years: 1 # plan.md §43: Test = 1~2 年
step_months: 12 # 窗口步进
min_train_years: 5
# 测试期禁止重新调参:引擎层硬约束,train 段产出的参数对象冻结后传入
freeze_params_in_test: true
# ------------------------------------------------------------
# 分红处理(plan.md §30/§31)
# ------------------------------------------------------------
dividend:
# reinvest —— 现金分红按规则再投资(plan.md §31 模式 B)
# hold —— 现金留存不再投资
# cash_out —— 分红移出组合
cash_mode: reinvest
reinvest_rule: same_stock_next_open
apply_dividend_tax: true
# 是否处理送股/转增/配股(plan.md §30)
handle_stock_dividend: true
handle_rights_issue: true
# ------------------------------------------------------------
# 基准(plan.md §28.4)
# ------------------------------------------------------------
benchmark:
- { code: 000300.SH, name: 沪深300 }
- { code: 000922.CSI, name: 中证红利 }
- { code: 000001.SH, name: 上证指数 }
# 无风险利率(年化),用于 Sharpe / Sortino
risk_free_rate: 0.02
# ------------------------------------------------------------
# 成交撮合
# ------------------------------------------------------------
fill:
# next_open —— 信号次日开盘成交(默认,最贴近实盘)
# next_close / same_close
price: next_open
partial_fill: false
# 单笔成交不超过当日成交量的比例(流动性约束)
max_volume_pct: 0.05
# 涨跌停处理:skip(跳过当日)/ defer(顺延到下一可成交日)
limit_up_down_rule: skip
suspended_rule: defer
# ------------------------------------------------------------
# 可复现性(plan.md §47 原则 6)
# ------------------------------------------------------------
reproducibility:
record_code_version: true
record_data_version: true
seed: 42
+43
View File
@@ -0,0 +1,43 @@
# ============================================================
# 交易成本模型(★ 可直接修改)
# 对应 plan.md §29:Commission / Stamp Duty / Transfer Fee / Slippage
# A 股默认口径(2023 年后):
# 佣金 双边 万2.5,最低 5 元
# 印花税 卖出 千1(2023-08-28 起由千1减半为万5,此处按现行万5)
# 过户费 双边 万0.1
# 滑点 10 bps(可改为 tick / fixed)
# ============================================================
version: 1
model: a_share_default
commission:
rate: 0.00025 # 万 2.5
min: 5.0 # 单笔最低佣金(元)
side: both # both | buy | sell
stamp_duty:
rate: 0.0005 # 万 5,仅卖出
side: sell
transfer_fee:
rate: 0.00001 # 万 0.1,双边
side: both
slippage:
# bps —— 按成交金额的万分比
# fixed —— 每股固定价差(元)
# tick —— A 股最小变动价位 0.01 元的倍数
mode: bps
value: 10
# ------------------------------------------------------------
# 分红所得税(plan.md §30/§31 的正确性依赖)
# 持股期限差异化税率;持有超过 1 年免征
# ------------------------------------------------------------
dividend_tax:
enabled: true
holding_based: true
rates:
"1m": 0.20 # 持股 <= 1 个月
"1y": 0.10 # 1 个月 < 持股 <= 1 年
"gt1y": 0.00 # 持股 > 1 年
+86
View File
@@ -0,0 +1,86 @@
# ============================================================
# 数据源配置
# 约定:本文件不含任何密钥;密钥经 *_env 字段引用根目录 .env
# 与 ~/project/qlib 共用同一 MariaDB 与 Tushare 账号。
# ============================================================
version: 1
database:
host: 127.0.0.1
port: 3306
db: qlib
user: qlib
password_env: MYSQL_PASSWORD
charset: utf8mb4
pool_size: 8
pool_recycle_sec: 3600
# ---- 只读表白名单(qlib 既有表)----
# db.py 会在 SQLAlchemy 事件层拦截对这些表的 INSERT/UPDATE/DELETE。
# 例外:index_weight 是空表,P0a 需要填充成分股权重(纯新增),
# 见 allow_write_tables。stock_daily/daily_basic/adjust_factor 的历史回补
# 由 P0b 专用通道处理,需显式开启 allow_backfill。
read_only_tables:
- stock
- stock_daily
- adjust_factor
- daily_basic
- financial_indicator
- stock_name_history
- trading_calendar
# 允许写入的 qlib 既有表(仅新增行,禁止 UPDATE/DELETE)
allow_write_tables:
- index_weight
# 历史回补专用允许列表(P0b 使用;默认关闭)
# 注意:回补通道使用 INSERT IGNORE,**绝不覆盖既有行**,
# 因此对 qlib 现有数据是纯追加,可安全用于 2015-2018 区间补齐。
backfill_tables:
- stock_daily
- adjust_factor
- daily_basic
allow_backfill: false
# ---- 本项目自有表前缀 ----
# ddl.py 拒绝创建非该前缀的表;写操作拒绝指向非该前缀的表
own_prefix: hd_
# qlib 的 alembic 版本链禁止被本项目触碰
forbidden_tables:
- alembic_version
forbid_delete: true
tushare:
api_url: http://api.tushare.pro
token_env: TUSHARE_TOKEN
# 限频是**按接口**计算的(实测:dividend 为 200 次/分钟,
# 超过会返回「抱歉,您访问接口(dividend)频率超限(200次/分钟)」并中断任务)。
# 因此这里为每个接口单独配置,取值留安全余量。
rate_limit_default: 380
rate_limits:
dividend: 180 # 实测上限 200/min
fina_indicator: 180
cashflow: 180
balancesheet: 180
income: 180
index_weight: 180
suspend_d: 180
stk_limit: 180
daily: 480
adj_factor: 480
daily_basic: 480
index_daily: 180
retry: 4
retry_backoff_sec: 2
timeout_sec: 60
# 单次请求最大等待(长任务)
max_retry_backoff_sec: 30
# 命中限频时强制等待的秒数(Tushare 为滑动 60 秒窗口)
rate_limit_cooldown_sec: 62
paths:
output_dir: output
templates_dir: templates
assets_dir: assets
log_dir: logs
+95
View File
@@ -0,0 +1,95 @@
# ============================================================
# 个股特性(StockProfile)配置(★ 可直接修改)
# 对应 plan.md §8–§15:股息率分析 / 估值分布 / 财务质量 / 分红质量 /
# 风险特征 / 安全边际模型
# ============================================================
version: 1
name: default_profile
# 统计窗口(年)。plan.md §13 要求观察 5 / 8 / 10 年趋势。
windows_years: [5, 8, 10]
# ------------------------------------------------------------
# TTM 股息率口径
# ------------------------------------------------------------
ttm_dividend:
# TTM 每股分红的回看天数
window_days: 365
# 宽限期:A 股年度分红除权间隔中位数约 366 天(实测招商银行最长 393 天),
# 严格 365 天窗口会制造「空窗期」把股息率算成 0 —— 统计假象。
# 仅当严格窗口结果为零时,回退到 window_days + grace_days。
grace_days: 45
# 单个窗口内最少样本数(日频),不足则该窗口统计标记为 insufficient
min_obs_days: 500
# 序列落库上限(每股每指标),供 HTML 画图;超出时等间隔降采样(统计量仍用全量日频)
series_max_points: 1500
# 哪些指标需要落序列(供图表);不在此列表的指标只算统计量,不落序列
series_metrics: [dv_yield, pe_ttm, pb, close, drawdown]
# ------------------------------------------------------------
# 计算哪些指标
# ------------------------------------------------------------
metrics:
# 估值:PE / PB / PS / 股息率(plan.md §9/§12)
valuation: [dv_yield, pe_ttm, pb, ps_ttm]
# 财务质量:plan.md §13
financial:
[roe, roic, revenue, net_profit, gross_margin, net_margin,
ocf, fcf, debt_to_assets, interest_cover]
# 分红质量:plan.md §14
dividend:
[dps, dps_cagr, dps_volatility, payout_ratio,
fcf_dividend_cover, dividend_continuity_years]
# 风险特征:plan.md §11 + §28.2
risk: [vol_60d, vol_250d, max_drawdown_3y, beta_300, avg_amount_20d]
# 收益特征
return: [ret_1y, ret_3y, ret_5y, cagr_5y]
# ------------------------------------------------------------
# 分位数(plan.md §9 明确要求)
# ------------------------------------------------------------
percentiles: [10, 25, 50, 75, 90]
# ------------------------------------------------------------
# 股息率波动分析(plan.md §11)
# 各频率下的滚动窗口长度(交易日 / 月 / 季 / 年)
# ------------------------------------------------------------
dividend_yield_volatility:
daily_window: 250
monthly_window: 60
quarterly_window: 20
annual_window: 10
# 至少多少个观测才输出,否则记为 insufficient
min_obs: 20
# ------------------------------------------------------------
# 安全边际模型(plan.md §15)
# ------------------------------------------------------------
safety_margin:
# separate —— 分项展示(第一版,plan.md §15 建议)
# composite —— 加权综合评分
mode: separate
weights:
dividend_yield: 0.30
valuation: 0.25
financial_quality: 0.25
balance_sheet: 0.10
dividend_quality: 0.10
score_bounds: [0, 100]
# 分项打分时的分位锚点(分位越高/越低对应满分)
score_anchors:
# 股息率:分位 >= 90 得满分,<= 10 得 0 分
dividend_yield: { best_percentile: 90, worst_percentile: 10 }
# 估值(PE/PB 越低越好):分位 <= 10 得满分
valuation: { best_percentile: 10, worst_percentile: 90 }
financial_quality: { metric: roe, best_abs: 0.20, worst_abs: 0.00 }
balance_sheet: { metric: debt_to_assets, best_abs: 0.20, worst_abs: 0.80 }
dividend_quality: { metric: fcf_dividend_cover, best_abs: 3.0, worst_abs: 0.0 }
# ------------------------------------------------------------
# 数据充分性门槛:不足时画像页显式标注「数据不足」,不猜
# ------------------------------------------------------------
sufficiency:
min_history_years_dividend: 5
min_history_years_price: 3
min_dividend_records: 5
+81
View File
@@ -0,0 +1,81 @@
# ============================================================
# 报告与图表输出配置(★ 可直接修改)
# 对应 plan.md §33/§34:K线与交易点、策略研究页面
# 约定:固定格式 HTML,单文件自包含,离线可开,无 CDN 依赖
# ============================================================
version: 1
templates_dir: templates
output_dir: output
assets_dir: assets
theme: light # light | dark | auto
offline_assets: true # 禁止 CDN;图表库必须来自本地 assets/
# 图表库引用方式:
# shared —— 输出 output/assets/echarts.min.js 并相对引用(默认,体积小、生成快)
# inline —— 直接内联进每个 HTML(单文件可携带,但每份约 +1MB)
asset_mode: shared
include_sql_provenance: true # 页脚展示数据来源 SQL + run_id,便于复现
# ------------------------------------------------------------
# 图表开关(plan.md §33 的四联图 + §28 的绩效图)
# ------------------------------------------------------------
charts:
# 个股页:K线 / 股息率 / PE / PB / 回撤 共享 X 轴四联图
kline_signals: true
# 回测页
equity_curve: true
drawdown: true
yearly_returns: true
monthly_heatmap: true
rolling_metrics: true
sector_exposure: true
position_weights: true
# 画像页
yield_percentile: true
yield_histogram: true
financial_trend: true
# 研究页
sensitivity_heatmap: true
walkforward_compare: true
# ------------------------------------------------------------
# 固定输出命名(可预测路径)
#
# 注意:静态报告统一输出到 reports/ 子目录,与统一前端(output/index.html
# + output/app/)分离 —— 站点根只保留前端入口,避免几十份报告平铺在一起。
# 资源引用(assets/echarts.min.js)会按子目录深度自动补 ../。
# ------------------------------------------------------------
# 文件名一律带执行 id:同一天用同一 asof 跑多次会产生多条数据库记录,
# 若文件名只按 asof 命名,后一次会覆盖前一次的报告,与「每次运行都留痕」矛盾。
# 注意:index 是「静态报告目录的索引」,必须放在 reports/ 下。
# 站点根 output/index.html 归统一前端(web/index.html)所有 ——
# 早期把两者都指向 index.html,导致跑一次 `audit --html` 就把前端首页覆盖成静态索引。
naming:
index: "reports/index.html"
audit: "reports/data_audit_{date}.html"
universe: "reports/universe_{asof}_{run_id}.html"
profile: "reports/profile_{symbol}_{asof}_{run_id}.html"
backtest: "reports/backtest_{run_id}.html"
walkforward: "reports/walkforward_{wf_id}.html"
sensitivity: "reports/sensitivity_{sens_id}.html"
# 注:策略说明报告(plan.md §36)属第四版扩展(P8),尚未实现,
# 因此不在此声明命名 —— 配置不应承诺不存在的产物。
# ------------------------------------------------------------
# 页面排版参数
# ------------------------------------------------------------
layout:
# 内容最大宽度(px)
max_width: 1440
# 表格分页大小
table_page_size: 100
# K 线默认展示天数
kline_default_days: 1500
# 净值曲线是否归一化到 1.0
normalize_nav: true
# 数值小数位
decimals:
ratio: 4
money: 2
price: 2
+94
View File
@@ -0,0 +1,94 @@
# ============================================================
# 高股息均值回归策略 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
# 分批建仓(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
+143
View File
@@ -0,0 +1,143 @@
# ============================================================
# 股票筛选条件(★ 可直接修改)
# 对应 plan.md §4/§5 的 MarketFilter / RiskFilter / DividendFilter /
# FinancialQualityFilter。
#
# 所有阈值只允许出现在本文件;代码中不得硬编码。
# 修改后 config_hash 变化,历史 run 仍可完整复现。
# ============================================================
version: 1
name: high_dividend_universe
evaluation:
# point_in_time —— 每个 asof 日期独立求值(回测必须用这个)
# latest —— 仅在最新交易日求值一次
mode: point_in_time
asof: latest
# ------------------------------------------------------------
# MarketFilter —— 市场属性
# ------------------------------------------------------------
market:
# 交易所白名单。需要北交所时加 BSE。
exchanges: [SZSE, SSE]
# 板块白名单:主板 / 创业板 / 科创板 / 北交所
markets: [主板, 创业板, 科创板]
# 上市年限下限(年)—— plan.md §4「上市 ≥ 10 年」
min_listing_years: 10
# 总市值下限(元)—— plan.md §4「市值 ≥ 500 亿」
min_market_cap: 50000000000
# 流通市值下限(元);null = 不限制
min_float_market_cap: null
# 近 20 日日均成交额下限(元)—— 流动性过滤
min_avg_amount_20d: 20000000
# 要求 asof 当日正常交易(非停牌)
require_trading_on_asof: true
# ------------------------------------------------------------
# 行业豁免(对 RiskFilter 与 FinancialQualityFilter 同时生效)
# 银行/保险/证券的负债率天然在 90% 上下、且没有「自由现金流」的概念,
# 若不做豁免,高股息策略里最重要的银行板块会被全部误杀
# (实测:招商银行 90.2% 会被 risk.max_debt_to_assets 直接淘汰)。
# ------------------------------------------------------------
industry_exemptions:
# 豁免资产负债率上限的行业
leverage: [银行, 保险, 证券, 信托]
# 豁免「自由现金流为正 / FCF 覆盖分红 / 经营现金流对利润」要求的行业
free_cash_flow: [银行, 保险, 证券, 信托]
# ------------------------------------------------------------
# RiskFilter —— 风险过滤
# ------------------------------------------------------------
risk:
# 排除 ST / *ST(依据 stock_name_history 按日期还原,而非只看当前名)
exclude_st: true
st_lookback_from_history: true
# 排除退市整理 / 已退市
exclude_delisting: true
# 排除 asof 当日停牌
exclude_suspended: true
# 排除净资产为负
exclude_negative_equity: true
# 资产负债率上限;null = 不限制。金融股由 industry_exemptions.leverage 豁免
max_debt_to_assets: 0.80
# 大股东质押比例上限;null = 不限制(需额外数据源)
max_pledge_ratio: null
# 重大诉讼;当前无数据源,保持 false
exclude_major_litigation: false
# 商誉异常;当前无数据源,保持 false
exclude_goodwill_anomaly: false
# ------------------------------------------------------------
# DividendFilter —— 分红过滤
# ------------------------------------------------------------
dividend:
# 股息率口径:
# computed —— 自算 PIT-TTM(按 imp_ann_date 生效),无未来函数【回测主口径】
# dv_ttm —— 直接用 daily_basic.dv_ttm(仅交叉校验)
yield_source: computed
# 当前股息率下限 —— plan.md §4「股息率 ≥ 3%」
min_dividend_yield: 0.03
# 连续分红年限下限 —— plan.md §4「连续现金分红 ≥ 5 年」
min_continuous_years: 5
# 认定一次分红的规则(D6 决策):
# cash_div_tax_gt_0 —— 税前现金分红 > 0,且 div_proc='实施',且有 ex_date 或 pay_date
continuous_rule: cash_div_tax_gt_0
# 考察窗口(年)
window_years: 6
# 窗口内最少分红年数
min_dividend_years_in_window: 5
# 分红支付率上下限;null = 不限制
max_payout_ratio: 1.00
min_payout_ratio: null
# 5 年 DPS 复合增速下限;null = 不限制
min_dps_cagr_5y: null
# 要求自由现金流为正
require_positive_fcf: true
# 自由现金流对分红的覆盖倍数下限(FCF / 现金分红总额)
min_fcf_dividend_cover: 1.0
# 当「验证所需的数据缺失」时如何处理(安全边际策略的关键取舍):
# pass —— 放行(宽松)。避免因数据未同步而误杀,但会让「无法验证现金流」
# 的股票进入组合,与「安全边际」的初衷相悖。
# reject —— 淘汰(严格)。忠于「FCF 必须为正」的策略逻辑,
# 但结果会依赖数据完整度(数据补齐前后股票池会变化)。
# 实测:当前全部在市股票均有现金流数据,缺失的 10 只均为 1990 年代退市股
# (已被 risk.exclude_delisting 排除),因此两种设置结果相同。
# 若你希望「宁可错过、不可踩雷」,改为 reject。
on_missing_data: pass
# ------------------------------------------------------------
# FinancialQualityFilter —— 财务质量过滤
# ------------------------------------------------------------
quality:
# 5 年平均 ROE 下限
min_roe_5y_avg: 0.08
# 5 年平均 ROIC 下限;null = 不限制
min_roic_5y_avg: null
min_gross_margin: null
min_net_margin: null
# 经营现金流 / 净利润 下限(盈利质量)
min_ocf_to_profit: 0.60
# 覆盖 risk.max_debt_to_assets;null = 不覆盖
max_debt_to_assets: null
# 利息保障倍数下限;null = 不限制
min_interest_cover: null
# 兼容字段:留空则使用顶层的 industry_exemptions
exempt_industries: []
# PIT 纪律:只使用公告日 <= asof 的财报
pit_rule: announce_date_le_asof
# 用哪一期财报:latest_announced(最近已公告)/ ttm
report_basis: latest_announced
# ------------------------------------------------------------
# 输出控制
# ------------------------------------------------------------
output:
# 成员数少于该值触发 WARN(可能是条件过严或数据缺口)
min_members: 10
# 成员数上限;null = 不限制。用于回测时控制持仓候选规模
max_members: 200
warn_on_empty: true