diff --git a/.dockerignore b/.dockerignore index e726169..42c6e72 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,7 @@ __pycache__ .env logs .DS_Store +# 开发代理的本地配置和嵌套工作树不进入运行镜像。 +.claude +.codex +.agents diff --git a/.gitignore b/.gitignore index ea7fcdc..ec858a0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ _to_delete/ docker-compose.override.yml /.claude/output-styles/readable-chinese.md /.claude/settings.json + +# 保留旧 Claude 工作树供接续,不把嵌套检出加入主仓库。 +/.claude/worktrees/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..11bb9b3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,148 @@ +# AGENTS.md + +本文件是 Codex 在本仓库工作的项目指令,也是项目开发规则的唯一维护入口。`CLAUDE.md` 保留兼容入口,指向本文件。 + +用可读中文沟通和写文档。写完整句子,一句一事,少用符号,不自造缩写。术语先用白话解释。命令和表格内部不受此限。这些规则直接保存在本文件,不依赖开发机上缺失的输出风格文件。 + +## 接续开发从哪里开始 + +当前接续记录是 `docs/开发接续_2026-10-12.md`。接手中断的工作时先读它,再按任务查设计文档。它记录了两个 Claude 修复的整合状态、测试隔离方式、尚未执行的探针和部署判收。 + +开工先看 `git status --short`、`git worktree list` 和相关提交。已有的未提交文件与其他工作树属于用户正在进行的工作,不覆盖、不清理。判断是否推送要直接核对远程,不凭过期的 `origin/main` 引用下结论。 + +创建新分支时默认用 `codex/` 前缀。旧的 `claude/` 分支仍是有效的 Git 分支,保留名字和工作树即可接续。合并时核对测试文件是否重名,不能让一批测试覆盖另一批。 + +历史交接里的服务器状态只代表记录当天。过期的执行日期不会自动变成今天的执行授权。2026-10-12 用户确认先以当前仓库为准,原定 10-09 的两项探针均未执行。 + +## 这是什么 + +这是一套命令驱动的持仓管理系统,代码里和文档里都简称 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/unit_test_guard.py scripts/test_batch3_units.py +``` + +每个 `scripts/test_batch*.py` 都是能独立运行的脚本,`scripts/run_tests.py` 只是把它们逐个 subprocess 起来。测试设计为不连接外部服务,服务层用内存桩(见 `scripts/test_wiring.py` 的 `FakeRepo`)。 + +**单测必须隔离配置并禁止联网。** `scripts/run_tests.py` 逐批经 `scripts/unit_test_guard.py` 启动,清除业务环境变量,在没有 `.env` 的临时目录运行。各测试文件直接运行时也先安装联网护栏。Python 套接字调用会在连接前被阻断,即使业务代码吞掉异常,整批仍以失败退出。`install_fakes` 已补齐观察读数仓储与扫描中遗漏的外部依赖。新增服务调用必须补内存桩,不能依赖异常降级让测试通过。 + +开发机可以在单独虚拟环境安装 `requirements.txt` 后经隔离入口跑全量回归。服务器部署前仍要用匹配镜像的断网容器验证,不能把本地通过称作服务器验收。2026-09-20 留下的四行历史假数据尚未清理,修测试不会自动删除库里的旧记录。 + +## 改代码时最容易踩的坑 + +**改了 Python 代码必须重建镜像加 force-recreate,绝不能用 `docker compose restart`。** 源码是打进镜像的,`restart` 只是把老容器停了再起,跑的还是旧镜像里的旧代码,而且一点报错都没有。`make deploy` 和 `make up` 走的都是正确路径。这是本项目头号坑,`Makefile` 顶部、`scripts/deploy.sh`、`run_tests.py` 里都反复写了这一条。 + +**模拟仓 155 是个例外,要另记(2026-09-20 实测)。** 155 的项目目录里有一个服务器本地文件 `docker-compose.override.yml`,内容同仓库里的 `docker-compose.dev.yml`,它把整个仓库目录绑定挂载到四个容器的 `/app`。这个文件被 `.gitignore` 忽略,仓库里看不到;真实仓 188 没有它。后果有三条。一,在 155 上 `git pull` 之后,页面静态文件立刻就是新的,后端四个进程仍是旧代码,直到重启或 `make deploy`。二,`make test` 与 `make stale` 起的一次性容器读的也是工作树,所以在 155 上「指纹一致」恒成立,查不出「拉了代码没重启」。三,往 155 拉代码等于把页面先上线了,拉之前先看 `git log` 里有没有别的会话提交的、还没到上线日的改动。`make deploy` 在两台机器上都仍是对的做法。 + +**下发模式由参数 `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 调度总表与调度守卫。调度位以代码和 `scripts/test_wiring.py` 中的哨兵清单为准。 + +进程边界值得单独理解一下。出手的 `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` 开头。 + +**待核实的例外。** 上段是历史约定。PMS 自有表的部分列实际是 `DATETIME`,`pms_repo.py` 与 `qmt_repo.py` 用容器内 `datetime.now()` 写值。10-08 的两个探针因此提出先用心跳和快照校准时区。用户已确认探针尚未执行,这个差异尚无新的真机结论。读这些列前要核对写入路径和实际时区,并明确标注原值与换算依据,不把两种口径混在同一份读数里。 + +## 前端的两个坑 + +管理页面是无构建步骤的单页,这两个坑都实际炸过页面,都写在 README 里。 + +1. 模板里不能用自闭合的自定义标签。`` 这种写法浏览器 HTML 解析器不认,会把后面所有内容当成它的子节点;若它带 `v-if` 且条件为假,整页就跟着消失。一律写成 ``。 +2. `setup()` 里定义给模板用的东西必须真的 `return` 出去。漏一个函数没接出去,用到它的地方会在渲染时报错并白屏。`scripts/test_page_wiring_guard.py` 就是为这个坑加的护栏(2026-09-10 白屏即漏了十一个显示函数所致)。 + +页面有三道静态护栏:`scripts/test_page_enum_guard.py`、`scripts/test_page_wiring_guard.py`、`scripts/test_page_copy_guard.py`。改页面后要一起跑,涉及布局和交互时还要浏览器验收。 + +页面措辞要专业,不出现内部设计词,不自造简写。同一概念统一用基本面、技术面、择时三种维度。看多用红色,看空用绿色,中性用灰色,无读数用虚框。说明放在悬停里,表格要能在中栏完整显示。 + +## 加测试时的登记纪律 + +`scripts/run_tests.py` 把「缺一个测试文件」当失败处理,而不是当「跳过」——因为镜像旧了的时候,新加的那批测试正好全部「不存在」,一条没跑却会报全部通过。所以新增一个 `test_batch*.py`,必须同时登记进 `scripts/run_tests.py` 的 `SUITES` 清单和它上面的例数说明表,否则会被判失败。 + +代码里还有若干「哨兵断言」,改了对应的东西就得回到那里改断言,断言不动就是漏了。清单写在 `run_tests.py` 顶部的注释里(DDL 表数、调度位、API 路由、密钥名单、动作求值器次序等)。改这些结构性的东西时先去那份清单对一遍。 + +## 部署与运维纪律 + +在开发机修改代码,通过git提交,然后在服务器上部署 + +部署方式与服务器信息以 `DEVLOG.md` 开头那段为准(PMS 跑在哪台机、决策系统与因子桥各在哪、各自怎么生效)。有模拟仓与真实仓两套实例,靠表名前缀(`PMS_TABLE_PREFIX`)与容器名前缀区分,共用同一套代码。对真实仓做写操作前先确认清楚是哪一套。 + +服务器上的容器操作偏敏感:不要随意重启或关停容器,涉及容器与数据库的操作先与用户确认,不碰本仓库以外的项目。登录到哪台机、面对的是哪一套实例,不清楚时先问。 + +每次交付写明验证命令在哪台机器上运行,以及预期结果。一个命令块只放一台机器的命令。重要节点及时追加到 `DEVLOG.md`,包含做了什么、动了哪些文件、部署方式、真机判收、还欠着什么。判收只写「真机见过」或「未判收」,本地通过不代替真机判收。 + +## 文档地图 + +仓库根目录有大量设计与交接文档,需要细节时按主题查这几份: + +- `README.md`:总览,最全。上面各节多是从它摘的。 +- `DEVLOG.md`:开发节点流水账,每条含「做了什么、动了哪些文件、部署方式、真机判收、还欠什么」五样。查某个改动的来龙去脉看这里。 +- `docs/开发接续_2026-10-12.md`:Claude 到 Codex 的仓库迁移记录、未合入分支和当前待办入口。 +- `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/CLAUDE.md b/CLAUDE.md index af7ed98..8e287cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,127 +1,7 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +本仓库的开发规则统一维护在 [AGENTS.md](AGENTS.md)。开始工作前请完整阅读并遵守该文件,不在这里另存一份规则。 -本文件用可读中文写成,正文遵循 `.claude/output-styles/readable-chinese.md` 的规矩:完整句子、一句一事、少用符号。命令和表格内部不受此限。 +中断后的接续记录见 [2026-10-12 开发接续](docs/开发接续_2026-10-12.md)。历史文档中的 `.claude/output-styles/readable-chinese.md` 已不在当前开发机上,可读中文规则已直接写入 `AGENTS.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` 里都反复写了这一条。 - -**模拟仓 155 是个例外,要另记(2026-09-20 实测)。** 155 的项目目录里有一个服务器本地文件 `docker-compose.override.yml`,内容同仓库里的 `docker-compose.dev.yml`,它把整个仓库目录绑定挂载到四个容器的 `/app`。这个文件被 `.gitignore` 忽略,仓库里看不到;真实仓 188 没有它。后果有三条。一,在 155 上 `git pull` 之后,页面静态文件立刻就是新的,后端四个进程仍是旧代码,直到重启或 `make deploy`。二,`make test` 与 `make stale` 起的一次性容器读的也是工作树,所以在 155 上「指纹一致」恒成立,查不出「拉了代码没重启」。三,往 155 拉代码等于把页面先上线了,拉之前先看 `git log` 里有没有别的会话提交的、还没到上线日的改动。`make deploy` 在两台机器上都仍是对的做法。 - -**下发模式由参数 `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` 对照。 +旧的 `claude/` 分支和 `.claude/worktrees/` 中的未合入工作继续保留。迁移开发工具不代表这些改动已经合并、部署或验收。 diff --git a/DEVLOG.md b/DEVLOG.md index f4dc4d4..9b86248 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1737,6 +1737,46 @@ docs/交接_2026-09-20.md(新)、docs/dev_preview/mock_preview.py 与 sample --- +## 2026-10-12 · 从 Claude 接续到 Codex,整理未合入修复与未执行探针 + +**做了什么** +以当前仓库为准完成开发规则迁移。根目录 `AGENTS.md` 作为唯一规则入口,`CLAUDE.md` 改为兼容引用。补回缺失的可读中文要求与页面偏好,补全三道页面静态守卫,记录单测隔离缺口和数据库时间口径的待核实差异。新增接续记录,区分主分支、两个未合入的 Claude 修复分支、未提交讨论稿和探针。用户确认原定 10-09 的两项探针均未执行,先不另找旧会话资料。 + +**动了哪些文件** +`AGENTS.md`、`CLAUDE.md`、`README.md`、`docs/开发接续_2026-10-12.md`、`.gitignore`、`.dockerignore`、`DEVLOG.md`。忽略规则排除本地代理目录与嵌套工作树;两个工作树仍保留。接手时已有的台账、讨论稿与两个探针内容未改。 + +**部署方式** +仅开发机规则与文档迁移,未提交、未推送、未部署。直接查询代码服务器确认远程 `main` 与本机同为 fbe0014;两个 Claude 修复分支未返回同名远程引用。后续提交要把规则入口与接续文档一起提交,避免兼容入口指向不存在的文件。 + +**真机判收** +未判收。本次未连接 155 或 188 核对运行状态,未运行业务全量测试。开发机三道页面静态守卫与 `git diff --check` 均通过,两个探针只做语法解析。原有台账、讨论稿和两个探针的文件摘要与接手时一致。 + +**还欠着什么** +测试装配仍漏替换 `consensus_stat_repo`,需先修复隔离。两个修复分支重用了 `test_batch35_units.py`,合并时要保留两批测试并重新统计。命令完成后的 GATED 批次收尾仍待明确。排序开关的当前状态、两个探针的新执行时间、方案第二轮拍板与真实仓准备仍待接续。 + +--- + +## 2026-10-12(续)· 修复单测隔离并整合命令结算与一字板修复 + +**做了什么** +在 `codex/test-isolation-integrate` 整合 Claude `ead20bf` 与 `01dbcdb` 的修复,原工作树保留。补齐观察读数仓储、扫描取数和量比等内存桩。增加配置隔离与禁止联网入口,外部调用被吞异常也判测试失败。两分支重名测试分别登记为第三十六、三十七批,隔离回归登记为第三十五批。 + +用户确认命令完成时取消未解锁批次。完成前持久化扣除额,取消本命令的 GATED 方案后再写完成状态,故障后可重试,方案记录仍保留。补非零目标无方案不完成,以及昨收空值、陈旧值、无效价格不缓存的保护。 + +**动了哪些文件** +`app/core/command_spec.py`、`planner.py`、`exec_timing.py`、`rule_gate.py`,`app/repo/pms_repo.py`,`app/services/command_service.py`、`executor.py`、`exec_advisor.py`、`market.py`。测试改动集中在 `scripts/unit_test_guard.py`、`test_wiring.py`、`test_batch35_units.py` 至 `test_batch37_units.py`、各批护栏入口和 `run_tests.py`。同步规则与接续记录。没有 DDL、依赖或运行参数变更。 + +**部署方式** +开发机准备提交后,在 155 模拟仓先用无网络、无生产配置的临时容器验证,再按 `make build` 和 `make up` 重建镜像与四个 PMS 服务。本次无需建表。188 真实仓不在本次部署范围。 + +**真机判收** +未判收。开发机临时 Python 3.9 环境全量 41 批、983 例通过,日志 `/tmp/pms-integrated-fifth.log`。校正旧统计:原有 941 例,本次新增 42 例。155 只读核对见仓库 `fbe0014`、四服务运行且网页健康;它仍挂载源码,必须另核后端重建时间。服务器隔离测试与部署验收待执行。 + +**还欠着什么** +完成 155 的容器回归与部署验收。历史假读数和历史 DONE 命令遗留批次不在本次自动清理范围。盘中才封住的跌停、因子库前复权基准、两项旧探针和 188 真实仓仍按原计划单独接续。 + +--- +