方案副本同步:复审记录与上线协同阶段工作方案(2026-09-17),选股计划调整方案五处文字修订
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
a78e737e74
commit
15d18a76e2
|
|
@ -0,0 +1,265 @@
|
|||
# 上线与协同阶段工作方案与规格书(2026-09-17):修复小包、持仓管理系统同序、周报底本
|
||||
|
||||
> 2026-09-17 下午写,供拍板与开发。承接两份规格书:《选股计划调整方案与规格书_2026-09-17》(排序轴换质地,代码已交付,复审见 `docs/评审记录_2026-09-17.md`)与《部署与判收阶段方案与规格书_2026-09-16》(数据基座 09-18、09-22、09-29 三个部署日,原样不动)。
|
||||
> 开发者可以是人,也可以是大模型。写法遵守 `.claude/output-styles/readable-chinese.md`。第一到第十一节是正文,附录是实现细节,标了「可不读」,但开发者动手前必须读对应工作包的附录。
|
||||
> 基线:选股系统仓库 `main` 4d7ca55(随本方案推送),选股系统机器停在 3d3f317,等 09-21 拉。数据基座 `master` 79abcf2、开发分支 `nextphase-2026-09` 末尾为本方案副本提交,基座机停在 3f45294,等 09-18 拉。持仓管理系统仓库 `main` 停在 a78e737 之后的文档提交,机器上的代码未动。
|
||||
> 本文件是四个仓库逐字同步的副本之一,改一份要同步其余。
|
||||
|
||||
---
|
||||
|
||||
## 一、这一阶段为最终目标买什么
|
||||
|
||||
排序轴换质地的代码已经写完并复审。这一阶段要把它干净地上线,并让下游真正吃到这个序。买三样东西。
|
||||
|
||||
1. **09-22 的第一份质地序计划,两条路给出同一份。** 复审查出装配路与快照路对"已启动成员"的主题限额判定不一致,同一天 md 与接口可能给出不同的前二十。修复小包把截取规则收成一处实现,再上线。
|
||||
2. **持仓管理系统的候选序与选股系统同一个序。** 今天它拿到三百只前缀后自己按"基本面立场桶、原始分"重排,两边是两套质地序各排各的。协同项让它在上游是质地轴时按上游名次排、关掉自己的分桶,选股系统、入池、持仓管理系统三处同源。这条 09-17 的规格书里标为"另批",本方案就是那次审批的规格。
|
||||
3. **09-25 起的周报读的是当天真正出过的计划。** 周报现在把每个计划日重新装配一遍,用的是跑周报那一刻的评析报告。四周复核比的是两份名单谁更好,两份都应该是当天落盘的那份。这一条要拍板。
|
||||
|
||||
---
|
||||
|
||||
## 二、原则
|
||||
|
||||
沿用五条:加不改;开关关掉逐字回旧;同日重跑结果一致;不做回测、因子合成、IC;评价只用候选单复盘周报。本阶段再守两条。
|
||||
|
||||
1. 一处规则一处实现。两条路要同一个结果,就让两条路调同一个函数,不在两处各写一遍再靠单测钉。
|
||||
2. 下游按上游的序,不各自发明序。上游给了名次与主轴名,下游读它,不重排。
|
||||
|
||||
---
|
||||
|
||||
## 三、做哪几件事
|
||||
|
||||
三个工作包。甲在选股系统仓库,乙在持仓管理系统仓库外加选股系统的一处小改,丙在选股系统仓库、要拍板。
|
||||
|
||||
### 工作包甲:修复小包(选股系统仓库,必做)
|
||||
|
||||
对应评审记录第四节的三处必改,一个提交,09-18 复审后推送,09-21 拉。
|
||||
|
||||
1. **主题限额的截取规则收成一处。** 新建一个模块级纯函数做"按主题限额截取",`collect` 里的 `_pick` 与模块级的 `pick_rows` 都调它,只是各自告诉它怎么从一条记录取主题。主题的取法与 `_row` 写 `evidence` 的取法同源,含已动成员视图的补充。这样已启动成员在两条路上都算进所在主题、受限额;没有主题的行在质地轴下免限额、不计数。为什么这样改:不是给装配路加一个"已启动成员也算主题"的特判,而是让两条路根本没有第二份规则。
|
||||
2. **`encoding` 句只在质地轴下追加。** 旧轴下这句话与实际次序相反,持仓管理系统会把它原样展示给人看。
|
||||
3. **交接文档补一段。** 在 `docs/交接_2026-09-10.md` 末尾加一节带日期的补记,台账那一行改成 063,四仓库同步。
|
||||
4. **单测。** `test_plan_rank_axis.py` 加两例:已启动成员在两条路上都算进所在主题;无主题行在质地轴下免限额、在旧轴下归"无传导"桶计数。`test_plan_snapshot.py` 里"拿真实快照逐行比对"那一例不动,它在部署机上是这件事的最终读数。
|
||||
|
||||
细节见附录甲。
|
||||
|
||||
### 工作包乙:持仓管理系统按上游名次排(持仓管理系统仓库,另加选股系统一处小改)
|
||||
|
||||
**做什么。** 持仓管理系统取候选时,上游计划带 `rank_axis` 为 quality 就按上游名次升序排,不再按立场桶与原始分重排;上游没带这个键(旧计划、或选股系统回退)就走现在的路。一个开关,默认开,靠上游的键自门控,所以 09-22 之前它什么都不改。
|
||||
|
||||
**为什么。** 入池已经改成"新序里被指向的前五十",选股系统的候选单已经是质地序。持仓管理系统若仍按自己的立场桶排,三处里就它一处是另一个序,四周复核时说不清它拿到的前三十为什么与选股系统的前三十不一样。它的立场桶本来就是在没有上游质地序时的替代品,上游有了,替代品退场。
|
||||
|
||||
**六件事。**
|
||||
|
||||
1. 计划应答归一时把每行新加的四个键与顶层的 `rank_axis`、`rank_rule` 带进来,缺就是空。
|
||||
2. 候选选择函数加一个参数"按上游名次排",只在计划的 `rank_axis` 为 quality 时生效,生效时排序键是上游名次升序、同名次按原始分降序,立场桶不再参与。返回里加一键写明本次用的是哪种序。
|
||||
3. 参数表加 `PMS_PLAN_RANK_BY_UPSTREAM`,默认开,说明文字写清自门控。
|
||||
4. 名册快照的元数据里写一键 `rank_mode`,让选股系统的周报能还原当天交付名单的序。
|
||||
5. 选股系统 `plan_review.pms_roster` 读到元数据里 `rank_mode` 为 upstream 时按名册里的名次升序取前 N,否则按原始分降序取前 N(今天的写法)。这一处小改放在选股系统仓库,随工作包丙或单独一个提交。
|
||||
6. 单测、台账、对接说明。既有的 `scripts/test_batch29_units.py` 里已有按质地分桶的三例,旁边加三例。台账一条,编号 016。持仓管理系统的对接说明加一段"前缀效应",与选股系统下游对接说明那段同义。
|
||||
|
||||
**上线。** 持仓管理系统在选股系统机器上,源码挂载进容器,改代码只要重启进程,重启要用户同意、收盘后做。放在 09-23 收盘后,前提是 09-22 的六处读数干净。不与 09-21 的选股系统拉代码同一天,避免两处同时变化说不清。
|
||||
|
||||
细节见附录乙。
|
||||
|
||||
### 工作包丙:周报改读当日快照(选股系统仓库,拍板点一)
|
||||
|
||||
**做什么。** 周报对每个计划日先找当天的 JSON 快照,读它的全量主榜与观察档行、候选单、关注单;快照不存在或读不出才回落到现在的重新装配。加开关 `PLAN_REVIEW_SOURCE`,默认 snapshot,设 live 回今天的做法。周报第一节注记每天用的是哪种底本。
|
||||
|
||||
**为什么。** 重新装配用的是跑周报那一刻的评析报告,评析查询取每只票最新一次运行、不按日期截断,所以 09-22 的名单在 09-25 算会用上 09-24 的报告。四周复核比的是当天真正出过的两份名单。快照落盘时的注释写着"它是复盘与对账的唯一底本",周报应该读它。
|
||||
|
||||
**它改变什么。** 从 09-02 有快照那天起的所有周报名单都改按快照取数,与之前周报的口径不同。09-25 之前不会有人比对旧口径,所以现在切最省事。旧口径靠开关随时能回。
|
||||
|
||||
细节见附录丙。
|
||||
|
||||
### 已排定、本方案不改的事
|
||||
|
||||
数据基座三个部署日按《部署与判收阶段方案与规格书_2026-09-16》附录甲、乙、丙执行,09-28 建议级小包复审(拍板点十一)与 09-30 判收读数不变。选股系统 09-21 拉代码与接口容器重启按《选股计划调整方案_2026-09-17》第四节执行。
|
||||
|
||||
---
|
||||
|
||||
## 四、日程
|
||||
|
||||
北京时间。每天只写这一阶段新增或改动的事,数据基座的部署日只列名字。
|
||||
|
||||
| 日期 | 星期 | 做什么 |
|
||||
|---|---|---|
|
||||
| 09-17 | 三 | 复审完成。开发者做工作包甲,一个提交;拍板点一通过则工作包丙并进同一个提交 |
|
||||
| 09-18 | 四 | 上午我复审工作包甲(含丙),通过即推送。收盘后数据基座 09-18 部署窗(附录甲,原样)。开发者开始工作包乙 |
|
||||
| 09-19 | 六 | 工作包乙交付,含选股系统那一处小改。我复审 |
|
||||
| 09-21 | 一 | 收盘后选股系统机器拉代码,接口容器重启并预热,经审批,避开七点到七点半与八点半到九点零五 |
|
||||
| 09-22 | 二 | 07:15 后核首份质地序计划的六处读数,再核"两条路同一份前二十"。收盘后数据基座第二批(附录乙) |
|
||||
| 09-23 | 三 | 09-22 读数干净,则收盘后重启持仓管理系统进程上线工作包乙,经审批 |
|
||||
| 09-24 | 四 | 08:45 后核持仓管理系统的候选序等于上游名次序 |
|
||||
| 09-25 | 五 | 首份带两份对照名单的周报,底本按拍板点一 |
|
||||
| 09-28 | 一 | 数据基座建议级小包复审(若做) |
|
||||
| 09-29 | 二 | 数据基座第三批与前端重建(附录丙) |
|
||||
| 09-30 | 三 | 数据基座判收读数 |
|
||||
| 10-16 前后 | | 排序轴四周复核,按《选股计划调整方案》工作包四第四条决定保留或切回 |
|
||||
| 长假后第一周 | | 总评审 |
|
||||
|
||||
---
|
||||
|
||||
## 五、开关与参数
|
||||
|
||||
新增三个,其余沿用两份规格书。
|
||||
|
||||
| 键 | 仓库 | 默认 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `PLAN_REVIEW_SOURCE` | 选股系统 | snapshot | 周报底本。live 回"每日重新装配"。拍板点一 |
|
||||
| `PMS_PLAN_RANK_BY_UPSTREAM` | 持仓管理系统 | 开 | 上游计划 `rank_axis` 为 quality 时按上游名次排、关掉立场桶;上游没带这个键时无作用 |
|
||||
| 名册元数据 `rank_mode` | 持仓管理系统写、选股系统读 | 无 | upstream 或 quality_bucket 或 score,周报据此还原交付名单的序 |
|
||||
|
||||
选股系统机器的 `.env` 仍不需要加任何键。持仓管理系统的参数走它自己的参数表,页面可改。
|
||||
|
||||
---
|
||||
|
||||
## 六、验证方式
|
||||
|
||||
每条命令标明在哪台机器上运行。选股系统机器与持仓管理系统在同一台:`ssh -p 2280 factor@192.168.16.155`,两个仓库分别在 `~/project/akg-factor-bridge` 与 `~/project/tradingSystem`。
|
||||
|
||||
**本地(选股系统仓库,工作包甲交付后)。** 预期三行都是 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_snapshot.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
|
||||
```
|
||||
|
||||
**本地(持仓管理系统仓库,工作包乙交付后)。** 预期末行 ALL SUITES PASS,例数只增不减。
|
||||
|
||||
```bash
|
||||
cd /Users/baobao/Documents/work/project/tradingSystem && python3 scripts/run_tests.py 2>&1 | tail -3
|
||||
```
|
||||
|
||||
**选股系统机器(09-22 早上 07:15 后)。** 两条路同一份前二十。这条读数是必改一的最终判收。预期打出快照文件名与 True。
|
||||
|
||||
```bash
|
||||
cd ~/project/akg-factor-bridge && docker exec akg_factor_bridge python3 -c "import json,glob,plan; p=sorted(glob.glob('data/plan/plan_*.json'))[-1]; s=json.load(open(p)); sp=s['shown_params']; got=[r['code'] for r in plan.pick_rows(s['main'], sp['top'], sp['theme_cap'])]; print(p, got==[r['code'] for r in s['main_shown']])"
|
||||
```
|
||||
|
||||
**选股系统机器(09-22)。** 首份质地序计划的六处读数,命令与预期照《选股计划调整方案_2026-09-17》第六节,不再抄一遍。
|
||||
|
||||
**选股系统机器(09-24 早上 08:45 后)。** 持仓管理系统的候选序等于上游名次序。读它当天的名册快照与元数据。预期 `rank_mode` 为 upstream,打出的名次序列严格递增。
|
||||
|
||||
```bash
|
||||
cd ~/project/akg-factor-bridge && docker exec akg_factor_bridge python3 -c "
|
||||
import json, db
|
||||
df = db.read_mysql('pms', 'SELECT roster_json, meta_json FROM pms_plan_snapshot ORDER BY id DESC LIMIT 1')
|
||||
meta = json.loads(df.iloc[0]['meta_json'] or '{}'); roster = json.loads(df.iloc[0]['roster_json'] or '[]')
|
||||
print('rank_mode =', meta.get('rank_mode'))
|
||||
rows = [r for r in roster if str(r.get('b') or 'main') == 'main']
|
||||
print('名册前十的名次:', [r.get('r') for r in rows[:10]])" 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 -12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、回退
|
||||
|
||||
- 工作包甲没有开关,它修的是规则不一致,不需要回退路径。真要回到 4d7ca55 的行为只能回代码,但那个行为本身是错的。
|
||||
- 工作包乙把 `PMS_PLAN_RANK_BY_UPSTREAM` 关掉即回今天的做法,不用重启,参数表页面可改。
|
||||
- 工作包丙把 `PLAN_REVIEW_SOURCE` 设 live 即回今天的做法。
|
||||
- 排序轴整体回退仍按《选股计划调整方案》第七节四个开关设旧值。持仓管理系统那边因为自门控,上游一回退它自动回旧,不用动。
|
||||
|
||||
---
|
||||
|
||||
## 八、拍板点
|
||||
|
||||
代定的写"建议",动工前不再问。要改就改这里。
|
||||
|
||||
1. **周报底本改读快照(工作包丙)。** 建议做,随工作包甲同一个提交,默认读快照,开关可回。理由在第三节。不做的话,四周复核读到的是"以复核当天的评析回头算的名单",要在周报里写明。
|
||||
2. **持仓管理系统协同项的默认值与上线日。** 建议默认开、靠上游的 `rank_axis` 自门控,09-23 收盘后上线。不与 09-21 同一天。
|
||||
3. **提交 4d7ca55 随本方案推送。** 代定。修复小包在它上面再提一个。
|
||||
4. **两份对照名单的口径。** 代定:两份都是全量序前二十,不设主题限额,与周报其余名单同口径。它比的是排序规则,不是当天交付的那二十只;交付名单另有"候选单"与"强传导交付名单"两行。
|
||||
5. **工作包乙里选股系统那一处小改(`pms_roster` 按 `rank_mode` 取序)** 代定随工作包丙同一个提交,若丙不做则单独一个提交。
|
||||
|
||||
---
|
||||
|
||||
## 九、交付要求
|
||||
|
||||
1. 每个工作包一个提交,全中文、无前缀,格式"主题:改了什么",落台账的带台账号。正文写清改了哪些函数、没改什么、为什么不合并。
|
||||
2. 先写单测再改代码,尤其是工作包甲那两例。
|
||||
3. 工作包甲交付证据:提交号、三个测试文件末行、逐字回旧那一例的名字、`git show --stat`。
|
||||
4. 工作包乙交付证据:提交号、`run_tests.py` 末三行与例数对比、三例新单测的名字、台账 016 原文、选股系统那一处小改的提交号。
|
||||
5. 工作包丙交付证据:提交号、开关名、本地用一份合成快照跑通周报取数的单测末行。
|
||||
6. 只加键不删键,既有键的值与含义不变。
|
||||
|
||||
---
|
||||
|
||||
## 十、评审方式
|
||||
|
||||
评审者只看证据。工作包甲核三件事:截取规则是不是只剩一处实现、两条路的主题取法是否同源、旧轴下无主题行是否仍归"无传导"桶计数。工作包乙核四件事:自门控是否只看计划顶层的 `rank_axis`、立场桶在上游序下是否完全不参与、旧计划下是否逐字回旧、名册元数据是否写了 `rank_mode`。工作包丙核两件事:无快照日是否回落、开关设 live 是否逐字回今天。判定三档同前。评审记录写 `docs/评审记录_<日期>.md`。
|
||||
|
||||
## 十一、风险与预判
|
||||
|
||||
1. 09-22 早上"两条路同一份前二十"那条读数若是 False,说明工作包甲没修干净,当天接口以快照路为准,持仓管理系统拿到的仍是自洽的一份,不影响交易,但要当天修。
|
||||
2. 持仓管理系统切到上游序后,页面上"候选榜第 N 名"与它的候选序第一次一致,人看到的名次会跳一次,属预期。
|
||||
3. 周报改读快照后,09-18 与 09-21 两个旧序日的"主榜新序前二十"其实是旧序前二十,注记写明。
|
||||
4. 工作包乙上线那天若持仓管理系统进程重启失败,回退是不重启,代码在挂载目录里但进程还是旧的,行为不变。
|
||||
5. 名册元数据的 `rank_mode` 只从工作包乙上线那天起有,之前的名册周报仍按原始分还原,注记写明"历史 N 与序不可还原"那句已有。
|
||||
|
||||
---
|
||||
|
||||
## 附录甲(可不读):工作包甲实现
|
||||
|
||||
行号按选股系统仓库 `main` 4d7ca55。
|
||||
|
||||
**截取函数。** 在 `plan.py` 模块级新增:
|
||||
|
||||
```python
|
||||
def take_by_theme(items, n, theme_cap, theme_of, uncapped_no_theme):
|
||||
"""按主题限额截取。items 可迭代;theme_of(item) 回主题字符串,无主题回 None。
|
||||
有主题的每主题最多 theme_cap 条(0 不设限)。无主题的:uncapped_no_theme 为真时
|
||||
免限额、不计数;为假时归"(无传导)"一个桶计数(旧轴原样)。取够 n 条即停。"""
|
||||
```
|
||||
|
||||
- `collect` 里的 `_pick`(约 523 到 541 行)改成一行调用:`take_by_theme(ranked.items(), n, theme_cap, lambda kv: _theme_of(kv[0]), uncapped_no_theme)`。
|
||||
- `_theme_of(k)` 是 `collect` 内的小函数,与 `_row`(约 546 到 552 行)写 `evidence` 的取法同源:传导视图有证据行取 `e[0]`;没有证据行、且候选卡的 `started_source` 为 moved_view 且带 `theme`,取 `c["theme"]`;否则 None。最好把 `_row` 里那几行也改成先调 `_theme_of` 再拼字典,让两处只有一份取法。
|
||||
- `pick_rows`(约 1014 到 1037 行)改成调用同一个函数:`theme_of` 取 `((r.get("evidence") or {}).get("theme")) or None`,`uncapped_no_theme` 的缺省推断不变。
|
||||
- `encoding`(约 712 行):把追加那半句挪到 `axis == "quality"` 的分支里,或改成"名次按 PLAN_RANK_AXIS:quality 时质地带优先,传导只在带内作次级键;transmission 时按 09-14 的调整分序"这样两种都对的写法。
|
||||
- 交接段骨架:标题"十一、2026-09-17 补记:排序主轴换成公司质地";四段:改了什么(一句)、下游看到什么(四个新键、名次含义、逐票对账接口仍是原始分序)、怎么回退(四个开关)、读数在哪(评析目标名单表、周报两份名单)。台账那一行改成"最新到第 063 条"。
|
||||
|
||||
**单测(`test_plan_rank_axis.py` 加两例)。**
|
||||
|
||||
11. 已启动成员算进所在主题:六条记录同主题"锂电",其中第六条通过 `theme_of` 取到主题的路子是候选卡的 moved_view 补充;`take_by_theme` 在 `theme_cap=5` 下只放五条;再用同样六条构造成带 `evidence.theme` 的行喂 `pick_rows`,也只放五条。
|
||||
12. 无主题行两轴各归各:六条无主题记录,`uncapped_no_theme` 为真放六条,为假放五条(归"无传导"桶)。
|
||||
|
||||
既有 `test_plan_snapshot.py` 第 50 到 58 行两例保持通过。
|
||||
|
||||
## 附录乙(可不读):工作包乙实现
|
||||
|
||||
行号按持仓管理系统仓库 `main` 当前代码,文件 `app/services/plan_feed.py`。
|
||||
|
||||
- `_rows`(约 279 到 330 行)加四键:`"quality_band": _int_or_none(it.get("quality_band"))`、`"pointed"`(值非空时转布尔,否则 None)、`"rank_axis": _text_or_none(it.get("rank_axis"))`、`"rank_old": _int_or_none(it.get("rank_old"))`。
|
||||
- `parse_plan`(约 339 到 395 行)返回字典加两键:`"rank_axis": _text_or_none(payload.get("rank_axis"))`、`"rank_rule": _text_or_none(payload.get("rank_rule"))`。
|
||||
- `select_candidates`(约 440 行起)签名加 `rank_by_upstream: bool = False`。生效条件 `upstream = bool(rank_by_upstream) and plan.get("rank_axis") == "quality"`。生效时排序键为 `(x.get("rank") or 10 ** 9, -(x.get("score") or 0.0))`,`rank_by_quality` 不再参与;不生效时两个既有分支逐字不动。返回字典加 `"rank_mode"`:upstream、quality_bucket、score 三选一。`items` 每项加 `quality_band`、`pointed`、`rank_axis`、`rank_old` 四键原样透传。
|
||||
- 参数块(约 620 到 636 行)加 `"rank_by_upstream": ps.get_bool("PMS_PLAN_RANK_BY_UPSTREAM", True)`;调用点(约 1086 到 1094 行)传入。`app/services/param_store.py` 约 119 行旁加登记:`"PMS_PLAN_RANK_BY_UPSTREAM": (True, bool, "上游计划带 rank_axis=quality 时按上游名次排、关掉立场分桶;上游没带时无作用(关掉即回按分桶排)")`。
|
||||
- 名册快照元数据:`app/repo/pms_repo.py` 约 839 行的插入语句已有 `meta_json` 列,在 `plan_feed` 里构造元数据的那处加 `"rank_mode"`,值取自本次 `select_candidates` 返回的 `rank_mode`。
|
||||
- 选股系统 `plan_review.pms_roster`(`plan_review.py` 约 120 到 140 行):查询多取 `meta_json`;`rank_mode` 为 upstream 时 `rows.sort(key=lambda r: r.get("r") if r.get("r") is not None else 1 << 60)`,否则保持按 `s` 降序。注记字符串加一句"序按名册元数据 rank_mode 还原"。
|
||||
- 单测:`scripts/test_batch29_units.py` 约 298 到 324 行旁加三例:上游 `rank_axis` 为 quality 且开关开时按名次排且立场桶不参与;上游没带 `rank_axis` 时开关开也逐字回按分桶排;`rank_mode` 三种取值各对。若 `run_tests.py` 的文档字符串里写了各批例数,按哨兵改数。
|
||||
- 台账 016,五段式。对接说明加"前缀效应"一段。
|
||||
- 上线(选股系统机器,收盘后,经审批):`docker compose --profile sched --profile ws restart -t 30`,重启前用 `docker exec pms-ws date` 看容器时间确认已收盘。
|
||||
|
||||
## 附录丙(可不读):工作包丙实现
|
||||
|
||||
文件 `plan_review.py`,行号按 4d7ca55。
|
||||
|
||||
- `run`(约 374 行)把 `data = plan.collect(day, top=5000, obs_top=5000, theme_cap=0)` 改成先试快照:路径用 `regime.snapshot_path(day)`,读出的字典里 `main` 与 `observe` 就是全量行,`candidates` 与 `watch` 在顶层。把它整理成与 `collect` 返回同形的字典(`_full` 里放 `main` 与 `observe`),后面的代码一行不改。快照不存在、坏 JSON、或 `main` 不是列表,回落到 `plan.collect`,注记写"底本:现算(无快照)"。
|
||||
- 开关 `config.PLAN_REVIEW_SOURCE`,默认 snapshot;设 live 时直接走 `plan.collect`。
|
||||
- 每天的注记里加"底本:快照"或"底本:现算",落进第一节。
|
||||
- 单测:新文件 `test_plan_review_source.py`,合成一份最小快照写进临时目录,断言读到的 `_full.main` 与 `candidates` 来自快照;删掉文件后断言回落路径被走到(用假的 `plan.collect` 计数)。不连库。
|
||||
|
||||
## 附录丁(可不读):文件清单
|
||||
|
||||
工作包甲:`plan.py`、`test_plan_rank_axis.py`、`docs/交接_2026-09-10.md`(四仓库)。
|
||||
工作包乙:持仓管理系统 `app/services/plan_feed.py`、`app/services/param_store.py`、名册元数据的构造处、`scripts/test_batch29_units.py`、`scripts/run_tests.py`(仅例数)、`docs/复盘决定台账.md`、对接说明;选股系统 `plan_review.py`。
|
||||
工作包丙:`plan_review.py`、`config.py`、`.env.example`、新 `test_plan_review_source.py`。
|
||||
清单外一律不动。
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# 评审记录(2026-09-17):选股计划排序轴换质地,提交 4d7ca55
|
||||
|
||||
> 评审对象:选股系统仓库 `main` 上的提交 4d7ca55(开发者 2026-09-17 上午交付,交付时未推送)。基线 6138c01。
|
||||
> 评审依据:`docs/选股计划调整方案_2026-09-17.md` 第十节"评审方式"。评审者只看证据。
|
||||
> 写法遵守 `.claude/output-styles/readable-chinese.md`。本文件是四个仓库逐字同步的副本之一。
|
||||
|
||||
---
|
||||
|
||||
## 一、结论
|
||||
|
||||
**有条件通过。** 代码主体与规格书一致,五个测试文件本地全绿,四个开关设旧值时逐字回旧成立,分数编码与三门槛一确认三风险都没有动。三处必改,合成一个修复提交,09-21 拉代码之前做完并复审。两条建议级不阻塞。一条拍板项。
|
||||
|
||||
三处必改各一句话。
|
||||
|
||||
1. 装配路与快照路对"已启动成员"的主题限额判定不一致。同一天的计划,早上落盘的主榜前二十与接口读快照给出的前二十可能不是同一批。
|
||||
2. 计划里的 `encoding` 说明句在旧轴下也写"名次按质地带优先",与旧轴的实际次序相反。
|
||||
3. 交接文档一段没写,规格书工作包五第三条没有完成。
|
||||
|
||||
修复小包的规格写在《上线与协同阶段工作方案与规格书_2026-09-17》工作包甲。本记录只写核对结果与理由。
|
||||
|
||||
---
|
||||
|
||||
## 二、我亲手跑出的证据
|
||||
|
||||
**本地单测(开发机,选股系统仓库)。** 五个文件末行全部 ALL OK:`test_plan_rank_axis.py`、`test_plan_quality_rank.py`、`test_card.py`、`test_pool_logic.py`、`test_plan_snapshot.py`。四个开关设旧值再跑 `test_plan_rank_axis.py`,仍 ALL OK。七个改动的模块 `py_compile` 通过。开发者说只能在部署机跑的五个测试文件,我本机同样跑不起来,原因与他说的一致:缺 `psycopg` 或 `fastapi`,或本机 Python 3.9 不支持联合类型注解。这是既有状况,不是本次引入。
|
||||
|
||||
一条要说明:`test_plan_snapshot.py` 里"拿真实快照逐行比对"那一例在本机是**跳过**的,因为本机没有快照文件。它只有在部署机上跑才真正生效。必改一正是它会查出的那件事。
|
||||
|
||||
**旧路径等价(开发机)。** 入池的 `tier` 与 `candidate` 两个分支从内联代码抽成了纯函数 `select_plan_rows`。我用随机合成的主榜行做了三百轮对照,每轮六种组合(两个来源乘三种档位白名单),新函数与旧内联逻辑的输出零差异。
|
||||
|
||||
**部署机只读读数(选股系统机器,09-16 快照)。** 仓库停在 3d3f317,与规格书基线一致。`.env` 里有 `POOL_TOP=50`、`POOL_MAX=100`、`CARD_REQUIRE_STARTED=0`,没有 `POOL_SOURCE`、`PLAN_RANK_AXIS`、`POOL_THEME_CAP`,所以拉代码后默认值直接生效,与规格书第五节说的一致。
|
||||
|
||||
09-16 快照主榜全量五百三十二行。质地分布:好十六、中四十八、差一百三十四、无评析三百三十四。我按规格书的词典序在这份快照上模拟了一遍质地序,前二十里好十六、中四。前二十里有六行没有传导主题,正好落在"第六只起被限额顶掉"的位置,规划代理那条发现在真实数据上成立。前二十里"储能"主题五只,正好碰到限额边界。
|
||||
|
||||
"已启动成员"的行(证据由已动成员视图补上的行)全量八行,旧序名次八十二、三百五十三、三百九十、四百九十一、五百、五百二十、五百二十六、五百三十。旧序前二十里没有它们;模拟质地序前二十里有一行。这就是必改一在真实数据上的样子。
|
||||
|
||||
---
|
||||
|
||||
## 三、逐项核对
|
||||
|
||||
按规格书第十节的核对步骤逐条记。
|
||||
|
||||
1. **diff 对照附录己的文件清单。** 十五个文件改动,全在清单内。`factors.py` 与 `api.py` 没有出现在 `git show --stat` 里。清单里的"交接文档"没有改,见必改三。
|
||||
2. **本地跑测试文件。** 见第二节。
|
||||
3. **四个开关设旧值时的逐字回旧断言是不是真断言。** 是,但覆盖方式要说清。第一例只断言 `_rerank` 在旧轴下与 09-14 的 `_rerank_by_quality` 序列与分值逐字相同,它验的是分派。另外三个开关各有自己的旧路径断言:入池旧路径由我的三百轮对照补上,评析名单的 `score` 分支是第七例,卡内序关掉是第八例,主题限额例外关掉是第九例。四条合起来才是"四开关逐字回旧"。
|
||||
4. **主题限额的例外只在质地轴下生效。** 是。装配时按 `config.PLAN_RANK_AXIS` 传入,快照路按行上的 `rank_axis` 推断。回退那天读旧快照仍按质地轴截取,这是对的,因为那份快照的序就是质地序。但两条路对"已启动成员"的判定不一致,见必改一。
|
||||
5. **卡内序、入池、评析名单三处的次级键与去重。** 卡内序在判决与涨幅之间插入负的质地带,关掉时元组与旧实现逐字相同。入池 `rank` 源只取被指向的行、按新序截前五十,备用开关并进旧序强传导前五十、去重、受 `POOL_MAX` 兜底。评析名单两路并集去重后按新序名次排,`score` 分支按原始分。三处都有确定的末位键。
|
||||
6. **`_row` 只加键。** 是。加了 `quality_band`、`rank_axis`、`pointed` 三键,质地轴下再加 `rank_old`。`score`、`score_adj`、`tier` 的值与含义没有变。顶层 `encoding` 的值变了,见必改二。
|
||||
7. **分数编码。** `factors.py` 一字未动。组内分从原始分反解,档位用整除,与编码一致,我逐个区间验过。
|
||||
8. **旧序名次。** `rank_old` 取自全量序,不是截断后的序。主榜与观察档两个序列的代码不重叠,两张名次表合并不会互相覆盖。
|
||||
9. **一百二十天守卫。** 守卫是活的:评析摘要里的 `age_days` 由数据日减报告运行日算出,取值真实。守卫对质地带、质地门、卡内序三处生效。它**不对**旧轴的加减八分生效,`score_adj` 也不受守卫。这与规格书第五节表里那一行的写法不一致,但代码是对的,因为规格书交付要求第四条要求 `score_adj` 的值不变。规格书这一行我已改正,见第五节建议三。
|
||||
10. **入池不变式的验证命令是否成立。** 成立。入池走 `plan.collect` 要五百行的宽池、主题限额取 `POOL_THEME_CAP`(默认零),再交给纯函数选行;规格书第六节那条命令拿快照全量行喂同一个纯函数,两边看到的是同一个序。
|
||||
11. **评析目标名单拿到的是全量行。** 是。`persist` 收到的是落盘快照,`main` 是全量新序行,每行带原始分与新序名次。
|
||||
12. **周报的底本。** 周报不读快照,它用 `plan.collect` 把当日重新装配一遍再取全量行。两份对照名单都从这份重算取。这是既有做法,不是本次引入,但它决定了四周复核读到的是什么,见第六节拍板项。
|
||||
|
||||
---
|
||||
|
||||
## 四、必改
|
||||
|
||||
### 必改一:装配路与快照路对"已启动成员"的主题限额判定不一致
|
||||
|
||||
**是什么。** 主榜截取有两条路。装配路是 `collect` 里的内部函数 `_pick`,早上出计划时用它截主榜前二十,写进 md 与快照的 `main_shown`。快照路是模块级函数 `pick_rows`,接口读快照时用它按请求参数截取,持仓管理系统要的三百只前缀从这里来。两条路本该同一套规则,`test_plan_snapshot.py` 拿真实快照逐行比对钉住这件事。
|
||||
|
||||
这次的例外判定在两条路上写法不同。装配路看的是传导视图有没有这只票的证据行:没有证据行就当无主题、免限额。快照路看的是行上的 `evidence.theme`:没有主题才免限额。差别出在"已启动成员"上。已启动成员在传导视图里没有证据行,但装配时 `_row` 会用已动成员视图的目标环节把 `evidence.theme` 补上(`plan.py` 约 550 行)。于是同一只已启动成员,装配路把它当无主题免限额、不计数,快照路把它算进所在主题、受限额。
|
||||
|
||||
**为什么以前没炸。** 旧序下这些行排在八十名以后,碰不到前二十的限额。旧代码里两条路对它们其实也不一致(装配路把它们归进"无传导"桶计数,快照路归进所在主题),只是不影响前二十。质地序把被指向的已启动成员提到了前列,09-16 快照模拟出来前二十里有一行,"储能"又正好五只碰到边界,差一只就会让两条路给出不同的第二十名。
|
||||
|
||||
**后果。** 同一天,md 与快照里的 `main_shown` 是一份前二十,接口给持仓管理系统的是另一份。规格书第二节"同日重跑结果一致"这条原则在两条路之间失守。部署机上跑 `test_plan_snapshot.py` 会在第一个出事的交易日变红。
|
||||
|
||||
**修法。** 不在两处各打补丁,改成一处实现两处调用:把"按主题限额截取"抽成模块级纯函数,装配路与快照路都调它,主题的取法与 `_row` 写 `evidence` 的取法同源(含已动成员视图的补充)。这样装配路对已启动成员的判定也变成"算进所在主题",与快照路一致。这一步同时改正了旧轴下的既有分歧,改的只是 md 与 `main_shown` 这条路对已启动成员的计数,接口路(持仓管理系统读的那条)原样不动。逐字回旧那一例单测不受影响。细节与单测在工作方案附录甲。
|
||||
|
||||
### 必改二:`encoding` 句在旧轴下写错了
|
||||
|
||||
`collect` 返回的 `encoding` 一句无条件追加了"名次按质地带优先,传导只在带内作次级键(开关 PLAN_RANK_AXIS)"。旧轴下名次不按质地带优先,这句话与实际相反。持仓管理系统把这句原样收进它的状态页给人看。修法是只在质地轴下追加,或把句子改成条件式写法。一行改动。
|
||||
|
||||
### 必改三:交接文档一段没写
|
||||
|
||||
规格书工作包五第三条要求交接文档一段并四仓库同步,提交里没有。修法是在最新一份交接文档 `docs/交接_2026-09-10.md` 末尾加一节带日期的补记,把台账那一行从"最新到第 057 条"改成 063,四仓库同步。交接段的骨架在工作方案附录甲。
|
||||
|
||||
---
|
||||
|
||||
## 五、建议级(不阻塞)
|
||||
|
||||
1. **评析名单 `score` 分支不是逐字回旧。** 旧代码取快照序前一百,快照序是 09-14 的调整分序;新代码按原始分排前一百。差别只在第一百名附近被加减八分挪动的几只。评析名单只管覆盖,这点差别没有后果。不改,记在这里。
|
||||
2. **两处小瑕疵。** `_rerank_by_axis` 的 `axis` 参数没有用到;`card.sort_key` 在函数体内导入 `config`。都不影响行为。
|
||||
3. **规格书两处文字改正。** 第五节表里 `COMPANY_REVIEW_STALE_DAYS` 那一行说守卫对加减分也生效,工作包二第二条说"也不再加减分",与代码和交付要求第四条矛盾,按代码改正。附录乙说周报"从快照 `_full` 读主榜行",实际是现算后取 `_full`,按实际改正。这两处我已经改在规格书里,随本记录同步。
|
||||
4. **周报里"主榜新序前二十"对改动前的日子。** 09-25 周报的窗口含 09-18 与 09-21 两个旧序日,那两天这一行的名单其实就是旧序前二十,标签会略有误导。旧序名单那一行对这两天正确地跳过了。周报一段注记即可,不改代码。
|
||||
|
||||
---
|
||||
|
||||
## 六、拍板项:周报的底本
|
||||
|
||||
周报用 `plan.collect` 把每个计划日重新装配一遍,而不是读当天落盘的快照。重新装配用的是跑周报那一刻的评析报告:查询取每只票最新一次运行,不按日期截断。于是 09-22 的"新序前二十"在 09-25 算出来时,用的是 09-25 手里的质地档。一家 09-24 才有报告的公司,会被回头算进 09-22 的前列。
|
||||
|
||||
这在质地是次级项的时候是小事,在质地成为主轴之后不是。四周复核比的是"新序前二十与旧序前二十谁更好",两份名单都应该是当天真正出过的那份。快照落盘时的注释写着"它是复盘与对账的唯一底本",周报却没有读它。
|
||||
|
||||
我的建议是周报改成先读当日快照的全量行,没有快照的日子才回落到重新装配,加一个开关默认读快照。改动小,但它改变周报所有既有名单的取数方式,从 09-02 有快照那天起的读数会与旧周报的口径不同,所以要用户拍板。写在工作方案工作包丙与拍板点一。
|
||||
|
||||
---
|
||||
|
||||
## 七、答开发者的两个问题
|
||||
|
||||
**要不要现在推送。** 复审已经做完,不必等 09-19。提交 4d7ca55 我随本记录一起推到远端,修复小包在它上面再提一个。选股系统机器只在 09-21 收盘后由人拉代码,中间远端有一个待修的提交没有风险。
|
||||
|
||||
**要不要现在补交接段。** 要,放进修复小包,与必改一、必改二同一个提交。
|
||||
|
||||
---
|
||||
|
||||
## 八、附:文件清单对照
|
||||
|
||||
提交改了十五个文件:`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`、`docs/选股说明_下游对接.md`、新 `test_plan_rank_axis.py`、`test_plan_quality_rank.py`、`test_plan_snapshot.py`。附录己清单里只差交接文档。清单外没有文件被动。
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
沿用五条:加不改;开关关掉逐字回旧;同日重跑结果一致;不做回测、因子合成、IC;评价只用候选单复盘周报。本方案再加三条。
|
||||
|
||||
1. 分数编码不动。`factors.py` 里"主榜分等于二百加档位乘二十加组内分"的编码注册在外部平台当因子,改它会污染平台的因子历史。次序的改动全部放在计划装配这一层,与 09-14 那次质地重排(台账 058)同一个落点。
|
||||
2. 质地只认新鲜的报告。质地进任何判决或排序都受同一个新鲜度守卫(一百二十天),与逻辑状态第五路和持仓管理系统的基本面立场同口径。今天质地门与加减八分不看日龄,是一处不一致,顺手对齐。
|
||||
2. 质地只认新鲜的报告。质地进任何判决或排序都受同一个新鲜度守卫(一百二十天),与逻辑状态第五路和持仓管理系统的基本面立场同口径。今天质地门与加减八分不看日龄,是一处不一致;质地门顺手对齐,旧轴的加减八分保持原样(09-17 复审修订,见第五节表)。
|
||||
3. 评析覆盖不能被排序轴锁死。评析目标名单如果只取新序前一百,新被指向的公司永远排在已评析公司后面,永远得不到报告。名单要与排序轴解耦。
|
||||
|
||||
---
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
**做什么。** 三门槛、一确认、三风险一个字不动(生产上门槛二"已启动"本来就关着)。改两处。
|
||||
|
||||
1. 卡内序从"先判决、再当日涨幅"改成"先判决、再质地带、再当日涨幅"。候选单与关注单的名次由此变成质地优先。开关 `CARD_SORT_BY_QUALITY`,默认开,关掉回旧序。
|
||||
2. 质地门"差降关注"与工作包一的质地带共用同一个判定函数,带上一百二十天守卫;超期报告不再降档,也不再加减分。
|
||||
2. 质地门"差降关注"与工作包一的质地带共用同一个判定函数,带上一百二十天守卫;超期报告不再降档。旧轴下的加减八分保持 09-14 原样,不加守卫(09-17 复审修订,理由见第五节表)。
|
||||
|
||||
**为什么不把质地升成门槛。** 无评析的公司还能进候选(排在后面),是为了不让评析覆盖不到的新公司被系统性排除。质地差已经进不了候选,等价于门槛,不必再改判决逻辑。
|
||||
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
| 日期 | 星期 | 做什么 |
|
||||
|---|---|---|
|
||||
| 09-17 到 09-18 | 三到四 | 开发五个工作包,本地单测全绿,一个提交,交第十节的证据 |
|
||||
| 09-19 | 六 | 我复审 |
|
||||
| 09-19 | 六 | 原定我复审。实际 09-17 已复审(`docs/评审记录_2026-09-17.md`),修复小包 09-18 复审 |
|
||||
| 09-21 | 一 | 收盘后选股系统机器拉代码,次日 07:10 的计划就是新序(盘前链每步都是子进程,读挂载目录里的新代码)。接口容器仍建议同窗重启一次并预热计划接口,经审批、避开七点到七点半与八点半到九点零五:接口进程内存里持着旧的装配代码,JSON 走快照直通没问题,但 md 格式的主榜表与快照读不到时的实时回落会走旧序 |
|
||||
| 09-22 | 二 | 第一份新序计划。看候选单、前三十名、入池名单三处读数,交回 |
|
||||
| 09-25 | 五 | 第一次带两份名单对照的周报(二十点十分由基座触发) |
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
| `CARD_SORT_BY_QUALITY` | 1 | 卡内序先质地带;0 回"先判决再涨幅" |
|
||||
| `POOL_SOURCE` | rank | 新增取值;tier 与 candidate 保留 |
|
||||
| `REVIEW_TARGETS_AXIS` | both | score 回"只按原始分前一百" |
|
||||
| `COMPANY_REVIEW_STALE_DAYS` | 120 | 已有;从此对质地带、质地门、加减分都生效 |
|
||||
| `COMPANY_REVIEW_STALE_DAYS` | 120 | 已有;从此对质地带、质地门、卡内序生效。旧轴的加减八分不加守卫,因为 `score_adj` 的值要求不变(09-17 复审修订) |
|
||||
| `PLAN_QUALITY_BONUS` | 8 | 已有;只在 transmission 轴下起作用 |
|
||||
| `POOL_RANK_KEEP_OLD` | 0 | 备用:旧序强传导前五十并进池,只在"池包含候选"不变式核不过时打开 |
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ cd ~/project/akg-factor-bridge && grep -n "新序前二十\|旧序前二十\|档
|
|||
- `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` 不动。
|
||||
- `plan_review.py` 约 374 行用 `plan.collect` 把当日重新装配一遍再取 `_full`(不是读快照;改读快照见《上线与协同阶段工作方案与规格书_2026-09-17》工作包丙);约 419 行附近加两份名单"主榜新序前二十"(按 `rank`)与"主榜旧序前二十"(按 `rank_old`,缺该键的旧快照跳过这两份名单);约 431 到 436 行的档位分组外再加档位乘质地带的交叉分组,分组名写"档位×质地=强传导×好"这样;`compare_table` 不动。
|
||||
|
||||
## 附录丙(可不读):单测清单
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue