本轮会话的三项正确性改造(均为「不报错、只让结果静默错」的类型):
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 除本轮修改外,
也含此前遗留的未提交改动(无法按文件切分)。
108 lines
3.9 KiB
YAML
108 lines
3.9 KiB
YAML
# ============================================================
|
||
# 数据源配置
|
||
# 约定:本文件不含任何密钥;密钥经 *_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/adj_factor/daily_basic:**不要设成 480**。
|
||
# 2026-10-04 回补 2005-2014 时实测:以约 196 次/分钟跑 `daily`,
|
||
# 300 次调用后即被 Tushare 拒绝(此 token 的实际上限 ≤ 200/min)。
|
||
# 一旦触发限频,客户端要冷却 62 秒再重试 —— 逐日回补 2,400+ 天时,
|
||
# 这种「撞墙再等」远比「按额度平滑配速」慢,而且有耗尽 4 次重试的风险。
|
||
daily: 170
|
||
adj_factor: 170
|
||
daily_basic: 170
|
||
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
|
||
|
||
# ------------------------------------------------------------
|
||
# 行情同步的「完整性」判定(决定断点续传是否重拉整段历史)
|
||
#
|
||
# 一个交易日被视为已完整同步,要求:
|
||
# 当日股票数 >= max(min_symbols_floor, min_symbols_ratio × 当年应有上市股票数)
|
||
#
|
||
# 早年市场小得多(2005 年约 1,350 只、2010 年约 1,700 只、2024 年约 5,400 只),
|
||
# 用单一绝对阈值会把 2005-2009 的每一天都判成「未完成」——回补一旦中断就得
|
||
# 从第一天重来。所以改为「按当年规模成比例」判定。
|
||
# ------------------------------------------------------------
|
||
sync:
|
||
# 绝对下限:低于它一定判为不完整(防止比例判定在极早年失效)
|
||
min_symbols_floor: 200
|
||
# 相对下限:占「当年应有上市股票数」的比例
|
||
min_symbols_ratio: 0.6
|