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

488 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 量化因子导出与合成设计 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` 一并提交。