# 开发节点记录(三系统量化交易) > 这份文件的用途只有一个:**让下一次交接不必重新查一遍**。 > 每个重要节点写一条,写完就补,不攒到最后。一条节点包含五样东西: > 做了什么、动了哪些文件、部署方式、真机判收结果、还欠着什么。 > > 三个系统的部署方式(改错等于白改): > - **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 个交易日。 --- ## 2026-08-28 · 公示导出重做:模板文件直填 + 持仓合一 + 含费口径, 588 例全绿 **做了什么** 公司流程要求每日把持仓与净值按固定 excel 版式导出公示。在既有实现 (按钮/接口/参数/ 净值快照表/调度 都已就位) 的基础上重做了两层: 1. **版式不再用代码重画**: 把公司模板裁成空模板进仓库 (app/assets/publish_template.xlsx, 保留全部字体/颜色/边框/列宽/数字格式/行高), 导出时只插行、填数、按最终行位重排合并区 与行高 (openpyxl 插删行不搬 merges 与 row_dimensions, 全部手工重排)。图表按模板标题 与占位重建 (原模板图表引用外部工作簿, 数据改为本表净值序列)。 2. **口径按模板逐项对数**: 持仓明细按**股票合并一行** (多批加权成本、起始取最早); 平仓明细按 **(股票, 平仓日)** 合并; 自然天数**含头含尾** (7-03→8-03 记 32, 原实现差 1); 持仓行盈亏不含费 (模板 S=Q×L 逐分一致)、**平仓行盈亏 = 已实现 − 当日该票费用** (模板卖出行含双边费, 费用取 pms_cash_flow FEE 按日按码聚合); 买入侧/校准费摊不进 明细的部分在表尾按残差如实标注 —— S 合计=持仓Σ+平仓Σ 是模板恒等式, 不为塞费用破坏它。 表内金额与比率一律写公式 (SUM/引用), 不写死数。 **动了哪些文件** app/services/publish_export.py (重写)、app/repo/pms_repo.py (fee_sum_by_code / sum_fee_all 两个聚合)、scripts/test_wiring.py (FakeRepo 补 lot 明细/净值快照/费用桩, 新增公示导出装配用例)、scripts/run_tests.py (67/588)、新增 app/assets/publish_template.xlsx。 **部署方式** tlai4090: git pull 后 make deploy (openpyxl 已在 requirements, pms_nav_daily 建表由 init_db 幂等带上)。无新 env。 **真机判收** 开发机: 全部 20 套 588 例 ALL PASS; 生成样例经 LibreOffice 全量重算 54 条公式 0 错误, 恒等式 (S合计/剩余可开仓/仓位/累计净值) 现场核验; 样例与公司原表并排渲染逐区比对 (段落标签/小计粉底/存量合计黄条/底行/图表占位)。部署后判收: 页面点「导出公示表」, 下载的表与人工表并排看一屏。 **还欠着什么** 1. 三个口径请使用者过目拍板 (都按模板反推, 改口一句话): 平仓按(票,平仓日)合并; 平仓行含费=已实现−当日该票 FEE; 起始时间=系统接管日 (不回填历史)。 2. 图表纵轴范围/网格样式与公司原图未逐像素对齐 (原图数据在外部工作簿, 只对齐了 标题/位置/尺寸), 有要求再调。 --- ## 2026-08-28 · 公示导出正式机实锅: 模板隐藏行把合计与图整段藏掉 **做了什么** 正式机首次导出缺了最底部合计行、图表空框。根因: 公司原表把老平仓行折叠隐藏 (24~97 行 hidden), 裁模板时 openpyxl 的 delete_rows 不搬 row_dimensions, 这批隐藏 标记原样进了模板; 表体一长到 24 行 (真机 7 持仓+10 平仓正好踩中), 卖出小计/存量 合计/底行/图表数据全落隐藏带 —— 此前样张 7+7 只到 23 行, 所以怎么测都完整。 开发机 openpyxl 3.1.5 实验复现坐实。修法两层: 模板重裁 (行属性只留 1~11 行、隐藏 标记清零、剔除全部残留条件格式区间与外部工作簿链接, 顺带补回裁剪时丢的「存量合计」 「初始规模+已回收益后可开仓总金额」两个 B 列标签 —— 与上次「平层卖出」同类伤); 代码兜底 (进场即清模板行位之外的行属性与残留条件格式, 行高重排时强制 hidden=False)。 红绿条件格式按最终行位重建: Q/R/S 红涨绿跌字色、持仓 S 列红绿底色 (0 也算红), 颜色与原表 dxf 逐字节同 (openpyxl 去重后直接命中原 18 个 dxf)。图表按原图重做双系列: 净值主轴(左)+仓位占比副轴(右), 每点带标记 (首日单点也看得见), plotVisOnly=0, smooth=0。O/P 融资两列原表就是折叠的, 属版式, 保留。 **动了哪些文件** app/assets/publish_template.xlsx (重裁)、app/services/publish_export.py (卫生+条件 格式+双系列图)、scripts/test_wiring.py (公示导出用例补大表回归: 7+10 越过隐藏带, 三行合计/无隐藏行/条件格式区间/双系列带标记断言钉死)。 **部署方式** 两台都要: git pull 后重新 build 部署 (模板打进镜像, 必须重建)。真机 188 与模拟机 tlai4090 同法。无新 env、无 DDL。 **真机判收** 开发机全部 20 套 588 例 ALL PASS (含新回归); 云端 LibreOffice 重算 92 公式 0 错误, 恒等式核过; 7+10 形状与 30 日净值序列两版渲染逐区目验 (三行合计齐、黄条粉底在、 双系列图两轴两色带标记)。部署后判收: 重新点「导出公示表」, 底部三行 (卖出小计/ 存量合计/底行) 与图表单点标记都应出现。 **还欠着什么** 1. **样式仍有出入 (未决, 新会话第一件事)**: 隐藏行修复后使用者反馈「表格样式还是不对」, 具体差异点没来得及描述。新会话需要: 让使用者指认哪里不对 (截图或把导出的 xlsx 发回), 对照基准用仓库根目录的 公司原表-量化数据2026.8.3.xlsx (2026-08-28 已入库), 逐区 diff (候选疑点: O/P 融资列折叠是否符合预期、黄条/粉底跨的列数、字体字号、列宽、边框、 图表配色与轴样式、标题行)。改样式动 app/assets/publish_template.xlsx 与 publish_export.py, 改完跑 scripts/test_wiring.py + 全套 588 例。 2. 部署状态待确认: 本节点三个文件已回写开发机工作区, commit/push 与两台服务器 (真机 188 / 模拟机 tlai4090) 的 git pull + make deploy 是否已做, 新会话先问。 3. 上节点的三个口径拍板照旧欠着; 图表逐像素对齐照旧按需。 --- ## 2026-08-31 · 查透「通道要人工做一次全量对账」+ 参数列 1406 截断 + iPad 点不开侧栏 + 信号标计划 **做了什么** 使用者报三件事: 页面一直挂「通道要人工做一次全量对账」不知怎么操作; iPad Pro 上点 左右窄条 (信号流 / 待办与操作) 展不开; 想在速览「没持仓的票」段标出选股计划里的票。 只读排查模拟机 (155), 挖出三个真问题, 全部修掉: 一、**水位卡死三天** (对账横幅的根源)。2026-08-28 17:21 pms-ws 重启首连时, 对端补发的 99 条 pong 全部验签失败被丢 (SIG_INVALID, 一次性, 之后再无), 每条占一个 seq → 水位卡在 365003 洞前; `_baselined` 一经置位终生不再对齐, 会话内的洞永远等不来 (ws 是有序流, §6.1 补发只在重连握手时), 暂存涨到 5.3 万条、ack 停摆、重连时对端整段重发 4.9 万条。 修法两层: `_session` 每连接重置 `_baselined` (重连即重对齐, 连续时是空操作); 新增**弃洞前进** watchdog (`_maybe_skip_gap` + 纯函数 `ws_codec.skip_gap`): 水位在同一 位置卡满 PMS_QMT_GAP_SKIP_SEC (默认 300 秒, 参数中心可调, 0=关) 就按 §6.2 认定补不出 来 —— 推水位到已到达最高序号、清暂存、置 resync 标记走全量对账。暂存序号全部来自验签 通过的消息, 推进目标不受伪造数据影响。另加 sig_invalid / pending_gap / gap_skips 三个 stat 计数, ws_smoke status 逐条给人话告警。 二、**pms_runtime_param.param_value VARCHAR(200) 装不下会长大的 JSON** (每分钟两处 1406 截断错的共同病根)。清场名单 PMS_EXIT_CLEANUP_DONE (30 只闭仓票就超长; 存不上时 「每次闭仓只清一次」防呆失守, 闭仓后新下的建仓单会被清场误撤) 与回放游标 PMS_REPLAY_CURSOR (08-28 改 v2 带 seen 去重字典后必超; 游标推不动, 表回放每分钟报 「游标写入失败」)。修法: DDL 放宽为 TEXT, 已有库跑新脚本 scripts/migrate_param_value_text.py (幂等, 默认演练, --yes 执行, 显式过 map_tables, batch19 [K2] 白名单同步加上)。 三、**页面**: ① 运维抽屉补上「清除通道对账标记」按钮 —— 后端接口 (POST /api/ws-channel/clear-resync, 管理员) 早就有, 页面一直没入口, 横幅让人「经运维 抽屉清除」却无处可点; 按钮带确认弹窗, 只在 resync_required 时出现。② iPad 点不开侧栏: 本地台架实测鼠标路径完好, 判为 iPad Safari 对非控件元素点按不合成 click 的老毛病; 修法是窄条与蒙层加 touchend 直连 (touchstart 记起点, 位移 >12px 判滑动误触不开, preventDefault 挡合成 click 防双触发), CSS 加 touch-action:manipulation 与 @media(pointer:coarse) 命中区加大 (窄条 40→48px)。鼠标端零改动 (无 touch 事件, 走原 @click; coarse 媒询不命中)。③ 速览「没持仓的票」段: 行首加蓝色「计划」标 —— 票在 今天上游选股计划主榜 (planRows, 登录即随 loadPlan 加载) 就标, 段标题写明含义; 「更早的信号」段同样标 (仅非持仓行)。 顺带查明账实差异现状: 账本与 ws 快照差 5 只 (002436 少 1600 / 002709 少 300 / 300308 多 900 / 301308 多 100 / 688676 多 3600), 正是缺口期丢成交所致; 爆炸半径闸 判据是「>5 只且 >34%」, 5 只不触发, 15:10 日终结算的全量对账会自动照 ws 修正留痕。 另发现 trading_position 表只有 2 只与 ws 快照 (7 只) 不同步, 事实源仲裁已按 ws 为准 并告警 —— 「谁在写那张表」要与 QMT 侧核对。 **动了哪些文件** app/ws/runner.py (每连接重置基线 + 弃洞 watchdog + sig_invalid 计数)、 app/core/ws_codec.py (skip_gap)、app/web/static/index.html (清标记按钮 + 触屏直连 + 计划标)、ddl_pms_v1.sql (param_value → TEXT)、新增 scripts/migrate_param_value_text.py、 scripts/ws_smoke.py (三条新告警)、scripts/test_batch6_units.py (弃洞 3 例, 65→68)、 scripts/test_batch19_units.py ([K2] 白名单)、scripts/run_tests.py (共 591 例)、README.md。 **部署方式** 模拟机 155: git pull 后 make deploy (源码打镜像必须重建); 之后跑一次 `docker compose run --rm --no-deps pms-web python scripts/migrate_param_value_text.py --yes` (ALTER 秒级, 不用重启, 新库建表自带 TEXT 不用跑)。真机 188 同法, 时点等 QMT 侧联调 恢复时一并 (它同样带着 VARCHAR(200) 和旧 runner)。 **真机判收** 开发机: 全部 20 套 591 例 ALL SUITES PASS; 本地台架 1024×1366 实测 —— 鼠标点窄条开浮层、 触摸序列开浮层、滑动 380px 误触不开、触摸蒙层关闭、控制台无脚本错误。 部署后待判收: ① pms-ws 起来后 5 分钟内日志出现「水位弃洞前进」且 make ws-status 水位 追到当前、暂存归零; ② 页面「账本对账」差异清零后点「清除通道对账标记」, 横幅消失且 不复发; ③ worker 日志不再刷 1406; ④ iPad 实机点窄条能展开 (模拟器只能模拟到触摸事件 层, Safari 真机行为要使用者过手); ⑤ 速览出现蓝「计划」标。 **还欠着什么** 1. 155 部署与迁移待用户批准执行 (容器重建 + ALTER TABLE 各要一次授权)。 2. 全量对账清差异 + 清标记按钮点掉横幅, 待部署后走一遍 (15:10 结算大概率已自动修正 差异, 部署后核对 make watch 与页面对账结果即可)。 3. 与 QMT 模拟侧核对两件: 08-28 17:21 那批 pong 为何验签失败 (重放重签? 密钥轮换?); trading_position 表还该不该有人写。 4. iPad 实机判收 (第 ④ 条) 待使用者过手。 5. 真机 188 的同款部署与迁移, 等 QMT 侧联调窗口。 --- ## 2026-09-01 · iPad 浮层实锅重修: teleport 出栈 + 只用老写法的浮层几何 **做了什么** 使用者发来 iPad Pro 实机截图: 点右窄条浮层**能开但渲染烂了** —— 面板无背景、无遮罩, 内容与底下持仓表的浮动操作列穿插, 选股计划抽屉的深色提示块也粘在屏上。与 08-31 的判断 修正一处: 实机上点击事件是通的, 坏的是浮层渲染。两个独立机制都补死: 一, **层叠上下文困局**: 浮层 aside 在 position:sticky 的 side 容器里, sticky 自成层叠 上下文, 固定定位的浮层被困在里面, 与主区 el-table 浮动列 (自带 z-index) 的绘制次序在 部分 Safari 上穿插。修法: 两个浮层与遮罩用 Vue 内建 teleport 挂到 body 下 (:disabled 绑定「非浮层态」, 常驻停靠时原地不动, 行为与从前逐字节相同)。 二, **新式 CSS 整条被丢**: 浮层宽度用 min(560px,88vw)、遮罩用 inset:0, 旧 Safari (min 需 11.1+, inset 需 14.5+) 会把整条声明丢掉 —— 遮罩变 0×0 不可见。改成 width+max-width 与 top/left/right/bottom 四边的等价老写法, 面板背景加一条 #fff 兜底 再让变量覆盖。顺带: 触屏上 ? 号悬停气泡点按后粘住不消失 (截图那块黑浮块), @media(hover:none) 下不出气泡; 新增 ?uidbg=1 远程排查小牌 (印浏览器 UA / 视口 / 停靠态 / 浮层态, 平时不出现), iPad 再有样式问题一张截图即可定位。 **动了哪些文件** app/web/static/index.html (teleport ×3、浮层几何老写法、背景兜底、气泡抑制、uidbg)。 **部署方式** 随 08-31 那批一起: 155 git pull 后 make deploy, 另跑一次 migrate_param_value_text.py --yes。 **真机判收** 开发机台架两形态实测: 1024×1366 浮层 —— 面板已挂 body 下、position fixed、宽 560、 白底、遮罩 display:block、z 2010; 1920×1080 常驻 —— 两栏 parent 仍是 side 容器 (teleport 未触发)、窄条隐藏、三栏布局与改前一致; 控制台无脚本错误。 部署后待判收: iPad 实机点窄条, 浮层应当白底带阴影、背后有半透明遮罩、点遮罩即关。 **还欠着什么** 1. 随 08-31 节点: 155 部署+迁移待批; iPad 实机判收待使用者过手 (地址后加 ?uidbg=1 截图可带上排查小牌)。 2. 模拟仓重启方案已给使用者建议 (彻底清盘: 盘中紧急离场卖光 → 收盘后 purge-dead-orders + reset-ledger → 次日从零自动建仓; 清账前先存档), 待拍板。 --- ## 2026-09-01 · 模拟仓清盘重启 + 08-31 那批修复上线判收 + 查明研判闸云端失败的真因 **做了什么** 用户拍板模拟仓从零重跑 (总规模仍 200 万)。执行顺序与真机读数: ① 清账前存档: 19 张 pms_* 表全量导出 JSON, 435,580 行, 落 155:~/project/tradingSystem/backups/20260901_pre_reset.tgz (9.6MB)。 ② 155 部署 a507e7a (用户执行 make deploy + make test, ALL SUITES PASS 591 例; 我随后 docker exec 跑 migrate_param_value_text.py --yes, param_value 已 TEXT)。 ③ 08-31 修复全部真机判收过: **弃洞前进 09:31:04 实弹触发** —— 水位卡 365005 满 300 秒, 一跳推到 436610, 暂存 36,227 条消化、35,378 个死洞放弃; 1406 截断错误归零 (迁移后 2 分钟窗口 0 条); 每分钟两处报错双双绝迹。 ④ 09:40 用户页面点一键清仓 (CMD_20260901_0001), 09:54 持仓 0 / 在途 0 / 命令 DONE。 ⑤ 用户跑 make purge-dead-orders (删 11 行) + make reset-ledger (清 3,023 行, 游标 重新播种到最新成交, RECON_STREAK/HIGH_WATER/BRAKE 归零, 水位与通道两表未动)。 ⑥ 重启后核查: 规模 2,000,000 / 信号开 / 自主 full / 新建仓 full / 下发 ws; 账本 0; 自动扫描已出第一张 OPEN 提议 (002594) —— 但停在 WAIT_USER, 引出下一条。 **研判闸云端失败的真因 (bionic_trader, 只读查明, 未动)** PMS 侧 40 分钟内研判闸全数降级 (WAIT_USER), 两种交替: 「75s 超时」与「模型输出非法 JSON 回退 MAINTAIN」。查明: 188 bionic .env 里 LLM_PROVIDER/BRAIN/INTRADAY 全是 local, 仅 LLM_PROVIDER_PMS_JUDGE=deepseek (bfb4e3e 已部署, worker Up 4 天); 失败都在 trader_worker_pms (今天 8 次)。pms_judge 用 PMS_JUDGE_ONLINE_EFFORT=medium + MAX_TOKENS=8192 + httpx 70s (皆 settings 默认, .env 未覆盖) —— DeepSeek 推理档 medium 的思考 token 计入 max_tokens 且拉长时延: 思考吃掉预算则正文 JSON 截断 (非法 JSON), 思考拖过 70s 则超时, 两个症状同一个根。夜间深度分析 (tasks_brain, 走本地) 的 4 次 attempt-1 失败均由 attempt-2 严格重试自愈, 不是问题; 但 tasks_intraday 的同款严格重试开关 PMS_JUDGE_REPARSE 默认 0 没开。 **建议修法 (188 bionic .env 三行 + force-recreate trader_worker_pms, 待用户执行)**: PMS_JUDGE_ONLINE_EFFORT=low / PMS_JUDGE_ONLINE_MAX_TOKENS=16384 / PMS_JUDGE_REPARSE=1。 回退: 删这三行再 force-recreate。只影响 pms_judge 环节, 夜间分析与风控不涉。 **动了哪些文件** 无代码改动 (bionic 只查未动)。155 服务器状态变更如上; DEVLOG 本节点。 **真机判收** 见上文各步读数。待判收: iPad 浮层 (teleport 版) 用户过手; 运维抽屉新按钮走一遍。 **还欠着什么** 1. **模拟账户残留 400 股 002436** (兴森科技, 账本 0 / 账户 400, 清仓命令 DONE 后仍在): 随 QMT 侧重置一并消掉 —— 已建议用户通知 QMT 把模拟账户重置为**现金 200 万、零持仓** (顺带把净值基线对齐规模)。若 QMT 不重置, 备选: 页面点「账本对账」把 400 股收编, 再对该票点清仓。 2. 对账横幅: 等账户重置/残留清掉后, 页面「账本对账」(差异应为空) →「清除通道对账标记」。 3. ~~bionic 三行 env 修法~~ **已执行并判收过** (2026-09-01 10:30 前后): .env 三行 + force-recreate worker-pms (注意 compose 服务名带横线, 容器名 trader_worker_pms 会报 no such service); probe_bionic 实测 pms_judge **7.9 秒**返回 REJECT(85) 且理由完整, 对比修前 70s 超时/截断。自然流量的下一张 OPEN 提议应自动过闸。遗留的 002594 WAIT_USER 提议是修前降级产物, 用户同意或驳回均可。顺带观察: pms_exec 探活报探活 代码 600000.SH 的昨夜结论停在 20260730 —— 探活票不在分析池属预期, PMS_EXEC_IMPL=B 不受影响。 4. trading_position 表写入方与 QMT 不同步照旧待与 QMT 侧核对; 155 上四周前的残留 一次性容器 tradingsystem-pms-web-run-a4f4a87e7662 (unhealthy) 建议顺手删掉。 --- ## 2026-09-11 · 技术面接入工作包一:技术面这一路打通(后端,未真机判收) **做了什么** 落地《技术面接入与三源合议方案_2026-09-11》第五节工作包一的后端部分,让每只票多出一份结构化的技术面立场。数据来自决策系统新开的全市场技术面接口。 - 先只读探活了 188 的接口 `GET /api/v1/market/technical`,坐实字段形状:外层是 status、data_date、algo_version、matched、items;每只票带 boll(含 squeeze 收口布尔、bandwidth_pct、pos、state)、bbiboll(含 state 多头区/中性区/空头区)、sar(含 side 多空、flip_days 翻向天数、value 止损位)、reanchored、quality。代码是 SH600000 式,落库前归一成点式 600000.SH。 - 新表 pms_tech_daily 存全市场每日读数,唯一键是读数日加代码,保留 40 个交易日。存全市场而非只存持仓与候选,是因为新进榜的票要 20 个交易日历史才判得了震荡。 - 相位合成 `app/core/tech_rules.py` 是纯逻辑:先判有没有读数(无读数一律弃权,绝不折成看空),再判震荡市(最近 20 个交易日 SAR 翻向四次),再按固定次序判九个相位(收口等待、开口向上向下、转空转多、趋势多空、分歧、震荡)。五个阈值一次定死,记在台账 006。 - `app/services/tech_service.py` 分页拉接口、宽松解析、落表(接口状态非 OK 整轮不落表),每早对在持、计划主榜观察、待拍板的票合成立场写进映射 `PMS_TECH_STATE_MAP`,手法照 logic_state_service:读不到留空带原因,超期按无读数。 - 调度新增 06:30 的 `pms.tech_pull`;`plan_pull` 在 08:40 拉完计划后补拉一次并重建映射,让覆盖带上当天的榜。 - 三个接口:`GET /api/tech/status`、`POST /api/ops/tech-pull`(管理员)、`GET /api/research/{ts_code}`(单票研究面,工作包一先给技术面一块,其余块工作包二补)。 - 十一个 PMS_TECH_* 参数登记进 param_store 的 RUNTIME_EXTRA 与 _RANGES。 **动了哪些文件** 新增 `app/core/tech_rules.py`、`app/repo/tech_repo.py`、`app/services/tech_service.py`、`scripts/test_batch27_units.py`。 改 `ddl_pms_v1.sql`(第 20 张表)、`app/core/tradedays.py`(加 prev_trade_day)、`app/services/param_store.py`、`app/scheduler.py`、`app/web/main.py`、`scripts/test_batch6_units.py`(DDL 表数 19 改 20)、`scripts/test_wiring.py`(调度位加 tech_pull、路由加三接口)、`scripts/run_tests.py`(登记 batch27,顺手补登记既有遗漏的 batch26)。 **部署方式** 改了 Python,要 `make deploy` 重建镜像加 force-recreate,再 `init_db` 建出 pms_tech_daily。只在模拟仓 155 收盘后执行,先经用户审批。接口地址留空即沿用研判接口根地址 `PMS_JUDGE_API_BASE`。 **真机判收** 未判收。开发机临时 venv 跑全量单测见 ALL SUITES PASS(含新增 batch27 二十七例)。接口字段已只读探活 188 坐实。待 155 部署后按方案第七节判收:make test、pull_and_map 行数五千上下、映射只数等于持仓加候选、status、第一个交易日 08:4x 映射写入时刻与读数日。 **还欠着什么** 1. 页面已补技术面这一路的核心三项:顶栏新鲜度芯片、我的持仓「研究面」列、单票抽屉「研究面·技术面」节;设置页自动列出十一个 PMS_TECH_* 参数(无需另做设置卡)。持仓接口每行挂 tech 立场。开发机起本地服务冒烟确认不白屏、芯片与列渲染正确。剩下与三源合议耦合的两项——候选栏技术芯片、管理视图持仓总览修显示错误——并入工作包二,与提议卡三芯片、基本面质地芯片一次铺齐,避免返工。 2. 工作包二(三源合议与仓位矩阵)、工作包三(离场纪律)、工作包四(选股打分,另一仓库单独审批)、工作包五(README 调度总表补 13 位等文档)未做。 3. BIONIC_PMS_INTERFACE.md 需补技术面只读接口一节(本次接口探活的字段事实源)。 --- ## 2026-09-12 · 提议卡重构:卡面留结论、详情进弹窗、加技术面(未真机判收) **做了什么** 用户反馈新建仓提议卡信息过载 —— 选股系统、决策系统、公司深度三段整句全平铺在卡面,公司深度那句最长,占了大半屏。重构成卡面只留结论、点开弹窗看详情。 - 卡面留数量、建议档位、四行来源结论(选股系统、决策系统、公司深度、技术面),采纳驳回。每行一个彩色结论标签加一句副文案,点开弹窗。 - 弹窗 teleport 到 body,层级 3000/2995 同单票信号抽屉,从底部滑出。四类详情各自排版:选股系统给判决整句、理由、榜单;决策系统给结论、把握度、原话、择时层两期限;公司深度给质地档、估值、十视角计数、买方论点、失效条件、反方、催化、报告链接,分段带彩色小标题;技术面给立场加布林、多空布林线、SAR 三个指标块,点开时调 /api/research 取完整读数。 - 颜色按页面口径红涨绿跌:技术面看多用红、看空用绿;评价类(候选、质地好)用中性蓝,交你定用黄。 **动了哪些文件** app/web/static/index.html(CSS 加来源行与弹窗样式、模板卡面重构加弹窗、JS 加四个结论标签函数与弹窗开关、return 导出),app/web/main.py(/api/proposals 每条挂技术面立场 tech)。 **部署方式** 改了 Python 与页面,make deploy 重建镜像加 force-recreate;收盘后 155,先经审批。 **真机判收** 未判收。两道页面守卫 ALL OK。开发机注入一条华测导航假提议起本地服务,截图确认卡面四行结论、技术面弹窗三指标块、公司深度弹窗四段分组都渲染正确、不白屏。待 155 有真实提议时判收。 **还欠着什么** 合议行、候选芯片仍是工作包二。提议卡的技术面立场依赖工作包一的技术面映射(已上,未真机判收)。 --- ## 2026-09-11 · 三源合议接入下单链路:工作包二的接入部分(后端加页面,未真机判收) **做了什么** 把三源合议这三块「大脑」(纯逻辑、装配服务、仓位矩阵,上一轮已写好并全量绿)接进了新建仓与持仓的下单链路。落地《技术面接入与三源合议方案_2026-09-11》第五节工作包二的接入部分,也就是进度交接文档第五节留给下一轮的活。每一处都守「开关关掉即逐字回旧」,并先写这条单测再改。 一,候选装配合议。提议服务的新建仓扫描在候选进动作引擎之前,批量取一次昨夜定性与技术面映射,逐只装配三票,把合议路由、四块意见、六个硬数字键挂到候选上。装不上就当没有合议,退回旧路,绝不拦扫描。开关 PMS_CONSENSUS_ROUTE 关掉整段不做。 二,新建仓合议分流。动作引擎的候选扫描里加合议分流,放在选股判决分流之后、名额判断之前。跳过与观察不占名额不占金额,观察带处置词 wait_tech。交人打强制人工确认。放行照常。缺合议退回旧路。 三,定档改用仓位矩阵。合议开着且候选装配了四块意见时,定档从旧的 advise 改用 advise_v2 的基本面乘技术面矩阵。六个硬数字键进评审账本与提议卡,但一个都不进送研判白名单——机器证明:judge 裁剪后确实不含这六键。 四,持仓增持门。持仓行早上装配一份合议四块。合议看空停回踩补足、盈利加仓、补仓三类。技术面看空停回踩补足与盈利加仓。基本面看空停补仓。减持侧一律不受门影响。开关 PMS_TECH_GATE_INCREASE 关掉即不设门。 五,候选按质地排序。候选先按基本面立场分桶,质地看多的桶排前,再按分数,在主题限额与截断之前生效。技术面不参与排序只当门。观察档里质地看多的行进池另有开关,默认关。 六,页面。提议卡在四行来源之后加了「合议」一行,显示三方多数方向与强弱,点开弹窗看三票与路由理由;合议读的是下单当时记进硬数字的那份。候选处置新增「等技术面开口」,归到在盯那一档而不是今天没建仓那一档。 **动了哪些文件** app/core/action_engine.py(合议分流五处:开关、跳过观察分流、advise_v2 定档、六键进硬数字、交人确认;增持门 consensus_gate_why 与 scan 里那道门;处置词常量 DISP_WAIT_TECH);app/services/consensus_service.py(decorate_positions 给持仓装配四块);app/services/proposal_service.py(_attach_consensus 批量装配挂候选、扫描主流程装配持仓合议、处置快照透传 disp、扫描参数加两个开关、导入 consensus_service);app/services/plan_feed.py(select_candidates 按质地分桶排序与观察档进池、_fund_stance_of、候选入口传两个开关与 _params 登记);app/services/param_store.py(登记 PMS_TECH_GATE_INCREASE、PMS_PLAN_RANK_BY_QUALITY、PMS_PLAN_OBSERVE_IF_FUND_BULL 三个开关);app/web/static/index.html(提议卡合议行、合议详情弹窗、候选等技术面开口、三个函数导出);scripts/test_batch29_units.py(新增二十四例);scripts/run_tests.py(登记第二十九批与例数、总数改 787);docs/复盘决定台账.md(台账 007 基本面立场、008 三源合议方向与路由、009 候选排序与增持门)。 **部署方式** 改了 Python 与页面,make deploy 重建镜像加 force-recreate(绝不能用 restart)。已于 2026-09-11 收盘后(容器内北京时间 16:29)在模拟仓 155 部署,一并拉上了上一轮已推远程但 155 未拉的 advise_v2(0dd5b68)。真实仓 188 本次不动。提交 51815d5 已推远程。 **真机判收** 真机见过(155,2026-09-11 收盘后)。make deploy 后 20 张表就绪(pms_tech_daily 4991 行),make test 见 ALL SUITES PASS,make stale 见镜像与工作树指纹一致(0c74c2ce0902)。四个容器全 Up、pms-web healthy,页面 HTTP 200。全链试算(dry_run,不落表)真机确认合议分流在跑:技术面映射 176 只且新鲜(14:45 写、读数日 09-10);三只新建仓候选(603728.SH、002074.SZ、002371.SZ)被合议以「方向看空(基本面看空、技术面看空、择时中性)」正确跳过——接入前它们只过选股判决、会照常出候选;688100.SH 走「等技术面开口」观察。今天没有候选因缺买方评析被跳过(与拍板前查实的主榜前五十全有评析一致)。**尚未真机见到的**:提议卡合议行要等部署后产生的新提议(旧的 15 条提议不带六键,合议行按 v-if 不显示);持仓增持门今天没有持仓触发增持动作,未在真机上看到那道门实际拦下一次。 **还欠着什么** 一,工作包三离场纪律未做:eval_tech_exit 转空离场、盘中 SAR 止损线、弱基本面试探仓的紧止盈自动挂载。二,工作包四选股打分在另一仓库,单独审批后再动。三,工作包五其余文档:README 调度总表补到十三位、BIONIC_PMS_INTERFACE 技术面只读接口一节。四,页面管理视图持仓总览「证据列印两遍」的显示错误与研究面列,本轮未碰。五,提议卡合议行与增持门实际拦截,等有新提议、有持仓触发增持时在 155 补看。 --- ## 2026-09-11 · 工作包三离场纪律(part 1+2):技术面转空自动离场 **做了什么** 把技术面从「只管入场投票」推进到「也管离场纪律」。这次做工作包三的前两部分,即动作引擎里的转空离场评估器,策略层的两部分(盘中 SAR 止损线、弱基本面试探仓紧止盈自动挂载)留到下一增量。 一,新评估器 eval_tech_exit。持仓票的技术面相位是「转空」且 SAR 翻空不超过两个交易日时评。确认转空清仓全部可卖量,未确认减三分之一。入场靠投票、离场靠纪律:转空像保垫减仓一样按规则自动执行,不进合议投票。档位 PMS_TECH_EXIT_AUTONOMY 三档,off 不评、propose_only 交人、full 自动执行,默认 full。同一次翻空只处理一次,用运行参数 PMS_TECH_EXIT_DONE 承载去重,SAR 翻多即清除,不动表结构。 二,卖出优先级改成来源分档。同一轮多条减持留一条,次序是目标价到价、研究证据走弱、技术面转空、保垫减仓。既有三类的相对先后一字不动,转空插在研究走弱与保垫减仓之间。 三,策略票判据改按函数判。到价清仓与转空离场都是 EXIT 动作,但策略票的 SAR 该由策略层的盘中止损线管,所以策略票只评到价清仓、不评转空离场。原先按动作名判会把转空离场对策略票误放行。 **动了哪些文件** app/core/action_engine.py(eval_tech_exit、SRC_TECH_EXIT、EVALUATORS 加一条改成七条、_sell_priority 来源分档、策略票判据按函数判);app/services/proposal_service.py(扫描参数加离场档位、持仓挂技术面状态、去重集加载与写回两个辅助函数);app/services/param_store.py(登记 PMS_TECH_EXIT_AUTONOMY、PMS_TECH_EXIT_TRIM_RATIO、PMS_TECH_EXIT_DONE,减持比例加校验范围);scripts/test_batch30_units.py(新增十八例);scripts/test_batch12_units.py(EVALUATORS 次序哨兵改成七条);scripts/run_tests.py(登记第三十批、总数 805);docs/复盘决定台账.md(台账 010)。 **部署方式** 改了 Python,make deploy 重建镜像加 force-recreate。收盘后 155,本次与工作包二接入同批已授权直接部署。 **真机判收** 真机见过(155,2026-09-11 收盘后)。提交 8fdcc2a 已推远程并 make deploy 到 155,make test ALL SUITES PASS、make stale 指纹一致(f9810bb8acf9)。试算真机确认转空离场路径就绪:全市场 8 只处在转空相位(如 002881.SZ、603728.SH、688279.SH),但当前 11 只持仓无一在转空相位,故本轮转空离场执行 0、入队 0。这一增量对策略票不回退:接入前策略票也没有 SAR 离场。**注意 155 下单模式是 ws(连模拟 QMT),不是影子模式**——所以持仓哪天转空,full 档位的转空离场会真下发模拟卖单(模拟仓的用途就是全链路验证);要先观察不真卖,把 PMS_TECH_EXIT_AUTONOMY 改 propose_only。**尚未真机见到的**:真有持仓转空时的自动离场一跳、去重集写回。 **还欠着什么** 工作包三剩两部分:跟踪止盈策略加盘中 SAR 止损线(09:45 后现价低于昨日 SAR 千分之三即卖、当日一次,每天 09:40 把映射里的 SAR 刷进自动挂载的跟踪止盈);弱基本面加技术面看多的试探仓次日 09:40 自动挂回撤 3%、硬目标 8%、带 SAR 线的紧止盈(台账 011)。工作包四选股打分另一仓库单独审批。工作包五文档。 --- ## 2026-09-11 · 工作包三离场纪律(part 3+4):策略层的两道技术面离场 **做了什么** 补齐工作包三的后两部分,都在策略层。至此工作包三四部分全做完。 一,跟踪止盈加盘中 SAR 止损线。跟踪止盈评估器加一道 SAR 止损:09:45 后现价跌破昨日 SAR 值的千分之三缓冲即全清,当日只触发一次。SAR 值由 09:40 的策略自动挂载一跳刷进每条自动挂载的跟踪止盈的参数。取不到读数就撤掉旧的 SAR 线,不拿旧读数当今天。没刷进 SAR 线时这道线不判,加不改。 二,弱基本面试探仓的紧止盈自动挂载。基本面看空加技术面看多的试探仓,次日 09:40 自动挂一条回撤百分之三、硬目标百分之八、带 SAR 线的跟踪止盈,不占每日新挂名额。认它靠入场账本里 advice.tight_trail 这个标记(仓位矩阵 advise_v2 给这类试探仓打的),顺着首批未平批次到指令到账本放行记录找。冷却与在途照常让路。 **动了哪些文件** app/services/strategy_runner.py(跟踪止盈评估器 _eval_trail 加盘中 SAR 止损线,防了单测传 now=None 的空指针);app/services/strategy_advisor.py(09:40 一跳加 _refresh_sar_lines 刷 SAR 线;_entry_tight_trail 认入场标记、_attach_tight_trail 挂紧止盈,接进主循环无策略票走常规边之前);app/services/param_store.py(登记 PMS_TECH_SAR_STOP_ON_TRAIL、PMS_TECH_SAR_STOP_BUFFER、PMS_TECH_TIGHT_TRAIL_GIVEBACK、PMS_TECH_TIGHT_TRAIL_TARGET,三个加校验范围);scripts/test_batch30_units.py(part3 九例、part4 六例,共扩到 33 例);scripts/run_tests.py(例数与总数 820);docs/复盘决定台账.md(台账 011)。 **部署方式** 改了 Python,make deploy 重建镜像加 force-recreate。收盘后 155,已授权直接部署。 **真机判收** 真机见过(155,2026-09-11 收盘后)。提交 2d019a1 已 make deploy 到 155,make test ALL SUITES PASS、make stale 指纹一致(d8ee35a4dfed)、四容器健康。策略自动挂载试算 ok、无报错。**注意 155 上策略层总开关 PMS_STRATEGY_ENABLED 没开**,所以 scan 早返回(checked 0),part 3+4 的代码是「已部署但休眠」——启用策略层才激活。这是最安全的部署态。开发机全量 ALL SUITES PASS(第三十批 33 例)。**尚未真机见到的**:启用策略层后 SAR 线刷新、弱基本面试探仓次日紧止盈挂载、策略票 SAR 翻空止损。 **还欠着什么** 工作包三四部分全做完。剩:工作包四选股打分在 akg-factor-bridge 另一仓库,单独审批后再动;页面管理视图持仓总览「证据列印两遍」的显示错误与研究面列本轮未碰。真机判收要看真有弱基本面试探仓入场次日的紧止盈自动挂载、以及策略票 SAR 翻空当天的止损。 ---