From b1ebb3659e4bc43bdfc548f916fc7039674b38e5 Mon Sep 17 00:00:00 2001 From: zlt Date: Mon, 14 Sep 2026 10:43:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=8C=85=EF=BC=9A=E6=94=B9?= =?UTF-8?q?=E6=8E=89=E5=BD=B1=E5=AD=90=E6=A8=A1=E5=BC=8F=E8=BF=87=E6=97=B6?= =?UTF-8?q?=E8=AF=B4=E6=B3=95=E3=80=81CLAUDE.md=20=E5=85=A5=E5=BA=93?= =?UTF-8?q?=E3=80=81DEVLOG=20=E8=BF=BD=E5=8A=A0=E6=9C=AC=E8=BD=AE=E8=8A=82?= =?UTF-8?q?=E7=82=B9=EF=BC=88=E6=8B=8D=E6=9D=BF=E7=82=B9=E4=BA=94=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CLAUDE.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ DEVLOG.md | 23 ++++++++++ README.md | 6 +-- 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bd634e1 --- /dev/null +++ b/CLAUDE.md @@ -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` 的规矩:完整句子、一句一事、少用符号。命令和表格内部不受此限。 + +## 这是什么 + +这是一套命令驱动的持仓管理系统,代码里和文档里都简称 PMS(Position Management System,持仓管理系统)。它在整条交易链路里只负责一段:用户在管理页面下达大方向命令,例如设定总规模、调仓位上限、升降仓、对某只股票做 T;PMS 据此把命令拆成分股分批的方案,并管理账本(批次、摊薄成本、安全垫、纪律)。 + +链路上还有另外两个系统,它们不在本仓库里,但 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. 模板里不能用自闭合的自定义标签。`` 这种写法浏览器 HTML 解析器不认,会把后面所有内容当成它的子节点;若它带 `v-if` 且条件为假,整页就跟着消失。一律写成 ``。 +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` 对照。 diff --git a/DEVLOG.md b/DEVLOG.md index e2786e9..ed999d7 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1479,6 +1479,29 @@ app/services/strategy_runner.py(跟踪止盈评估器 _eval_trail 加盘中 SA **还欠着什么** 一,八个拍板点(规格书第八节),其中「策略层总开关是否有意打开」今天就要答,因为 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.py(build_map 后调 record_map_cover)、app/services/ledger_service.py(日报小节)、app/services/param_store.py(PMS_CONSENSUS_STAT_TIMES)、Makefile(consensus-review 目标)、scripts/run_tests.py(第 31 批登记)、scripts/test_batch6_units.py(DDL 表数 20→21)、scripts/test_batch10_units.py(record_round 进 SOFT_FAIL)、docs/复盘决定台账.md(台账 013)。 +加固包(提交 e21b73b):改 app/core/consensus.py、app/services/consensus_service.py、app/services/param_store.py(PMS_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)。 + +**部署方式** +改了 Python,make 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 那一跳(策略层已确认有意打开)的摘要盘后要拉给用户判读。 + ---