tradingSystem/docs/选股计划调整方案_2026-09-17.md

284 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.

# 选股计划调整方案与规格书2026-09-17公司质地为排序主轴传导降为雷达与带内次级键
> 2026-09-17 用户拍板通过。开发者可以是人,也可以是大模型。写法遵守 `.claude/output-styles/readable-chinese.md`。
> 改动全部落在选股系统仓库 `akg-factor-bridge`。数据基座、择时决策系统代码不动;持仓管理系统有一条协同项,单独审批。
> 第一到第十节是正文,附录是实现细节,标了「可不读」,但开发者动手前必须读附录甲乙丙。
> 基线:选股系统仓库 `main` 停在 18c55fb含 09-16 的小改 bb286c5选股系统机器停在 3d3f317等 09-18 拉。
> 本文件是四个仓库逐字同步的副本之一,改一份要同步其余。
---
## 一、这件事为最终目标买什么
**为什么现在改。** 三份读数指向同一件事。
1. 09-02 那份改进方案第 1.8 节用一个月样本做过传导有效性检验:传导越强,超额越负。强传导档五日超额 2.04,弱传导 0.39,无传导 0.00。结论写得很清楚:传导对"该看哪个环节"的定位有信息量,但"在环节里挑还没动的补涨"这一步把信息量丢掉了。
2. 08-28 的周报08-11 到 08-28十二个计划日又重复了这个形状主榜里强传导档五日超额 1.97、十日 4.84,无传导档 1.09 与 1.93;候选单整体跑输全池。样本仍然"只看方向",但方向没有变过。
3. 09-11 抽查主榜前一百名:有评析的七十只里质地差五十、中十四、好六。选股确实偏题材传导。
传导分的公式本身就解释了这个结果。今天的传导分等于源数乘以"一减已动比例",它奖励的正是"环节被指向但成员还没动",也就是被证明为负信号的那一步。而"环节被指向"这一位本身定位不负。所以正确的降权不是把权重系数调小,而是把传导从"排序主轴"换成"雷达门加带内次级键":它继续决定"该看哪个环节"和"为什么是现在",不再决定"谁排前面"。
**为什么是质地。** 个股深度评析已经能对三百五十九家公司算出质地档(好二十一、中八十七、差二百五十一),第二批定性视角与第三批预期视角两周内上线。这是主观选股四件事(行业逻辑、公司质地、估值预期差、时点)里第二件第一次有了稳定的数据源。主观选股经理的顺序是先选公司再等催化,排序主轴换成质地,是让计划的次序回到这个顺序。
**用户拍板三条2026-09-17。** 传导有效性不足,打分权重要降低,但对系统仍有价值;直接改现有候选单,不做并行名单;公司质地成为排序主轴。
**这件事买四样东西。**
1. 主榜、观察档、候选单三处的次序都由质地档决定,传导只在同一质地档内决定先后。持仓管理系统拿到的前三十名与择时决策系统的夜间分析池跟着变成"好公司优先"。
2. 传导保留两个位置:候选卡门槛一"环节被指向"不动;同一质地档内被指向的排在前面。它的"雷达"角色完整保留。
3. 一条开关逐字回旧,不改分数编码,外部平台的因子契约不受影响。
4. 周报四周内并列出"新序前二十"与"旧序前二十"两份名单的读数,四周后按读数决定是否保留。
---
## 二、原则
沿用五条加不改开关关掉逐字回旧同日重跑结果一致不做回测、因子合成、IC评价只用候选单复盘周报。本方案再加三条。
1. 分数编码不动。`factors.py` 里"主榜分等于二百加档位乘二十加组内分"的编码注册在外部平台当因子,改它会污染平台的因子历史。次序的改动全部放在计划装配这一层,与 09-14 那次质地重排(台账 058同一个落点。
2. 质地只认新鲜的报告。质地进任何判决或排序都受同一个新鲜度守卫(一百二十天),与逻辑状态第五路和持仓管理系统的基本面立场同口径。今天质地门与加减八分不看日龄,是一处不一致,顺手对齐。
3. 评析覆盖不能被排序轴锁死。评析目标名单如果只取新序前一百,新被指向的公司永远排在已评析公司后面,永远得不到报告。名单要与排序轴解耦。
---
## 三、做哪几件事
五个工作包,全在选股系统仓库;另有一条持仓管理系统的协同项,单独审批。
### 工作包一:排序轴换成质地(`plan.py`
**做什么。** 主榜与观察档两个分数序列在装配时按新规则重排一次,四个出口(接口、快照、逻辑状态入库、评析名单)照旧共用这个序。
**规则。**
1. 质地带 `quality_band`:好为三,中为二,无评析、证据不足、报告超过一百二十天为一,差为零。无评析放在中之下、差之上:不知道不等于差,但不该排在已核实的好公司前面。
2. 主榜次序:先按质地带降序;同一带内,被传导指向的排前面;再按原有组内分(六成"还没热"加四成"还便宜")降序;最后按原始分、代码作稳定的末位键。观察档没有档位,带内直接按原始分再按代码。
3. 传导在这里只剩两个作用:被指向与否是带内第一个次级键;强弱档位不再进次序。原始分与档位标签照旧输出,只是不再决定名次。
4. 新增四个字段:`quality_band`、`rank_axis`(取值 quality 或 transmission、`rank_old`(同一行在旧规则下的名次,供周报对照,四周后可以停)、`pointed`(被指向与否,从候选卡门槛一带出)。`score`、`score_adj`、`tier` 三个既有键的值与含义不变。
5. 开关 `PLAN_RANK_AXIS`,默认 `quality`;设 `transmission` 时走 09-14 那套(质地加减八分、不跨档),逐字回旧,单测钉住。
6. 主题限额的一处例外。截断函数把没有传导主题的行归进"无传导"一个桶,每桶最多五只。旧序下无传导排在主榜末尾,碰不到这条限额;质地序下前二十有不少质地好的无传导票,若仍受五只上限,第六只起就被质地中的被指向票顶掉,主轴被限额悄悄推翻。所以质地轴下无主题的行不受主题限额,有主题的照旧每主题五只。限额防的是单板块刷屏,无传导不是板块。`transmission` 轴下原样。
7. 逐票对账接口里"主榜第 N 名"仍按原始分序,它的语义就是 `rank_old`,应答里加一键 `rank_axis` 写明是 transmission文案不动。
**为什么是词典序不是加权。** 加权要发明三个新系数,没有读数支撑,也说不清楚。词典序每一层都能在卡上用一句话解释:先看公司好不好,再看有没有被产业链指向,再看还没热且便宜。
细节见附录甲。
### 工作包二:候选卡内的次序与质地门(`card.py`
**做什么。** 三门槛、一确认、三风险一个字不动(生产上门槛二"已启动"本来就关着)。改两处。
1. 卡内序从"先判决、再当日涨幅"改成"先判决、再质地带、再当日涨幅"。候选单与关注单的名次由此变成质地优先。开关 `CARD_SORT_BY_QUALITY`,默认开,关掉回旧序。
2. 质地门"差降关注"与工作包一的质地带共用同一个判定函数,带上一百二十天守卫;超期报告不再降档,也不再加减分。
**为什么不把质地升成门槛。** 无评析的公司还能进候选(排在后面),是为了不让评析覆盖不到的新公司被系统性排除。质地差已经进不了候选,等价于门槛,不必再改判决逻辑。
### 工作包三:择时决策系统的夜间分析池与评析目标名单(`pool.py`、`review_targets.py`
1. 入池来源加一个取值 `POOL_SOURCE=rank`:从主榜新序里取被指向的行,按名次截前 `POOL_TOP`(机器上是五十)。原来的 `tier`(只收强传导)与 `candidate` 两个取值保留,回旧用。夜间推理量不变,只是从"强传导前五十"变成"质地优先且被指向的前五十"。入池决定谁能拿到吸筹评分,吸筹评分又是候选卡的确认线,所以池子必须与持仓管理系统看到的候选前缀同一个序,否则质地好的票永远缺确认。上线次日核一条不变式:持仓管理系统的候选都在池里。核不过就打开备用开关 `POOL_RANK_KEEP_OLD`(默认关),把旧序强传导前五十并进来,受 `POOL_MAX` 兜底。
2. 评析目标名单改成两路并集:原始分序前一百(跟着雷达走,新被指向的公司先有报告)加新序前五十(好公司的报告保持新鲜),观察档前二十不变。开关 `REVIEW_TARGETS_AXIS`,默认 `both`,设 `score` 回旧。
### 工作包四:周报对照(`plan_review.py`
1. 过渡期四周,第一节多两份名单:主榜新序前二十、主榜旧序前二十。旧序从快照行的 `rank_old` 取,不另存文件。
2. 第二节多一组交叉分组:主榜按"传导档位乘质地带"分格,看传导在质地档内还剩多少信息量。
3. 既有的候选单按质地分组、档位表按质地分组、传导档位分组都保留。
4. 四周后的复核判据写死:新序前二十相对旧序前二十的五日与十日超额都不更差,且候选单跑赢全池的占比不低于旧序。达不到就把 `PLAN_RANK_AXIS` 切回,台账记一条。
### 工作包五:单测、台账、交接
1. 单测新文件 `test_plan_rank_axis.py`,照 `test_plan_quality_rank.py` 的写法,八例见附录丙,末行 ALL OK。既有 `test_plan_quality_rank.py` 照旧全绿。
2. 台账一条,编号按落笔时顺延(数据基座那四条在各自部署日先占号)。五段式写清改动、依据(上面三份读数)、预期、复核日(上线后四周)。草稿在附录丁。
3. 交接文档一段README 的计划字段表加四个新键;四仓库同步。
### 协同项:持仓管理系统(另一仓库,单独审批)
持仓管理系统向选股系统要三百只主榜,再自己按(基本面立场桶、原始分降序、上游名次)重排(`plan_feed.select_candidates`,参数 `PMS_PLAN_RANK_BY_QUALITY` 默认开,立场由它自己的 `fund_rules` 从评析摘要合成)。所以上游次序换掉后,它直接看到的变化只有两处:三百只前缀按新序切,页面上"候选榜第 N 名"的数字变了;它内部仍是两套质地序各排各的。要让质地序真正成为它的候选序,需要它加一条改动:计划带 `rank_axis` 为 quality 时改按上游 `rank` 排,并把自己的立场分桶关掉,三处同源。这条不阻塞本方案上线,因为它的看多分桶已经把好公司排在前面。另有一条要写进它的对接说明:质地序下强传导但质地差的票可能落在三百只前缀之外,那边不要把"强传导票变少"当故障。
---
## 四、顺序与时间
| 日期 | 星期 | 做什么 |
|---|---|---|
| 09-17 到 09-18 | 三到四 | 开发五个工作包,本地单测全绿,一个提交,交第十节的证据 |
| 09-19 | 六 | 我复审 |
| 09-21 | 一 | 收盘后选股系统机器拉代码,次日 07:10 的计划就是新序盘前链每步都是子进程读挂载目录里的新代码。接口容器仍建议同窗重启一次并预热计划接口经审批、避开七点到七点半与八点半到九点零五接口进程内存里持着旧的装配代码JSON 走快照直通没问题,但 md 格式的主榜表与快照读不到时的实时回落会走旧序 |
| 09-22 | 二 | 第一份新序计划。看候选单、前三十名、入池名单三处读数,交回 |
| 09-25 | 五 | 第一次带两份名单对照的周报(二十点十分由基座触发) |
| 10-16 前后 | | 四周复核,按工作包四第四条决定保留或切回 |
与数据基座的部署日不冲突09-22 基座是收盘后重建工作进程选股系统是前一天拉代码两台机器各做各的。09-18 那次选股系统拉代码与接口重启照旧,本方案的代码不在那次里。
---
## 五、开关与参数
| 键 | 默认 | 说明 |
|---|---|---|
| `PLAN_RANK_AXIS` | quality | transmission 即 09-14 行为,逐字回旧 |
| `CARD_SORT_BY_QUALITY` | 1 | 卡内序先质地带0 回"先判决再涨幅" |
| `POOL_SOURCE` | rank | 新增取值tier 与 candidate 保留 |
| `REVIEW_TARGETS_AXIS` | both | score 回"只按原始分前一百" |
| `COMPANY_REVIEW_STALE_DAYS` | 120 | 已有;从此对质地带、质地门、加减分都生效 |
| `PLAN_QUALITY_BONUS` | 8 | 已有;只在 transmission 轴下起作用 |
| `POOL_RANK_KEEP_OLD` | 0 | 备用:旧序强传导前五十并进池,只在"池包含候选"不变式核不过时打开 |
机器上的 `.env` 不需要加任何键,默认值就是新行为(今天机器上没有设 `POOL_SOURCE`,代码默认值生效)。回退时才写旧值。一条要记住:`.env` 里新加的键子进程能读到,但容器建起时已经在 `.env` 里的键改了值要重建容器才生效。备用开关 `POOL_RANK_KEEP_OLD` 默认关,只在第六节那条不变式核不过时打开。
一条说明:一百二十天的新鲜度守卫眼下是空转的,评析 09-09 才上线,最老的报告不到十天,要到明年一月才会第一次命中。现在定口径,不改今天任何一只票的读数。
---
## 六、验证方式
每条命令标明在哪台机器上运行。选股系统机器指 `ssh -p 2280 factor@192.168.16.155`,仓库在 `~/project/akg-factor-bridge`
**本地(选股系统仓库)。** 预期两行都是 ALL OK。
```bash
cd /Users/baobao/Documents/work/project/akg-factor-bridge && python3 test_plan_rank_axis.py 2>&1 | tail -1 && python3 test_plan_quality_rank.py 2>&1 | tail -1
```
**本地。** 逐字回旧。预期末行 ALL OK其中那一例断言四个开关都设旧值时装配输出与旧实现逐字节相同。
```bash
cd /Users/baobao/Documents/work/project/akg-factor-bridge && PLAN_RANK_AXIS=transmission CARD_SORT_BY_QUALITY=0 POOL_SOURCE=tier REVIEW_TARGETS_AXIS=score python3 test_plan_rank_axis.py 2>&1 | tail -1
```
**选股系统机器(上线次日 07:15 后)。** 预期候选单前几名的质地列以"好"开头;主榜前十二名里质地好或中占绝大多数;每行带 `quality_band`、`rank_old`、`pointed`。
```bash
cd ~/project/akg-factor-bridge && sed -n 1,30p $(ls -t data/plan/plan_*.md | head -1) && docker exec akg_factor_bridge python3 -c "import json,glob; d=json.load(open(sorted(glob.glob('data/plan/plan_*.json'))[-1])); print([(r['rank'],r['code'],r.get('quality_band'),r.get('pointed'),r.get('rank_old'),r.get('tier')) for r in d['main'][:12]])"
```
**选股系统机器。** 入池名单来源。预期干跑输出里每只票的 `_pool_source` 是 rank_top且都被指向。
```bash
cd ~/project/akg-factor-bridge && docker exec akg_factor_bridge python3 run.py push-pool --dry-run 2>&1 | tail -15
```
**选股系统机器(上线次日 08:45 后)。** 池子包含持仓管理系统的候选。预期打出的差集为空;不为空就打开备用开关 `POOL_RANK_KEEP_OLD` 并交回读数。
```bash
cd ~/project/akg-factor-bridge && docker exec akg_factor_bridge python3 -c "
import json, glob, pool
d = json.load(open(sorted(glob.glob('data/plan/plan_*.json'))[-1]))
cands = {r['code'] for r in d.get('candidates') or []}
in_pool = {r['code'] for r in pool.select_plan_rows(d['main'], 50, 'rank', set())}
print('候选不在池里:', sorted(cands - in_pool))"
```
**选股系统机器。** 评析目标名单两路并集。预期当日行数在一百到一百五十之间,`source` 为 main 的行里既有原始分前列也有新序前列。
```bash
cd ~/project/akg-factor-bridge && docker exec akg_factor_bridge python3 -c "import db; print(db.read_mysql('factor','SELECT source, COUNT(*) n, MIN(\`rank\`) r0, MAX(\`rank\`) r1 FROM t_akg_review_targets WHERE plan_date=(SELECT MAX(plan_date) FROM t_akg_review_targets) GROUP BY source').to_string())" 2>&1 | grep -v Warning
```
**选股系统机器09-25 周报后)。** 预期第一节出现"主榜新序前二十"与"主榜旧序前二十"两行,第二节出现"档位×质地"分组。
```bash
cd ~/project/akg-factor-bridge && grep -n "新序前二十\|旧序前二十\|档位×质地" $(ls -t data/review/复盘_*_next_close.md | head -1) | head
```
---
## 七、回退
四个开关全部设回旧值(`PLAN_RANK_AXIS=transmission`、`CARD_SORT_BY_QUALITY=0`、`POOL_SOURCE=tier`、`REVIEW_TARGETS_AXIS=score`),下一次装配即逐字回旧,不用重启,不用回代码。回退也记台账。
---
## 八、拍板点
代定十条,动工前不再问。要改就改这里。
1. 质地带四档:好三、中二、无评析与证据不足与超期一、差零。
2. 带内次序用词典序(被指向、组内分、原始分、代码),不用加权。
3. 传导强弱档位不再进次序,只保留"被指向"这一位。原始分与档位标签照旧输出。
4. 三门槛一确认三风险不动;质地门保持"差降关注",加一百二十天守卫。
5. 无评析的公司仍可进候选单,排在有评析的中之后。
6. 入池改成新序里被指向的前五十;夜间推理量不变。
7. 评析目标名单两路并集,与排序轴解耦。
8. 周报四周对照,判据在工作包四第四条。
9. 持仓管理系统的次序对齐作为协同项另批,不阻塞上线。
10. 09-03 方案里"不改传导公式、档位、分数编码、候选卡两个旋钮"这一句,本方案守住了公式、编码、两个旋钮,改的是装配层次序。台账里写明这是对 09-03 那条的有意修订。
---
## 九、交付要求
1. 一个提交,全中文、无前缀,格式"主题:改了什么",末尾带台账号。正文写清五个工作包各改了哪些函数、没改什么、为什么不合并。
2. 先写逐字回旧那一例单测,再改代码。
3. 所有排序带确定的末位键(代码),两次装配结果逐字节相同。
4. 只加键不删键,`score`、`score_adj`、`tier`、`verdict`、`card_rank` 的值与含义不变。
5. 台账、交接、README 三处一起改;四仓库同步。
6. 交付证据:提交号、两个测试文件的末行、逐字回旧那一例的名字、`git show --stat` 对照附录己的文件清单。
---
## 十、评审方式
评审者只看证据。核对步骤:读 diff 对照附录己的文件清单;本地跑测试文件;核四个开关都设旧值时的逐字回旧断言是真断言;核主题限额的例外只在质地轴下生效;核卡内序、入池、评析名单三处的次级键与去重;核 `_row` 只加键。上线次日与 09-25 周报后再核第六节的读数。判定三档同前:通过、有条件通过(只差运行样本读数)、退回(单测红、改了既有键语义、排序没有末位键、分数编码被动、主题限额把无传导票压在五只以内)。评审记录写 `docs/评审记录_<日期>.md`
## 十一、风险与预判
上线前写在纸面上,上线后逐条对表。
1. 候选单只数基本不变,变的是次序。判决规则一条没动。
2. 持仓管理系统看到的变化比想象的小,直到它按上游名次排。这是协同项,不阻塞。
3. 前缀效应:质地差的强传导票可能落在持仓管理系统的三百只前缀之外,与"质地差降关注"同向,写进那边的对接说明。
4. 入池名单从"强传导前五十"变成"质地优先且被指向的前五十",夜间推理量不变;不变式核不过就开备用开关。
5. 名单粘性:已评公司只有三百五十九家,新序前二十会被同一批好与中的票长期占据,日间变化只剩带内传导序。周报两序重合率就是这个粘性的读数,评析铺开会稀释它。
6. 一天滞后:新被指向的公司当天在未评带,评析名单按原始分序把它送给基座凌晨跑,最早次日进好或中带。当天它仍能进池、仍能判候选,可交易口径不受影响。
7. 盘中重跑:基座每小时出一批评析,中午重跑装配会读到更多报告、带会变。同日重跑一致只对相同输入成立,快照是唯一底本;接口按请求补的评析摘要不改名次。
8. 逐票对账接口的"主榜第 N 名"仍是原始分序,与计划里的 `rank` 会不一致,加 `rank_axis` 一键说明。
---
## 附录甲(可不读):`plan.py` 改动
行号按选股系统仓库 `main` 18c55fb。
- `_quality_bonus`(约 111 行)与 `_rerank_by_quality`(约 124 行)保留,作为 `transmission` 轴的实现。
- 新增 `quality_band(cr, stale_days) -> int`:读 `overall``age_days`;缺报告、证据不足、或 `age_days` 大于 `stale_days` 回一;差回零;中二;好三。同一个函数给 `card.py` 用(放在 `card.py` 里由 `plan.py` 导入也可以,取决于哪边不引入循环导入)。
- 新增 `_rerank_by_axis(ranked, cards, axis, stale_days)``quality` 轴按(质地带降序,被指向降序,组内分降序,原始分降序,代码升序)排。"被指向"以候选卡门槛一的判定为准,即 `cards[k]["gates"]["pointed"]``card.py:106-107`),不能只看传导视图的证据行,因为已启动成员在那张视图里没有证据行。组内分直接从 `score` 反解:主榜分减二百再减档位乘二十就是组内分,档位用 `int((score - 190) // 20)`;观察档没有档位,带内直接按原始分。返回值仍是分数序列,只改顺序,分值不动。
- 调用点在约 476 到 477 行两处,换成按 `config.PLAN_RANK_AXIS` 分派:`quality` 走新函数,`transmission` 走 `_rerank_by_quality`。质地轴下先用 `_rerank_by_quality` 算一遍旧序拿 `rank_old`(全量序里的名次,不是截断后的),再算新序。
- `_pick`(约 479 到 492 行)与 `pick_rows`(约 941 到 957 行)加参数 `uncapped_no_theme`:质地轴下无主题的行不计数、不受主题限额;`transmission` 轴下原样。`pick_rows` 缺省时从行的 `rank_axis` 推断,周报重建旧序时显式传假。既有单测 `test_plan_snapshot.py` 里钉住"无主题行同桶受限额"的那条不改,旁边加一条质地轴的对照用例。
- 返回字典只在质地轴下加 `rank_axis`、`rank_rule`(一句话写清次序规则)、`quality_band_counts`(主榜与观察档各带只数)三键。
- `_row`(约 494 行)加四键:`quality_band`(整数)、`rank_axis`(字符串)、`pointed`(布尔,来自候选卡 gates、`rank_old`(整数或空;只在 `quality` 轴下填,由 `transmission` 轴再算一遍序得到名次;四周后可以用开关关掉填空)。
- `encoding` 那一句(约 651 行)追加"名次按质地带优先,传导只在带内作次级键,开关 PLAN_RANK_AXIS"。`card_params.rules` 不改。
- `config.py``PLAN_RANK_AXIS`、`CARD_SORT_BY_QUALITY`、`REVIEW_TARGETS_AXIS` 三个键,`POOL_SOURCE` 的注释补 `rank` 取值,每个键三行注释写为什么。
## 附录乙(可不读):`card.py`、`pool.py`、`review_targets.py`、`plan_review.py`
- `card.py` 约 202 到 210 行:`cr.get("overall") == "差"` 改为质地带等于零(同一个函数,带一百二十天守卫);`sort_key`(约 247 行)在判决与涨幅之间插入负的质地带,受 `CARD_SORT_BY_QUALITY` 控,关掉时元组与旧实现逐字相同。`judge()` 的返回值加 `quality_band` 一键,`_row` 从这里取。
- `pool.py` 约 293 到 301 行:把选行那段抽成纯函数 `select_plan_rows(main_rows, top, source, tiers, keep_old=False)`,离线可测。`rank` 分支:`[r for r in main_rows if r.get("pointed")][:top]``_pool_source` 记 `rank_top``keep_old` 为真时再并进按 `rank_old` 排的强传导前 `top`,去重,`_pool_source` 记 `rank_old_strong`,总数受 `POOL_MAX` 兜底;`tier` 与 `candidate` 分支逐字保留。入池上下文 `strategy_context_entry` 只加 `quality_band`、`rank_axis`、`rank_old` 三键。
- `plan_reconcile.py` 的应答加 `rank_axis: "transmission"` 一键,文案不动。
- `review_targets.py` 约 14 到 15 行与 `build_rows``persist(date, snap)` 拿到的 `snap["main"]` 已是新序的全部行,每行带 `score``rank`。`both` 时主榜名单取"按 `score` 降序前一百"与"按 `rank` 升序前五十"的并集,去重后按 `rank` 排;`score` 时只取按 `score` 降序前一百(逐字回旧)。观察档照旧前二十。
- `plan_review.py` 约 378 到 384 行从快照 `_full` 读主榜行;约 419 行附近加两份名单"主榜新序前二十"(按 `rank`)与"主榜旧序前二十"(按 `rank_old`,缺该键的旧快照跳过这两份名单);约 431 到 436 行的档位分组外再加档位乘质地带的交叉分组,分组名写"档位×质地=强传导×好"这样;`compare_table` 不动。
## 附录丙(可不读):单测清单
`test_plan_rank_axis.py`,合成六到八行主榜数据加候选卡字典,各一例:
1. 四个开关都设旧值时,输出与 `_rerank_by_quality` 逐字节相同(逐字回旧)。
2. `quality` 轴下好在中前、中在无评析前、无评析在差前。
3. 同一带内被指向的在前,再按组内分。
4. 两次装配顺序相同(确定性)。
5. 超期一百二十天的好公司落到第一带。
6. 入池 `rank` 取值只取被指向的,数量等于 `top`
7. 评析名单两路并集去重且按新序。
8. 卡内序开关关掉回"先判决再涨幅"。
9. 主题限额:质地轴下五只无传导的好票全部进前二十;`uncapped_no_theme` 为假时只进五只;有主题的仍受每主题五只。
10. 备用开关:`keep_old` 为真时旧序强传导前 `top` 全在结果里,同一票不重复,总数不超过 `POOL_MAX`
既有单测要跟着动的两处:`test_plan_quality_rank.py` 的接线检查把匹配串改成新函数名;"不跨档"那一例只对 `transmission` 轴断言,质地轴本来就跨档。
## 附录丁(可不读):台账草稿
- 编号顺延 · 上线日 · 选股计划排序轴换成公司质地传导降为雷达门与带内次级键。依据09-02 第 1.8 节、08-28 周报、09-11 抽查三份读数一致,传导分的公式奖励的正是被证明为负的"未动补涨"。预期:候选单与前三十名以质地好中为主,四周内新序前二十相对旧序前二十的超额不更差。复核日:上线后四周。旧条 09-03"不改档位"那一句在此修订。
## 附录己(可不读):文件清单
`plan.py`、`card.py`、`pool.py`、`review_targets.py`、`plan_review.py`、`plan_reconcile.py`(只加一键)、`config.py`、`.env.example`、`README.md`、`docs/选股计划入池_对接说明.md`、`docs/选股说明_下游对接.md`、新 `test_plan_rank_axis.py`、既有测试的两处小改、`docs/复盘决定台账.md`、交接文档。清单外一律不动;`factors.py` 与 `api.py` 不碰。