akg-factor-bridge/docs/量化因子设计评审_2026-07-26.md

488 lines
27 KiB
Markdown
Raw Normal View History

2026-07-27 09:49:05 +08:00
# 量化因子导出与合成设计 v2.0 · 交叉评审2026-07-26
> 评审范围:`akg-factor-bridge` 全部代码 + `量化因子导出与合成设计.md` v2.0 + astock-kg 供数侧
> `transmission.py` / `hotspot.py` / `chainmap.py` / `market_snapshot.py` / `themes.py` /
> `graph_store.py` / `claim_store.py` / `enums.py` / `factor_coverage_probe.py`+ 相关设计文档。
> 结论按"必须修 / 需要决策 / 需要知道"三档排列,每条附代码出处。
---
## 0. 总体判断
**方向可行,我认同 v2.0 的转向。** §2.2 的「观察 vs 推断」类型学是全文最有价值的部分:
传导 = 当日 movers × 当前态图谱,两个输入都无耐久历史,回填必然携带成员性前视 —— 这个论证
成立,而且是三条否决理由里唯一决定性的一条(另两条只是"效果不好",这一条是"方法上不可能")。
把回测从生死闸降为仪表盘,在一个自己承认"无法准确估值"的领域里是正确的取舍。
**但 S1 现在有 3 处会让因子"看起来在跑、实际不成立"的硬伤1 处方法论表述与实际行为不符,
以及 1 处架构级缺口(赛道门槛 C 在当前四视图上无法实现)。** 这 5 项都在 G3 之前必须处理,
其中 1 项(快照冻结)每晚一天就永久少一天历史。
一句话版本:**代码质量不错、点时纪律做得比多数量化工程好;问题全部集中在"桥以外"
—— 桥假设上游是确定的、可复现的、可查询的,而这三条上游都不完全满足。**
---
## 1. 必须修:三处硬伤
### 硬伤 1 · 第一权重项不可复现,且被两道截断天花板锁住
`transmission.py:116` 在装配 `quiet` 落库字段时写的是:
```python
for m in quiet[:12]: # ← 只有前 12 个未动成员进 quiet JSONB
```
叠加 `scan()``max_candidates: int = 12``transmission.py:48`
`v_factor_transmission` 的**结构上限就是 12 × 12 = 144 行/日** —— 实测 83 行完全吻合,
说明它不是"覆盖稀薄",而是**被一个为旁批/展示写的截断锁住了**。
更麻烦的是这 12 个是哪 12 个。`quiet` 来自 `graph_store.topic_context("Segment", name)`
其 Cypher 是:
```cypher
MATCH (c)-[m:IN_SEGMENT {status:'active'}]->(t:Segment {segment_name:$id})
RETURN ... LIMIT $cap -- cap 默认 30且没有 ORDER BY
```
三个后果:
1. **同一天重跑 `transmission_scan` 可能得到不同的 `quiet` 集合与不同的 `moved_ratio`**
—— 顺序由 Neo4j 执行计划决定。桥内写入是幂等的,但**上游不是**,所以判收标准 §10
「重跑不产生重复或漂移」按现状无法成立。
2. 成员数 > 30 的大环节,`members_total` / `moved_ratio` 建立在一个**任意的 ≤30 抽样**上,
`moved_ratio` 直接是 `factor_value` 的一个乘数因子。
3. 哪只股票能进因子表,事实上由数据库返回序决定。
**修法(基座侧,属确定性缺陷修复,不算"新增计算",不破 §4 铁律)**
- `topic_context``ORDER BY coalesce(c.ts_code, c.entity_key)``cap` 提高或参数化;
- `transmission.scan` 落库的 `quiet` 存**全量**未动成员,旁批 prompt 仍只喂前 12
(两个用途本来就该分开);
- 视图额外暴露 `members_total` / `moved`,让桥能自检 `members_total == 30` 这类可疑值。
### 硬伤 2 · `n_paths` 被重复计数系统性抬高
`v_factor_transmission``jsonb_array_length(t.paths)` 当路径数
`sql/astock_kg_slot_views.sql:56`),而 `paths` 是**没有去重的**
- `graph_store.cascade()` 主干是变长边 `-[:SEGMENT_UPSTREAM_OF*1..N]->`
**变长匹配会把每种长度的路径各返回一条** —— `A→B``A→X→B` 同时出现、末节点都是 B
去重只做了路径内去环,跨路径不去重,且 `LIMIT 60``ORDER BY`
- `graph_store.transmission_targets()` 三个桶updown / supply / drives各自 DISTINCT
但桶间用 `out += ...` 简单拼接,**同一 target 可以从 supply 和 drives 各来一次**。
- `transmission._add()` 对每条 `(source, target, via)` 都 append 一条。
于是 `n_paths` 既不是"多少个源指向它",也不是"多少条独立路径",而是"多少次被提及"。
`factor_value = n_paths × (1 moved_ratio)``n_paths` 是主量级 —— 排序偏差直接传导到因子。
**修法(只改视图,零基座改动,成本最低的一条)** 把它改成 distinct source 数,
这也更贴合传导模块自己写的语义"多源汇聚 = 传导逻辑更硬"
```sql
CREATE OR REPLACE VIEW v_factor_transmission AS
SELECT t.scan_date,
q->>'ts_code' AS ts_code,
(SELECT count(DISTINCT p->>'source')
FROM jsonb_array_elements(COALESCE(t.paths, '[]'::jsonb)) p) AS n_sources,
jsonb_array_length(COALESCE(t.paths, '[]'::jsonb)) AS n_paths_raw,
t.moved_ratio,
t.members_total, -- 桥用来识别 ≤30 截断
t.moved
FROM transmission_candidates t
CROSS JOIN LATERAL jsonb_array_elements(COALESCE(t.quiet, '[]'::jsonb)) AS q
WHERE q->>'ts_code' IS NOT NULL AND q->>'ts_code' <> '';
```
桥侧 `build_transmission` 改用 `n_sources``n_paths_raw` 留着当观察列。
### 硬伤 3 · 事件回填会静默返回空README 里那条示例命令现在就是无效的)
`factors.build_event``common.trading_days(start, end)` 当交易日历(`factors.py:137`
`common.trading_days` 查的是**热度表**
```python
# common.py:24-30
"SELECT DISTINCT trade_date FROM stock_fund_heat_scores WHERE trade_date BETWEEN %s AND %s"
```
热度最早只有 **2025-03-26**(文档 §6.2 自己记着)。所以:
```bash
python run.py build akg_event --mode history --start 2024-01-01 --end 2026-07-24 # README 第 58 行
```
`cal` 为空 → `return _EMPTY` → 只打印「无数据(跳过)」,**不报错、不告警**。
文档 §7 声称事件可回填到 2014 年,实际上 2025-03-26 之前一天都拿不到。
**修法**:日历改用 `gp_day_data``DISTINCT timestamp`5584 天,覆盖全历史),
热度表最多作为并集补充。顺带在 `cal` 为空时打印告警而不是静默跳过。
### 附带两条:放量时会炸,现在不炸
- **`_read_gp_price` 不带 universe 过滤也不分块**`factors.py:64-67`):单日 ~5000 行没问题,
`--mode history --start 2006-01-01` 会把千万级行拉进 pandas。加 `AND \`{col}\` IN (...)`
+ 按月分块。
- **`write_factor` 单事务 `executemany` 全量**`common.py:69-75`):热度全史约
322 × 1674 ≈ 54 万行一次提交,走 ShardingSphere 代理有风险。按日期分块提交。
另外 `write_factor(mode)` 这个参数收了但没用到,可以删。
---
## 2. 需要决策0.5 / 0.3 / 0.2 实际上是"传导优先",不是"加权混合"
这是我最想让你在 G3 开工前想清楚的一条,因为它是**逻辑选择而不是回测选择**。
传导在池内约 95% 为 0实测 83/1675。对一列 95% 是 0 的数做
`(x mean) / std``std` 完全由那 5% 决定,于是**非零值的 z 落在 +4~+5零值落在 0.2 附近**,
`0.5 · z_T` 的组间落差约 2.2;而 `0.3 · z_H + 0.2 · z_V` 用稳健 z 之后全幅只有约 1.7。
落差大于全幅 → 跨组不可逆。
我做了数值验证200 只候选、其中 10 只有传导、其余口径按 §3.4 实现600 次重复):
| w_T | top20 中传导票 | top20 中非传导票 |
|---|---|---|
| **0.50(当前)** | **9.8 / 10** | 10.2 |
| 0.30 | 9.1 / 10 | 10.9 |
| 0.20 | 7.8 / 10 | 12.2 |
| 0.15 | 6.6 / 10 | 13.4 |
| 0.10 | 4.7 / 10 | 15.3 |
也就是说:**当前参数下有传导的票几乎必然全部进入前列0.3 和 0.2 只在"有传导组内"
和"无传导组内"各自排序,跨组不起作用。** 要让"很冷很便宜的非传导票"真有机会赢过
"边际传导票"`w_T` 得降到 0.10~0.15。
两条路,选一条并写进文档:
- **A我推荐而且我认为它更贴合你原话"传导第一"**:承认它是两段式,直接写成
`primary = 传导档位0 / 1 / 2``tiebreak = 0.6·z_H + 0.4·z_V`。
好处:语义与实际行为一致、可解释、调试时能一眼看出是哪一段在起作用、没有隐藏非线性。
代价:无。这同时把 §9-9 的「还没热该不该做成条件项」自然解决了 ——
**现状在 95% 稀疏下已经等价于条件项了,不如写明**
- **B**:真要连续混合 → `w_T ≈ 0.12`,或对 `z_T` 做有界变换(如 `clip(z,±3)/3 × 1.5`
实测能把 9.8 拉到 7.5)。
**顺带一个共线性提醒**:门槛② (`upside ≥ θ_v`) 与 `z_V` (`upside`) 是同一个变量进两次;
而"还没热"与"还便宜"在 A 股高度共线(涨上去了 → 又热又贵)。
建议 G1 的 probe 直接加一节输出池内 `(z_T, z_H, z_V)` 的相关矩阵 ——
**如果 `corr(z_H, z_V) > 0.5`,那"三项加权"实际是"两项",权重讨论要重开。** 这一节几乎零成本。
---
## 3. 架构缺口:赛道门槛 C 在当前四视图上无法实现
**图谱在 Neo4j不在 PostgreSQL**`graph_store.py:17` 起全走 `neo4j` driver。PG 侧
`industry_pools` 的完整 DDL 只有 4 列(`claim_store.py:439-445`
```sql
theme TEXT PRIMARY KEY, members JSONB NOT NULL, stats JSONB, refreshed_at TIMESTAMPTZ
```
`members` 元素只有 `ts_code / name / tier / confidence / supporting` ——
**成员级既没有 segment也没有 layer**。环节归属是读时从 Neo4j 重算的
`chainmap.py:61` → `graph_store.chain_view(theme)`)。
所以 `frontier_tracks.yml` 里规划的 `kg_segments` / `kg_concepts` 两个映射,
**在现有四个只读视图上没有数据源**。三条路:
1. **从 claims 重建**(可行,一条 SQL`WHERE predicate='IN_SEGMENT' AND object_type='Segment'`
再 join `entity_links` / `company_master` 取 ts_code。但 `claims` 是**融合前的只追加表**
没有 `status` / `supersede` / tier 裁决(那些只长在 Neo4j 边上),会把已被顶替的历史归属
一起捞出来;且 object 侧没有 norm 生成列,同义环节不会合并。**可以救急,不能当正解。**
2. **桥直连 Neo4j** —— 破坏"桥只连三库"的设计,且把裁决逻辑复制进桥。不推荐。
3. **推荐:基座加投影表 + 第五、第六个只读视图。**
`v_factor_segment_members(segment_name, chain, ts_code, tier, status, updated_at)`
`v_factor_segment_edges(up, down)`,由基座现有的 Neo4j 投影 beat 顺手物化到 PG。
这是**投影**不是**打分**,不破 §4 的"零新增因子计算",桥仍然只连三库。
`probe.py:12-13` 的注释其实已经想到了这件事("G2 把池清单固化成第五个插槽视图"
只是低估了它的必要性 —— 它不是优化,是 C 门槛能不能实现的前提。
### 3.1 四层枚举(材料→设备→制造→应用)在系统里根本不存在
全仓唯一的层级来源是 `chainmap._topo_layers()``chainmap.py:310-336`)的 Kahn 拓扑 depth
**per-theme、相对、孤点与环上节点为 `None`**,标签由 `_layer_label` 现算成上游/中游/下游,
不落库。而且"不存层级"是刻意的 —— `enums._SEGMENT_BAD_SUBSTR`
「上游 / 中游 / 下游 / 环节 / 场景 / 领域」列为环节名**禁用词素**,注释写明
"链位置由图上边表达"。
结论:`track_members.layer` 只能**在 yml 里人工指定**,并记一列 `layer_source`
`yml` / `graph_depth` / `unknown`)。而且 **S1 的公式压根不用 layer** ——
建议把它降级为可选元数据,**不要让它阻塞 G2**。
### 3.2 赛道覆盖风险的具体量级
`industry_pools` 的起点是人工建的两个池(光通信 / 消费电子,见 `涌现式应用层重构设计.md:30`
之后靠周一 `refresh_pools` 自动收录达标涌现主题≥3 环节且 ≥3 上市成员,
`graph_store.EMERGE_MIN_SEGMENTS/EMERGE_MIN_LISTED`。theme 是 `IN_SEGMENT.chain` 修饰符原文,
`segment_name_ok` 卫生闸≤12 字中文裸名)。
**在这个生成机制下,核聚变 / 低空经济 / 商业航空航天大概率查不到** ——
它们需要有人先投过对应的产业链研报。文档 §3.2 已经把这列为"关键前置风险",但给的对策
"覆盖为空的赛道宁可先不放进来")会让 C 塌成「通信 + 算力 + AI」也就是
**退化成"电子 + 计算机",门槛几乎不筛东西**。
建议 C 做成**两级并强制标注来源**
| source_rule | 证据强度 | 来源 |
|---|---|---|
| `graph` | 强,可审计到具体 claim | `IN_SEGMENT` / `HAS_CONCEPT` 边 |
| `fallback` | 弱,仅作占位 | 概念标签命中 / 申万行业白名单 |
`track_members` 里逐行记 `source_rule`,仪表盘里把两级分开看。这样 C 既不会因为图谱覆盖
不足而变空集,也不会把弱证据伪装成强证据。**同时,覆盖为空的赛道本身就是一张采集清单**
—— 这正是基座「需求闭环」的用法,比放弃赛道有价值。
---
## 4. 逻辑一致性v2.0 把 v1.1 一个仍然成立的开放问题删掉了
v1.1 §8-2 问过「**覆盖池历史快照缺失**:回填期 universe 用当前池近似(有轻微前视)
还是曾入池并集?」—— v2.0 的 §9 十个开放问题里**没有这一条**。
但按 §2.2 自己的论证,这一条现在**更重要**
- `v_factor_universe` = 全部 `industry_pools` 成员并集,而 `industry_pools`
**只存最新态、每周一自动长大**beat 表:池刷新周一 6:00
- §7 判定"可直接回填"的三路heat / event / upside**全部经过这个 universe 过滤**。
于是它们的**值**是点时正确的,**成员性**却是前视的 —— 用今天的池去筛 2025 年的历史,
而今天的池里有大量主题是 2026 年的研报才长出来的。**这就是 §2.2 批评传导时用的同一条论证,
只是上升了一层。**
- 更直接的工程后果:`z` 统计量mean / std / median / MAD在**每周一发生结构性跳变**
IC 序列和分层读数会带一个周期性伪影。
**建议(两条一起做,都很便宜):**
1. **子因子表去掉 universe 过滤**,全市场出行。理由:子因子表的定位是"可独立观察的仪表"
过滤只该发生在消费端。行数代价很小heat 池内 1674 → 全市场约 5000
换来的是三张点时干净的面板,且平台上的子因子 IC 读数才有市场级意义。
2. **universe 只在 `akg_score` 侧当过滤器,并每日落快照**(见下节)。
基座侧同步加一张 `industry_pools_history(snapshot_date, theme, members)`
`refresh_pools` 顺手写。
---
## 5. 今天最该做、也是唯一不可补救的一件事:开始冻结快照
传导、`industry_pools`、Segment 边、前复权 close —— 这四样都是"过期即不可复原"
- 传导§2.2 已经论证清楚。
- 池 / Segment 边:随研报持续生长,只存最新态。
- **前复权 close 比文档写的更麻烦一层**`gp_day_data` 是前复权、锚在最新日,
所以每次有股票除权,**历史 close 会被整体重写** —— `akg_upside` 的历史值因此
**不可复现**(今天回填的和三个月后回填的不是同一组数)。文档 §7 只提了
"除权点系统性偏移",实际还叠加一个可复现性问题。
而 §10 写的「GRU 复活条件:传导积累出 ≥1 年 live 历史」——
**这个计时器只有在开始存快照的那一天才启动。** 现在已经过去 3 天07-11 起),
每晚一天就永久少一天。
**建议在 G1 之前插一个 G0.5「冻结与修复」里程碑**,做三件事,都不依赖赛道清单:
1. **输入冻结**:每日把 `universe` / 四路原始值 / 门槛判定结果 / 最终分
落一份 `data/frozen/YYYY-MM-DD.parquet`(桥自己的 `data/`,与赛道快照同一机制)。
这一条**同时满足** §10 的「重跑不产生漂移」、「赛道成员快照可审计」、
以及未来任何回溯需求 —— 是全文性价比最高的工程动作。
2. 修掉第 1 节的三个硬伤。
3. 基座开始积累 `industry_pools` / Segment 边的每日(或每周)快照。
---
## 6. 供数侧需要知道的事实(会影响读数怎么解释)
### 6.1 传导链条的上游有两张空表 —— 这才是 83 行/日的真正原因
`数据源盘点.md §1a` 记着:`gp_sector_daily` **当前空表**、`gp_market_sentiment` **当前空表**
`t_signal_daily_results` **05-15 停更**。于是 `hotspot._pick_anomalies` 的四路异动源里:
| 源 | 状态 |
|---|---|
| ① 板块相对强度(`gp_sector_daily` | ❌ 空表 |
| ② 概念 / 行业资金净流入 | ✅ |
| ③ 池内个股(`pct_change ≥ 5` **或** `total_score ≥ 0.9` | ⚠️ 只剩涨幅门(评分停更) |
| ④ 事件 / 盘中流雷达 | ✅ |
**本来最该驱动"板块传导"的板块级异动源现在是缺的。**
所以传导稀薄不主要是"图谱覆盖不够",而是「空表 + 12×12 截断」两件事叠加。
**要提传导覆盖,优先补 `gp_sector_daily`,比调任何参数都有用。**
### 6.2 movers / quiet 不看 `scan_date`
`hotspot._movers_set()``_latest_mkt("stock")``SELECT max(trade_date) FROM mkt_daily`
`hotspot.py:45-55, 86-89`)。也就是用**最新快照**,不是 `scan_date` 当天的快照。
live 同日运行时两者一致,没问题。但如果 17:30 的 `sync_market` 失败或晚点,
**18:15 的传导扫描会用 T1 的 movers写成 `scan_date = T`,而桥完全无从察觉** ——
第一权重项拿到一份"陈旧数据贴着新鲜标签"。
建议:基座加一道 freshness 断言(`mkt_daily` 无 `scan_date` 当日 stock 行则 abort scan
并在视图里带上实际用到的 `mkt_trade_date` 让桥能自检。
这也是**除 §2.2 之外,传导不该回填的第二个独立的、机械层面的理由** —— 值得补进文档。
### 6.3 一致预期的口径细节(影响门槛②怎么理解)
`sync_consensus``market_snapshot.py:493-588`
- `asof_date = date.today()`,聚合窗口 `report_date >= today 90d`
→ 桥的 `merge_asof(backward)` **点时正确 ✅**,这块没问题。
- `target_mid_avg` 是**90 天内全部研报行的简单平均**`market_snapshot.py:548`
遍历的是 `rows` 而不是 `qrows`**不按机构去重、不按时间加权**。
一家券商 90 天发 5 篇就有 5 倍权重89 天前的目标价与今天的等权。
- 实测 `max_price` 非空仅 0.4% / `min_price` 31%docstring 记录),所以"目标价中枢"
**主要由单值目标价构成**"中值"的语义比文档写的弱。
**含义**θ_v = 0 这道门槛在急涨阶段会自动收紧(分母是今天的价、分子是最长 90 天前的目标价),
它**天然带反动量色彩**。这和你的漏斗逻辑同向(涨上去了就没容错度),
但要知道它不是一道纯"估值"门槛 —— 这也加重了第 2 节说的三项共线性问题。
### 6.4 §9-6upside 历史重建 甲 / 乙):甲案,成本远低于文档估计
`sync_consensus` 只要把 `date.today()``market_snapshot.py:529`)和
`since``:507`)参数化成一个 `asof: date`,就能对任意历史日重放**同一套聚合逻辑**
live 与 history 共用一份代码、零漂移风险。改动量大约 3 行。
乙案(桥侧自行聚合 `gp_report_rc`)会让聚合逻辑在基座和桥各存一份 ——
这正是文档自己指出的漂移风险,而甲案的"工作量在基座侧"这个缺点被高估了。
回填时记得把用到的 `close` 一并落进 `data/frozen/`(前复权可复现性问题,见第 5 节)。
### 6.5 事件因子的年报污染 —— S2 上否决闸之前必须处理
`v_factor_events``ts_code``documents.meta->>'company_ts_code'`。公告确实 100% 可靠,
但**年报也有这个锚**,而**一份年报能抽出十几条 EVENT** —— hotspot 为此专门做了防刷屏
`hotspot.py:92-99` 的注释:"实测年报副产物把台账刷满的教训",对策是 other 不进、
同主体同类型只取最新、单主体 ≤2 条)。**桥侧没有任何等价保护。**
后果:年报披露日会形成一个巨大的尖峰,且年报里提到的**历史**诉讼 / 处罚会被记成
当日的负面事件。若 S2 按计划用 `akg_event < θ_e` 做否决闸,
**会因为一份年报提到历史诉讼而否决掉一只好股。**
修法S2 之前):按文档类型只取公告,或按 `(ts_code, event_type, 自然日)` 去重 + 单日封顶。
极性表§9-5在这个问题修掉之前调得再准也没意义。
### 6.6 probe 与桥的事件口径不一致
`factor_coverage_probe.cov_event``:115-128`)用 `claims.subject_id` 去匹配池成员名/码,
而桥用文档锚 `documents.meta->>'company_ts_code'`。**两个数不会对得上** ——
读 G1 报告时别把它们当同一个指标。建议顺手把 probe 改成同一口径,否则会误判覆盖面。
### 6.7 一处枚举不一致(小,但会咬人)
`enums.EntityKind` 只有 6 个成员,**没有 Segment**;但 `_ID_KEYS`、`ALLOWED_OBJECT_TYPES`
都把 Segment 当一等节点。类型闸门走的是 `ALLOWED_OBJECT_TYPES` 所以不炸,
`EntityKind` 已经不是节点类型的可信清单了 —— 谁下次拿它做遍历会漏 Segment。
---
## 7. 判收标准的两处需要改写
**§10「每日调度幂等重跑不产生重复或漂移」**
桥内成立 ✅,上游不成立 ❌(硬伤 1 的 Neo4j 返回序、前复权重写)。建议拆成两条:
- 桥内写入幂等 —— 现状已满足;
- **外部不可复现的部分靠输入冻结解决** —— 即第 5 节的 `data/frozen/`
把"可复现"重新定义为"可从冻结输入重算出同一个值",这是唯一诚实且可达的口径。
**§7-A「`akg_score` 只在当日传导表有数据的交易日出行」**
应加强为 **当日「候选池内」传导非空**。传导 83 行/日是**全池**口径,
过完 C ∩ V 之后池内可能一只都不剩 —— 那天照样会写出一张"第一权重项全为 0"的表,
正是 §7-A 想避免的情况。
同时建议每日日志固定打两个数:`|P|`(候选池规模)与 `|P ∩ 传导>0|`
后者才是真正决定榜首的数量。若它长期是 0~3那么"组合"实际上是 1~3 只票,
且这 1~3 只是从一个 12 × 12 截断列表里出来的 —— 集中度需要在文档里明写。
---
## 8. 给平台评价层的一个建议(成本极低、收益很大)
按现在的设计,`akg_score` 只在候选池内出行 →
**平台的 IC / 分层只能看到"池内排序"的价值,完全看不到两道门槛的价值**
而门槛才是这套逻辑的主体。仪表盘会系统性低估这个因子。
加一个因子就解决:
| 因子 | 出行范围 | 回答的问题 |
|---|---|---|
| `akg_gate` | **全覆盖池**0/11 = 过 C ∩ V | 门槛本身有没有价值(分层直接读出门槛内外的收益差) |
| `akg_score` | 候选池内 | 池内排序有没有价值 |
两个仪表各答一个问题,"哪一项在拖后腿"一眼可见。这比在一个因子上纠结 IC 高低有用得多,
而且完全符合"回测是仪表盘不是判官"的定位。
---
## 9. 开放问题§9我的建议答案
| # | 议题 | 建议 | 依据 |
|---|---|---|---|
| 1 | 热度语义翻转 | **同意取负**,但别做成独立加性项 → 做成组内 tiebreak | 第 2 节 |
| 2 | 完整赛道清单 | **先跑 G1 体检再定清单**不要先定清单再体检C 做两级graph / fallback并强制标 `source_rule` | 3.2 |
| 3 | θ_v 的 q | 先留 q = 0等 G1 分布定;但注意 upside 主要由单值目标价构成,**绝对水平的可信度低于相对排序** | 6.3 |
| 4 | 无券商覆盖股 | S1 保持"不过门槛",但**单独输出一张「赛道内 + 无覆盖」观察名单** —— 这正是你论题里最该出现的标的,别让它无声消失 | §1 研报滞后论 |
| 5 | 事件极性 / 半衰期 | 延后到 S2 没问题,但**先修年报污染** | 6.5 |
| 6 | upside 重建 甲 / 乙 | **甲案**,只需给 `sync_consensus``asof` 参数(约 3 行) | 6.4 |
| 7 | score 历史 A / B | **A**,且加强为"**池内**传导非空" | 第 7 节 |
| 8 | 权重 0.5/0.3/0.2 | 次序认可;**建议改结构(两段式)而不是改数值** | 第 2 节 |
| 9 | 还没热是否条件项 | **S1 就上**。现状在 95% 稀疏下已经等价于条件项,不如写明 | 第 2 节 |
| 10 | 调度时点 | **T 日 18:40 算(热度用 T1**。漏斗赌的是左侧位置,热度差一天的信息损失远小于因子延迟一天的机会损失;且 T+1 早间补算会让"T 日因子"依赖 T+1 数据,语义上更难向下游解释 | §3.4 末 |
| **11新增** | **universe 历史快照** | v1.1 §8-2 被 v2.0 删掉了,但它现在更重要 → 子因子去掉池过滤 + 池每日快照 | 第 4 节 |
---
## 10. 对 G 系列里程碑的微调建议
```
G0 文档评审(含本评审的取舍)
G0.5 ★新增★ 冻结与修复 —— 不依赖赛道清单,且第 3 项每晚一天永久少一天历史
① data/frozen/ 输入冻结机制
② 修硬伤 1/2/3其中硬伤 2 只改视图)
③ 基座开始积累 industry_pools / Segment 边快照
G1 赛道覆盖体检 + probe 加两节:
corr池内三项相关矩阵· tracksyml 草案在图谱里做成员性试算)
+ 原有三件factor_coverage_probe 补跑 / upside 分布定 q / gp_day_data 起始日复核)
G2 赛道成员表落地layer 降级为可选元数据,不阻塞)+ 第五/第六个插槽视图
G3 漏斗实现 + 注册 akg_score **与 akg_gate**(第 8 节)
G4 回填与仪表盘upside 走甲案)
G5 每日调度上线T 日 18:40
```
---
## 11. 提交提醒
已核对两个仓库的实际状态(`git status`
**`akg-factor-bridge` 有 4 项未提交**(最后一次 commit 是 `42659b9 历史深度数据探查`
```
M README.md
M run.py
?? probe.py ← G1 体检脚本,全新未跟踪
?? 量化因子导出与合成设计.md ← v2.0,全新未跟踪
```
**`astock-kg` 工作区干净** —— `factor_coverage_probe.py` 已在 `32448c3` 入库,无需再提交。
**一处文档不同步需要处理**`astock-kg/docs/量化因子导出与合成设计.md` 还是 **v1**
2026-07-24四路对称 + 平台拟合合成),而桥仓库那份已经是 **v2.0**(自洽公式)。
两份同名文档在两个仓库里讲相反的方案,下次翻文档一定踩坑。
建议:**只留桥仓库这一份为唯一事实源**`astock-kg/docs/` 那份改成一行指路
"本设计已迁至 akg-factor-bridge 仓库,见该工程根目录同名文档"),并在提交信息里写明。
开发机执行按硬规矩push 由你做):
```bash
cd ~/Documents/work/project/akg-factor-bridge
git add -A
git commit -m "docs: 量化因子设计 v2.0(推翻拟合合成,转自洽景气度漏斗)
feat: G1 赛道覆盖体检 probepools/price/upside 三节,只读)
docs: README 补 probe 用法与数据前沿自检"
# 文档去重后(把 astock-kg/docs 那份改成指路)
cd ~/Documents/work/project/astock-kg
git add -A
git commit -m "docs: 量化因子设计迁至 akg-factor-bridge 仓库v1 留指路,避免与 v2.0 并存)"
```
本评审文档建议放 `akg-factor-bridge/评审_v2.0_2026-07-26.md` 一并提交。