文档包:改掉影子模式过时说法、CLAUDE.md 入库、DEVLOG 追加本轮节点(拍板点五)

- README 与 CLAUDE.md 里「当前是影子模式、没有系统自动下单」已过时(模拟仓 155
  从 8-3 起就是 ws、发过真实委托)。安全相关的说法过时比一般过时更危险。按拍板点五
  改成:下发模式由 PMS_DISPATCH_MODE 决定、以页面顶部横幅为准;写清模拟仓 155 现为
  ws、真实仓 188 尚未追平上线等 QMT 侧、追平先进影子期。
- CLAUDE.md 入库(原为未跟踪)。
- DEVLOG 追加观察读数包与加固包的五段式节点(未真机判收,待收盘后 155 部署)。

文档包其余项随对应工作包做:台账 012(盘中确认包)、014(真实仓准备包)、
接口契约单票研究面(页面收尾包)。台账 013 已随观察读数包提交。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zlt 2026-09-14 10:43:27 +08:00
parent e21b73b46d
commit b1ebb3659e
3 changed files with 151 additions and 3 deletions

125
CLAUDE.md Normal file
View File

@ -0,0 +1,125 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
本文件用可读中文写成,正文遵循 `.claude/output-styles/readable-chinese.md` 的规矩:完整句子、一句一事、少用符号。命令和表格内部不受此限。
## 这是什么
这是一套命令驱动的持仓管理系统,代码里和文档里都简称 PMSPosition Management System持仓管理系统。它在整条交易链路里只负责一段用户在管理页面下达大方向命令例如设定总规模、调仓位上限、升降仓、对某只股票做 TPMS 据此把命令拆成分股分批的方案,并管理账本(批次、摊薄成本、安全垫、纪律)。
链路上还有另外两个系统,它们不在本仓库里,但 PMS 时时与它们打交道:
1. 决策系统,代码里叫 bionic。它负责研判该不该做与盘中择时何时做。PMS 不自建研判栈,研判和择时都可以委托它。
2. 下游系统,即 QMT 侧的执行服务。它只负责挂单成交。PMS 经一条 WebSocket 长连接直连它。
一句话概括分工PMS 管「做什么、多少」,决策系统管「该不该、何时」,下游只管执行。
## 三条铁律(改任何东西前先读)
这三条写在 README 开头,是整个系统的设计前提,违反它们的改动一律是错的。
1. 命令至上。自动决策不得突破用户的命令参数;冲突时命令优先;命令之间的冲突交用户裁决。
2. 分工不越权。研判不可用时降级为保守规则加人工确认,绝不自建第二套研判。
3. 先记账后动作,故障即守成。指令先落表再下发;出了故障不产生新指令;账本与下游定期对账,以下游为实际持仓的事实源。
## 常用命令
日常操作都由 `Makefile` 包了一层,`make help` 看全部目标。下面这些是最常用的。所有 `make` 命令都在部署 PMS 的服务器上跑(源码打进容器镜像,本机直接跑 Python 会因为缺少容器环境而对不上)。
```bash
make deploy # 一句话部署git pull → 重建镜像 → 重建容器force-recreate→ 建表
make deploy-local # 同上但跳过 git pull本地已经改好时用
make test # 跑全部单测(不连库,秒级)。应输出 ALL SUITES PASS
make watch # 盯盘看这一个:通道/账户/在途指令/出口委托/账本持仓 一屏看完,只读
make health # 页面健康检查(配置装载自证 + 库连通自证)
make check # 实机连通性与表结构自检(需要真实 .env
```
跑单元测试有两种粒度。跑全部用 `make test`它会先比对容器镜像与工作树的代码指纹对不上就报警防「git pull 了没重建,跑的还是旧代码而且照样通过」)。只跑某一批时,直接把那个测试文件当脚本跑:
```bash
# 只跑某一批择时实现B 与规则闸那一批)
docker compose run --rm --no-deps pms-web python scripts/test_batch3_units.py
```
每个 `scripts/test_batch*.py` 都是能独立运行的脚本,`run_tests.py` 只是把它们逐个 subprocess 起来。测试全部零外部依赖、不连库,服务层用内存桩(见 `scripts/test_wiring.py``FakeRepo`)。
## 改代码时最容易踩的坑
**改了 Python 代码必须重建镜像加 force-recreate绝不能用 `docker compose restart`。** 源码是打进镜像的,`restart` 只是把老容器停了再起,跑的还是旧镜像里的旧代码,而且一点报错都没有。`make deploy` 和 `make up` 走的都是正确路径。这是本项目头号坑,`Makefile` 顶部、`scripts/deploy.sh`、`run_tests.py` 里都反复写了这一条。
**下发模式由参数 `PMS_DISPATCH_MODE` 决定,实际以页面顶部横幅为准,不要拿这段文字当现状。** 设置文件的初值是 `shadow`(影子模式:指令照常生成、过规则闸、记账、置为已下发,但不写下游,需人工在 QMT 侧执行,成交由回放任务按先进先出认领回账本)。但模拟仓 155 运行时已切到 `ws`,指令经 `pms-ws` 真向模拟 QMT 发出、自主动作按档位可自动执行,不再需要人工下单。真实仓 188 的 PMS 尚未追平到最新代码,上线等 QMT 侧,追平部署时按真实仓准备包的初始参数清单先进影子期。切换只是 `PMS_DISPATCH_MODE``PMS_QMT_WS_ENABLED` 两个开关加一次联调,不是再写代码。改动涉及下单链路时,务必先确认自己面对的是哪一套实例、此刻是哪个模式。
**业务参数一律走 ParamStore 读,不要直接读 settings。** `config/settings.py` 里的业务参数(`PMS_TOTAL_SCALE`、各种档位阈值等)只是「初值」。运行时以数据库表 `pms_runtime_param` 的值为准,页面改参数即时生效。取业务参数统一经 `app/services/param_store.py`,直接读 settings 会让页面调参失效。基础设施类配置(连接串、密钥)才从 settings 与 `.env` 读。
**密钥只从 `.env` 注入,不入库、不进 ParamStore、不上页面。** 会话密钥、QMT 通道的两把 Ed25519 密钥都属此类。新增密钥类配置时,要同时登记进 `param_store.py``SECRET_KEYS`,否则它会被当成可调参数暴露到页面快照里。
## 架构分层
代码在 `app/` 下按依赖方向分层,越靠上越纯。
- `app/core/`:纯逻辑,零外部依赖,可单测。系统的算数与纪律都在这里——批次拆分与组合约束(`sizer.py`)、摊薄成本与安全垫状态机(`cushion.py`)、命令目录与校验(`command_spec.py`)、方案生成(`planner.py`)、规则闸终检(`rule_gate.py`、择时实现B`exec_timing.py`)、动作引擎(`action_engine.py`、QMT 协议编解码与签名(`ws_codec.py`)等。改核心逻辑先在这一层,它有最密的单测覆盖。
- `app/db/session.py`:三个数据库的连接,外加一道严格单表访问守卫(下面单列一节)。
- `app/repo/`:单表数据访问层。每个函数只碰一张表。
- `app/services/`:编排层,把 core 的纯逻辑接上 repo 的取数与落表。命令下达在 `command_service.py`,方案转指令与出手在 `executor.py`,下发通道适配器在 `dispatcher.py`,运行参数中心在 `param_store.py`,组合快照在 `portfolio.py`
- `app/ws/runner.py`:常驻的 QMT 连接进程,容器名 `pms-ws`。它只做通道,不碰账本。
- `app/web/`FastAPI 后端加一个单页前端(`static/index.html`Vue3 加 ElementPlus无构建步骤
- `app/scheduler.py`Celery beat 调度总表,九个调度位加三条守卫。
进程边界值得单独理解一下。出手的 `executor` 跑在短命的 Celery worker 任务里,而 QMT 连接必须是一条常驻长连接(协议规定 PMS 只准开一条多开会让指令乱序。两者只经数据库耦合worker 把待发委托写进出口表 `pms_qmt_order``pms-ws` 进程亚秒轮询取走并签名发出QMT 的回报落进 `pms_qmt_inbox`,再由回放任务认领进账本。因此 `pms-ws` 这个服务绝不能扩副本。
三组容器服务:默认起 `pms-web`(管理页面,端口 38100`--profile sched` 起 `pms-beat``pms-worker`(调度与执行);`--profile ws` 起 `pms-ws`QMT 直连)。
## 严格单表访问守卫
153 代理库不支持多表联查,写错了要到线上才报错。为把这条纪律钉死,`app/db/session.py` 在每条 SQL 的执行入口做静态检查,出现 JOIN、逗号连表、跨表子查询一律拒绝抛异常。写数据访问代码时一条 SQL 只能碰一张表;要联的数据在 service 层分两次查再在内存里拼。`upsert` 语句里的 `ON DUPLICATE KEY UPDATE` 是这道守卫的历史误伤点,改 upsert 时留意。
## 数据库与外部依赖
三个 MySQL 库(键名与 bionic 对齐,同一份 `.env` 可服务两个系统):`PROXY_DB_URL` 是 153 代理,放 PMS 全部自有表加决策系统结论表;`SOURCE_DB_EXT_DSN` 是因子库,当前 PMS 不用;`DB_MYSQL_URL` 是大盘指数库,只做页面区制提示。建表脚本是 `scripts/init_db.py`,建表语句在 `ddl_pms_v1.sql`
Redis 有几路决策系统的盘中信号流与风控卖出流、实时行情、PMS 自己的 Celery 总线。**`redis` 依赖必须锁在 5.x**6.x 默认按 RESP3 握手,而现网的 Redis 版本偏旧会回 `unknown command HELLO`,导致行情与调度总线一起断。`requirements.txt` 已锁版本,升级依赖时别放开。
## 时区口径(很容易读错)
不能按「哪个系统」一刀切,要按「哪种时间」分。容器时钟与容器日志正文是北京时间,不要加八小时。数据库里的时间戳列(如各种 `created_at`、`updated_at`)是 UTC读的时候要加八小时。日期列`trade_date`、`audit_date`)是北京日期,不换算。写记录时一律用北京时间,引用数据库时间列先加八小时并标注已换算。完整依据见 `DEVLOG.md` 开头。
## 前端的两个坑
管理页面是无构建步骤的单页,这两个坑都实际炸过页面,都写在 README 里。
1. 模板里不能用自闭合的自定义标签。`<el-alert .../>` 这种写法浏览器 HTML 解析器不认,会把后面所有内容当成它的子节点;若它带 `v-if` 且条件为假,整页就跟着消失。一律写成 `<el-xxx ...></el-xxx>`
2. `setup()` 里定义给模板用的东西必须真的 `return` 出去。漏一个函数没接出去,用到它的地方会在渲染时报错并白屏。`scripts/test_page_wiring_guard.py` 就是为这个坑加的护栏2026-09-10 白屏即漏了十一个显示函数所致)。
`index.html` 没有别的自动化测试,页面的两道静态护栏是 `test_page_enum_guard.py``test_page_wiring_guard.py`,改页面后要一起跑。
## 加测试时的登记纪律
`run_tests.py` 把「缺一个测试文件」当失败处理,而不是当「跳过」——因为镜像旧了的时候,新加的那批测试正好全部「不存在」,一条没跑却会报全部通过。所以新增一个 `test_batch*.py`,必须同时登记进 `run_tests.py` 顶部的 `SUITES` 清单和它上面的例数说明表,否则会被判失败。
代码里还有若干「哨兵断言」,改了对应的东西就得回到那里改断言,断言不动就是漏了。清单写在 `run_tests.py` 顶部的注释里DDL 表数、调度位、API 路由、密钥名单、动作求值器次序等)。改这些结构性的东西时先去那份清单对一遍。
## 部署与运维纪律
在开发机修改代码通过git提交然后在服务器上部署
部署方式与服务器信息以 `DEVLOG.md` 开头那段为准PMS 跑在哪台机、决策系统与因子桥各在哪、各自怎么生效)。有模拟仓与真实仓两套实例,靠表名前缀(`PMS_TABLE_PREFIX`)与容器名前缀区分,共用同一套代码。对真实仓做写操作前先确认清楚是哪一套。
服务器上的容器操作偏敏感:不要随意重启或关停容器,涉及容器与数据库的操作先与用户确认,不碰本仓库以外的项目。登录到哪台机、面对的是哪一套实例,不清楚时先问。
## 文档地图
仓库根目录有大量设计与交接文档,需要细节时按主题查这几份:
- `README.md`:总览,最全。上面各节多是从它摘的。
- `DEVLOG.md`:开发节点流水账,每条含「做了什么、动了哪些文件、部署方式、真机判收、还欠什么」五样。查某个改动的来龙去脉看这里。
- `POSITION_MGMT_DESIGN.md`:总体设计定稿。
- `QMT_WS_PROTOCOL.md`PMS 与 QMT 的 WebSocket 协议,下发通道的唯一实现依据。
- `BIONIC_PMS_INTERFACE.md`PMS 与决策系统的接口契约(研判闸与盘中择时两个接口)。
- `UPSTREAM_PLAN_API.md`:上游选股计划接口的接入记录,候选池的事实源。
- `docs/复盘决定台账.md`:近期改动按「台账」编号推进,提交信息里的台账号对应到这里。
## 提交约定
提交信息用简短中文,描述这次改动的行为而非罗列文件。近期工作按台账编号组织,提交里出现的台账号可回到 `docs/复盘决定台账.md` 对照。

View File

@ -1479,6 +1479,29 @@ app/services/strategy_runner.py跟踪止盈评估器 _eval_trail 加盘中 SA
**还欠着什么** **还欠着什么**
一,八个拍板点(规格书第八节),其中「策略层总开关是否有意打开」今天就要答,因为 09:40 那一跳会是工作包三后两部分的首次实弹。二,八个工作包一个都还没动工,建议顺序与交易日安排见规格书第四节;观察读数包要在 09-17 复核前部署。三CLAUDE.md 仍未跟踪,按文档包入库。 一,八个拍板点(规格书第八节),其中「策略层总开关是否有意打开」今天就要答,因为 09:40 那一跳会是工作包三后两部分的首次实弹。二,八个工作包一个都还没动工,建议顺序与交易日安排见规格书第四节;观察读数包要在 09-17 复核前部署。三CLAUDE.md 仍未跟踪,按文档包入库。
---
## 2026-09-14 · 观察读数包与加固包(开发机完成,未真机判收)
**做了什么**
下一阶段第一优先级是「补观察读数」。做了两个工作包,都在开发机完成并全量单测通过,尚未在 155 部署。
观察读数包:把每轮扫描里合议相关的判定归类落进新表 pms_consensus_stat第 21 张),供台账 006 到 011 复核。之前这些数只活在扫描返回值与页面即时快照里,复核日期到了拿不出数。动作引擎给合议跳过项(跳过分无评析、看空两类,观察打等开口)与增持门(带 fill/add/dca打类别标签只在对应开关开着的分支里打关掉逐字回旧。落表服务 consensus_stats.record_round 按检查点归类落表:转空离场实弹随时写,路由与装配两类只在检查点写(默认 0935,1030,1330,1445一天最多四次试算不写映射重建时 record_map_cover 记覆盖只数与逐只无读数。复核脚本 scripts/consensus_review.py 与 make consensus-review 打出台账要的四个读数,日报加合议观察小节。新参数 PMS_CONSENSUS_STAT_TIMES 控制检查点。
加固包:评审第五节四处小加固。转空去重集只在 SAR 明确为多时才清除(原来「不是空就清」把缺读数也当翻多,会重复减仓),抽出纯函数 _keep_tech_exit_done。持仓行的择时票带上当日盘中转多留痕decorate_positions 从买入信号同一来源取 flip_map。登记 PMS_CONSENSUS_WEAK_CONFIRM默认开关掉则弱表态放行consensus.decide 加 weak_confirm 参数。第二十四批时钟用例改相对当前时刻构造陈旧告警,任何时刻跑都过。
**动了哪些文件**
观察读数包(提交 f2d6b37新增 app/repo/consensus_stat_repo.py、app/services/consensus_stats.py、scripts/consensus_review.py、scripts/test_batch31_units.py改 ddl_pms_v1.sql第 21 张表、app/core/action_engine.py四个 tag 常量与两处打标签、app/services/proposal_service.py_attach_consensus 写 consensus_seen、scan_and_route 末尾调 record_round、app/services/tech_service.pybuild_map 后调 record_map_cover、app/services/ledger_service.py日报小节、app/services/param_store.pyPMS_CONSENSUS_STAT_TIMES、Makefileconsensus-review 目标、scripts/run_tests.py第 31 批登记、scripts/test_batch6_units.pyDDL 表数 20→21、scripts/test_batch10_units.pyrecord_round 进 SOFT_FAIL、docs/复盘决定台账.md台账 013
加固包(提交 e21b73b改 app/core/consensus.py、app/services/consensus_service.py、app/services/param_store.pyPMS_CONSENSUS_WEAK_CONFIRM、app/services/proposal_service.py、scripts/test_batch24_units.py、scripts/test_batch28_units.py加 3 例、scripts/test_batch30_units.py加 3 例、scripts/run_tests.py例数到 846
**部署方式**
改了 Pythonmake deploy 重建镜像加 force-recreate并建新表 pms_consensus_stat。收盘后在 155 部署先经用户审批本包重建容器、加新表按服务器铁律先列命令拿批准。命令git pull --ff-only && make deploy && make test预期 ALL SUITES PASS、建表输出 21 张含 pms_consensus_stat。观察读数包与加固包一起部署。
**真机判收**
未判收。开发机全量单测 846 例 ALL SUITES PASS新增第 31 批 20 例,第 28 批到 40、第 30 批到 36。155 部署待收盘后。判收要看:部署下一个交易日 09:37 后 make consensus-review DAYS=1 打出当天读数(第一天转空离场应为零);映射重建后 pms_consensus_stat 有 map_cover 行;台账 006 到 009 的第一次正式复核在 09-17。
**还欠着什么**
一,收盘后在 155 部署观察读数包与加固包并真机判收判收读数回填到本节点。二文档包其余项随对应工作包做README 影子模式过时说法要改拍板点五安全相关待用户认框架、CLAUDE.md 入库、台账 012盘中确认随盘中确认包与 014真实仓初始参数随真实仓准备包、接口契约单票研究面随页面收尾包。三盘中确认包、页面收尾包、分段启用包按规格书第四节在 09-17 起推进。四,今天 09:40 那一跳(策略层已确认有意打开)的摘要盘后要拉给用户判读。
--- ---
<!-- <!--
下一条节点从这里往下写,格式照抄上面: 下一条节点从这里往下写,格式照抄上面:

View File

@ -174,8 +174,8 @@ docker compose logs -f pms-ws
| 模式 | 行为 | 什么时候用 | | 模式 | 行为 | 什么时候用 |
|---|---|---| |---|---|---|
| `shadow`(默认) | 指令照常过规则闸、照常置 DISPATCHED但**不写下游**。你在 QMT 侧人工执行,成交由回放按 FIFO 认领回账本 | **当前仍是这个状态**(切 ws 需先配密钥并跑完 S1/S2 联调) | | `shadow`(默认) | 指令照常过规则闸、照常置 DISPATCHED但**不写下游**。你在 QMT 侧人工执行,成交由回放按 FIFO 认领回账本 | 设置文件的初值。真实仓上线初期按初始参数清单用它先进影子期 |
| `ws` | WebSocket 直连 QMT 执行服务,协议见 `QMT_WS_PROTOCOL.md` V1.0 | 目标形态。**通道已实现**2026-07-28联调通过即可切 | | `ws` | WebSocket 直连 QMT 执行服务,协议见 `QMT_WS_PROTOCOL.md` V1.0 | 目标形态。**模拟仓 155 已切 ws 运行**(通道 2026-07-28 实现) |
**ws 模式的进程边界**(理解这条通道的关键):连接是一条常驻长连接,且协议 §1 规定 PMS 只准开一条(多开会让指令乱序),所以它由独立的 `pms-ws` 进程独占;而 `executor` 跑在 celery worker 这种短命任务进程里。两者**只经数据库耦合** **ws 模式的进程边界**(理解这条通道的关键):连接是一条常驻长连接,且协议 §1 规定 PMS 只准开一条(多开会让指令乱序),所以它由独立的 `pms-ws` 进程独占;而 `executor` 跑在 celery worker 这种短命任务进程里。两者**只经数据库耦合**
@ -201,7 +201,7 @@ QMT ──trade/order_update──▶ pms-ws ──落 pms_qmt_inbox──▶
**目标架构2026-07-28 已定)**`trading_service` 全量退出业务,只保留看板与统计展示;新写一个 QMT 直连服务承担挂单与订单/持仓/资金回写PMS 经 WebSocket 与它直连PMS 只管决策与账本。 **目标架构2026-07-28 已定)**`trading_service` 全量退出业务,只保留看板与统计展示;新写一个 QMT 直连服务承担挂单与订单/持仓/资金回写PMS 经 WebSocket 与它直连PMS 只管决策与账本。
**当前处境(重要)**:旧通道(下游直接执行决策系统卖出信号、轮询 `trading_buy_plan` 自动挂单)已按双方约定**即刻停用**。新的 ws 通道代码已就绪,但 `PMS_DISPATCH_MODE` 仍是 `shadow``PMS_QMT_WS_ENABLED=False`,所以现在**依然没有任何系统会自动下单**——PMS 影子运行,指令照常生成、过闸、记账,但需人工在 QMT 侧执行。这是安全的状态,但要知道它是这个状态。切 ws 是**两个开关加一次联调**,不是再写代码。 **当前处境(重要)**:旧通道(下游直接执行决策系统卖出信号、轮询 `trading_buy_plan` 自动挂单)已按双方约定**即刻停用**。下发模式由 `PMS_DISPATCH_MODE` 决定,**实际以页面顶部横幅为准,不要拿本文的快照当现状**。模拟仓 155 现为 `ws`:指令过闸、记账后经 `pms-ws` 真向模拟 QMT 发出,自主动作按各自档位可自动执行,不再需要人工在 QMT 侧下单。真实仓 188 的 PMS 尚未追平到最新代码(阶段 A 最小台架),上线等 QMT 侧;追平部署时按真实仓准备包的初始参数清单先进影子期(`shadow`、自主关闭),当前不动 188。切 ws 只是 `PMS_DISPATCH_MODE``PMS_QMT_WS_ENABLED` 两个开关加一次联调,不是再写代码。
影子模式下的完整闭环:页面下命令 → 方案落表 → 方案转指令 → 择时按日配额给出「今天该出多少、什么价、挂到几点」→ 你照着在 QMT 下单 → 5 分钟一次的回放把成交认领回批次账本 → 命令进度自动推进。整条链路除了「人手下单」这一步,其余与实盘接管后完全一致。 影子模式下的完整闭环:页面下命令 → 方案落表 → 方案转指令 → 择时按日配额给出「今天该出多少、什么价、挂到几点」→ 你照着在 QMT 下单 → 5 分钟一次的回放把成交认领回批次账本 → 命令进度自动推进。整条链路除了「人手下单」这一步,其余与实盘接管后完全一致。