1076 lines
101 KiB
Markdown
1076 lines
101 KiB
Markdown
# 开发节点记录(三系统量化交易)
|
||
|
||
> 这份文件的用途只有一个:**让下一次交接不必重新查一遍**。
|
||
> 每个重要节点写一条,写完就补,不攒到最后。一条节点包含五样东西:
|
||
> 做了什么、动了哪些文件、部署方式、真机判收结果、还欠着什么。
|
||
>
|
||
> 三个系统的部署方式(改错等于白改):
|
||
> - **tradingSystem(PMS)** 跑在 `factor@factorevaluation`,源码打进镜像,
|
||
> 改码必须 `make deploy`,跑完 `make test` 见 ALL SUITES PASS,盯盘 `make watch`。
|
||
> 容器时钟是北京时间。
|
||
> - **bionic_trader(决策系统)** 跑在 `192.168.16.188:38000`,挂载卷,
|
||
> `git pull` 后 `docker compose restart backend-api worker-brain` 即生效;
|
||
> **新增 env 必须 `docker compose up -d --force-recreate`**,restart 不重读 env。
|
||
> - **akg-factor-bridge(因子桥)** 在 `factorevaluation`,容器名 `akg_factor_bridge`,
|
||
> API 在 8300,常驻 API 改码要 compose restart。
|
||
>
|
||
> **时区(2026-08-06 查实、2026-08-07 再证一次,这里写的以实测为准)**:
|
||
> 不能按「哪个系统」一刀切,要按「哪种时间」分:
|
||
> - **容器时钟与容器日志正文** —— 北京时间,**不要加八小时**。
|
||
> 证据:bionic 容器内 `date` 为 `15:37 CST`。
|
||
> - **数据库的时间列**(`strategy_audit_log.created_at`、
|
||
> `strategy_daily_results.updated_at` 等)—— **UTC,要加八小时**。
|
||
> 证据:08-07 北京 09:03 时该库 `SELECT NOW()` = `2026-08-07 01:03:32`。
|
||
> - **日期列**(`audit_date`、`trade_date`)—— 北京日期,不换算。
|
||
> - `docker logs -t` 自己加的那个时间戳前缀是 UTC,跟正文里 Python logging
|
||
> 打的时间不是一个口径,别混着读。
|
||
>
|
||
> 记录纪律:判收状态只写「真机见过」或「未判收」两种,不写「应该没问题」。
|
||
> 时间一律写北京时间,引用数据库时间列时先加八小时并标注已换算。
|
||
|
||
---
|
||
|
||
## 2026-08-27(续二)· 查清「池外的票被下单」:不是 bug,是分析池比下单候选浅,决定把分析池调成超集
|
||
|
||
**背景**
|
||
盘中发现 688599(天合光能)被下单,但它不在我们的 AKG_PLAN 股票池里,怀疑系统越界买了池外的票。
|
||
|
||
**查明的结论**
|
||
不是 bug,是两件事叠在一起。
|
||
|
||
第一件,两个「池」的深度本来就不一样,而且允许不一样。分析池 AKG_PLAN 是决策系统每晚做深度分析的范围,深度由 akg-factor-bridge 的 POOL_TOP 决定,原值 20。PMS 真正下单的候选不读这个 Mongo 池,而是直接调选股计划接口 plan_api,取主榜按分数前 PMS_PLAN_TOP_N 名,原值 30。688599 当天主榜第 25 名、强传导。25 比 30 浅,所以进了下单候选;又比 20 深,所以不在分析池,正好卡在中间。这个差在两处配置注释里都写明是有意的,PMS 的建仓引擎里甚至拿「候选池第 25 名」当例子。
|
||
|
||
第二件,那一笔是人工强下的。账本三条显示,9 点 43 自动闸判 REJECT,理由是主力净流出、主散背离、无盘中买入信号、证据不足;9 点 46 被「页面人工裁决」改成 PASS;9 点 47 成交 1600 股。查上游买入计划表 trading_buy_plan 与决策账本 decision_ledger,对 688599 当天都是 0 行,证明它没走上游建仓仲裁那条路,全程在 PMS 侧。
|
||
|
||
**用户拍板的改动(待在服务器执行)**
|
||
把分析池做成下单候选的超集,让能买的票一定先被夜间分析,也便于观察。要长期守的不变式:POOL_TOP 不低于 PMS_PLAN_TOP_N。本次 POOL_TOP 由 20 提到 50,POOL_MAX 由 60 提到 100,两个都在 akg-factor-bridge 的 .env。执行方式:改 .env 两行,然后 docker compose up -d --force-recreate akg-factor-bridge(restart 不重读 env),再 docker exec akg_factor_bridge python run.py push-pool,先 --dry-run 后真写。成本:夜扫每只约两三分钟,POOL_MAX 决定总时长,100 只约四小时,嫌久就调小 POOL_MAX。设计细节写进了 akg-factor-bridge/docs/选股计划入池_对接说明.md 第 7 节。
|
||
|
||
**动了哪些文件**
|
||
akg-factor-bridge/docs/选股计划入池_对接说明.md 加了第 7 节,讲池深与 PMS 下单候选的关系。POOL_TOP 与 POOL_MAX 的实际改动在服务器 .env,待用户执行。
|
||
|
||
**还欠着**
|
||
一,上面两个 env 改动要在桥机 factorevaluation 执行并判收,push-pool 之后 688599 应当进池。
|
||
二,TECH_POOL 怎么喂进下单候选还没查,用户说后面再做。
|
||
三,早前做的两个只读回测脚本 scripts/backtest_churn.py 与 scripts/backtest_entry.py 已在库里。结论是:决策系统的快卖多为正确止损,卖出端不加最小持有期护栏;但入场时点系统性偏晚,全体买入五个交易日后平均还在亏;账本目前只有一个月、一段行情,入场过滤先不上,等数据攒够再验。
|
||
四,bionic 云端通路三处改动(config/settings.py、app/services/llm_client.py、workers/tasks_intraday.py)仍待用户在自己终端提交并上 192.168.16.188 部署。
|
||
|
||
## 2026-08-27 · PMS 加登录与两个角色(系统管理员 / 交易员),认证接第三方 bshop
|
||
|
||
**背景**
|
||
管理页面之前没有任何登录,谁开着 38100 都能看全部持仓、下命令、改参数、跑运维。这次加一层最小限度权限:先登录才能进,进来分两种人——交易员管日常交易,系统管理员在此之上再管系统维护(回放 / 对账 / 日终 / 日报 / ws 通道 / 行业导入这些运维操作)。登录不自己造账号,接第三方登录系统 bshop.bmbs.tech(手机号 + 密码,POST /authProxy/api/auth/login,multipart)。关键在于它登录成功的返回里直接带这个人的权限清单 permissions(每条 system_key:menu_key)和组织 orgs,所以角色能从登录返回里直接读出来,PMS 不必碰 stock_vix 的库,两个系统解耦。用户拍板两条:一,角色的边界按前端两块视图划分——交易员工作台上能点到的都归交易员,只有运维视图里的维护操作归系统管理员;二,一个人可以同时持有两个角色,两个权限都占就两个角色都显示、都生效,不折叠成一个。
|
||
|
||
**做了什么**
|
||
一,认证与角色。新模块 app/web/auth.py 全是纯函数(不连库,只有 call_bshop_login 一处碰网络,便于离线单测):parse_bshop_login 解析 bshop 返回(判 code==200,抽手机号 / 用户名 / 权限 / 组织);roles_from_permissions 按权限**集合**判角色——占管理员标记就有系统管理员,未配交易员准入(留空 = 人人是交易员)或占了交易员权限就有交易员,两者都不占则返回空列表 = 拒绝进入;is_admin 供各处统一判定。
|
||
二,会话。PMS 自签会话票(base64 装 {phone, username, roles, exp} 再接 HMAC-SHA256,只用标准库不引新依赖),放 HttpOnly cookie,服务端不存会话。密钥 PMS_SESSION_SECRET 只从 .env 注入;开着登录却为空时服务一律拒绝放行(失败即关门,绝不用空密钥签票)。
|
||
三,拦截。app/web/main.py 加一个中间件加三个接口(POST /api/auth/login、POST /api/auth/logout、GET /api/me)。判定用「管理员黑名单」:/api/ops/* 全族、/api/ws-channel/*、/api/industry/* 三个前缀,外加一条精确的 POST /api/proposals(人工补录提议的造数调试口),命中要系统管理员,其余已登录的人都放行——交易员的能力跟着他那一页走。中间件里只 return JSONResponse,不 raise(在这里抛异常会变成 500 而非预期的 401 / 403)。
|
||
四,前端。app/web/static/index.html:未登录弹全屏登录框;顶栏显示登录人和他持有的**全部**角色,带退出按钮;非管理员锁在交易员视图、顶栏「运维」切换按钮对他不显示,运维那一整块因此够不到;页面所有请求的统一出口函数里分开处理 401(弹登录框)与 403(提示无权限),业务用的 ok() 失败回 200 与这套真状态码互不影响。
|
||
五,顺带翻出并修掉一个隐藏 bug:早前几处「系统管理员」被多写了两字前缀写成了别的词,其中前端判管理员那行拿这个写错的角色名去比后端发的「系统管理员」永远比不中——即按旧码任何人登录都不会被认成管理员、运维入口谁都看不到。这类只错在字符串值上、语法完全正常,靠语法检查发现不了,这次全仓库统一修正并加脚本复查。
|
||
|
||
**动了哪些文件**
|
||
app/web/auth.py(新增:认证 / 角色集合 / 会话签验 / 接口鉴权,纯函数);app/web/main.py(登录中间件加三个鉴权接口,登录与 /api/me 改读写 roles 列表,中间件按 is_admin 判定);config/settings.py(新增 PMS_AUTH_ENABLED / SESSION_SECRET / SESSION_TTL_HOURS / SESSION_COOKIE / BSHOP_LOGIN_URL / BSHOP_TIMEOUT / ADMIN_PERMS / TRADER_PERMS 八个显式字段);app/web/static/index.html(登录框 + 顶栏多角色显示与退出 + 锁运维视图 + 401/403 处理);.env.example(加 PMS_SESSION_SECRET 密钥段与两个角色标记 PMS_ADMIN_PERMS / PMS_TRADER_PERMS);scripts/test_batch18_units.py(新增 16 例:多角色判定含同占两角色、会话签验、bshop 解析、接口鉴权维护类归管理员、公开白名单);scripts/run_tests.py(注册 batch18)。
|
||
|
||
**部署方式**
|
||
桥机 factorevaluation 上 make deploy(源码打进镜像),make test 见 ALL SUITES PASS。上线前 .env 必须加一行 PMS_SESSION_SECRET(用 python -c "import secrets; print(secrets.token_urlsafe(48))" 生成);角色标记 PMS_ADMIN_PERMS(默认 pms:admin)与 PMS_TRADER_PERMS(留空 = 任何登录人都是交易员)可按需在 .env 指到测试账号真有的权限,方便联测。新增 env 记住要 force-recreate 才重读。回滚一行:.env 设 PMS_AUTH_ENABLED=false 重启,页面即回到无登录旧行为。另需在 stock_vix 的「用户与权限」页加一条管理员标记权限(建议 system_key=pms、menu_key=admin)并绑给该当管理员的账号。
|
||
|
||
**真机判收**
|
||
未判收。本地纯逻辑单测 16 例 ALL PASS(角色集合、会话签验、bshop 解析、接口鉴权)。真正连 bshop 的那一跳,桥机与开发机都被各自出网白名单拦掉,只能在能连通 bshop 的部署机联调:先 curl bshop 登录看回 code==200 加 access_token;再走 PMS 自己的 /api/auth/login 拿 cookie——未登录访问受保护接口应 401、带 cookie 应 200、交易员点 /api/ops/* 应 403、同时持两权限的账号 /api/me 回 roles 两个。
|
||
|
||
**还欠着什么**
|
||
桥机联调真连 bshop 的登录闭环(上面几条 curl)。另:交易员操作日志 op_log 的 by 目前仍写死「user」,没接登录人;要审计到人的话,后续把中间件里的 request.state.user 接进 _oplog。
|
||
|
||
## 2026-08-25(续)· 策略自动挂载:个股打法状态机(吸筹挂网格 / 高热挂止盈 / 转派发暂停买入 / 接力切换)
|
||
|
||
**背景**
|
||
用户要把个股策略从「手工挂」推进到「默认自动用」:有吸筹标志就开始网格建仓,热度很高就挂跟踪止盈。方案文件 STRATEGY_AUTO_ATTACH_PLAN.md(V2 定稿)已入库,五处拍板均已当面定下:只认「明确吸筹」档触发;建仓资格不放宽(吸筹只影响候选排序,不给入场资格);不设观察期直接全自动;实现骨架取「状态机含接力切换」(网格票站上区间上界且热度达标时自动换挂止盈);判分闭环进 V1。上线前在桥机跑过一轮只读探测(scripts/probe_strategy_signals.py):最新结论日吸筹定性 420 只(明确 164),热度全市场 5195 只、不低于 0.80 的共 194 只(约前 4%,阈值 0.80 因此保留),候选池 31 只中 17 只带明确吸筹(拍板②成立),两信号源日龄 1 天。探测同时暴露一个问题:62 只「词表外」定性,根因是首版归类用前缀匹配,而 state 常带前后缀修饰——数据底座 feed.py 用的是子串包含,本次全部改为与它同法(子串 + 派发优先的保守次序)。
|
||
|
||
**做了什么**
|
||
一,新模块 app/services/strategy_advisor.py:每交易日 09:40 对每只持仓票判阶段、走四条边——明确吸筹且新鲜挂网格(区间锚支撑压力、锚不住退百分比带,锚不出合法区间就放弃不硬凑);热度超阈值且安全垫为正挂跟踪止盈(双命中也取止盈,负垫永不挂止盈,把位置留给补仓评估);定性转派发或标志失效只暂停网格的买入(来源标 accum,回到明确吸筹自动解除,卖出照常);自动网格站上区间上界且热度达标撤网格换挂止盈(接力),高水位从当日现价起算。挂载与切换只做配置动作,真正买卖仍由 strategy_runner 逐笔过规则闸与全部熔断。自动策略的身份全靠 note 约定承载(「自动挂载: 」「自动挂载(接力): 」「[接力撤下]」),每日新挂上限、人工撤下冷却(同票同规则 10 个交易日)、接力冷却(同票网格边 10 个交易日)全部从策略表推导,不加新表不加状态参数。排除项:人工策略不碰、冻结与黑名单不挂网格、有在途不挂、热度表停更时热度边整轮不动、词表外定性按无标志处理并整轮报样本。
|
||
二,strategy_service 补 clear_buypause(按来源解除买入暂停,advisor 只清自己 accum 停的,不放开风控停的);scheduler 新任务 pms.strategy_attach(09:40,在宏观扫描 09:35 之后,宏观要降仓时在途票自动让路);web 补试算端点 POST /api/ops/strategy-attach-scan?dry_run=true(只判不写,供判收与日常复核)。
|
||
三,13 个 PMS_AUTO_* 参数进 settings 与参数页(总开关、边清单、日上限 2、吸筹日龄 3、网格带宽/档距/投入比例、热度阈值 0.80、止盈回撤/卖出比例、接力开关与两种冷却),全带中文说明与范围校验,边清单在写入口拦未知边名;总开关进 param_store.FAIL_CLOSED——参数表读不到时按关处理,故障期间宁可不挂。
|
||
四,探测脚本升 v1.1(归类改子串包含并打印词表外原文样本;对读注释改口径说明:探测是单日分布、前端清单是 30 天窗口,比结构不比绝对数)。
|
||
|
||
**动了哪些文件**
|
||
app/services/strategy_advisor.py(新增);app/services/strategy_service.py(clear_buypause);app/services/param_store.py(DESC 13 条 / _RANGES 10 条 / FAIL_CLOSED 加总开关 / 边清单校验);config/settings.py(PMS_AUTO_* 参数块);app/scheduler.py(strategy_attach 任务 + beat 09:40 + 调度总表注释);app/web/main.py(strategy-attach-scan 端点);scripts/probe_strategy_signals.py(v1.1);scripts/test_batch17_units.py(新增 34 例:定性归类子串与保守优先级、网格参数生成与三条放弃路径、连边矩阵含双命中与负垫、note 字面量钉死、日上限接力不占、两种冷却推导、接力判定、clear_buypause 来源匹配、编排冒烟 dry_run 滴水不写/名额/边三/边四全链/三道总闸);scripts/test_wiring.py(beat 哨兵集合加 strategy_attach,路由清单加试算端点);scripts/run_tests.py(注册 batch17,总数 519 → 553)。
|
||
|
||
**部署方式**
|
||
桥机 factorevaluation 上收盘后 make deploy(源码打进镜像),make test 见 ALL SUITES PASS。不动 .env,不需要迁移。上线即全自动(拍板③),随时可在参数页把 PMS_AUTO_STRATEGY_ENABLED 或单独把接力 PMS_AUTO_HANDOFF_ENABLED 关掉,即时生效。
|
||
|
||
**真机判收**
|
||
部分判收(2026-08-25 盘中)。桥机 make deploy + make test 见 ALL SUITES PASS;dry-run 试算真机四票读数全部合理:688802.SH 想挂网格但「按投入比例折出 47,250 元买不起一手」被挡(科创板高价票,判定正确);002179.SZ 有在途指令主动让路;其余两票无信号静默跳过;unknown_states 为空(子串归类修对了)。**尚未判收的**:真挂一条(等在途清了重扫或次日 09:40)、边三/边四真机走一遍、strategy_runner 对自动网格的逐档发单。
|
||
|
||
**次日午间第七批(2026-08-26):布局重设计——撤掉双栏,改「持仓全宽 + 信号速览带 + 信号中心收起」**
|
||
第六批的左右双栏上真机即被否:持仓表被压窄到操作列整个被切掉;信号卡片本是全宽设计,塞进右栏后股票名逐字竖排;右栏比左栏长数倍,「同屏盯两块」根本没实现。重新分析:交易员要随时看的不是整个信号中心(几百条,翻查用),而是与决策相关的信号——尤其持仓票的信号。重做为融合式三层:一,持仓表恢复全宽(操作列回来),并新增「今日信号」列——每只持仓票显示今天与它相关的最新信号(类别、方向、时刻,多条显示 +N,悬停看前三条),信号出现在交易员本来就在看的那一行上;二,持仓表下方新增全宽「信号速览」带——持仓票的信号置顶标「持仓」,其余只收重要与警告级,决策系统买卖、风控卖出、择时入场三路独立信号一并汇入,一行一条按时间倒序,高度受控内部滚动,右上角一键展开完整信号中心;三,完整信号中心整块移到「今日在办」之后、默认收起,卡片恢复全宽可读。区块顺序:持仓 → 信号速览 → 策略 → 今日在办 → 信号中心。全量 560 例通过。
|
||
|
||
**次日上午第六批(2026-08-26):前端文字整改与双栏布局(用户三条标准)**
|
||
用户定下三条标准并全页执行:交易员可读且含义明确;开发人员口吻的文字彻底清除;系统文字最大限度精简,提示能删则删、必须留的进悬浮问号不占版面。整改清单:删掉「随便点」「只是看一眼」这类不严肃表述,预演说明并入按钮旁的悬浮提示;预演结果头行压成「预演结果(未执行)· 查 N 只持仓」;执行状态行改为「今日自动挂载已于 09:41 执行:查 4 · 挂载 0 · 被挡 1 · 跳过 1」句式;颜色约定整条彩字图例压成一个悬浮问号;告警级别筛选的「只前端过滤、不查后端」删除改悬浮;上游信号来源行去掉内部代号(mtf);操作日志说明收紧为一句;空策略引导语改严肃措辞;后端返回里会显示到页面的英文类型码全部换成中文名(网格、跟踪止盈、做T)。布局:交易员视图改双栏栅格(左七右五)——左栏持仓加策略,右栏上游信号常驻展开,两边可同屏实时盯;窗口窄于 1500 像素自动叠回单栏,其余区块次序不变。全量 560 例通过。
|
||
|
||
**次日上午第五批(2026-08-26 盘中):把「预演」与「正式跑」在页面上分清**
|
||
用户反馈「试算只判断」的提示看不懂到底有用没用——根子是页面只有预演镜,没人回答「今天的正式一跳跑没跑、做了什么」,而零动作的一跳在台账上没有任何痕迹(设计如此,台账只记真动作)。补法:advisor 每次真跑(含总开关关着的那次)在每个出口写一份摘要进运行参数(新登记 PMS_AUTO_LAST_SCAN,写失败只告警;set_param 返回值按纪律接住),策略接口把总开关状态、今天跑没跑、各类计数一并返回;页面「我的策略」常驻一行仪表:「今天的正式一跳已在 09:41 跑过:查 4 只,挂载 0、被挡 1、跳过 1」或「还没跑(09:40 自动执行)」,按钮改名「预演 · 现在系统想做什么」,预演结果头一句明说「什么都没动」。批十七加一例钉住「真跑每个出口都写、预演一笔不写」,总数 560。
|
||
|
||
**次日上午第四批(2026-08-26 盘中):每票阶段可视化**
|
||
用户拍板把状态机补成看得见的:扫描结果新增 stages,对**每只**持仓票各出一行——处在五阶段(吸筹震荡、启动拉升、高位派发、深亏修复、中性)的哪一段、一句话理由、热度、浮盈、定性原文;已挂自动策略的票阶段以策略为准(网格即吸筹震荡进行中、止盈即启动拉升保护中),人工策略票按信号判并注明「自动挂载不碰」;信号判定次序与连边一致,派发优先于深亏(保守方向)。页面按钮的结果顶部渲染五阶段视图,不动的票也说清为什么不动。判定函数 stage_of 纯逻辑可测,批十七扩到 40 例,总数 559。同日运维记录:桥的计划触发已在北京 07:10 跑通(计划日 T-1,判收通过);deploy 恰好压掉 09:40 调度位,用试算端点的正式模式手动补跑(补跑与调度位同一份实现);清掉两个三周前 compose run 未带 --rm 的残留容器。
|
||
|
||
**当日盘中第三批(赶进度拍板后加的两件)**
|
||
一,科创板取整从「挂载侧兜」补到「执行侧真修」:strategy_runner 新增 _min_lot(688/689 按 200 股),网格手工挂的 100 股档在评估时抬成合法数量、卖出量不足 200 股原地等不推进档位;跟踪止盈的部分卖低于 200 股时量够就抬到 200(方向是保利润,偏保守)、可卖本身不足 200 时部分卖放弃,全清路径按交易所例外允许不足 200 股一次性全卖。主板行为一字未变,单测把两侧都钉住(批十七扩到 38 例,总数 557)。做T是命令授权的手工策略,科创板做T的开仓与平回数量怎么处理需要单独拍板,本次不动。
|
||
二,管理页「我的策略」加了「自动挂载 · 看看今天想挂什么」按钮:调 09:40 调度位同一份实现的试算模式,把想挂、想换挂、想停买入、被挡、跳过的每一条连原因渲染成可读文字,不再需要敲命令;空结果明说「没有想动的是正常克制」。
|
||
|
||
**当日盘中补两处(第二次交付)**
|
||
一,科创板一手口径:dry-run 里 688802.SH 暴露全库按 100 股一手,而 688/689 最小申报 200 股——自动网格若生成 per_lot=100 会被券商拒单。advisor 补 lot_of()(688/689→200),买得起一手与 per_lot 下限都按它算(200 仍是 100 的整数倍,runner 的取整不会磨掉)。runner 侧卖出数量对科创板的整百取整仍不完美,见欠账。二,判分读数脚本 scripts/report_strategy_score.py 交付:四段只读统计(样本盘点 / 网格差价按策略内均价配对 / 止盈「卖点之后又跌多少=保住的钱」/ 挂上 vs 名额挡下的对照组涨跌),样本不足三条只报数不下结论,连不上库或取不到现价都说人话。batch17 扩到 36 例,总数 555。
|
||
|
||
**还欠着什么**
|
||
一,判分脚本已交,但**有意义的读数要等两周样本**——现在跑是空跑冒烟,别拿首周数字调阈值(热度 0.80 / 日上限 2 等数据回调)。二,探测顺带发现候选池计划日停在 2026-08-21(探测日 08-25,日龄已 2 个交易日以上),疑似上游桥的日更链又停了,且 PMS_PLAN_STALE_TDAYS 可能被调宽过——与本特性无关,单独排查。三,科创板的卖出数量取整:strategy_runner 的 _round_lot 全库按 100 股取整,688 开头的票止盈卖出若折出 100 股会被拒单(科创板低于 200 股只能一次性全卖)——影响面小(当前仅一只 688 且它连网格都挂不上),排查后单独修。
|
||
|
||
---
|
||
|
||
## 2026-08-25 · 软归档:四类记录加 archived_at,已完成默认从在办视图移除(补记)
|
||
|
||
**做了什么**
|
||
命令 / 策略 / 指令 / 提议四张表加 archived_at 列做软归档:只归**终态**记录(各表终态集在 pms_repo._ARCHIVE_TERMINAL 一处钉死,前端「可移除」判据用同一份),归档只写时间戳不删行,列表默认带 archived_at IS NULL、页面勾「显示已完成」传 include_archived=true 拉回来。repo 层 archive_*/unarchive_* 均为单表 UPDATE、过单表守卫;web 层四类各一对归档/取消归档端点。第十六批单测 5 例钉死:只归终态、默认排除、单表守卫、终态集与设计一致。
|
||
|
||
**动了哪些文件**
|
||
app/repo/pms_repo.py(archive_*/unarchive_*/list_* 的 include_archived);app/web/main.py(归档端点与列表参数);app/web/static/index.html(显示已完成开关与可移除判据);scripts/ddl_pms_v1.sql(建表带列);scripts/migrate_archived_at.py(新增:给**已有库**补列,幂等,先演练后 --yes);scripts/test_batch16_units.py(5 例);scripts/run_tests.py(batch16 注册,总数 519)。
|
||
|
||
**部署方式**
|
||
桥机 make deploy 之外**必须先跑一次迁移**:`docker compose run --rm pms-web python scripts/migrate_archived_at.py`(演练)确认四条 ALTER,再加 `--yes` 执行。init_db 只认 CREATE TABLE IF NOT EXISTS,不会给旧表加列——不跑迁移,新代码的列表 SQL 一执行就报未知列。新库不需要(建表语句已带列)。
|
||
|
||
**真机判收**
|
||
未判收。判收建议:迁移脚本两跑(演练/实做)各表 added 或 skip 清楚;页面把一条 DONE 命令归档后从在办列表消失,勾「显示已完成」回来且标已归档;ALL SUITES PASS 含 batch16。
|
||
|
||
---
|
||
|
||
## 2026-08-20(续)· 清仓与减仓对策略的优先级理顺:减仓不掐网格、清仓补撤在途买单、在途文案分开
|
||
|
||
**背景**
|
||
上一节紧急清仓修复留了三笔尾账,这次结掉前两笔。同时按用户要求排查一件更要紧的事:有没有哪里会让网格建仓、做T这类个股策略被误挡失效。用户给了判定尺子——清仓是优先级最高的,可以覆盖一切;减仓则要看情况,默认不该动策略。全部改动都在持仓系统的执行层与命令层,其他系统不动。
|
||
|
||
**做了什么(三处,都带单测)**
|
||
一,减仓命令不再撤掉策略腿的在途买单。原来整体降仓命令会把所有在途买入指令一律撤掉,其中就包括网格和做T挂出的买单,等于一执行减仓、网格就被掐断。现在减仓只撤非策略的在途买入(自主、提议、命令三种照撤,因为减仓时确实不该再往里投新钱),策略腿留着继续跑。清仓类命令仍然全撤、一股不留,因为清仓优先级最高。补一个理由:减仓命令只物化一次,就算撤了策略的在途买单,策略下一分钟又照发一张,撤了等于没撤,只是白撤一笔、还在页面上报“撤了几条策略买单”误导人。
|
||
|
||
二,清仓某股和清仓某行业补上撤在途买单这一层。上一节给三类清仓命令都加了即时撤策略,但只有一键清仓的方案生成器会顺带把在途买单排进撤单项。清仓某股和清仓某行业的方案生成器只排一条卖出,不排撤买单。结果是撤策略只改了策略状态,已经下发到券商的那张网格或做T买单还挂着继续成交——你正清仓这只票,它却又买进来一笔当天卖不掉的货。现在即时停买入侧这一步里,对被清仓的票直接撤掉当前在途买入指令。一键清仓时这些单多半已被撤过、不在在途集里,补这道只是空跑,幂等无害。
|
||
|
||
三,把“已全部在途、等成交”和“当日配额已出完”这两句话分开。上一节修好配额口径之后,紧急清仓和窗口末日单在“该出的量都挂在券商正等成交”这种情形下,仍复用“当日配额已出完”这句话,行为对但措辞会让人误以为又犯了老毛病。现在紧急单和末日单在可再投放为零时,改报“已全部在途,等成交回来”;普通多日单才报“当日配额已出完”。两条择时路径(内置择时的硬闸、策略即时决策)各改一处,用同一个函数出这句话,口径不串。
|
||
|
||
**排查结论:网格建仓与做T有没有被误挡**
|
||
通读了策略层和规则闸,结论是没有系统性失效,但有两处值得拍板的地方,这次先不擅自改,列出来等你定。
|
||
先说没问题的三点。昨夜决策系统看空某只票,不会挡住这只票的网格买入,因为策略即时那条路本就不带看空定性,网格照常逐档买。宏观偏热闸和买入暂停都只挡买入开腿,卖出、平回、跟踪止盈一律照常,符合设计。做T的平回腿在熔断和买入暂停下都放行,不会把一条开着的T仓卡住。
|
||
待定第一处:组合刹车(熔断)触发时,网格的买入腿会被规则闸挡下。原因是网格买单在系统里标为“非命令”,规则闸对非命令的自主增持在刹车期一律拦,对命令只提示不拦。可网格恰恰是逢跌买入,跌得狠正是刹车最容易触发的时候,于是刹车一响、网格买入全停。做T不受这条影响,因为做T标为命令、刹车只对它提示。问题是网格算“自主”还是算“你特意下的命令”。若你认为网格也该像做T那样在刹车期只提示不拦,我再改;这是安全阀的行为改动,不敢擅动。
|
||
待定第二处:全局暂停买入(你手动踩的买入总闸)打开时,做T的买回腿也会被规则闸挡下。反T是先卖后买、要靠买回来平掉,买回被挡就意味着这一轮T只卖不买、把底仓卖出去没接回来,连14:50强制平回也会被同一道闸挡住。这多半是你的本意——既然全局停买,就连做T买回也别买;但副作用是反T会留下没接回的敞口。要不要给做T的平回腿在全局暂停买入下开个口子,请你定。
|
||
|
||
**拍板(2026-08-20)**:上面两处都保持现状不改。刹车(熔断)照挡网格买入腿——刹车是组合级熔断,网格逢跌买入正撞在最该拦的时候,先不放开更稳。全局暂停买入照挡做T买回腿——买入总闸的语义要一致,代价是反T会留一小截没接回的敞口,属已知取舍。两处都无需改码,当前行为即最终行为。
|
||
|
||
**动了哪些文件**
|
||
app/core/exec_timing.py(新增 quota_wait_reason 出“在途/配额已出完”两句话,硬闸 left<=0 分支改用它);app/services/exec_advisor.py(策略即时决策同样按在途口径出话,签名加 is_last_day 与 urgent 透传);app/services/command_service.py(减仓走 _pending_buys(exclude_strategy=True) 剔策略腿;_stop_buyside_for_exit 补撤在途买单一层,进度回执多一项 instructions);scripts/test_batch15_units.py(原 7 例扩到 12 例:新增文案拆分四例、减仓剔策略腿一例,原撤策略例加断言撤在途买单);scripts/run_tests.py(batch15 计数改 12,总数 510)。
|
||
|
||
**部署方式**
|
||
桥机 factorevaluation 上 make deploy(源码打进镜像,必须重建容器),make test 见 ALL SUITES PASS(已含改后 batch15)。收盘后部署,让下一次清仓或减仓发生在你在场时。不新增参数,不动 .env。
|
||
|
||
**真机判收**
|
||
未判收。开发容器全量单测 ALL SUITES PASS(510 例,batch15 由 7 例扩到 12 例)。判收建议:下达减仓命令后,挂着策略的票在命令进度回执 stopped_buyside 里不再报撤策略买单,网格继续按档买卖;对某只挂策略的票下清仓某股,命令进度回执 stopped_buyside.instructions 里能看到那张在途买单被撤;盯盘时“已全部在途、等成交回来”与“当日配额已出完”两句话分得开。
|
||
|
||
**还欠着什么**
|
||
一,上面两处待定(刹车挡网格买、全局暂停买入挡做T买回)已于当日拍板:都保持现状不改,无需改码,详见上面“拍板”一段。二,减仓是否要更进一步、连策略本身也暂停(而不只是不撤在途买单),本次按“默认不动策略”处理;若你想要“减仓也暂停这些票的策略买入开腿”这种中间档,说一声再加。三,上一节欠的第三笔——持仓系统侧可卖量与券商侧可用持仓偶有不一致(出口委托里有 INSUFFICIENT_POSITION 拒单),仍未动,属对账口径,单独排查。
|
||
|
||
---
|
||
|
||
## 2026-08-20 · 紧急清仓修复:配额不重复扣今日成交 / 清仓命令即时撤策略
|
||
|
||
**背景**
|
||
2026-08-19 盘中下一键清仓,部分票卖完,剩三支拖到次日早盘(08-20 开盘)才清。用户观察:这三支既没跌停也在盘中,按紧急清仓逻辑本应当天直接卖掉。据 make watch 与 make t-ins/t-gate 的实机数据排查,定位两条根因,本次一并修,都在执行与命令这一层,其他系统不动。
|
||
|
||
**根因甲,当日配额把今日已成交量重复扣了一次。**
|
||
当日配额(今天最多投放多少股)是拿“剩余未成交量”算的,剩余量等于委托量减累计成交量,已经扣过成交一次。可是判断“今天还能不能再投放”时,用的是“配额减今日已投放量”,而“今日已投放量”里既算今天还挂着没成交的委托、又算今天已经成交的委托。于是今天成交的那部分被扣了两次。当天成交越多,“可再投放”越快变成负数,指令发一两笔、成交回来之后就误判“当日配额已出完”,当天不再补单。更糟的是这道判断排在紧急直通和 14:45 强制兜底前面,连兜底都被挡住,要等次日配额清零才继续。实机印证:300953 昨天两笔卖了 200,剩 100 停在“配额已出完”,今早才卖;002128 今早还挂着 200 同样卡在这里。
|
||
|
||
**根因乙,清仓命令没有立刻停掉相关票的买入侧。**
|
||
002128 挂着网格策略,而“清仓完成清场”撤策略要等持仓归零才触发。于是那天网格整个上午都在逐档买入、清仓命令同时在卖,买进来的又是 T+1 当天卖不掉,等于清仓命令在追一个自己还在被买进的持仓。账本里那条撤策略的 CLEANUP 记录时间是今早 09:34,证明策略今天才被撤下。这违背设计铁律“命令至上”——命令一下达,冲突的自主动作就该让位。
|
||
|
||
**注**:三支里也有纯 T+1 的(如 300738 昨天上午 09:37 刚建仓 900 股,当天不可卖),这部分是市场规则,紧急清仓也绕不过,只能次日,非缺陷。甲、乙修的是本可当天卖却被卡住的那部分。
|
||
|
||
**做了什么(两处修复,都带单测)**
|
||
甲,app/services/executor.py:run_tick 里当日配额的“今日已投放量”口径分两种。紧急单或窗口末日单改用新加的 _inflight_today(只算今日在途、已终态一律不算),这样“可再投放 = 剩余减在途”,永不重复扣,紧急清仓当天会持续补单直到卖完或撞上 T+1 可卖为零;普通多日单仍用 _consumed_today(今日成交加在途)做节流,行为不变。超卖与重复下单由既有的“可卖量”闸与券商侧另兜一道,本改动不放大风险。exec_timing.py 未改,配额闸的行为靠上面这个口径修正自然纠正。
|
||
乙,app/services/command_service.py:新增 _stop_buyside_for_exit,在 LIQUIDATE_ALL、EXIT_STOCK、SECTOR_EXIT 三类全额清仓命令下达时立刻撤掉相关票的活跃策略、驳回待确认的买入提议;在途买单本就由 planner 的撤单项处理,三层合起来做到“命令一下、这只票不再有任何新买入”。撤策略只改策略状态、不动持仓,安全;幂等、失败不抛,单只失败记 errors 不拖垮命令本体。
|
||
|
||
**动了哪些文件**
|
||
app/services/executor.py(run_tick 配额口径分流 + 新增 _inflight_today);app/services/command_service.py(plan_command 加停买入侧调用 + 新增 _stop_buyside_for_exit + 进度回执 stopped_buyside);scripts/test_batch15_units.py(新增 7 例:在途口径、老新口径下紧急清仓 FIRE 与配额已出完的分野用真函数钉死根因、全在途正确等待、run_tick 分流源码级防回归、撤策略与驳回买入提议不误伤卖出与无关票、plan_command 走一遍 LIQUIDATE_ALL 确实撤策略);scripts/run_tests.py(登记 batch15,总数 505)。
|
||
|
||
**部署方式**
|
||
桥机 factorevaluation 上 make deploy(源码打进镜像,必须重建容器),make test 见 ALL SUITES PASS(已含 batch15)。收盘后部署,让下一次紧急清仓发生在你在场时。不新增参数,不动 .env。
|
||
|
||
**真机判收**
|
||
未判收。开发容器全量单测 ALL SUITES PASS(505 例,含新增 7 例)。桥机判收:部署后下一次一键清仓,预期同一只票在同一天内持续补单直到卖完或卖到 T+1 的墙,不再出现“卖两笔就停在配额已出完、要等次日”;且清仓命令一下达,挂着策略的票在命令进度回执 stopped_buyside 里能看到策略已撤、账本有 CLEANUP 行,网格不再边卖边买。
|
||
|
||
**还欠着什么**
|
||
一,本次只对全额清仓类命令即时撤策略;减仓类(REDUCE_EXPOSURE/REDUCE_STOCK)是否也要停对应票的加仓侧,未做,按需再议。二,配额“已全部在途、等成交”这一情形目前仍复用“当日配额已出完”这句话,行为正确但措辞会让人误以为是老毛病,下次顺手把这条 WAIT 的文案分开。三,PMS 侧 avail_qty 与券商侧可用持仓偶有不一致(出口委托里有 INSUFFICIENT_POSITION 拒单),本次未动,属对账口径,单独排查。
|
||
|
||
---
|
||
|
||
## 2026-08-18 · 宏观择时层上线:股汇对冲指数管整体仓位,外加个股宏观闸
|
||
|
||
**背景与依据**
|
||
方案与全部拍板记录在 MACRO_TIMING_PLAN.md(V3 可读版)。参数初值来自当天在桥机实跑的标定,报告存档在 MACRO_CALIB_2026-08-18.md。模拟仓拍板:默认总开关开、档位全自动、不设观察期;转实盘前要把这两个默认改回保守档。
|
||
|
||
**做了什么**
|
||
给持仓系统加了一个宏观择时层。全部改动都在 tradingSystem,其他四个系统零改动。行为分四句话说。每个交易日九点三十五分算一次股汇对冲指数。指数进偏冷区(低于负二十)当天,像用户一样在命令台下升仓命令。指数进偏热区(高于正二十五)当天,先落宏观闸:自主增持四类动作和策略的买入开腿暂停,卖出与平回照常。指数从极值回落穿出正十五时,下降仓命令,幅度按本轮峰值深度取对数映射(S0=0.049、k=1.6、单周期封顶两成、降仓地板是总仓位两成)。
|
||
|
||
护栏与铁律的落点。它动仓位的唯一出口是 command_service.issue,署名 macro。与用户在途命令冲突就放弃并留痕,绝不带强制标志。升仓让位于全局暂停买入和组合刹车。数据取不到就判不可用,不动作、不落闸。信号每日一行落新表 pms_macro_signal(第 18 张表)。闸状态放运行参数 PMS_MACRO_GATE_STATE,由扫描写入,提议层与策略层只读,读不到一律当不生效——闸的安全方向是不额外拦。三张数据源表全在 153 代理,是标定实测钉死的(zs_day_data、gp_fx_daily 的 USDCNH.FXCM、gp_shibor 的 rate_1m)。
|
||
|
||
**动了哪些文件**
|
||
新增五个:
|
||
- app/core/macro_rules.py —— 纯逻辑:四步计算与 as-of 对齐、区域迟滞、周期状态、对数映射决策
|
||
- app/repo/macro_repo.py —— 三张源表单表取数 + pms_macro_signal 读写
|
||
- app/services/macro_service.py —— 编排、信号注册表、闸状态、建议采纳
|
||
- scripts/test_batch14_units.py —— 25 例(计算对照手算/迟滞/周期映射/让路冷却/闸/采纳幂等)
|
||
- (scripts/calibrate_macro_signal.py 此前已交付并实跑,保留做月度复盘)
|
||
|
||
修改十二处,全部只加行、不改既有行为:
|
||
- config/settings.py —— PMS_MACRO_* 参数一段(含模拟仓默认与转实盘提醒)
|
||
- app/services/param_store.py —— 说明/范围/合法值校验;GATE_STATE 运行键;PMS_MACRO_ENABLED 进 FAIL_CLOSED(文件初值是开,参数表读不到时按关处理)
|
||
- app/scheduler.py —— macro_scan 调度位(交易日 09:35,带守卫)
|
||
- app/services/proposal_service.py —— 两处扫描入口加宏观闸检查(拦买入侧、保垫减仓照常)
|
||
- app/services/strategy_runner.py —— 买开腿判定点加闸条件(与 buypause 同点,平回天然不拦)
|
||
- app/web/main.py —— 三个接口:/api/macro/status、/api/ops/macro-scan、/api/macro/adopt
|
||
- app/web/static/index.html —— 宏观择时面板 + 偏热闸与数据不可用两条横幅
|
||
- ddl_pms_v1.sql —— 第 18 张表 pms_macro_signal
|
||
- scripts/check_db.py —— 表清单加 pms_macro_signal(顺带记录:pms_strategy 与 pms_op_log 本就不在清单里,属既有欠账,按不夹带纪律未补,单独提)
|
||
- scripts/run_tests.py —— 登记 batch14,总数改为 498 例
|
||
- scripts/test_batch6_units.py —— DDL 表数哨兵 17 改 18(哨兵注释写明本次加表)
|
||
- scripts/test_wiring.py —— 调度表哨兵集合加 macro_scan
|
||
|
||
**部署方式**
|
||
桥机 factorevaluation 上 make deploy(源码打进镜像,必须重建容器),跑 make initdb 建新表(幂等),make test 要见 ALL SUITES PASS。**务必收盘后部署**:默认就是全自动档,部署完成后的下一个交易日 09:35 就是第一次真实扫描。参数全是新键,不存在页面旧值覆盖的问题。
|
||
|
||
**真机判收**
|
||
未判收。开发容器里已过两道:全量单测 ALL SUITES PASS(498 例,含新增 25 例与两处哨兵更新);页面无头渲染判收八项全过、零页面错误(面板、指数值、区域标签、建议卡、采纳按钮、闸横幅、最近命令、二十根迷你条)。桥机判收按 MACRO_TIMING_PLAN.md 第六节走:make test → make check 见 18 表 → dry_run 试算并与标定脚本同日值对照 → 次日 09:35 看第一跳与面板。
|
||
|
||
**还欠着什么**
|
||
1. 桥机部署与第六节判收(含冲突演练那一步)。
|
||
2. 第一个极值触发日的表现(模拟仓,让它自然发生)。
|
||
3. 月度复盘:数据多一段就重跑标定脚本,校准阈值与 S0/k。
|
||
4. check_db 表清单缺 pms_strategy 与 pms_op_log 的既有欠账,单独提。
|
||
|
||
**同日修订(2026-08-19,桥机判收后两条)**
|
||
一,判收补记。桥机 make test 与建表全部通过。dry_run 试算返回 +14.7197、区域中性,与标定报告同日值一致,方案第六节的前四步完成。宿主机的 python3 版本旧,不认 json.tool 的 --no-ensure-ascii 参数,去掉该参数即可,不是系统问题。
|
||
二,前端改版(用户要求:不占大块,放页面第一行)。撤掉「宏观择时」大面板,改为首行一枚紧凑徽标,与市场、买入、执行、自主、计划等徽标并排。常态显示指数值与区域;有建议、闸生效或数据不可用时转警示色;有建议时徽标显示"有建议",点击即走采纳确认。全部详情移进鼠标悬浮提示:近二十日迷你走势、阈值与档位、闸状态、建议卡带采纳按钮、最近宏观命令;运维模式下悬浮里另有试算与立即扫描两个按钮。偏热闸与数据不可用两条横幅保留。无头渲染判收九项全过、零页面错误(徽标带值、有建议标记、大面板已删、悬浮里建议卡/采纳按钮/二十根迷你条/最近命令/闸说明、横幅仍在)。改动只有 index.html 一个文件,回滚还原它即可;改的是前端资源,生效仍需 make deploy 重建镜像。
|
||
|
||
---
|
||
|
||
## 2026-08-18 · 决策系统卖出采纳复核:降门槛+全留痕 / 确认即加速 / 清仓完成清场
|
||
|
||
**背景**
|
||
用户观测"系统似乎不采纳决策系统的卖出",复核整条链(bionic 生产端 → PMS 消费端)。结论:
|
||
接线、代码格式(都是 Tushare 点式,能对上持仓)、置信度量纲(风控 0~100 归一到 0~1)都没
|
||
问题;"不采纳"是门槛标定 + 挂策略路由所致。真机取数印证:bionic 对持仓票的卖出置信度几乎
|
||
全落在 60~84(002015 60/70/70、300474 65、300433/300354 72、688256 70…),而原门槛是
|
||
低于 75 忽略、75~84 落提议、≥85 才自动清仓——于是持仓票的卖出大多被静默忽略或排队等拍板。
|
||
|
||
**做了什么(三件,用户经 AskUserQuestion 拍板)**
|
||
一,降门槛 + 全留痕。`PMS_SIGNAL_SELL_CONF_MIN` 默认 0.75 → 0.60(页面可再调),60~84 一律
|
||
浮上来变提议、可见可一键采纳,≥85 仍自动清仓;并在 signal_service 对**持仓票**的"未采纳
|
||
卖出"(低于门槛的 IGNORE)也留痕(action=SIGNAL / verdict=NOTE,当日按来源+股票+动作去重
|
||
一条),不再让一只连日被喊卖的持仓票在账本里查不到。
|
||
二,确认即加速。决策系统卖出命中一只**已有在途清仓指令**的票时,不再简单跳过,而是把该股
|
||
在途的卖出**指令**升级为紧急直通(progress.urgent=True,下一跳 run_tick 按现价直卖、不等
|
||
均价不分桶),每条升级账本留痕。仅挂着提议(还没成指令)的不动。人工清仓 + 风控独立确认
|
||
= 最强离场信号,走最快的路。
|
||
三,清仓完成清场。持仓归零(status=CLOSED 且 qty≤0)后,自动撤下该股 策略(网格/做T/跟踪)、
|
||
在途建仓方案与买单、挂着的相关提议;每只闭仓票靠标记(PMS_EXIT_CLEANUP_DONE)**只清一次**,
|
||
避免把闭仓后新下的建仓命令误撤(重开的票掉出标记,下次闭仓再清)。挂在 command_poll 每分钟
|
||
一跳、幂等。解决"清仓离场后网格还挂着 ACTIVE、一旦持仓重建又逢跌买入"的隐患。
|
||
|
||
**动了哪些文件**
|
||
config/settings.py(门槛 0.60);app/services/signal_service.py(持仓票未采纳留痕 +
|
||
`_escalate_inflight_sells` 确认加速);app/services/command_service.py(`cleanup_exited_positions`
|
||
+ 纯逻辑 `_cleanup_todo` 标记);app/scheduler.py(command_poll 挂清场);
|
||
scripts/test_batch13_units.py(新增 4 例:门槛分档 / 清场标记 / 确认加速 / 闭仓清场,打桩 repo)
|
||
+ scripts/run_tests.py(登记 batch13)。
|
||
|
||
**部署与判收**
|
||
factorevaluation `make deploy`(动了后端与配置,要重建容器),`make test` 见 ALL SUITES PASS
|
||
(已含 batch13)。门槛降了默认值,若表里 pms_runtime_param 已覆盖过该键,需在页面把
|
||
PMS_SIGNAL_SELL_CONF_MIN 也改成 0.60 才生效。真机待看:① 持仓票被喊卖时账本出现"未采纳"
|
||
留痕或提议;② 人工清仓一只票后决策系统再喊卖,该清仓指令 progress.urgent 变 True、当即出手;
|
||
③ 清仓走完后该股策略变 CANCELLED、在途建仓/提议被撤、账本有 CLEANUP 行。
|
||
|
||
**还欠着什么**
|
||
当日去重仍是"先到先赢"(同股同动作一天只消化第一条够格的卖出)——先到的若落成提议、把去重键
|
||
烧掉,后到本可自动清仓的高置信卖出会被当重复丢;本轮未动它(避免过度扩面),若实盘发现高置信
|
||
卖出被前面的弱卖出挡住,再加"更强的卖出可升级已有提议为清仓"。自动清仓线 0.85 未动,按用户
|
||
选择保留,可后续按盘中体感调。
|
||
|
||
---
|
||
|
||
## 2026-08-18 · 上游信号面板改版:删下线框 + 按重要度分档 + 资金异动 z 可读化 + 待触发条
|
||
|
||
**做了什么**
|
||
重排管理页面「上游信号」面板(app/web/static/index.html 单页,纯前端,不动后端)。四件事。
|
||
一,删掉「放量异动」框:该源(volume_capitulation/breakout)6/15 已被资金分布取代,
|
||
生产端(intraday_timing orchestrator 方向源清单)不再发它,框永远只显示历史残留键。
|
||
二,卡片按重要度分三档、大小不一:行动级(决策买卖 c-wide、择时入场、风控卖出、多源升级)
|
||
有数据才出大卡;判读级(资金异动、QRS、资金分布、资金流强度)中卡两列;参考级(股价异动
|
||
小卡、实况分榜宽卡)。用 12 列网格 + c-wide/c-half/c-third 跨列实现。
|
||
三,资金异动「异动z」换成净额领读 + 强度带:净额折成万/亿带正负号打头,强度按 z_dd 绝对值
|
||
分档(显著 2–3 / 强 3–5 / 极强 ≥5)用小方块条 + 中文标签表达,原始 z 收进悬停提示。
|
||
四,空的「在用」信号收进一条「在用·今日待触发」横条(armed 待命,不再各占空框)——这是
|
||
本次核心:此前四个空框和一个死框摆一起,看不出「活着但安静」和「已下线」的区别,用户误以为
|
||
下线了。逐个追到生产端核实:只有放量异动真下线,多源升级/择时层BUY/风控卖出/资金流强度都是
|
||
在用但罕见或条件触发。「其他」桶只在非空时出现(空=所有源已登记=健康)。
|
||
|
||
**动了哪些文件**
|
||
app/web/static/index.html:新增 CSS(sig-tier/sig-grid/c-*/intens/ibar/armed 等);新增 JS
|
||
(moneyCn、zBand、zBars、dirUp、SIG 组装、armed 待触发、CARDS_B、tierHasA/B/C 计算属性,
|
||
并加入 setup return);替换整个 up-grid 模板为三档 + 待触发条 + 其他桶。旧的 alertGridFixed
|
||
等计算属性留着未删(无害,减少改动面)。
|
||
|
||
**部署方式**
|
||
factorevaluation 上 `make deploy`(前端资源打进镜像)。纯前端改动,不动后端与取数口径。
|
||
回滚只需还原 index.html 一个文件。
|
||
|
||
**真机判收**
|
||
开发机已做无头渲染判收(Playwright 加载真页面 + 造数):三档正确渲染、待触发条正确收纳空信号、
|
||
强度带与万/亿格式生效、放量异动消失、无任何 Vue 编译/运行告警;node 语法检查与括号平衡通过;
|
||
全页 Vue 模板编译无错。真机待看:盘中有真数据时各档卡片与截图一致,且级别筛选条仍联动。
|
||
|
||
**还欠着什么**
|
||
待触发条目前只标「今日待触发」,未显示「最近一次触发时间」(那需要后端跨日扫历史键,会给每次
|
||
刷新加延迟,暂不做;用户认可先不加)。强度分档三个阈值(2–3/3–5/≥5)可按盘中手感再调。
|
||
|
||
**同日修订(改版二,用户反馈:分档布局太乱、要固定成对)**
|
||
把"按重要度分三档 + 卡片宽度随内容变(c-wide/c-third)"改成**固定成对网格**,位置稳定不再随
|
||
数据重排,整体更对称干净。三对(用户指定)各占一行、两两并排等宽等高:① mtf 资金异动 |
|
||
mtf 实况分榜(这对通常列表长,单独拉高到 min-height 340,两边等高并排);② 决策系统买卖 |
|
||
日QRS对称;③ 资金分布 | 股价异动。这六个常用框**常驻**(空了框内显示"暂无",不再消失/重排)。
|
||
四个稀有或条件触发的信号(风控卖出、多源升级、择时层BUY、资金流强度)归入下方「事件信号」区:
|
||
触发了才出卡,没触发的收进一条「在用·今日待触发」;「其他/未登记源」桶也并进这条、非空才显示。
|
||
去掉了三档标题与 c-wide/c-half/c-third 变宽卡,新增 .sig2(成对行)/.sig2.tall/.sig-ev(事件卡);
|
||
删掉不再用的 tierHasA/B/C、CARDS_B、armed 计算属性。判收:无头渲染六框全在、事件卡与待触发条
|
||
各自正确、零 Vue 告警。旧的"分档版"未曾部署,改版二是最终形态。
|
||
|
||
**同日再修(风控卖出显示为空,真机暴露)**
|
||
部署后风控卖出卡出现 40 行、每行只有一个"SELL"、无股票无理由无时间。根因是后端
|
||
`upstream_signals._read_sell_actions` 直读流的顶层字段,而 `bionic:signals:llm_sell_actions`
|
||
的真实载荷整包在 `data`(JSON 字符串)里、顶层只有 data 一个字段——这正是第一份审查报告
|
||
F3 标过的疑点,今天真机确认。修法:`_read_sell_actions` 按 data 解包(与消化端
|
||
`signal_rules.parse_risk_sell` 同口径),字段名对齐 bionic 写入端——理由 `llm_reason`、
|
||
时间 `timestamp`(epoch 毫秒)、`dominant_signal` 区分风控止损与止盈(take_profit);顶层
|
||
字段留作兜底兼容未来扁平化。前端卡片相应不再硬写"SELL",改显示 股票名 + 风控卖出/止盈
|
||
标签 + 置信 + 时间 + 理由。判收:后端纯逻辑单测(data 包裹 + 扁平兜底两形态各解出
|
||
ts_code/理由/时间/主导信号);前端无头渲染确认卡片显示完整字段、止盈与风控区分正确、零告警。
|
||
动了 app/services/upstream_signals.py 与 app/web/static/index.html 两个文件。
|
||
|
||
---
|
||
|
||
## 2026-08-17 · 清仓择时改造:紧急直通 + 卖出分桶收口 + 自主卖出窗口不作废
|
||
|
||
**做了什么**
|
||
清仓的执行节奏按用户拍板改造,三件事一次落地。一,紧急直通:一键清仓(LIQUIDATE_ALL)
|
||
与高置信风控清仓(信号消化 ≥ PMS_SIGNAL_AUTO_EXIT_CONF 转出的 EXIT)带 urgent 标志,
|
||
择时不再避开开盘半小时、不再等均价,有价在时段有配额即出手,限价用更激进的
|
||
PMS_URGENT_SELL_DISCOUNT(默认 0.995),过了兜底时点自动转 forced 挂到收盘。此前
|
||
一键清仓的方案注记写着「紧急, 不做择时优化」而执行层不认识这个语义,文案与行为不一致。
|
||
二,卖出分桶收口:日内按 PMS_SELL_BUCKET_TIMES(默认 11:30,14:00,加兜底共三桶)
|
||
给每桶分一份当日配额,桶内照旧择价(价好就多卖),桶收口时累计应出量没跟上就无条件
|
||
补齐差额(限价 = 现价×0.998,不置 forced,TTL 到期撤掉按新价重下)。动机:
|
||
「现价≥均价才卖」是只在强势时放行的条件,下跌日全天不满足,全部数量堆到 14:45
|
||
一笔打出。分桶后跌日的强制完成分散到多个时点。参数留空 = 不分桶,回到旧行为。
|
||
三,自主卖出窗口耗尽不作废:window_verdict 增加 side 参数,自主类卖出(信号清仓、
|
||
保垫减仓)窗口耗尽置 PARTIAL 保持在途、按末日节奏继续出手,不再 EXPIRED——与
|
||
hard_gate 里「宁可买不上, 不能卖不掉」对齐口径。自主买入到期作废的口径不变。
|
||
|
||
**动了哪些文件**
|
||
app/core/exec_timing.py(parse_bucket_times / _bucket_due 新增;hard_gate、decide 加
|
||
urgent;window_verdict 加 side);app/services/executor.py(materialize_plans 给
|
||
LIQUIDATE_ALL 指令写 urgent;run_tick 传 urgent 与两个新参数;sweep_windows 传 side);
|
||
app/services/exec_advisor.py(decide 加 urgent 透传,紧急与分桶都在 hard_gate 消化,
|
||
实现A/B 一律生效);app/services/signal_service.py(_make_exit 写 urgent);
|
||
config/settings.py 与 app/services/param_store.py(PMS_SELL_BUCKET_TIMES /
|
||
PMS_URGENT_SELL_DISCOUNT 两个新参数与页面描述);scripts/test_batch3_units.py
|
||
(新增三组用例:紧急直通、分桶收口、自主卖出不作废)。
|
||
|
||
**部署方式**
|
||
factorevaluation 上 `make deploy`(源码打进镜像,restart 无效),跑 `make test`
|
||
见 ALL SUITES PASS。两个新参数页面可调、即时生效;PMS_SELL_BUCKET_TIMES 清空
|
||
即整体退回旧行为,不用回滚代码。
|
||
|
||
**真机判收**
|
||
未判收。开发机全量单测 ALL SUITES PASS(含新增三组用例)。待真机看三样:
|
||
① 下一条紧急清仓的指令 progress.last_decision.reason 出现「紧急卖出直通」;
|
||
② 下跌日的普通清仓在 11:30 后、14:00 后各有一笔「分桶收口」子单;
|
||
③ 窗口耗尽的信号清仓不再变 EXPIRED,progress.window_verdict.note 带「不作废」。
|
||
|
||
**还欠着什么**
|
||
分桶份额目前按桶数等分,未按成交量 U 形加权;紧急直通没有跳过跌停一字板的顺延
|
||
(封板时挂单也成交不了,维持顺延是有意的,记录在此防止误会)。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 新建仓动作:方案已出,待拍板
|
||
|
||
**做了什么**
|
||
读完 PMS 的动作引擎主链代码,出了《给动作引擎加「新建仓」动作》的方案,等待拍板。
|
||
一行代码都还没改。
|
||
|
||
**读代码得到的、与既有文档不一致的事实(以代码为准)**
|
||
|
||
1. `OPEN` 这个动作名在 PMS 里**已经存在**,而且从方案到下单的整条管道是通的:
|
||
`planner.py:31` 定义 `A_OPEN`、`executor.py:41` 的 `BUY_ACTIONS` 已含 `OPEN`、
|
||
`command_service.py:647` 已经在用 `action="OPEN"` 写评审账本。
|
||
缺的只有自主提议这一段:`action_engine.py:27` 的动作词表没有 OPEN,
|
||
`action_engine.scan()` 的输入是持仓列表,结构上不可能引入新标的。
|
||
2. `caps_ctx(view, ts_code=...)` 对**从未持仓过的票**不会带出行业名,
|
||
不显式传 `sector` 的话,行业集中度那道硬拦截会整段静默跳过。
|
||
3. 从未持仓过的票在 `pms_position` 里没有行,`update_position` 会影响 0 行。
|
||
建行要显式调 `ensure_position`,它只写 `ts_code / status='PLANNED' / updated_at`。
|
||
4. `command_spec.py` 里那个 `"planner": "..."` 字段**全代码库没有任何一处读它**,
|
||
真正的分派是 `command_service._dispatch_planner` 里一串硬编码的 `if cmd_type ==`。
|
||
5. 参考位取数(`ledger_service` 盘前那一跳)只对 `only_open=True` 的持仓取,
|
||
候选票没有支撑压力。这对择时实现 A 无影响(它用决策系统自己库里的结论),
|
||
但实现 B 的「回踩带」判据对新票是空的。
|
||
|
||
**动了哪些文件**
|
||
无。方案文件 `NEW_POSITION_ACTION_PLAN.md` 已放进 tradingSystem 项目目录。
|
||
|
||
**部署方式**
|
||
不涉及。
|
||
|
||
**真机判收**
|
||
未判收(尚未动码)。
|
||
|
||
**还欠着什么**
|
||
方案第十节的四件待拍板:新建仓的档位怎么给、每天最多新开几只、研判闸这一轮做不做、
|
||
盘中参考位漂移要不要加防护。拍板之后才动手。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 新建仓动作:四条取舍已拍板,决策系统侧评估完毕,方案定稿
|
||
|
||
**做了什么**
|
||
四条取舍拍板:新建仓单独一个档位参数且默认开启;不设每日开仓上限,由既有上限、资金、
|
||
候选池与择时自己收敛;研判闸这一轮双侧一并加上;参考位漂移做首答锁定加偏离即停。
|
||
按第三条读了决策系统的相关代码,方案更新为定稿版 V2。代码仍然一行未改。
|
||
|
||
**读决策系统代码得到的三条事实(以代码为准)**
|
||
|
||
1. **择时那一侧一个字都不用改。** `app/services/pms_advisor.py` 的 `_advise`
|
||
(163 到 226 行)从头到尾没有读过 `action` 字段,只用 `ts_code`、`side`、`day.price`。
|
||
买入区间由昨夜支撑压力推出,与这笔买单是建新仓还是加老仓无关。
|
||
2. **研判那一侧没有任何 action 白名单。** `app/api/main.py:323` 的路由是裸 dict,
|
||
只校验总开关与 `ts_code` 非空;worker 侧 `tasks_intraday.py:512` 是
|
||
`_task_by_action.get(_act, 兜底文案)`。所以今天发 `action="OPEN"` 就能跑通,
|
||
但拿到的是给加仓写的兜底文案,后面还跟着一句「仓位纪律不归你管」。
|
||
要改的是三处,都在 `workers/tasks_intraday.py`:加一条 OPEN 判据、
|
||
OPEN 且现价缺失时显式回 UNAVAILABLE、边界声明按动作分岔。
|
||
3. **新票的现价链路最脆弱。** `tasks_intraday.py` 449 到 456 行,新票没有持仓快照,
|
||
`fetch_realtime_close` 读不到分钟线时返回 `0.0` 被 `or None` 变成 None,
|
||
提示词渲染成「当前现价 未知 元」。加仓类还有摊薄成本可依,建仓判断没有现价锚不成立。
|
||
|
||
**新撞出来的一条硬约束(工程事实,不是取舍)**
|
||
`app/scheduler.py:45` 给所有调度任务设了 `task_soft_time_limit=240, task_time_limit=300`,
|
||
而 `judge.request` 是同步阻塞、单次超时上限 90 秒。**三只票送研判就是 270 秒,已经超过
|
||
软超时。** 今天没出事是因为送研判的只有三只持仓票且多数轮次被去重挡掉。新建仓不节流、
|
||
候选池默认取前 30 只,第一跳就会捅穿。
|
||
处理办法是给单轮扫描一个研判时间预算 `PMS_JUDGE_TICK_BUDGET_SEC`(默认 150 秒),
|
||
用尽就把剩下的候选留到下一分钟的心跳,并写进 skipped 留痕。
|
||
**这不是节流:一条候选都没丢,也没有按天计的上限**,只是让一次心跳做得完。
|
||
|
||
**动了哪些文件**
|
||
无代码改动。`NEW_POSITION_ACTION_PLAN.md` 更新为定稿版 V2,已放进项目目录。
|
||
|
||
**部署方式**
|
||
不涉及。定稿版里写明:决策系统这次只改 worker 代码、不动 `.env`,
|
||
所以 `git pull` 后 `docker compose restart backend-api worker-brain` 即可;
|
||
哪天真要往 `.env` 写值(比如切研判独立队列)才必须 `up -d --force-recreate`。
|
||
PMS 侧因为新建仓档位默认就是自动执行,**建议收盘后 `make deploy`**,
|
||
让第一次真实建仓发生在次日开盘、人在场的时候。
|
||
|
||
**真机判收**
|
||
未判收(尚未动码)。
|
||
|
||
**还欠着什么**
|
||
等点头后开工。开工顺序:决策系统侧先上并用 `pms_smoke.py` 手工发一条
|
||
`action=OPEN` 的研判请求验证,再回来做 PMS 侧。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 新立一条规矩:动 tradingSystem 以外的系统之前先答三题
|
||
|
||
**做了什么**
|
||
用户指出决策系统本来就不管仓位(它是聚合大量信号的判断系统),往里面塞仓位信息
|
||
会把别的在跑的逻辑带偏。据此立了一条通用规矩,并按它把方案重过一遍,改成 V3。
|
||
|
||
**规矩(改 tradingSystem 以外的系统,动手前先答,答不上就别动)**
|
||
|
||
1. 这段代码是不是只有我这条路径会走到?共用的提示词模板、裁决解析、上下文组装、
|
||
配置项,都不能在里面加只对我有意义的分支。
|
||
2. 我加进去的字段,会不会被别的逻辑读到并当真?尤其是写共享表、共享缓存、
|
||
往共享 context 里塞键。
|
||
3. 我的东西全删掉,原系统能不能一字不差地回到今天?答不上「能」就不是加法。
|
||
|
||
**按规矩核过的结论**
|
||
决策系统这三处改动全部落在两段 `if direction == "PMS_JUDGE":` 里
|
||
(`workers/tasks_intraday.py` 439 到 523 行、632 到 656 行),不碰共用的提示词模板
|
||
(557 到 576 行)、共用的裁决解析(579 到 590 行)、资金与板块注入(530 到 555 行);
|
||
不写 `decision_ledger`;不加 settings 字段;留痕仍只有 PMS_PASS / PMS_REJECT /
|
||
PMS_UNAVAILABLE 三个值,而 `pms_advisor._today_exit_verdict` 第 102 行明确跳过
|
||
`PMS_` 开头的行,所以写进去的东西不会反过来影响择时。第三题答案是能,`git revert` 一次。
|
||
|
||
**额外补的一条:仓位数字不进研判请求**
|
||
`judge.request` 现在把 `hard_numbers` 整个塞进请求体,决策系统会逐键渲染进提示词。
|
||
新建仓的 hard_numbers 里有名额、可投金额、批次比例——等于当面请一个不该管仓位的系统
|
||
去看仓位。所以在 PMS 侧加键白名单,只对 OPEN 生效:送现价、分数、主题、档位、
|
||
预期空间、热度、名次;不送名额、金额、批次、组合占比。账本照旧存全量。
|
||
|
||
**重过一遍还改掉与补上的(详见方案 V3 第三节)**
|
||
改掉两处:去掉 `PMS_OPEN_TARGET_PCT` 与建持仓行(改由首次成交时既有的
|
||
`ensure_position` 自然建行,省掉僵尸空行的清理);去掉 `PMS_OPEN_MIN_SCORE`
|
||
(与候选池的 `PMS_PLAN_MIN_SCORE` 重复)。
|
||
补上六处,其中三条是真问题:
|
||
- **候选之间要滚动扣减**。`_route_one` 每条各自用本轮开始时的旧快照算上限,
|
||
一跳内五条各自不超、加起来超,规则闸拦不住。命令那条路有 `_ctx_after` 滚动,
|
||
自主这条没有。滚动放进 `scan_open`,复用 `planner.check_all_caps`。
|
||
- **研判驳回要做当日去重(只对 OPEN)**。`_rejected_today_keys` 只读 arbiter='rule',
|
||
研判驳回不在里面,`_inflight_keys` 也认不出,于是被驳回的候选每分钟往账本写一行
|
||
一模一样的记录——正是 07-29 那个把判分锚淹掉的教训。
|
||
- **可投金额要用真实可用资金封顶**,并加 `PMS_OPEN_REQUIRE_WS_CASH`:
|
||
拿不到资金快照就不自动新建仓。规则闸那条「拿不到资金只告警不拦」是为已排好的命令
|
||
设计的,不适合无人值守地从零建仓。
|
||
另外三条:行业名一次批量取本轮复用;研判预算用尽的候选整条跳过不入人工队列;
|
||
新建仓退实现B 时择时判据只剩均价一条(已知降级,要在理由里写明)。
|
||
|
||
**一条顺带发现、但这次不夹带的**
|
||
`industry.get_many` 走 `gp_stock_category` 时是逐只查库、没有缓存,
|
||
可以照 `_hybk_many` 加按日缓存。收益是每跳少几十次查询,但它改的是既有函数的时序行为,
|
||
按上面第 1 题的规矩不夹带,要做单独一条、单独拍板。
|
||
|
||
**动了哪些文件**
|
||
无代码改动。`NEW_POSITION_ACTION_PLAN.md` 更新为 V3。
|
||
|
||
**部署方式**
|
||
不涉及。
|
||
|
||
**真机判收**
|
||
未判收(尚未动码)。
|
||
|
||
**还欠着什么**
|
||
同上,等点头开工。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 研判闸第一次真发请求就全军覆没:请求体里的 Decimal
|
||
|
||
**这是今天最要紧的一条。**
|
||
|
||
三条新建仓提议进了人工确认队列,档位明明是 `full`。查提议的研判理由,三条一模一样:
|
||
|
||
```
|
||
研判: UNAVAILABLE | 研判请求失败: TypeError: Object of type Decimal is not JSON serializable
|
||
```
|
||
|
||
**源头**:`proposal_service._recent_ledger()` 把评审账本流水塞进研判请求的 context,
|
||
其中 `"price": r.get("price_at")` —— `pms_action_ledger.price_at` 是数据库的 DECIMAL 列,
|
||
读出来是 Python 的 `Decimal`,`requests.post(json=payload)` 序列化不了,直接抛 TypeError。
|
||
|
||
**危害是渐进的,这才是它可怕的地方**:只要一只票在评审账本里有过**任何一行**,
|
||
它的研判请求就必然失败;而账本只会越积越多,于是过几天几乎所有票的研判都会失败,
|
||
全部降级成人工确认——「不用每天靠人」这件事会一点点失效,
|
||
而每一条看起来都只是「研判不可用,降级人工确认」这种系统里天天都有的正常降级,
|
||
不会有人觉得不对。今天只撞上三只,是因为多数候选还是第一次出现在账本里。
|
||
|
||
**为什么今天才暴露**:研判闸此前从没真正接通过——`PMS_JUDGE_API_BASE` 一直是空的,
|
||
`judge.available()` 为假就直接返回不可用,请求体根本没构造过。
|
||
今天是它第一次真发请求,第一次就撞上。这也意味着接口契约 §6 里 #10 那条判收
|
||
(「评审账本出现 arbiter=judge 的行」)今天才第一次真正达成。
|
||
|
||
**降级逻辑救了场**:错误没有被吞掉——报错、回 UNAVAILABLE、入人工队列、理由写进提议,
|
||
一路留痕,所以一条查询就看见了。这正是「拿不到意见绝不当成通过」那条纪律的价值。
|
||
|
||
**改法**:`judge.jsonable()` 递归把 Decimal 换成 float、日期换成字符串,
|
||
在请求出口统一拦一道。修在出口而不是修 `_recent_ledger` 一处,是因为请求体的任何一层
|
||
将来都可能再冒出数据库类型——一次拦住比每加一个字段就想一次靠谱。
|
||
`_recent_ledger` 那边也顺手把 `price` 转成 float,让日志与留痕里的数字也干净。
|
||
择时那条路(`exec_advisor._advice`)目前不受影响:它的 refs 与 position 都取自
|
||
`positions_view()`,那里每个数值都过了 `float()`/`round()`;哪天它也报同样的错,
|
||
照 `jsonable` 的写法在它的 `_post` 前包一层即可。
|
||
|
||
单测里放了一条**复现用例**:用 Decimal 与 date 造出当天的现场,转换后必须真的
|
||
`json.dumps` 得出来。另加一条源码检查,钉住 `_recent_ledger` 的 float 转换。
|
||
全量单测 **ALL SUITES PASS,469 例**。
|
||
|
||
**顺带一条结构性发现,还没处置**:`300570.SZ` 是今天唯一一只既在候选池、
|
||
又被决策系统判过盘中转多的票,它倒在规则闸的 `NO_CHASE_MA5` 上(距 MA5 12.22%,上限 6%)。
|
||
而那 67 条转多理由里满是「放量突破」「量比 8.55」「站上关键位」——**按定义就是脱离均线的形态**。
|
||
所以「信号只做加速器、其余闸门一道不放宽」这个口径,在结构上就注定收效甚微:
|
||
就算把候选池调大、重合率提上去,信号票也会成片倒在不追高那道闸上。
|
||
要让信号真正有增量,只能是口径三(信号票在不追高或择时上放宽一档),
|
||
而那要碰「不追高、接受买不上」这条纪律,必须先拍板。**本轮不动。**
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 新建仓盘中真跑了 40 分钟,第一份实测;跳过原因改成分得开
|
||
|
||
**这一条是实测记录,不是设计。** 新建仓在今天下午约 14:07 起真跑了四十分钟
|
||
(部署时间比原计划提前,是在盘中做的)。
|
||
|
||
**研判判据生效,方向对。** 评审账本里 11 条 `arbiter=judge、action=OPEN、verdict=REJECT`,
|
||
理由**每一条都在谈「开新仓」**——「建仓缺乏资金支持」「开新仓风险过高」「新建仓证据不足」,
|
||
没有一条把它当成加仓在谈;而且确实用上了决策系统自动补的实时资金分布(主力净流占比、
|
||
主散背离)去对照「把这只票挑出来的驱动到今天还成不成立」。判收第一步通过。
|
||
|
||
**当日去重立竿见影。** 驳回集中在 14:07 到 14:38 这 31 分钟,扫完一遍候选池之后就静默了。
|
||
没有这道去重,这 22 只会每分钟重送一次研判,一个下午上千次大模型调用与上千行账本。
|
||
研判速度也对得上预期:31 分钟十几只,正是 150 秒预算下一跳一到两条的节奏。
|
||
|
||
**今天一股没建,驳回率 100%。** 多条理由提到「宏观空头环境」「市场空头相位」「熊市共振」
|
||
「下降通道」,今天本来就是空头,全驳回大概率是合理判断,不是判据写窄。
|
||
**但埋了一处要盯**:OPEN 判据末尾那句「这是开新仓不是加仓,没有既有仓位做缓冲,建错了
|
||
代价全额承担,标准应当比加仓类更严,不确定就 REJECT」,加上边界声明里的「宁可错杀不可
|
||
错放」,**是双重保守**。若接下来两三天在正常或偏多的市场里仍接近全驳回,就是这句话把模型
|
||
推过头了,要调轻。一天的数据不足以判断。
|
||
|
||
**这份实测暴露的可读性问题,已改**:22 条跳过全写着同一句「已有在途提议或指令, 或今日已被
|
||
闸门拒过」,把四种完全不同的处境揉成一句——有在途提议、有在途指令、今日被规则闸拒过、
|
||
今日被研判闸驳回过。前两种明天照样被挡,后两种日切就重新评估,而它们长得一模一样,
|
||
看的人判断不了这只票明天还会不会再被评估。这违反了 `scan` 入口自己立的那条规矩:
|
||
「什么都没发生」和「明着跳过了」必须是两回事——现在虽然明着跳过了,却说不清为什么。
|
||
|
||
改法:`skip` 从集合改成 `{(代码, 动作): 原因}` 字典。`in` 的语义不变,
|
||
新增 `action_engine.skip_why()` 取原因,**传集合进来仍然工作**(退回一句兜底话,
|
||
既有单测与临时调用不受影响)。三个来源各写各的原因,在途的排在最后覆盖被拒的——
|
||
一只票既有在途指令又今天被拒过时,「有在途」更贴近它此刻的状态。
|
||
在途那两条还带上提议号、指令号与状态,让人从这一行就能接着往下查。
|
||
|
||
**动了哪些文件**:`app/core/action_engine.py`(`skip_why` 与两个扫描入口)、
|
||
`app/services/proposal_service.py`(三个来源改成带原因的字典);
|
||
测试 `scripts/test_batch12_units.py` 加 3 例,`scripts/test_batch4_units.py` 与
|
||
`scripts/test_wiring.py` 各改一处断言(原本钉着那句笼统话的字面量)。
|
||
全量单测 **ALL SUITES PASS,467 例**。
|
||
|
||
**一条要更正的承诺**:先前说过「既有 `scan()` 一个字不动」,这次动了它——为的是让跳过留痕
|
||
说得清。`in` 的语义与向后兼容都保住了,但这个承诺本身要如实更正,不该含糊过去。
|
||
|
||
**我在这一轮犯的一个误判**:账本里 14:38 有两条对没持仓票的 `PMS_REJECT`,我据此推断
|
||
「新建仓还没上线,所以不可能,应该是探活脚本」——实际是新建仓已经部署并在跑。
|
||
**推理没落地就当结论**,与 07 月那次 `audit_date` 的教训同型。该先查账本再下判断。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 收盘后四条只读诊断,外加一个由它们暴露出来的窗口末日追高
|
||
|
||
**四条诊断的结论**
|
||
|
||
1. **买入信号 67 条,落在候选池里的只有 1 只**(`300570.SZ`)。
|
||
`intraday_signals:2026-08-06` 共 73 条,动作分布 BUY 67 / SELL 6。
|
||
说明**决策系统的盘中转多判断与上游选股计划的排序是两套几乎不相干的视角**——
|
||
互补而不是互相验证。所以「必须同时在候选池里」这条口径在 `PMS_PLAN_TOP_N=30`
|
||
的当前参数下,插队排序的增量只有 1/67,基本无效。
|
||
顺带日志说破另一件事:`主榜正好吃满 top=300 (打分池 440)`,上游还有 140 只没拿到。
|
||
两条改法(调大 `PMS_PLAN_TOP_N`,或让只被 top_n 纯排序截断切掉的信号票凭信号捞回来)
|
||
记在下面「还欠着什么」里,等明天有真实留痕再定。
|
||
|
||
2. **`trading_buy_plan` 最新一行是 07-30,七天没动。** 四个状态全是旧数据 →
|
||
老的建仓链路是死腿,**不存在两套系统同时买的风险**,ENTRY_GATE 关不关都行。
|
||
|
||
3. **悬项四定案:不是口径差。** 差额 564(08-05)→ 51(08-06 盘中)→ **4.00 元**(收盘后),
|
||
递减到接近零,就是取数时点差加收盘价精度差(4 元对 177,561 的持仓市值是 0.002%)。
|
||
**但「`cash_avail` 里 `+ sell_return_today` 双算」这个假设今天没被验证,只是没被触发**
|
||
——今天零卖出成交,那个加法加的是 0。真正的验证要等有卖出成交的那天。
|
||
|
||
4. **悬项八有结论:`trade_no` 是随机串**(`T-SHADOW-420911c4b8`),不是确定式,
|
||
S3 收尾函那个阻断项**不闭环**。后果是幂等:同一笔成交若被重推,随机串识别不出来。
|
||
得确认回放那侧的去重键用的是 `trade_no` 还是单调的 `seq`。
|
||
|
||
**由诊断暴露出来的问题:窗口末日会强制追高,而自主买入不该这样**
|
||
|
||
`ws_smoke inbox` 里 `000063.SZ` 那五笔成交是今天 **14:46**(时间戳换算),1700 股,
|
||
成交价 34.71 到 34.76。而交接信写的是「现价高于买入区间上沿,**不追**,窗口今天到期」。
|
||
判了不追,最后还是买了——走的是 `exec_timing.hard_gate` 买入分支的窗口末日强制完成,
|
||
而 `hard_gate` 在 `exec_advisor.decide` 里是**先于**咨询决策系统跑的,
|
||
14:45 之后根本不问择时,直接按 现价×1.002 追进去。
|
||
|
||
那一笔是命令驱动的(`INS_20260804_000063SZ_OPEN_003`),**强制完成是对的**:
|
||
用户下过「投这么多」的命令,到期必须完成。但同一段代码等新建仓上线就会作用在自主提议上,
|
||
那时它与「可以接受买不上」直接冲突,而且是无人值守。
|
||
|
||
更要紧的是 `window_verdict` 早就把口径写死了:`"PARTIAL" if is_command else "EXPIRED"`
|
||
——**自主类窗口耗尽直接作废**。既然到期就作废,就不该在到期当天先被强制完成一遍。
|
||
两处本来是矛盾的,这次是把它们对齐,不是新增策略。
|
||
|
||
**改法**
|
||
- `exec_timing.hard_gate` 新增 `is_command`,**必填、故意不给默认值**——漏传立刻
|
||
`TypeError`,不许静默按某一侧走。买入的窗口末日强制完成只对命令生效,自主返回等待
|
||
并写明「到期作废」。
|
||
- **卖出侧一个字没改**,两种来源都照常兜底。这不是漏改:买入的强制完成是「多背一份风险」,
|
||
卖出的是「少背一份风险」;自主减仓若也到期作废,那是把该降的风险留在账上。
|
||
宁可买不上,但不能卖不掉。
|
||
- `exec_timing.decide` 与 `exec_advisor.decide` 的 `is_command` 默认 `True`
|
||
(命令口径 = 改动前的行为),`executor.run_tick` 从指令的 `progress.is_command` 显式传下来。
|
||
- 影响面说破:**已有的 FILL / ADD / DCA 自主买单也跟着改**——从前也会在末日强制完成,
|
||
现在到期作废。「回踩补足」在末日追高买本身就自相矛盾,所以对它们同样是修正,
|
||
但确实是既有行为的改变。
|
||
|
||
**动了哪些文件**
|
||
`app/core/exec_timing.py`、`app/services/exec_advisor.py`、`app/services/executor.py`;
|
||
测试 `scripts/test_batch12_units.py` 加 6 例(含一条「executor 真的把 is_command 传下去了」
|
||
的签名与源码检查,防这道分岔形同虚设),`scripts/test_batch11_units.py` 的夹具补一个参数。
|
||
全量单测 **ALL SUITES PASS,464 例**。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 决策系统的买入信号一直被 PMS 丢在门口,已接上
|
||
|
||
**做了什么**
|
||
用户问「新建仓能不能并入决策系统原有的信号链路」。查下来先纠正了一个前提,又撞出一个洞。
|
||
|
||
**先纠正的前提**:决策系统的**建仓侧本来就是轮询**,不是订阅。
|
||
`workers/celery_app.py:94-97` 的 `entry-gate-poll` 是 `crontab(minute="*", hour="9-11,13-14")`,
|
||
每分钟去 `trading_buy_plan` 捞 `is_active=7`。真正事件驱动的是告警链
|
||
(`scripts/intraday_watcher.py` 常驻进程 `xreadgroup` 读上游告警流)与卖出链。
|
||
所以「PMS 只能轮询不合理」对既有架构不成立——候选池是日频静态清单,没有事件可订阅,
|
||
轮询是它唯一的读法,而且与决策系统自己的建仓入口同构。
|
||
|
||
**撞出来的洞(与 08-05 那次同型)**:决策系统盘中判出 `REVERSAL_BUY` 会往 db2 的
|
||
`intraday_signals:{日期}` 广播(`workers/tasks_intraday.py:783-785`),**PMS 一直订阅得到**
|
||
(`signal_service.streams()` 第一条就是它,每分钟拉一批),但走到
|
||
`signal_rules.digest`(第 106 到 108 行)被归进 `ACT_RECORD`,而
|
||
`signal_service._handle` 的 RECORD 分支**只给持仓票写账本**。
|
||
于是「决策系统今天看多了某只没持仓的票」——**正是新建仓关心的那一批**——PMS 收到了、
|
||
计了个数,然后一个字都不留:账本查不到、页面看不见,事后复盘问「那天系统看见了吗」答不上来。
|
||
当年那句注释的理由(「买什么买多少由动作引擎决定」)在动作引擎没有新建仓动作时成立,
|
||
现在失效了。
|
||
|
||
**改法(口径:信号只做加速器,不改资格)**
|
||
- `signal_rules.py`:新增 `ACT_NOTE_BUY`。BUY 信号**仍然不产生任何买入动作**,
|
||
但持没持仓都要留痕。HOLD 等其余类型口径一个字未动。
|
||
- `signal_service.py`:新增 `ACT_NOTE_BUY` 分支,落 `action=SIGNAL_BUY`、`verdict=NOTE`
|
||
的账本行,按(日期,来源,股票,动作)当日去重。用 `NOTE` 不用 `PASS`——
|
||
「记下来」和「放行」在账本里必须分得开。
|
||
- `pms_repo.buy_signals_today`:只读,今天有转多留痕的票。
|
||
- `proposal_service._scan_open` → `action_engine.scan_open`:有信号的候选**排最前**。
|
||
**只影响先后,不影响资格**——不在候选池里的票不会因为有信号就被建仓,
|
||
候选层那一整套过滤(ST、黑名单、分数下限、主题限额、预期空间)一道都不绕。
|
||
- `judge.OPEN_JUDGE_KEYS` 加两个键,把「你自己今天判过这只票转多」送回决策系统,
|
||
让它拿自己的结论对照一次。这是定性材料不是仓位数字,符合那条白名单的立意。
|
||
- 新参数 `PMS_OPEN_SIGNAL_PRIORITY`(默认开),关掉即退回纯分数排序。
|
||
|
||
**为什么走账本传递而不是让 signal_service 直接调建仓**
|
||
两边各管各的一件事:信号消化管「收到了、记下来」,动作引擎管「买不买、买多少」,
|
||
中间靠账本这个既有事实源接。不新增跨模块调用、不复制闸门逻辑。
|
||
代价是最多差一分钟(两个调度位各自每分钟一跳),而后面还要等择时区间,这点延迟无所谓。
|
||
|
||
**插队为什么是实打实的增量**:候选按分数降序取,名额只剩两个时第 25 名永远轮不上,
|
||
哪怕决策系统刚刚判它转多。分数是昨夜算的静态排名,「此刻转多」是盘中才有的新信息,
|
||
两者不同量纲,折算成分数得凭空定系数;插队直接表达了这件事。
|
||
|
||
**动了哪些文件**
|
||
`app/core/signal_rules.py`、`app/services/signal_service.py`、`app/repo/pms_repo.py`、
|
||
`app/services/proposal_service.py`、`app/core/action_engine.py`、`app/services/judge.py`、
|
||
`config/settings.py`、`app/services/param_store.py`;测试 `scripts/test_batch12_units.py`
|
||
加 7 例,`scripts/test_batch5_units.py` 与 `scripts/test_wiring.py` 各改一例
|
||
(那两例原本钉着 BUY→`ACT_RECORD` 的旧行为,「不买」这条口径没变,变的是留痕范围)。
|
||
全量单测 **ALL SUITES PASS,458 例**。
|
||
|
||
**还欠着什么**
|
||
1. `trading_buy_plan` 四个状态都有行,说明老的建仓链路还活着。**关掉 ENTRY_GATE 之前
|
||
必须先确认那些行是不是今天的**——若老链路今天仍在挂单,而 PMS 同时开始建仓,
|
||
就是两套系统都在买。查法见交接说明。
|
||
2. 关 ENTRY_GATE 是往决策系统 `.env` 写 `ENTRY_GATE_ENABLED=False`,
|
||
**这是新增 env 值,必须 `docker compose up -d --force-recreate`,`restart` 不重读 env。**
|
||
3. 口径三(信号票在择时上放宽一档)本轮没做,要碰「不追高、接受买不上」那条纪律,
|
||
等积累一段实证再谈。
|
||
|
||
---
|
||
|
||
## 2026-08-06 · 新建仓动作:两侧代码写完,容器内全量单测通过,待实机部署
|
||
|
||
**做了什么**
|
||
按 V3 方案把两侧代码写完了。容器里拼了一份可运行的副本跑全量单测:**ALL SUITES PASS,
|
||
451 例**(新增的第十二批 26 例 + 既有 425 例,含装配自检 58 例)。**尚未在实机部署。**
|
||
|
||
**动了哪些文件**
|
||
|
||
决策系统(`bionic_trader`)一个文件,48 增 4 删,三处全在 `if direction == "PMS_JUDGE":` 块内:
|
||
- `workers/tasks_intraday.py`:`_task_by_action` 加 OPEN 建仓判据(含必答「把这只票挑出来的
|
||
驱动到今天还成不成立」);OPEN 且现价缺失时块内早退回 `UNAVAILABLE`(加仓类有摊薄成本可依,
|
||
建仓没有现价锚就是盲判);边界声明按动作分岔(原句里的「配额」对新建仓不成立)。
|
||
|
||
持仓系统(`tradingSystem`)九个文件:
|
||
- `app/core/action_engine.py`:加 `A_OPEN` 并进 `JUDGE_ACTIONS`;新增 `eval_open` 与
|
||
`scan_open`。**既有 `scan()` 一个字未动。**
|
||
- `app/services/proposal_service.py`:新增 `_scan_open`(候选池取数、批量取行业、
|
||
真实资金封顶);`_route_one` 加新建仓特判与档位分支;研判时间预算。
|
||
- `app/services/judge.py`:`OPEN_JUDGE_KEYS` 键白名单,新建仓只送定性材料。
|
||
- `app/services/exec_advisor.py`:存 `observed` 的支撑压力区间;`_check_ref_drift` 首答锁定、
|
||
偏离即停。
|
||
- `app/services/executor.py`:等待分支看到 `ref_drift` 落一条账本 WARN,**一天一条**。
|
||
- `app/repo/pms_repo.py`:新增只读 `judge_rejected_today` 与 `opened_names_today`。
|
||
- `app/services/param_store.py`:四个新参数的说明、校验、`FAIL_CLOSED`。
|
||
- `config/settings.py`:四个新参数;`PMS_JUDGE_ACTIONS` 默认值加 `OPEN`。
|
||
- `scripts/test_batch12_units.py`(新,26 例)与 `scripts/run_tests.py`(登记新批次)。
|
||
|
||
**写代码过程中改掉的一条设计(单测逼出来的)**
|
||
原方案的取数口径抄的是命令驱动那条路:`want = min(单股目标, 剩余额度)`。单测发现它会在
|
||
可投金额只剩一万时开出一只 0.5% 的零头仓位——**拿一个持仓名额换一个永远补不到目标的半截仓,
|
||
还挡住了后面真正建得起来的票**。改成「钱不够一整只就不开」,并把与命令那条路的差别写在注释里:
|
||
命令是用户明确下了「投这么多」,最后一只缩水是命令的收尾;自主建仓没有这层意思。
|
||
|
||
**部署方式**
|
||
- 决策系统:不加配置、不动 `.env` → `git pull` 后 `docker compose restart backend-api worker-brain`。
|
||
- 持仓系统:源码打进镜像 → `make deploy`,跑完 `make test` 要见 ALL SUITES PASS。
|
||
**新建仓档位默认就是 full(自动执行),`make deploy` 一跑完下一个整分钟的心跳就可能真建仓,
|
||
所以务必收盘后部署**,让第一次真实运行发生在次日开盘、人在场的时候。
|
||
|
||
**真机判收**
|
||
未判收。判收步骤见 `NEW_POSITION_ACTION_PLAN.md` 第十节,顺序是决策系统先上并手工发一条
|
||
`action=OPEN` 的研判请求,确认理由是在谈从零建仓而不是加仓、且请求体里没有仓位数字。
|
||
|
||
**还欠着什么**
|
||
1. 实机部署与八步判收。
|
||
2. 第一周要盯的四个数:每天有几只候选落在买入区间内、研判驳回率、`intraday_exec` 一跳的
|
||
耗时分布、评审账本每天的行数。
|
||
3. 未夹带的一条:给 `industry.get_many` 的 `gp_stock_category` 分支加按日缓存
|
||
(每跳省几十次库查询,但改的是既有函数的时序行为,单独提、单独拍板)。
|
||
4. 文档补账仍欠着:`WS_INTEGRATION_STATUS.md` 停在 07-30;`BIONIC_PMS_INTERFACE.md`
|
||
要补新建仓这一路(研判闸第五类动作、硬数字裁剪、择时侧零改动)。
|
||
|
||
---
|
||
|
||
## 2026-08-07 · 股票评述里冒出「【手动重跑AKG_PLAN】」:运维口径的话被当成风控质询
|
||
|
||
**做了什么**
|
||
|
||
页面上一些股票的评述里出现了「【手动重跑AKG_PLAN】」这种谁也看不懂的字段。第一反应会怀疑
|
||
是前一天新建仓那批改动,但**不是**:三个仓库全文检索「手动重跑」只有一个出处,
|
||
`bionic_trader/scripts/rerun_pool.py` 第 237–238 行。链条如下(全部是源码,不是推测):
|
||
|
||
1. `rerun_pool.py` 下发 `analyze_strategic_plan.delay(c, reason, None, target_date)`。
|
||
**第二个位置参数不是留痕字段,是 `reviewer_feedback`** —— 风控官质询通道。
|
||
2. `tasks_brain.IntradayClassifier.classify()` 第一道判断是 `if "【盘中" not in text: return None`,
|
||
「【手动重跑】」不带这个前缀,返回 None。
|
||
3. 于是掉进 `elif reviewer_feedback:` 分支,被拼进提示词的
|
||
`[📢 CRITICAL CHALLENGE FROM RISK OFFICER]` 段,后面跟着
|
||
`You MUST address this in your analysis`。
|
||
4. 那是一句命令。大脑照办,把这句运维口径的话写进了 `analysis`。
|
||
5. `generate_chinese_report` → `save_to_database` → 覆盖进
|
||
`strategy_daily_results.analysis_summary`,也就是页面上的评述。
|
||
|
||
**这条真正的教训不是那个字段,是它顺带证实的悬项二。** `rerun_pool` 落库是
|
||
`INSERT ... ON DUPLICATE KEY UPDATE`,把 `signal_type / support_level / pressure_level /
|
||
analysis_summary` 一起就地覆盖。查实的落库时刻(库时钟是 UTC,下面已换算成北京时间):
|
||
|
||
| trade_date | 行数 | 落库时刻(北京,已换算) | 当时是什么时候 |
|
||
|---|---|---|---|
|
||
| 20260806 | 16 | 08-06 22:32 → 22:45 | 收盘后,安全 |
|
||
| 20260805 | 18 | 08-05 22:30 → **08-06 11:04** | **盘中**,改写的是当时正被读的昨夜结论 |
|
||
| 20260804 | 11 | **08-05 13:04** → 08-07 00:50 | **盘中**,补录历史日 |
|
||
|
||
08-06 早盘 11:04 与 08-05 午盘 13:04 各有一次盘中重跑。而这张表下游有两个人在读:
|
||
bionic 的 `pms_advisor`(支撑压力位算 PMS 买卖区间、`signal_type` 走 `BAD_Y_SIGNALS` 闸)、
|
||
桥 `pool.py` 的 `BAD_SIGNALS`(决定把哪只票请出候选池)。所以「盘中不跑 push-pool」这条纪律
|
||
不只针对桥,`rerun_pool` 同样在内 —— 这次是拿实测把它钉死了,不再是推测。
|
||
|
||
顺带记一条待查的:`SZ000800` 现在有两行相互打架的定性,`trade_date=20260806` 是 `SELL`
|
||
(08-06 22:38 写入),`trade_date=20260804` 是 `DROPPED`(08-07 00:50 写入,晚了两个多小时)。
|
||
补录历史日会写出一行「时间上更新、日期上更旧」的结论,取数方按 `trade_date` 排还是按
|
||
`updated_at` 排会拿到不同答案。两边取数口径要对一遍,这次没做。
|
||
|
||
**改法(两处,缺一不可)**
|
||
|
||
- **根治** —— `rerun_pool.py` 下发时第二个位置参数固定传 `None`;`--reason` 保留,但改成
|
||
只印在脚本自己的输出里给人看。
|
||
- **兜底** —— `tasks_brain.py` 加 `_is_ops_note()` 前缀护栏,认
|
||
`【手动重跑】/【运维】/【回补】`,命中就先打一行日志再把 `reviewer_feedback` 置空。
|
||
为什么两处都要:`rerun_pool` 的用法示例第 2 条明写着鼓励人手填
|
||
`--reason "调整X因子后重跑"`,只堵住默认值,下次手填照样中招。
|
||
|
||
护栏落点选在 `analyze_strategic_plan` 打完那行「🧠 Brain 正在介入」日志之后、
|
||
`IntradayClassifier.classify` 之前。**先打日志再丢弃**是有意的:「谁在什么时候用什么理由
|
||
重跑了这只票」这条线索要留在日志里,它只是不该进大模型的证据。拦在源头一次就够,因为
|
||
下游用到 `reviewer_feedback` 的只有 classify 和风控官质询段两处。
|
||
|
||
**已经被污染的评述救不回来**:就地覆盖,没有历史表。要洗只能改完代码后按同一个池重跑一遍
|
||
让它再覆盖一次,而那又是一次全量重算、支撑压力位会再动 —— 只能收盘后做,别在盘中洗。
|
||
|
||
**动了哪些文件**(都在 bionic_trader,PMS 侧一个字没动)
|
||
- `scripts/rerun_pool.py` —— 下发传 `None`;`--reason` 改为本地留痕;文件头补两段说明
|
||
(「什么时候不能跑」「为什么 --reason 不再下发给大脑」)。
|
||
- `workers/tasks_brain.py` —— 新增 `_OPS_NOTE_PREFIXES` 与 `_is_ops_note()`;
|
||
`analyze_strategic_plan` 里加四行护栏。既有两条路径(盘中事件、常规风控意见)零改动。
|
||
- `scripts/selftest_ops_note_guard.py`(新增,24 项)—— 不 import `tasks_brain`
|
||
(它拖着 milvus/llm),靠 AST 把 `_is_ops_note` 从源码里抠出来单独执行做真实断言,
|
||
再用 AST 校验护栏落点与 `delay` 的第二个实参。做过三次变异验证,确认它真的会失败:
|
||
说明文字塞回去、护栏只打日志不置空、前缀写宽一个字,三次都被抓住。
|
||
|
||
**部署方式**
|
||
决策系统那台:`git pull` 后 `docker compose restart worker-brain`。没有新增 env,
|
||
不需要 `--force-recreate`。**只重启 worker-brain 就够**,`backend-api` 没动。
|
||
|
||
**真机判收**
|
||
未判收。判收两步:① 容器里跑一次
|
||
`docker exec trader_worker_brain python scripts/selftest_ops_note_guard.py` 见「全部通过」;
|
||
② 收盘后拿任意一只票跑一次 `rerun_pool.py --group AKG_PLAN --limit 1 --reason "护栏验证"`,
|
||
确认 worker 日志里有「🛡️ 运维口径说明已从提示词中剔除」这一行,且该票新落库的
|
||
`analysis_summary` 里不含「手动重跑」。
|
||
|
||
**还欠着什么**
|
||
1. 上面两步真机判收。
|
||
2. `SZ000800` 那种「补录历史日写出时间更新、日期更旧的行」,取数方按 `trade_date` 还是
|
||
按 `updated_at` 排要对一遍口径(`pms_advisor` 与桥 `pool.py` 两处)。
|
||
3. 已污染的三批评述(16/18/11 行)要不要收盘后洗一遍,没定。
|
||
4. `rerun_pool.py` 里 `--dry-run` 的中文说明写的是「干跑」,项目里同类东西叫「试算」,
|
||
措辞不统一。这次没改,因为跟本条改动无关,单独提。
|
||
|
||
---
|
||
|
||
## 2026-08-28 · 全仓库审查后的一轮集中修复:47 个文件,585 例单测两端全绿
|
||
|
||
**做了什么**
|
||
以提交 654d88f 为基线做了一次全仓库审查(报告见 `CODE_REVIEW_2026-08-28.md`,已在 c058870 入库),
|
||
然后把审查确认的问题全部修掉。要点按危害排序:
|
||
|
||
1. **联调单识别闸修通**:`ledger_service` 入账三分流读错列名(`parent_instruction_id`,
|
||
真表叫 `parent_id`),SMOKE 联调单的假成交一直按真单入账。改对列名,并把 test_wiring
|
||
桩里同样写错的键名与那句写反的注释一起改对——桩错得和代码一样,所以从前全绿。
|
||
2. **成交回放游标改成 v2 时间游标**:原来按 order_id 字典序推进,乱序单号会被永久漏掉。
|
||
现在按「时间窗 + 已入账清单(seen)」推进,带 24 小时重叠回看与 48 小时清单裁剪;
|
||
冷启动只对齐锚点不追认历史。`daily_settle` 开头先补一次回放再结算。
|
||
3. **ws 入账逐笔推进**:每笔入账成功立刻 `inbox_mark`,中途崩溃只影响当笔(原来整批
|
||
最后一起标,崩一次重放整批)。费用记账对重复键容忍。剩余风险见「还欠着什么」第 1 条。
|
||
4. **撤销链路修通**:撤销命令的在途指令必须逐条经 `executor.cancel_instruction` 撤回下游,
|
||
撤不成的命令保持在途、单独列出可重撤(原来只把本端行标 CANCELLED,券商侧委托继续挂着)。
|
||
5. **科创板 200 股最小申报统一进 `sizer.lot_of`**:planner 批次拆分、rule_gate 买卖校验、
|
||
executor 当日配额、action_engine 四类动作与新建仓、signal 消化、strategy 两侧,全走同一口径。
|
||
6. **风控置信度按文档 0~100 制归一**(`_norm_conf_pct`):原启发式把 1 当 100% 直接触发
|
||
自动清仓,方向恰好反了。
|
||
7. **命令进度两处口径**:GATED 批金额不进完成分母(建仓命令不再必然拖成 PARTIAL);
|
||
目标金额 0 但方案未出清(盘前一键清仓无现价)不判 DONE。
|
||
8. **并发防护**:plan 认领锁(PENDING→PLANNING 条件更新)、四个调度任务加租约互斥、
|
||
指令/提议确定性编号撞唯一键自动重试、beat 任务 expires=50 防堆积。
|
||
9. 其余:动作引擎同轮 TRIM×买入互斥;除权调整同步缩放已核销批次;交易日历按年探测降级;
|
||
网格只买中枢下方;跟踪止盈部分卖一次性闩锁;策略买入暂停按来源分记;宏观失败路径保留
|
||
当日留痕;行业批量查询失败不写日缓存;市场取数失败不缓存;前端十来处(管理员按钮
|
||
v-if、试算防连点、撤销警示展示、今日账本口径等);脚本与回测口径若干。
|
||
10. **测试侧补桩补例**:FakeRepo 补上 pms_strategy 全套桩(原来一个策略函数都没有,
|
||
所有策略接线在单测里靠 try/except 吞异常混过去);FakeRedis 改成真消费组语义
|
||
(">"/"0"/PEL/ack,原桩恰好掩护了试算吞消息的真 bug);新增 test_batch19(17 例
|
||
审查回归);test_wiring 增到 66 例。全套 585 例。
|
||
|
||
**动了哪些文件**
|
||
app 下 26 个、scripts 下 21 个(含新增 `scripts/test_batch19_units.py`),完整清单看本次
|
||
提交的 diff。`.env.example`、`config/settings.py`、鉴权与 cookie 策略这轮**特意没动**
|
||
(属于要拍板的策略项,见「还欠着什么」)。没有 DDL 变更,没有新增 env。
|
||
|
||
**部署方式**
|
||
tlai4090 那台:`git pull` 后 `make deploy`(源码打进镜像,必须重建),再 `make test` 看
|
||
ALL SUITES PASS 且代码指纹与工作树一致。纪律照旧:**收盘后再部署**。
|
||
|
||
**真机判收**
|
||
容器(py3.11,与生产镜像同版本)与开发机本地(py3.10,pip 装齐依赖后)各跑一遍
|
||
`python3 scripts/run_tests.py`,两端都是 ALL SUITES PASS(585 例)。部署后的判收三步:
|
||
① `make test` 全绿且指纹对上;② ws 通道在线时跑一次 `scripts/ws_smoke.py`,SMOKE 单
|
||
必须**不**入账(联调单闸首次真正生效,账本里查不到 99.99 这类假价核销);③ 盘中盯一跳
|
||
`signal_digest` 与 `intraday_exec` 的日志,无新报错。
|
||
|
||
**还欠着什么**
|
||
1. 单笔成交「入账成功→inbox 标记」之间仍非原子:恰好崩在这个窗口会重复入账一笔。
|
||
根治要给 trade 入账加 trade_no 唯一键(DDL 变更),等拍板。
|
||
2. `.env.example` 里 `SIGNAL_REDIS_PASSWORD` 是真密码,建议轮换并改成占位符;
|
||
`PMS_ADMIN_PERMS=band:yhyqx` 维持联测拍板不动。
|
||
3. `/api/params` 目前只把 `PMS_DISPATCH_MODE` 收进管理员白名单,其余自治类参数键
|
||
(自主档位、总规模这类)要不要一并收,等拍板。
|
||
4. cookie 的 Secure 标记、登出的服务端失效、鉴权默认拒绝的结构调整,这轮没动。
|
||
5. GATED 批仍没有解锁机制(这轮只是让它不再拖坏进度结算),补解锁还是砍掉这层,等拍板。
|
||
6. 审查报告第八节里"疑似待核"的几条(交接冷却文档口径与实现不一致、派发→止盈边沿)未处理。
|
||
7. `.git` 下有几个 `index.lock.stale*` 空文件(云会话删不了文件只能改名避让),可随手删掉。
|
||
|
||
---
|
||
|
||
## 2026-08-28 · 页面口径两处修正:信号默认只看今天、方向色统一红涨绿跌
|
||
|
||
**做了什么**
|
||
1. **信号默认只看今天。** 风控卖出流 (bionic:signals:llm_sell_actions) 不按日期分键,
|
||
清晨没有新信号时 xrevrange 取到的「最新 N 条」全是昨天以前的, 而页面只显示时分 ——
|
||
昨天 14:49 的建议卖出挂在今天 13:20 的页面上冒充新消息。后端 upstream_signals 给
|
||
每行补 ymd 与 today 标记 (时间戳解析不了的按今天算, 风控从宽); 页面信号流速览默认
|
||
只显示今天, 昨天及以前收进底部「更早的信号」折叠区 (带日期、置灰、默认收起);
|
||
顶部消息栏不再把昨天的风控卖出算进今天的消息; 「今天的信号」列与持仓角标只数今天。
|
||
2. **方向色统一 A股口径**: 买入/看涨/流入类一律红, 卖出/看跌/流出类一律绿。原来模板
|
||
词表里没有「买入/卖出」, 买入落到 else 被涂成绿色, 方向恰好用反; 决策系统买卖广播
|
||
原来借状态色 (建议卖出=红 deny / 建议买入=绿 done) 同样是反的。新增方向配色函数
|
||
dirCls (认不出的词给中性灰, 不硬猜) 与方向药丸样式 sig-up / sig-down。
|
||
3. 顺带: 「把握 72(满分 1)」这类混乱显示统一折成百分比 —— 上游有的发 0.72、有的发
|
||
72, 页面见谁都显示 72%。
|
||
|
||
**动了哪些文件**
|
||
app/services/upstream_signals.py(风控卖出行补 ymd/today)、app/web/static/index.html。
|
||
|
||
**部署方式**
|
||
tlai4090: git pull 后 make deploy。纯展示层 + 只读接口字段增补, 无 DDL、无新 env、
|
||
无参数变更, 不影响任何下单与入账路径。
|
||
|
||
**真机判收**
|
||
开发机: 全部 20 套 585 例单测 ALL PASS; 页面内嵌脚本 node --check 与 esprima 解析
|
||
双通过; 模板标签配平。部署后开页面看三处: ① 信号流标题变成「今天 N 条」, 旧条目
|
||
只出现在「更早的信号」折叠区且带日期; ② 顶部消息栏不再出现晚于当前时刻的时间;
|
||
③ 信号里买入类是红字、卖出类是绿字, 建议卖出药丸是绿底。
|
||
|
||
**还欠着什么**
|
||
无新增欠账。pms-ws 启动日志与 ws_smoke 打印的端点仍取 settings 层 (非参数表优先的
|
||
最终生效值), 上一条节点已记, 待下轮一起改。
|
||
|
||
---
|
||
|
||
## 2026-08-28 · 模拟/正式双系统:定方案(一份代码两份 .env)+ compose 参数化
|
||
|
||
**做了什么**
|
||
QMT 从模拟转正式, 定下双系统方案并落了隔离改造。方案全文见 REAL_TRADING_DEPLOY_PLAN.md,
|
||
要点: 现有这套原地不动继续当模拟, 正式盘在另一台服务器单独部署; 不开分支不复制仓库,
|
||
一份代码两份 .env, 实例身份由 .env 与各自库里的参数表决定。必须隔离的五样:
|
||
PMS 业务库 (pms_* 必须新库)、Celery 总线 redis db (db8→db9, 同 db 两套 worker 互相抢
|
||
任务)、信号消费组名 (同组一条消息只投一家, PMS_SIGNAL_GROUP 已在 settings 声明,
|
||
.env 直接生效)、QMT 端点与签名密钥对 (绝不复用)、会话密钥。行情/大盘/mtf/决策系统
|
||
信号流是只读共享, 不用动。
|
||
|
||
代码侧只动了部署壳: docker-compose.yml 的容器名前缀 / 镜像标签 / 页面宿主机端口改成
|
||
从 .env 取值 (PMS_STACK_PREFIX / PMS_IMAGE / PMS_WEB_PORT_HOST), Makefile 的三处
|
||
硬编码 38100 改走 PORT 变量。**默认值与从前逐字一致, 现有部署零感知**; 将来两套要
|
||
合并到一台机器时也不用再改代码。
|
||
|
||
同日随拍板收敛到最终口径 —— **两套系统共用 my_quant_db, 全部靠表名区分**:
|
||
① 下游表: 正式 QMT 在原库写另一套表名, 四张下游只读表的表名做成 .env 可配
|
||
(settings.PMS_DS_TABLE_* → downstream_repo 统一 T_* 常量, 白名单校验, 不合法直接
|
||
拒绝启动 —— 宁可大声失败, 不能静默退回默认表名把另一套系统的账读进来);
|
||
② PMS 自有表: 表名前缀 (PMS_TABLE_PREFIX="real_" → real_pms_*)。全仓库 129 处表名
|
||
引用**不逐条改**, 在 db.session 这个 SQL 执行唯一入口统一映射 (词边界匹配小写
|
||
pms_* 标识符; 空前缀零改写, 模拟行为逐字节不变; 已带前缀不二次加; 前缀白名单校验,
|
||
非法值拒绝启动)。两个绕过入口直执 DDL 的脚本 (init_db / migrate_archived_at) 显式
|
||
过 map_tables —— 不接的话正式实例 make deploy 会建出**无前缀**的表, 与模拟的表撞
|
||
在一起; test_batch19 加 [K2] 旁路扫描钉死"不许有第三个绕行者"。
|
||
|
||
**动了哪些文件**
|
||
docker-compose.yml、Makefile、config/settings.py、app/db/session.py、
|
||
app/repo/downstream_repo.py、app/services/strategy_advisor.py、scripts/init_db.py、
|
||
scripts/migrate_archived_at.py、scripts/probe_strategy_signals.py、
|
||
scripts/test_batch19_units.py、scripts/run_tests.py(新增 2 例, 全套 587 例)、
|
||
新增 REAL_TRADING_DEPLOY_PLAN.md。
|
||
|
||
**部署方式**
|
||
模拟 (tlai4090): 随下次 git pull + make deploy 自然带上, 无需专门发版。
|
||
正式 (新服务器): 照 REAL_TRADING_DEPLOY_PLAN.md 第四节九步走。
|
||
|
||
**真机判收**
|
||
开发机: compose YAML 解析通过; make -n health 默认仍打 38100、PORT=38200 时打 38200;
|
||
表名映射按七种 SQL 形态现场验证 (查/插/改/DDL/反引号/大写参数键名/幂等), 映射后仍过
|
||
单表守卫; 全量 20 套 587 例单测 ALL PASS。模拟侧空前缀零改写, 部署后行为与从前逐字
|
||
一致; `docker compose ps` 容器名也应与从前完全一致 (前缀默认空)。
|
||
|
||
**还欠着什么**
|
||
1. 部署前还差两件对接: 向 QMT 侧要正式下游四张表的**实际表名** (填 .env 的
|
||
PMS_DS_TABLE_*); 交换正式 ws 密钥对。库不用建、代理不用动 (同库表名前缀方案)。
|
||
2. 正式盘上线初期纪律: shadow + propose_only + 新建仓 off, 影子期至少 3 个交易日。
|
||
|
||
---
|
||
|
||
<!--
|
||
下一条节点从这里往下写,格式照抄上面:
|
||
## YYYY-MM-DD · 一句话标题
|
||
**做了什么** / **动了哪些文件** / **部署方式** / **真机判收** / **还欠着什么**
|
||
-->
|