tradingSystem/DEVLOG.md

90 KiB
Raw Blame History

开发节点记录(三系统量化交易)

这份文件的用途只有一个:让下一次交接不必重新查一遍。 每个重要节点写一条,写完就补,不攒到最后。一条节点包含五样东西: 做了什么、动了哪些文件、部署方式、真机判收结果、还欠着什么。

三个系统的部署方式(改错等于白改):

  • tradingSystemPMS 跑在 factor@factorevaluation,源码打进镜像, 改码必须 make deploy,跑完 make test 见 ALL SUITES PASS盯盘 make watch。 容器时钟是北京时间。
  • bionic_trader决策系统 跑在 192.168.16.188:38000,挂载卷, git pulldocker compose restart backend-api worker-brain 即生效; 新增 env 必须 docker compose up -d --force-recreaterestart 不重读 env。
  • akg-factor-bridge因子桥factorevaluation,容器名 akg_factor_bridge API 在 8300常驻 API 改码要 compose restart。

时区2026-08-06 查实、2026-08-07 再证一次,这里写的以实测为准) 不能按「哪个系统」一刀切,要按「哪种时间」分:

  • 容器时钟与容器日志正文 —— 北京时间,不要加八小时。 证据bionic 容器内 date15:37 CST
  • 数据库的时间列strategy_audit_log.created_atstrategy_daily_results.updated_at 等)—— UTC要加八小时。 证据08-07 北京 09:03 时该库 SELECT NOW() = 2026-08-07 01:03:32
  • 日期列audit_datetrade_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 被「页面人工裁决」改成 PASS9 点 47 成交 1600 股。查上游买入计划表 trading_buy_plan 与决策账本 decision_ledger对 688599 当天都是 0 行,证明它没走上游建仓仲裁那条路,全程在 PMS 侧。

用户拍板的改动(待在服务器执行) 把分析池做成下单候选的超集让能买的票一定先被夜间分析也便于观察。要长期守的不变式POOL_TOP 不低于 PMS_PLAN_TOP_N。本次 POOL_TOP 由 20 提到 50POOL_MAX 由 60 提到 100两个都在 akg-factor-bridge 的 .env。执行方式改 .env 两行,然后 docker compose up -d --force-recreate akg-factor-bridgerestart 不重读 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/loginmultipart。关键在于它登录成功的返回里直接带这个人的权限清单 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_PERMSscripts/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.mdV2 定稿)已入库,五处拍板均已当面定下:只认「明确吸筹」档触发;建仓资格不放宽(吸筹只影响候选排序,不给入场资格);不设观察期直接全自动;实现骨架取「状态机含接力切换」(网格票站上区间上界且热度达标时自动换挂止盈);判分闭环进 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_attach09: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.pyclear_buypauseapp/services/param_store.pyDESC 13 条 / RANGES 10 条 / FAIL_CLOSED 加总开关 / 边清单校验config/settings.pyPMS_AUTO* 参数块app/scheduler.pystrategy_attach 任务 + beat 09:40 + 调度总表注释app/web/main.pystrategy-attach-scan 端点scripts/probe_strategy_signals.pyv1.1scripts/test_batch17_units.py新增 34 例定性归类子串与保守优先级、网格参数生成与三条放弃路径、连边矩阵含双命中与负垫、note 字面量钉死、日上限接力不占、两种冷却推导、接力判定、clear_buypause 来源匹配、编排冒烟 dry_run 滴水不写/名额/边三/边四全链/三道总闸scripts/test_wiring.pybeat 哨兵集合加 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 PASSdry-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_lot688/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.pyarchive_/unarchive_/list_* 的 include_archivedapp/web/main.py归档端点与列表参数app/web/static/index.html显示已完成开关与可移除判据scripts/ddl_pms_v1.sql建表带列scripts/migrate_archived_at.py新增已有库补列,幂等,先演练后 --yesscripts/test_batch16_units.py5 例scripts/run_tests.pybatch16 注册,总数 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 补撤在途买单一层,进度回执多一项 instructionsscripts/test_batch15_units.py原 7 例扩到 12 例新增文案拆分四例、减仓剔策略腿一例原撤策略例加断言撤在途买单scripts/run_tests.pybatch15 计数改 12总数 510

部署方式 桥机 factorevaluation 上 make deploy源码打进镜像必须重建容器make test 见 ALL SUITES PASS已含改后 batch15。收盘后部署让下一次清仓或减仓发生在你在场时。不新增参数不动 .env。

真机判收 未判收。开发容器全量单测 ALL SUITES PASS510 例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.pyrun_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.pyrun_tick 配额口径分流 + 新增 _inflight_todayapp/services/command_service.pyplan_command 加停买入侧调用 + 新增 _stop_buyside_for_exit + 进度回执 stopped_buysidescripts/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 PASS505 例,含新增 7 例)。桥机判收:部署后下一次一键清仓,预期同一只票在同一天内持续补单直到卖完或卖到 T+1 的墙,不再出现“卖两笔就停在配额已出完、要等次日”;且清仓命令一下达,挂着策略的票在命令进度回执 stopped_buyside 里能看到策略已撤、账本有 CLEANUP 行,网格不再边卖边买。

还欠着什么本次只对全额清仓类命令即时撤策略减仓类REDUCE_EXPOSURE/REDUCE_STOCK是否也要停对应票的加仓侧未做按需再议。二配额“已全部在途、等成交”这一情形目前仍复用“当日配额已出完”这句话行为正确但措辞会让人误以为是老毛病下次顺手把这条 WAIT 的文案分开。三PMS 侧 avail_qty 与券商侧可用持仓偶有不一致(出口委托里有 INSUFFICIENT_POSITION 拒单),本次未动,属对账口径,单独排查。


2026-08-18 · 宏观择时层上线:股汇对冲指数管整体仓位,外加个股宏观闸

背景与依据 方案与全部拍板记录在 MACRO_TIMING_PLAN.mdV3 可读版)。参数初值来自当天在桥机实跑的标定,报告存档在 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 PASS498 例,含新增 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~84002015 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.60app/services/signal_service.py持仓票未采纳留痕 + _escalate_inflight_sells 确认加速app/services/command_service.pycleanup_exited_positions

  • 纯逻辑 _cleanup_todo 标记app/scheduler.pycommand_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/breakout6/15 已被资金分布取代, 生产端intraday_timing orchestrator 方向源清单)不再发它,框永远只显示历史残留键。 二,卡片按重要度分三档、大小不一:行动级(决策买卖 c-wide、择时入场、风控卖出、多源升级 有数据才出大卡判读级资金异动、QRS、资金分布、资金流强度中卡两列参考级股价异动 小卡、实况分榜宽卡)。用 12 列网格 + c-wide/c-half/c-third 跨列实现。 三资金异动「异动z」换成净额领读 + 强度带:净额折成万/亿带正负号打头,强度按 z_dd 绝对值 分档(显著 23 / 强 35 / 极强 ≥5用小方块条 + 中文标签表达,原始 z 收进悬停提示。 四空的「在用」信号收进一条「在用·今日待触发」横条armed 待命,不再各占空框)——这是 本次核心:此前四个空框和一个死框摆一起,看不出「活着但安静」和「已下线」的区别,用户误以为 下线了。逐个追到生产端核实:只有放量异动真下线,多源升级/择时层BUY/风控卖出/资金流强度都是 在用但罕见或条件触发。「其他」桶只在非空时出现(空=所有源已登记=健康)。

动了哪些文件 app/web/static/index.html新增 CSSsig-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 模板编译无错。真机待看:盘中有真数据时各档卡片与截图一致,且级别筛选条仍联动。

还欠着什么 待触发条目前只标「今日待触发」,未显示「最近一次触发时间」(那需要后端跨日扫历史键,会给每次 刷新加延迟暂不做用户认可先不加。强度分档三个阈值23/35/≥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,不置 forcedTTL 到期撤掉按新价重下)。动机: 「现价≥均价才卖」是只在强势时放行的条件,下跌日全天不满足,全部数量堆到 14:45 一笔打出。分桶后跌日的强制完成分散到多个时点。参数留空 = 不分桶,回到旧行为。 三自主卖出窗口耗尽不作废window_verdict 增加 side 参数,自主类卖出(信号清仓、 保垫减仓)窗口耗尽置 PARTIAL 保持在途、按末日节奏继续出手,不再 EXPIRED——与 hard_gate 里「宁可买不上, 不能卖不掉」对齐口径。自主买入到期作废的口径不变。

动了哪些文件 app/core/exec_timing.pyparse_bucket_times / _bucket_due 新增hard_gate、decide 加 urgentwindow_verdict 加 sideapp/services/executor.pymaterialize_plans 给 LIQUIDATE_ALL 指令写 urgentrun_tick 传 urgent 与两个新参数sweep_windows 传 side app/services/exec_advisor.pydecide 加 urgent 透传,紧急与分桶都在 hard_gate 消化, 实现A/B 一律生效app/services/signal_service.py_make_exit 写 urgent config/settings.py 与 app/services/param_store.pyPMS_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 后各有一笔「分桶收口」子单; ③ 窗口耗尽的信号清仓不再变 EXPIREDprogress.window_verdict.note 带「不作废」。

还欠着什么 分桶份额目前按桶数等分,未按成交量 U 形加权;紧急直通没有跳过跌停一字板的顺延 (封板时挂单也成交不了,维持顺延是有意的,记录在此防止误会)。


2026-08-06 · 新建仓动作:方案已出,待拍板

做了什么 读完 PMS 的动作引擎主链代码,出了《给动作引擎加「新建仓」动作》的方案,等待拍板。 一行代码都还没改。

读代码得到的、与既有文档不一致的事实(以代码为准)

  1. OPEN 这个动作名在 PMS 里已经存在,而且从方案到下单的整条管道是通的: planner.py:31 定义 A_OPENexecutor.py:41BUY_ACTIONS 已含 OPENcommand_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_codesideday.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.0or None 变成 None 提示词渲染成「当前现价 未知 元」。加仓类还有摊薄成本可依,建仓判断没有现价锚不成立。

新撞出来的一条硬约束(工程事实,不是取舍) app/scheduler.py:45 给所有调度任务设了 task_soft_time_limit=240, task_time_limit=300judge.request 是同步阻塞、单次超时上限 90 秒。三只票送研判就是 270 秒,已经超过 软超时。 今天没出事是因为送研判的只有三只持仓票且多数轮次被去重挡掉。新建仓不节流、 候选池默认取前 30 只,第一跳就会捅穿。 处理办法是给单轮扫描一个研判时间预算 PMS_JUDGE_TICK_BUDGET_SEC(默认 150 秒), 用尽就把剩下的候选留到下一分钟的心跳,并写进 skipped 留痕。 这不是节流:一条候选都没丢,也没有按天计的上限,只是让一次心跳做得完。

动了哪些文件 无代码改动。NEW_POSITION_ACTION_PLAN.md 更新为定稿版 V2已放进项目目录。

部署方式 不涉及。定稿版里写明:决策系统这次只改 worker 代码、不动 .env 所以 git pulldocker 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_manygp_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 的 Decimalrequests.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 PASS469 例

顺带一条结构性发现,还没处置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.pyskip_why 与两个扫描入口)、 app/services/proposal_service.py(三个来源改成带原因的字典); 测试 scripts/test_batch12_units.py 加 3 例,scripts/test_batch4_units.pyscripts/test_wiring.py 各改一处断言(原本钉着那句笼统话的字面量)。 全量单测 ALL SUITES PASS467 例

一条要更正的承诺:先前说过「既有 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. 悬项四定案:不是口径差。 差额 56408-05→ 5108-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 inbox000063.SZ 那五笔成交是今天 14:46时间戳换算1700 股, 成交价 34.71 到 34.76。而交接信写的是「现价高于买入区间上沿,不追,窗口今天到期」。 判了不追,最后还是买了——走的是 exec_timing.hard_gate 买入分支的窗口末日强制完成, 而 hard_gateexec_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.decideexec_advisor.decideis_command 默认 True (命令口径 = 改动前的行为),executor.run_tick 从指令的 progress.is_command 显式传下来。
  • 影响面说破:已有的 FILL / ADD / DCA 自主买单也跟着改——从前也会在末日强制完成, 现在到期作废。「回踩补足」在末日追高买本身就自相矛盾,所以对它们同样是修正, 但确实是既有行为的改变。

动了哪些文件 app/core/exec_timing.pyapp/services/exec_advisor.pyapp/services/executor.py 测试 scripts/test_batch12_units.py 加 6 例含一条「executor 真的把 is_command 传下去了」 的签名与源码检查,防这道分岔形同虚设),scripts/test_batch11_units.py 的夹具补一个参数。 全量单测 ALL SUITES PASS464 例


2026-08-06 · 决策系统的买入信号一直被 PMS 丢在门口,已接上

做了什么 用户问「新建仓能不能并入决策系统原有的信号链路」。查下来先纠正了一个前提,又撞出一个洞。

先纠正的前提:决策系统的建仓侧本来就是轮询,不是订阅。 workers/celery_app.py:94-97entry-gate-pollcrontab(minute="*", hour="9-11,13-14") 每分钟去 trading_buy_planis_active=7。真正事件驱动的是告警链 scripts/intraday_watcher.py 常驻进程 xreadgroup 读上游告警流)与卖出链。 所以「PMS 只能轮询不合理」对既有架构不成立——候选池是日频静态清单,没有事件可订阅, 轮询是它唯一的读法,而且与决策系统自己的建仓入口同构。

撞出来的洞(与 08-05 那次同型):决策系统盘中判出 REVERSAL_BUY 会往 db2 的 intraday_signals:{日期} 广播(workers/tasks_intraday.py:783-785PMS 一直订阅得到 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_BUYverdict=NOTE 的账本行,按(日期,来源,股票,动作)当日去重。用 NOTE 不用 PASS—— 「记下来」和「放行」在账本里必须分得开。
  • pms_repo.buy_signals_today:只读,今天有转多留痕的票。
  • proposal_service._scan_openaction_engine.scan_open:有信号的候选排最前只影响先后,不影响资格——不在候选池里的票不会因为有信号就被建仓, 候选层那一整套过滤ST、黑名单、分数下限、主题限额、预期空间一道都不绕。
  • judge.OPEN_JUDGE_KEYS 加两个键,把「你自己今天判过这只票转多」送回决策系统, 让它拿自己的结论对照一次。这是定性材料不是仓位数字,符合那条白名单的立意。
  • 新参数 PMS_OPEN_SIGNAL_PRIORITY(默认开),关掉即退回纯分数排序。

为什么走账本传递而不是让 signal_service 直接调建仓 两边各管各的一件事:信号消化管「收到了、记下来」,动作引擎管「买不买、买多少」, 中间靠账本这个既有事实源接。不新增跨模块调用、不复制闸门逻辑。 代价是最多差一分钟(两个调度位各自每分钟一跳),而后面还要等择时区间,这点延迟无所谓。

插队为什么是实打实的增量:候选按分数降序取,名额只剩两个时第 25 名永远轮不上, 哪怕决策系统刚刚判它转多。分数是昨夜算的静态排名,「此刻转多」是盘中才有的新信息, 两者不同量纲,折算成分数得凭空定系数;插队直接表达了这件事。

动了哪些文件 app/core/signal_rules.pyapp/services/signal_service.pyapp/repo/pms_repo.pyapp/services/proposal_service.pyapp/core/action_engine.pyapp/services/judge.pyconfig/settings.pyapp/services/param_store.py;测试 scripts/test_batch12_units.py 加 7 例,scripts/test_batch5_units.pyscripts/test_wiring.py 各改一例 (那两例原本钉着 BUY→ACT_RECORD 的旧行为,「不买」这条口径没变,变的是留痕范围)。 全量单测 ALL SUITES PASS458 例

还欠着什么

  1. trading_buy_plan 四个状态都有行,说明老的建仓链路还活着。关掉 ENTRY_GATE 之前 必须先确认那些行是不是今天的——若老链路今天仍在挂单,而 PMS 同时开始建仓, 就是两套系统都在买。查法见交接说明。
  2. 关 ENTRY_GATE 是往决策系统 .envENTRY_GATE_ENABLED=False 这是新增 env 值,必须 docker compose up -d --force-recreaterestart 不重读 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_openscan_open既有 scan() 一个字未动。
  • app/services/proposal_service.py:新增 _scan_open(候选池取数、批量取行业、 真实资金封顶);_route_one 加新建仓特判与档位分支;研判时间预算。
  • app/services/judge.pyOPEN_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_todayopened_names_today
  • app/services/param_store.py:四个新参数的说明、校验、FAIL_CLOSED
  • config/settings.py:四个新参数;PMS_JUDGE_ACTIONS 默认值加 OPEN
  • scripts/test_batch12_units.py26 例)与 scripts/run_tests.py(登记新批次)。

写代码过程中改掉的一条设计(单测逼出来的) 原方案的取数口径抄的是命令驱动那条路:want = min(单股目标, 剩余额度)。单测发现它会在 可投金额只剩一万时开出一只 0.5% 的零头仓位——拿一个持仓名额换一个永远补不到目标的半截仓, 还挡住了后面真正建得起来的票。改成「钱不够一整只就不开」,并把与命令那条路的差别写在注释里: 命令是用户明确下了「投这么多」,最后一只缩水是命令的收尾;自主建仓没有这层意思。

部署方式

  • 决策系统:不加配置、不动 .envgit pulldocker 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_manygp_stock_category 分支加按日缓存 (每跳省几十次库查询,但改的是既有函数的时序行为,单独提、单独拍板)。
  4. 文档补账仍欠着:WS_INTEGRATION_STATUS.md 停在 07-30BIONIC_PMS_INTERFACE.md 要补新建仓这一路(研判闸第五类动作、硬数字裁剪、择时侧零改动)。

2026-08-07 · 股票评述里冒出「【手动重跑AKG_PLAN】」运维口径的话被当成风控质询

做了什么

页面上一些股票的评述里出现了「【手动重跑AKG_PLAN】」这种谁也看不懂的字段。第一反应会怀疑 是前一天新建仓那批改动,但不是:三个仓库全文检索「手动重跑」只有一个出处, bionic_trader/scripts/rerun_pool.py 第 237238 行。链条如下(全部是源码,不是推测):

  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_reportsave_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_typeBAD_Y_SIGNALS 闸)、 桥 pool.pyBAD_SIGNALS(决定把哪只票请出候选池)。所以「盘中不跑 push-pool」这条纪律 不只针对桥,rerun_pool 同样在内 —— 这次是拿实测把它钉死了,不再是推测。

顺带记一条待查的:SZ000800 现在有两行相互打架的定性,trade_date=20260806SELL 08-06 22:38 写入),trade_date=20260804DROPPED08-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_traderPMS 侧一个字没动)

  • 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 pulldocker 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 的中文说明写的是「干跑」,项目里同类东西叫「试算」, 措辞不统一。这次没改,因为跟本条改动无关,单独提。