12 KiB
系统自决、页面专业化与选股打分三包评审(2026-09-14 下午)
评审对象是《系统自决与页面专业化开发规格说明书_2026-09-14》落地后的六个本地提交(持仓管理系统 44ec6d4、de06c77、c94787a、77db6b8、459edff、a6d5f10)与选股桥仓库的一个提交(3d3f317)。评审时这些提交都在开发机上,没推、没部署。 写法遵守
.claude/output-styles/readable-chinese.md。读完第二节就知道能不能部署,第五节是发现,第七节是部署建议。
一、评审做了什么
- 逐行读了自决包对合议路由、动作引擎、提议分流、策略撤销、仓库、接口、参数、观察读数、复核脚本的全部差异,以及第三十四批单测与四处哨兵改动。
- 读了页面包的结构改动全文、文案包对后端字符串的改动、文案守卫的实现。
- 读了选股桥的重排实现,并核对快照截取、入池、评析名单三条出口是否保留重排后的顺序。
- 在开发机跑了全量单测、三道页面守卫、选股桥的新测试。
- 在模拟仓 155 只读检查了目标价与活动交易方案有没有重叠,没有改任何东西。
二、一句话结论
三个包都按规格落地,全量单测与守卫全绿,可以今天收盘后部署。但有两处小修建议在部署前做完,各不超过半小时:一是目标价自动清仓前只撤了方案、没撤在途买单,与命令清仓那条不一致;二是弱表态开关关掉时,只有一方表态的候选会按标准仓自动采纳。这两处今天在 155 上都不会立刻踩到,因为 155 现在没有设目标价的票,弱表态开关也开着;如果你决定先部署再修,要守住这两个前提。
三、对照规格的完成度
| 规格条目 | 状态 | 证据 |
|---|---|---|
| 3.6 改动一,合议路由加择时异议观察 | 完成 | timing_dissent 参数,observe 时回观察带 disp wait_timing,默认 confirm 回旧 |
| 3.6 改动二,动作引擎改写标记 | 完成,手法略异 | 保留了原 needs_user_confirm,另打 decide_kind 并按优先级取最保守的一种;eval_dca 与 eval_target 打 decide_kind 但不改 confirm,由分流层覆盖。效果与规格一致 |
| 3.6 改动三,自决块 | 完成 | _self_decide 按决定表判,位置在 force_queue 之后、四条件旁路之前,record 与 decline 两档降级正确 |
| 3.6 改动四,档位与数量一致 | 完成 | _apply_advice_tier 抽出,自动路落指令前调用,force_trial 压到试探仓,c["qty"] 真被改写 |
| 3.6 改动五,自动清仓两道前置 | 部分 | 可卖量夹紧做了;撤方案只改方案状态,没撤在途买单,见第五节第一条 |
| 3.6 改动六,自动放弃记账与当日不重评 | 完成 | 评审方 system 的 REJECT 行,system_decided_today 并入跳过集合,处置快照有说明 |
| 3.6 改动七,参数 | 完成 | 三个参数登记,FAIL_CLOSED 取 off,范围与合法值校验 |
| 3.6 改动八,人裁决接口 | 完成 | decide_reasons 码表与拼句,api_decide 兼容旧字段,新接口 decide-reasons |
| 3.6 改动九,观察读数与复核 | 完成 | 七个新类别随时写,复核第五第六张表,日报注意项 |
| 3.6 改动十,页面处置词 | 部分 | 择时待转多有了;系统已采纳与系统已放弃两种显示没做,开发方已在节点里标为非阻断 |
| 3.9 单测与哨兵 | 基本完成 | 第三十四批二十二例,test_wiring 目标价两例,test_batch4,路由清单,FakeRepo 两桩;缺「挂着方案时先撤方案」那一例 |
| 4 页面专业化包 | 完成 | 词表、抽屉三段加锚点、候选可点击、裁决弹窗、文案守卫、后端随行句 |
| 5.2 工作包四 | 完成 | 重排在 _pick 与 _full_rows 之前,score_adj,PLAN_QUALITY_BONUS 设 0 逐字回旧,台账 058,四条不变式测试 |
| 5.3 来源字段 | 完成 | plan_feed 收三键进名册快照 meta,顶栏芯片显示来源与赛道闸 |
| 6 文档 | 大部分 | 台账 015、README、COMMAND_PRIORITY、UI_COPY_AUDIT、FRONTEND_TRADER 已改;接口契约 decide 请求体未补;真实仓初始参数三条只写在台账 015 里,上午规格书附录己的清单表没同步 |
四、验证读数
| 项目 | 读数 |
|---|---|
| 开发机全量单测 | 三十四批全部通过,最后一行 ALL SUITES PASS,总数 897 例 |
| 三道页面守卫 | 枚举守卫、接线守卫、文案守卫各打 ALL OK;文案守卫三十一个禁用词 |
| 选股桥新测试 | ALL OK,四条不变式通过,真实快照那一轮在开发机跳过 |
| 155 目标价 | 没有任何票设了目标价 |
| 155 活动方案 | 只有 300627.SZ 一条网格 |
| 155 重叠 | 目标价与活动方案没有重叠 |
五、评审发现
建议部署前修好的两处
第一,目标价自动清仓前只撤方案、不撤在途买单。 新函数 strategy_service.cancel_by_code 只把方案状态改成 CANCELLED 并留一行账本。命令清仓那条 command_service._stop_buyside_for_exit 做的是三件事:撤方案、驳回待确认的买入提议、撤该票在途的买入指令。它的注释写得很清楚:网格已经下发到券商的那张买单还挂着继续成交,你正清仓它却又买进来一笔 T+1 锁死的货。自决的自动清仓正是这个场景。建议 cancel_by_code 直接复用 _stop_buyside_for_exit 的三件事,或至少补上撤在途买入指令那一步,并补一例单测「挂着方案且有在途买单时先撤再清」。今天 155 没有设目标价的票,所以不会立刻踩到,但设目标价之前必须修好。
第二,弱表态开关关掉时,只有一方表态的候选会按标准仓自动采纳。 scan_open 里给 decide_kind 打 weak_consensus 的条件是合议路由为交人且原因是只有一方表态。PMS_CONSENSUS_WEAK_CONFIRM 关掉时路由变成放行,就不会打这个标记,_self_decide 会把它当成合议放行强而按标准仓采纳。规格的决定表里没有「放行·弱」这一格。建议改成按合议块的强弱字段打标记:strength 为弱就打 weak_consensus,不看路由。155 上这个开关现在是开的,所以今天不会触发;但页面随时能改,不该留这个洞。
建议尽快修但不阻塞部署的六处
- 采纳没有要求「真通过」。规格说真通过指带应答体的 PASS。_self_decide 只看 verdict 与把握度,不看 raw 是不是字典。动作不在研判范围时研判会直接回 PASS 且把握度为空,这时候选会按试探仓采纳。新建仓现在在研判范围内(PMS_JUDGE_ACTIONS 含 OPEN),所以实际不会发生;但把 OPEN 从范围里拿掉的那一刻,所有新建仓都会不经研判按试探仓自动入场。建议加一道判断:raw 不是字典的 PASS 一律交人。
- 自动采纳的账本行评审方写的是 judge 或 rule,不是规格说的 system,每日上限的计数靠理由以「系统采纳」开头。能用,但脆弱:有人改了理由模板计数就失效。建议评审方写 system,计数按评审方数。改的话复核脚本与台账 015 同步。
- 目标价自动清仓的理由被替换成一句「目标价到价自动清仓」,原来那句带现价、目标价、股数的理由没了。建议把原理由接在后面。
- 逻辑存疑的候选先送研判再放弃。研判闸在自决块之前,存疑的票会白白占一次研判调用与时间预算,结果一定是放弃。建议在送研判之前先判 logic_doubt。
- 页面「系统已采纳」「系统已放弃」两种处置词没做。现在自动采纳的票显示「已下单」,自动放弃的票显示「未建仓」并带原因「今日系统已判放弃,明日重评」。能用,但不如规格清楚。
- 文档三处:接口契约没补 decide 请求体与 decide-reasons 接口;真实仓初始参数三条没写进上午规格书附录己的清单表;台账 015 写的评审方口径与规格不同,若按第二条改回 system 要同步。
核实过没有问题的
- _apply_advice_tier 确实改写 c["qty"] 与硬数字的目标仓位、分批,force_trial 会把非试探仓压到试探仓一批,弱基本面那类的紧止盈标记随账本走。
- 一次性守卫在 _make_instruction 内部调用,自动路不缺这一步。
- 自决开着时低把握的研判驳回不再改判不可用,REJECT 由研判闸落账 judge 并早退,解锁重问对它照常生效。
- 研判不可用、上游硬风险、合议装不上、判决词认不出四种一律交人;深亏补仓三分支、每日上限、record 与 decline 降级都有单测。
- 参数默认 off、读不到按 off、合法值校验、范围校验都有;死参数扫描通过。
- 哨兵:test_wiring 目标价那一例改成自决关时入队,新加自决开时自动清仓并按可卖量夹紧一例;test_batch4 加了 decide_kind 断言;路由清单加了 decide-reasons;FakeRepo 加了两个桩;run_tests 登记到 897。
- 文案包对后端的改动只改字符串不改分支。
- 页面:裁决弹窗的内置码表与后端一致;decide 只负责打开,submitDecide 校验「其他」必填补充;建仓候选整行可点,提议卡与未建仓表票名可点;抽屉三段固定结构加吸顶锚点,四个计算属性与两个重试函数都进了 return 清单;test_batch20 的旧弹窗断言已改。
- 选股桥:重排在 _pick 与 _full_rows 之前的两个分数序列上做;快照截取函数 pick_rows 从已排好的行里顺序取,不重排;入池走 collect 拿宽池,顺序随之;评析名单按快照序编号。四个出口同序。加成为零时用稳定排序原样返回,逐字回旧有测试钉住。
六、做得好的地方
- 自决块放在 force_queue 之后、旁路之前,off 时一行不走,既有八百七十一例全绿就是逐字回旧的机器证明。
- decide_kind 按优先级取最保守的一种,认不出最先、试探仓最后,比规格里的先来后到更稳。
- 自动清仓按可卖量夹紧、可卖量为零不落单并记跳过,与人工采纳那条路口径一致。
- 裁决理由码表前后端各一份且内容一致,取不到后端也能弹窗。
- 选股桥只改顺序不改分值,档位标签仍按原始分解码,四条不变式测试写得对。
七、部署建议
建议顺序如下。
- 开发方先修第五节的两处,跑全量单测,再推远程。两处合计不超过半小时。
- 持仓管理系统在 155 收盘后部署,先经审批。部署后页面把 PMS_SELF_DECIDE 设 full、PMS_SELF_DECIDE_TRIAL 设开,读回三个键。
- 选股桥在 155 拉码后重启,先经审批,现在这个时段可以。重启后预热一次计划接口。
- 明天 09-15 盘中做一次只读试算,收盘后跑复核脚本看第五、第六张表。
如果你决定不等修改先部署,要守两个前提:修好之前不给挂着交易方案的票设目标价;不把 PMS_CONSENSUS_WEAK_CONFIRM 关掉。
模拟仓 155,收盘后部署,先经审批。 预期 ALL SUITES PASS、指纹一致、四容器健康、建表 21 张。
cd /home/factor/project/tradingSystem && git pull --ff-only && make deploy && make test
模拟仓 155,设好参数后。 预期读回 full、True、3。
cd /home/factor/project/tradingSystem && docker exec pms-web python -c "from app.services import param_store as ps; print({k: ps.get(k, None) for k in ('PMS_SELF_DECIDE','PMS_SELF_DECIDE_TRIAL','PMS_SELF_DECIDE_DAILY_MAX')})"
模拟仓 155,选股桥重启前记基线,重启后再跑一次。 预期来源 snapshot、有 score_adj 为 True、rank 连续为 True、质地好只数上升。
curl -s "http://192.168.16.155:8300/plan?top=50" | python3 -c "import sys,json; d=json.load(sys.stdin); rows=d.get('main') or []; print('来源', d.get('plan_source'), '前50质地好', sum(1 for r in rows if (r.get('company_review') or {}).get('overall')=='好'), '/', len(rows), '有score_adj', all('score_adj' in r for r in rows), 'rank连续', [r.get('rank') for r in rows]==list(range(1,len(rows)+1)))"
模拟仓 155,明天盘中只读试算。 预期 executed 里有 route 为 auto 且 why 含「系统采纳」的条目,或 rejected 里有 by 为 system 的条目。
cd /home/factor/project/tradingSystem && docker exec pms-web python -c "from app.services import proposal_service as p; import json; d=p.scan_and_route(dry_run=True); print(json.dumps({'executed': d.get('executed'), 'queued': d.get('queued'), 'rejected': d.get('rejected')}, ensure_ascii=False)[:2000])"