feat(v0.2): 市场阶段与主线识别 + 因子挖掘全链路 + 数据层完善

- 市场环境: 新增情绪周期6阶段(冰点/启动/主升/高潮/退潮/修复, 连板梯队驱动,
  EMA平滑+2日确认+弱档否决, 平均段长9.7天)与概念/行业主线排名(涨停梯队聚合,
  可配置宽基/风格标签过滤); 市场环境页重构, regime 透明加列, 与5档state并存
- 挖掘: 因子与策略挖掘全链路(API/worker/进程锁/候选库/前端工作台/文档),
  周度调度默认关闭且永不自动发布
- 回测: 财务快照因子(点时口径), 批量回测预计算共享下期收益,
  信号路径矩阵列依赖展开修复(consecutive_limit_ups 缺列报错)
- 数据/性能: enriched 生成与预热治理, 重任务限流, 行情/K线缓存复用, 时区修复
- 测试: 后端全量 914 通过; GUI 黑盒验证截图存证 gui-test-screenshots/
This commit is contained in:
shy3130
2026-08-16 23:39:07 +08:00
parent eb869c7ad2
commit 697c27bb02
129 changed files with 20324 additions and 602 deletions
+5 -1
View File
@@ -8,7 +8,7 @@
## 🔍 选股引擎(Screener)
**20 个内置策略**,每个策略一个独立 Python 文件,基于 Polars 表达式向量化实现(`backend/app/strategy/builtin/`):
**18 个内置策略**,每个策略一个独立 Python 文件,基于 Polars 表达式向量化实现(`backend/app/strategy/builtin/`):
| 类型 | 代表策略 |
| :---------- | :------------------------------------------------------- |
@@ -54,6 +54,10 @@
输出净值曲线 · 夏普 · 最大回撤 · 胜率 · 交易明细。SSE 流式进度支持切页重连,不会丢失回测任务。
**因子与策略挖掘**:复用已有日频因子和 matrix-native 策略,通过 T-1 市场环境、相关去重和嵌套样本外验证生成研究候选。任务在 spawn worker 中运行,支持持久 run ID、取消、刷新重连和显式发布;自动周度任务默认关闭且永不自动发布。完整口径见 [因子与策略挖掘](./mining.md)。
**市场阶段与主线**:市场环境页在原 5 档状态之外新增情绪周期阶段(冰点/启动/主升/高潮/退潮/修复,由连板梯队的高度、宽度、晋级率、梯队完整度判定,平均段长约 10 天)与主线识别(概念/行业维度的涨停梯队聚合排名,可配置宽基标签过滤)。完整口径见 [市场阶段与主线识别](./market-phase.md)。
**ETF 支持**:三种模式的后端与 API 均支持 `asset_type=etf`,回测面板改从 `kline_etf_enriched` 读取(单次回测为单一资产类型,不混合股票与 ETF)。策略组合与因子回测页均有 `股票 / ETF` 切换,ETF 模式下策略列表与标的搜索跟随资产。需先开启 ETF 拉取并跑盘后管道。
---
+95
View File
@@ -0,0 +1,95 @@
# 市场阶段(情绪周期)与主线识别
本文说明市场环境页的阶段体系与主线识别的口径、阈值来源、持久化设计、已知偏差与运行方式。它与原有的 5 档环境 state 并存,不替代任何既有消费方。
## 功能边界
阶段体系回答"市场处于情绪周期什么位置",主线识别回答"当前/某阶段内什么板块概念在领涨"。两者都只用本地已存储数据(`consecutive_limit_ups``amount`、概念映射快照),不依赖扩展数据源,2020-08 起全历史可回算。
明确不做的:
- 挖掘、回测环境过滤、因子 regime 统计仍用原 5 档 `state`,本期不切换到阶段体系。
- 概念成分是当前快照,不改成 timeseries 积累模式(见「快照回填偏差」)。
- 不生成任何交易信号;阶段与主线仅供研究分析。
## 与 5 档 state 的关系
| | `state`(原有) | `phase`(本期新增) |
|---|---|---|
| 驱动量 | 赚钱/投机/抗跌/趋势 4 维综合分 | 连板梯队:高度、宽度、晋级率、梯队完整度 |
| 档位 | 强势/偏强/震荡/偏弱/弱势(5 档) | 冰点/启动/主升/高潮/退潮/修复(6 阶段) |
| 消费方 | 回测环境过滤、挖掘、策略页 | 市场环境页分析与主线识别 |
| 持续性 | 日频打分,切换频繁(历史 76.6% 天数发生切换,平均段长 1.1-1.5 天) | EMA 平滑 + 2 日确认,平均段长 9.7 天 |
`state`/`score` 列原样保留,新列(`phase``first_board``ge2_count``ge3_count``ge5_count``ladder_completeness``promo_rate``promo_pool`)对既有消费方透明。
## 每日梯队指标
全部由已存储的 `consecutive_limit_ups` 列向量化派生,实现在 `backend/app/services/market_phase.py`
- `first_board`:首板(1 连板)家数。
- `ge2_count` / `ge3_count` / `ge5_count`N 板以上家数(宽度)。
- `promo_rate` 晋级率:昨日连板池今日继续封板的比例。昨日连板数用 `consec.shift(1).over("symbol")` 按个股前一日连接(跨批次边界也正确,`_compute_batch` 在 warmup 截断前计算);池不足 10 家记 `null`(小样本噪声),不填 0。
- `ladder_completeness` 梯队完整度:2..height 档位中非空档位占比;height < 3 时为 `null`
- 高度(`max_consecutive`)与封板率(`seal_rate`)沿用 regime 已有列。
## 阶段规则
词汇与优先级:`climax 高潮 > rally 主升 > ebb 退潮 > ignite 启动 > ice 冰点 > repair 修复(兜底)`。冰点排在退潮前判定——长期死市不应被标成"自高位退潮"。
阈值标定自 2020-08~2026-08 全市场 1454 个交易日的 p10/p60/p90 分位数,全部集中在模块顶部常量,调整只改那里:
| 阶段 | 核心条件(EMA 平滑后) |
|---|---|
| 高潮 climax | ge2 ≥ 50p90 的 2 倍)或首板 ≥ 220(p90 的 2.5 倍),历史占比 <2% |
| 主升 rally | 高度 ≥7 且 ge2 ≥15 且晋级率 ≥0.23(全中位以上);或晋级率 ≥0.30(p85+)配合高度 ≥5、ge2 ≥12 |
| 退潮 ebb | 晋级率 <0.15p20)且宽度自 5 日前高位(ge2>12 或高度>6)回落;或晋级率 <0.13 且封板率 <0.57 双弱 |
| 启动 ignite | 宽度/高度自低位扩张(ge2 较 5 日前 +3 且 ≥8,或高度抬升且 ≥5)且晋级率恢复到 ~0.19-0.20 |
| 冰点 ice | 高度 ≤4、ge2 ≤6、首板 ≤24 同时贴地(均 ~p10) |
| 修复 repair | 兜底 |
**持续性设计**:驱动量先做 EMA 平滑(alpha=1/3,约 5 日),原始标签出来后再做 2 日确认(切换需连续 2 日出现新标签才生效)。
**弱档否决**5 档 `state ∈ {weak, lean_weak}` 时,正向阶段(主升/高潮/启动)一律降为修复。这修复了一类错标:连板梯队强但大盘崩的交易日(如 2024-01 微盘流动性危机)会被梯队指标误判为主升/启动——涨停生态与大盘背离时,以大盘弱势为准。
**回填验收**(1454 天真实数据):平均段长 9.7 天(对比原 5 档的 1.1-1.5 天)。抽查:2024-09-24→10-08 为启动→主升→高潮(九二四行情);2024-01/02 微盘崩为退潮/冰点;高潮 6 年仅 2 段(2024-10-08、2024-10-31 ST 重组潮 17 板),均为真实极端期。
已知特性,如实说明:退潮/冰点天然是短段(平均 2.8/2.5 天)——恐慌释放本身快于趋势形成;修复是占比最高的兜底段(~74% 天数),A股大部分时间没有处于可辨认的周期位置。
## 主线识别
实现在 `backend/app/services/market_mainline.py`。复用 `rps_rotation` 的概念映射加载,窄扫描 `kline_daily_enriched` 的 symbol/date/consecutive_limit_ups/amount 四列,按 (date, 概念/行业) 聚合:
- `limit_up_count` 涨停家数、`ge2_count` 二板以上家数、`max_boards` 最高板、`boards_sum``rungs_filled` 梯队档位数、龙头股(按连板数、成交额排序取第一)。
- 主线分 = 当日截面 rank 归一后 `0.35*涨停数 + 0.25*最高板 + 0.25*梯队档位数 + 0.15*二板宽度`。这是"同板块涨停越多、梯队越完整越是主升主线"判据的直接量化。
- 每概念当日涨停 <3 家不参与排名;每日持久化 top30 到 `data/mainline_history/part.parquet`upsert 按 date+kind 整日替换)。
- 行业维度取前两级(如 `计算机-软件开发`),作为概念维度的交叉验证。
### 宽基/风格标签过滤
融资融券(~7700 家)、沪深股通(~3300 家)这类超大概念会垄断 top1,需要过滤。配置存于用户偏好(`preferences.json`),市场环境页「过滤」面板可改:
- 成员数上限:默认 600,超过视为宽基/风格标签不参与排名(夹取范围 50-5000)。保留华为概念(2006)、人工智能(2166)等真主题。
- 成员数下限:默认 4(夹取 1-200)。
- 名称黑名单:手动屏蔽任意概念,支持逗号/分号/空格分隔。
修改保存后自动触发全量主线重算(`POST /api/regime/mainline/recompute`,秒级)。API 层为 `PUT /api/preferences/mainline-filter`
### 快照回填偏差
概念成分为**当前快照回看历史**(本地自 2026-07 起留存,无历史版本)。新纳入指数或改名的个股会出现在旧时段、成分调整会错归属,因此早年主线存在归属漂移,越近越准。此口径限制以 `membership_note` 字段随 API 返回并在页面展示。若后续把 ext 概念同步改为按月累积快照,主线历史精度会逐月自然提升。
## API 与运行
- `GET /api/regime/phases?start&end`:连续阶段段列表(阶段、区间、天数、高度/宽度/晋级率/封板率均值、段内 top 主线)。
- `GET /api/regime/mainline?start&end&top&kind`:窗口内主线排行(top1 天数、日均分、最高板)与每日明细。
- `POST /api/regime/mainline/recompute`:过滤配置变更后的全量重算(两类维度)。
- `POST /api/regime/recompute`:扩展为重算 regime 后自动重标 phase 并回填主线。
- daily pipeline 在 regime 步骤后追加主线增量(复用 `pipeline_regime_enabled` 开关,软失败不阻塞)。
阶段标签由 `regime_builder.refresh_phase_labels` 在每次 regime upsert 后对全量历史重标(1454 行,开销可忽略)——因为 EMA 与 2 日确认依赖完整序列。
## 测试
- `backend/tests/test_market_phase.py`:梯队聚合、晋级率(池不足记 null)、梯队完整度、阶段序列规则、弱档否决、持续性(噪声下不出现 1 日翻转)、refresh 往返。
- `backend/tests/test_market_mainline.py`:概念/行业聚合、宽基过滤、黑名单、最少涨停家数、同日 upsert 替换、增量补齐、偏好读写与夹取。
+138
View File
@@ -0,0 +1,138 @@
# 因子与策略挖掘
本文说明 V1 因子与策略挖掘的金融口径、执行边界、结果解释和运行要求。
## 功能边界
V1 仅研究仓库已经提供的日频因子和用户明确选择的 matrix-native 日线策略。它完成以下闭环:
1. 在训练区间重新计算因子方向与统计。
2. 按日截面 Rank IC 计算因子相关性并去重。
3. 搜索最多四个因子的受控排名组合;进入 beam 搜索的因子按训练折综合分排序并截断到 `beam_width` 个,保证单因子打分后组合搜索仍有代理预算可用。
4. 用嵌套样本外验证比较新组合;用户选择的已有策略作为对照轨在每个 outer 测试窗独立评估,不参与因子竞争,也不会占用候选名额。
5. 将运行、事件和结果持久化,允许刷新后重连。
6. 将候选保存到研究候选库;只有用户显式确认且通过晋级门槛后才发布独立策略。
V1 不生成任意公式,不接受 AI 自由代码,不使用扩展数据生成新因子,也不使用分钟数据优化入场。分钟级成交和分钟因子需要独立的数据完整性、防未来函数和性能边界,属于后续版本范围。
因子数量以运行时 `FACTOR_COLUMNS` 目录为准。当前目录与后续版本可能不同;单次请求硬上限为 48,不应把历史设计稿中的固定数量当作稳定 API 契约。
当前目录除价量技术类(动量、均线偏离、趋势、波动率、量价、价格位置、超买超卖)外,还包含三个 A 股实证维度:收益形态(`max_ret_20d` 彩票效应、`ret_skew_20d``up_days_20d`)、流动性(`amihud_20d` 非流动性、`turnover_z_60d` 换手异动)、涨停基因(`limit_up_count_20d/60d`,基于存储列 `consecutive_limit_ups` 计数,涨停判定沿用环境信号口径)。这些因子仍只用本地日频存储数据计算,不依赖扩展数据源。
财务因子(`pb_latest``roe_latest``gross_margin_latest``net_margin_latest``revenue_yoy_latest``net_income_yoy_latest``debt_ratio_latest`)来自本地财务快照(数据页同步),采用严格点时口径:**因子值只在晚于公告日的交易日生效**(公告多在盘后, 保守取公告次一交易日起),同一报告期以最新公告为准。无财务数据的标的、公告前的日期一律为空值并从当日截面剔除,绝不填 0;本地完全没有财务数据时因子回测直接返回明确错误而不是全空结果。回测区间早于首次公告时同样报告"无有效数据"。挖掘中财务因子在训练折覆盖不足时综合分为 0 并被自然淘汰,不会阻塞其他因子。财务报表同步按 `(symbol, period_end)` 累积历史(同 shares 表模式),每次同步只拉最新期并为新标的补全量历史;随着季度累积,财务因子的可回测区间会逐步变长。
## 时间与收益口径
### 全局交易日标签
1、3、5 日 forward return 都按全局交易日轴精确连接。停牌或缺行不会把“下一条标的数据”误当成下一交易日。
周频使用每个 ISO 周的第一个实际交易日,月频使用每月第一个实际交易日。节假日不会导致整周漏掉调仓。
### T-1 市场环境
交易日 T 只能使用上一交易日已经得到的环境标签 T-1。**统计与挖掘口径**聚合为三档:
```text
strong = strong + lean_strong
range = range
weak = lean_weak + weak
```
**策略回测的环境过滤**按原始五档匹配:勾选“强势”只允许 T-1 为 strong 的交易日入场,需要三档口径时必须同时勾选“强势+偏强”。挖掘内部的 strong/range/weak 环境拆分仍按上方三档聚合实现(显式枚举原始状态),两者互不影响。
正式区间缺少前驱交易日或前驱环境时会 fail-closed,不会用当日环境、最近自然日或默认环境补齐。
### 成交和成本
候选策略沿用现有回测撮合规则,包括 T+1、佣金、卖侧印花税、滑点以及涨跌停不可成交约束。因子统计中的 long-short spread 仅用于衡量因子区分能力,是理论价差,不表示 A 股可执行卖空。
## 防止未来数据
挖掘使用嵌套 walk-forward,而不是在完整样本上选择后再报告同一段表现:
- 每个 inner 训练折独立学习方向、计算统计、相关去重和组合搜索。
- inner test 只用于选择候选,不参与该折的训练计算。
- 选择完成后在完整 outer train 上重新训练。
- outer test 只进行一次最终样本外评估。
- 训练和测试之间保留 purge 与 embargo;默认 purge 为 30 个交易日。
任何 fold 缺数据、缺 T-1 环境或不能可靠撮合时都记录为 skipped 并给出原因,缺失指标返回 `null`,不会显示为 0。
## 候选证据口径
run 级的“有效折”统计只描述因子赛道:它按每折被选中的候选聚合,不是任何单个候选的样本量。单个候选的逐折证据由三类行组成,`folds.parquet``evaluation_kind` 区分:
- `selected`:该候选在该 outer 折经 inner 验证胜出后的 outer 测试结果。
- `cross`:该候选的定义在其他折胜出后,在本折补评的跨折结果。胜出定义会在所有 outer 折上评估,使单个候选的证据不再依赖“它恰好在哪里获胜”。
- `benchmark`:对照策略在该 outer 测试窗的独立评估,与因子赛道成败无关;即使因子赛道在某折没有任何候选完成内部验证,对照行仍会写入。
对照策略是固定定义,不需要逐折重拟合,因此每个 outer 折只评估一次;预算耗尽时写入带原因的 skipped 行,不会静默缺失。探索档(exploratory)结果固定为低置信度,只能保存为 pending 候选。
## 晋级与发布门槛
保存(promote)始终允许,结果进入研究候选库 pending 状态。发布(publish)在服务端强制校验以下证据门槛,不满足即拒绝并返回原因列表:
- 非探索档置信度(exploratory 运行产生的候选不能发布);
- 至少 2 个有效 outer 折;
- 正收益折比例不低于 2/3
- 样本外 Sharpe 不低于 0.5
- 最大回撤不劣于 -25%
- 样本外交易数不低于 60。
门槛按 artifact 行指标在读取时计算,不改变历史 artifact 的 schema,因此旧运行的候选会得到相同的门槛判断。工作台会在候选上显示“达标/未达标”标记,未达标的候选“显式发布”按钮被禁用并展示原因。
## 置信度
三个预算档使用不同的训练窗口:
| 档位 | 外层训练 | 外层测试 | 步长 | 用途 |
| --- | ---: | ---: | ---: | --- |
| exploratory | 126 | 63 | 63 | 数据较短时探索,只能作为低置信度 pending 候选 |
| balanced | 504 | 126 | 63 | 默认自动研究,至少需要 3 个 outer folds |
| strict | 756 | 126 | 126 | 更长训练窗,至少需要 3 个 outer folds |
探索性结果不是已验证策略。候选晋级仍需同时检查正收益折比例、样本外 Sharpe、最大回撤、交易数和环境样本覆盖;发布动作会按上文“晋级与发布门槛”在服务端强制执行同一组阈值。
## 任务与资源隔离
挖掘通过 `spawn` 子进程执行,不在 FastAPI 请求线程、实时行情回调线程或浏览器连接生命周期内运行。浏览器断开不会取消任务;刷新后可通过持久 run ID 重连。取消请求会进入 `cancelling`,界面应等待后端进入终态。
共享重任务限流容量为 2:普通回测、优化、walk-forward 和矩阵预热占 1,挖掘独占 2。因此一个挖掘任务不会与另一项大矩阵计算并发。
当前 run store 和重任务 limiter 使用进程内锁。生产环境必须只启动一个应用进程负责挖掘;多 worker Uvicorn 部署不能保证跨进程单飞、事件追加和容量限制。要支持多应用进程,必须先增加操作系统级文件锁或外部任务协调器。
每个运行目录包含 manifest、compact summary、最近 256 条事件和四个 Parquet artifact。大面板、完整矩阵对象和逐折宽结果不会通过 worker IPC 返回。
## 自动运行
周度自动挖掘默认关闭,只允许 balanced 或 strict。启用后,它会在北京时间配置工作日及其后的同周工作日、日线 enriched 刷新和环境更新成功后尝试入队;如果配置日的数据流水线失败,后续工作日可以补跑。
同一 ISO 周使用确定性的 claim,只触发一次。已经生成运行记录的失败或 `skipped_prerequisite` 仍会占用该周 claim,不重复消耗资源。数据或 T-1 环境覆盖不足时会生成可见的 `skipped_prerequisite` 运行,不会静默降级到 exploratory。挖掘失败不改变已经成功的日度数据流水线状态。
自动任务只生成 pending 结果,永远不会自动发布策略。
## 保存与发布
“保存候选”只从服务端已注册的 `candidates.parquet` 读取定义,并重新校验 artifact schema、canonical signature、原始请求中的因子/策略范围和当前策略兼容性,再写入研究候选库。客户端只能提交 run ID 与 candidate signature,不能在保存时重交权重、方向、公式或代码。保存按 `(origin_run_id, candidate_signature)` 幂等;如果候选库已写入但 artifact backlink 回写失败,重试只修复 backlink,不会创建重复候选。
“发布”是独立的显式动作:
- 已有策略候选返回原策略 ID,不复制或覆盖源文件。
- 因子组合候选由服务端根据 run ID 与 candidate signature 派生独立策略 ID;客户端不能提交策略 ID、权重、方向、公式或代码。
- 不同 run 即使得到相同组合,也会生成互相独立的策略文件;同一 run 与 signature 重试时只验证已有源码并修复 backlink。
- 发布过程执行 AST/META 校验、create-only 原子写入、引擎 reload、策略缓存失效和监控状态失效。
- reload 或运行时失效失败会回滚首次创建的新文件;backlink 回写失败不会删除已成功加载的策略,重试可修复 backlink。
- 发布不会修改共享 `factor_rank_research` 源码或 override。
发布仅表示把固定候选变成可使用策略,不代表样本外表现会在未来持续。
## 性能口径
相关矩阵按交易日计算 pairwise-finite Spearman,只聚合因子平方级的逐日结果,不物化百万行永久 rank 宽表。缺失集合不同的因子会先取共同 finite 样本再排名;无法估计的 pair 保持空值,不按零处理。
生产 runtime 只生成本次搜索 horizon 的一列 forward label,并在阶段间释放中间列。内存表示优化对照 run `46ed81d537e9404baa06324a3ac3a45f` 在 132.0 万行、243 个交易日、44 个因子上峰值 RSS 为 1.291 GiB,总耗时 25.140 秒;相对 2.395 GiB、37.358 秒的原始对照基线,RSS 和耗时分别下降 46.09% 和 32.70%,四个 Parquet artifact 逐项完全一致。
最终 `mining-v2` 额外修复了缺失掩码下的 pairwise Spearman 口径,因此 correlation artifact 与 v1 不再要求数值相等。最终 run `10574abb5d8e4b32ade7cbdd23975c45` 峰值 RSS 为 1.350 GiB、总耗时 40.699 秒,相对原始对照基线内存下降 43.63%、耗时增加 8.94%;仍满足 1.5 GiB 内存门槛和统计增强耗时不超过 30% 的目标。v2 的 Float32 聚合与 Float64 慢参考具有相同 pair counts、空值位置和 0.75 剪枝判断,相关系数最大绝对误差为 `1.70e-7`
这些结果只证明上述基准工作负载达到目标,不表示所有数据规模和搜索预算都具有固定内存上界。当前验证结果:后端全量测试 `876 passed, 24 warnings`,挖掘相关回归含基准轨、跨折证据与晋级门槛共 145 项通过,前端 TypeScript 检查和生产构建均通过;另以真实数据完成 exploratory 运行核验 `selected`/`cross`/`benchmark` 三类行与发布禁用。
+2
View File
@@ -16,6 +16,8 @@
| 量价 / 涨停 | 量价齐升 · 高换手强势 · 连板股 · 断板反包 · 涨停动量 · 接近涨停 |
| 反转 / 波动 | 超跌反弹 · 超卖反转 · 新低反转 · 低波动龙头 · 回踩 MA20 · 回踩支撑 · 强势开盘 |
内置目录 `backend/app/strategy/builtin/` 还包含一个仅供挖掘 worker 使用的受控因子排名研究模板。它不出现在普通选股列表,也不能通过普通策略 API 直接运行或保存 override;挖掘结果发布时会生成独立策略。详见 [因子与策略挖掘](./mining.md)。
内置目录 `backend/app/strategy/builtin/` 由项目维护,**AI 生成的策略不会落入此目录**。
---