18 KiB
PMS ↔ 决策系统 (bionic_trader) 接口契约 V1.1
待办 #9(择时实现 A)与 #10(研判闸)的实现依据。2026-08-03 定稿并双侧落码;同日按用户 审核意见把择时部分整个重做过一版(见 §7 变更记录),当前文本以重做后为准。 PMS 侧代码:
app/services/judge.py(原有)+app/services/exec_advisor.py(新增)。 bionic 侧代码:app/api/main.py两个接口 +workers/tasks_intraday.py新增PMS_JUDGE分支 +app/services/pms_advisor.py+config/settings.pyP 段。 机器口径:PMS 跑在 factorevaluation;bionic 跑在 4090 机(API 宿主端口 38000)。
1. 分工与原则
设计 POSITION_MGMT_DESIGN.md §1/§7/§8 的落点:持仓系统管「做什么、多少」,决策系统管
「该不该、何时」。2026-08-03 用户补充的三条原则,两个接口都必须守:
- 决策系统还在运行、还有其他职责,改造只做加法。 新功能全部走新增分支、新增接口、 带默认值的新配置;原有六个盘中裁决分支、原有 API、每晚判分、建仓仲裁的输入输出一律 不动。凡是「顺手写进共享表」这类会影响既有功能的副作用,都要在本文写明并给出处置。
- 提前计算为主、盘中监控为辅,不另做盘中判断。 大模型推理放在凌晨(每晚的分析本来 就在产支撑位、压力位、定性结论),盘中接口只读凌晨结论和当天已有的监控产出,做比对、 不做判断。可以接受买不上——现价跑出执行区间就等待,不追。
- 拿不到结论时,各按各的纪律降级。 对 PMS 而言「通过」「驳回」「拿不到」是三件事:
研判拿不到 → 降级人工确认(绝不当通过,也绝不当驳回——驳回会真的杀掉提议并记一笔
驳回账);择时拿不到 → 退回 PMS 内置的实现 B(绝不停出手)。所以 bionic 侧任何给不出
结论的情况(解析失败、越界裁决、结论缺失、超时、总开关关)都回
{"verdict": "UNAVAILABLE", "reason": ...},不许硬编一个看起来像结论的默认值。
另外三件事划清归属:配额、14:45 的强制完成、停牌一字板、不追高(当日涨幅与均线偏离)、
仓位上限、一手、T+1 可卖——这些检查全部留在 PMS 本地(择时的在 exec_timing.hard_gate,
其余在规则闸),请求根本不会送到 bionic。两个接口对 PMS 永远回 HTTP 200 + verdict 字段,
PMS 不需要区分状态码(网络层异常自然走 PMS 的超时分支,效果相同)。
2. 研判闸 POST /api/intraday/pms_judge(#10,走大模型,慢是正常)
PMS 调用方:judge.py(自主提议的二级关口,只审 PMS_JUDGE_ACTIONS 里的动作,默认
FILL/ADD/DCA/SWITCH;命令驱动的动作不过研判闸)。超时 PMS_JUDGE_TIMEOUT(默认 90 秒)。
2.1 请求(PMS → bionic,均为 PMS judge.request() 现有产出,未改)
{
"direction": "PMS_JUDGE",
"action": "DCA", // FILL / ADD / DCA / SWITCH
"ts_code": "600000.SH", // 点式
"qty": 200,
"reason": "浮亏 -8.5% 触发第一档补仓评估", // PMS 动作引擎给出的提议理由
"hard_numbers": {"cushion_pct": -0.085, "stage": 1, "...": "PMS 已算好的硬数字"},
"context": {
"position": {"ts_code": "...", "avg_cost": 9.1, "total_qty": 1000,
"cushion_pct": -0.085, "support_ref": 8.9, "...": "账本快照"},
"recent_ledger": [{"at": "...", "action": "...", "arbiter": "...",
"verdict": "...", "price": 9.0, "reason": "..."}]
},
"must_answer": ["下跌是杀逻辑还是杀情绪"] // DCA 必带 (设计 §7)
}
2.2 bionic 侧处理
process_intraday_audit 新增 PMS_JUDGE 分支,复用既有仲裁哲学与代码路径(同一个大模型
呼叫、同一条审计轨迹),按动作类型给定性判据:
| action | 核心问题 | 驳回判据(示例) |
|---|---|---|
| FILL 回踩补足 | 回踩是健康洗盘还是结构转坏 | 放量破位 / 主力持续净流出 / 弱市无个股独立证据 |
| ADD 盈利加仓 | 趋势延续还是冲顶兑现 | 放量滞涨 / 已到压力位 / 主力借涨派发 |
| DCA 补仓 | 必答:杀逻辑还是杀情绪 | 杀逻辑一律驳回;说不清 → 驳回 |
| SWITCH 调仓 | 换入侧证据是否成立 | — |
bionic 自动补三样 PMS 没有的上下文:实时资金分布(既有的上游接口)、板块动量与区制快照、
自己昨夜的 strategy_daily_results 结论。裁决收口只认 PASS/REJECT,越界与解析失败一律
回 UNAVAILABLE(§1 原则 3);「证据不足以支持」按提示词导向驳回(宁可错杀)。
同股同动作半小时内不重复研判(PMS_JUDGE_RETRY_TTL,默认 1800 秒,TTL 内直接回上次
结论,应答多一个 "cached": true)。原因:PMS 侧对研判驳回有意不做当日去重(其注释
「研判结论会变」),被驳回的候选可能每分钟重来——节流责任放在 bionic 侧,与建仓仲裁的
ENTRY_GATE_RETRY_TTL 是同一个思路。UNAVAILABLE 不缓存(瞬时故障要重试)。
留痕只写 strategy_audit_log(verdict 记 PMS_PASS / PMS_REJECT /
PMS_UNAVAILABLE,该列 VARCHAR(20) 装得下,已核对建表语句),不写 decision_ledger:
2026-08-03 复核 outcome_scorer.py 确认每晚判分对决策账本是全表扫描、不按裁决类型过滤,
建仓仲裁的历史注入也读整本账——PMS 的裁决写进去会混进这两个既有功能。PMS 自己的评审记录
本来就落在它侧的 pms_action_ledger(arbiter=judge),不需要在 bionic 重复记账。
2.3 应答(bionic → PMS)
{"verdict": "PASS" | "REJECT" | "UNAVAILABLE",
"reason": "50字以内一句话", "confidence": 0-100,
"direction": "PMS_JUDGE", "ts_code": "600000.SH", "action": "DCA",
"cached": false}
PMS 侧映射(judge.py 现有逻辑,未改):PASS/APPROVE 一类 → 放行进档位分流;REJECT/DENY
一类 → 驳回并记账;其余一律按 UNAVAILABLE → 降级人工确认 + ERROR 告警。
2.4 执行路径与超时
API 收到请求 → 投递 celery 任务(intraday.process_audit,队列 PMS_JUDGE_QUEUE)→
同步等结果(PMS_JUDGE_TASK_TIMEOUT,默认 75 秒)→ 回 JSON。三层超时必须保持
75(bionic 等任务)< 90(PMS 等 HTTP),否则 PMS 先断开、bionic 白算。
队列默认 brain_queue(12 并发,零部署改动);若盘中全量重算把它塞住、PMS 侧频繁超时
降级,再切独立队列(§5.2),这是预埋的第二档不是必选项。
3. 盘中择时 POST /api/intraday/pms_exec(#9,读凌晨结论,秒回)
PMS 调用方:exec_advisor.py。只在 PMS_EXEC_IMPL=A 且过了本地检查后咨询;应答按
valid_min 缓存在指令 progress.exec_advice(上限 PMS_EXEC_ADVICE_TTL_MIN,默认 10
交易分钟),失败进入冷却(PMS_EXEC_FAIL_COOLDOWN_MIN,默认 5 分钟)。超时
PMS_EXEC_TIMEOUT_SEC 默认 8 秒——这个接口必须秒回,大模型不进盘中路径。
3.1 请求(PMS → bionic)
{
"direction": "PMS_EXEC", "ts_code": "600000.SH",
"side": "buy" | "sell", "action": "OPEN|FILL|ADD|DCA|EXIT|TRIM",
"qty_left": 500, // 今日还该投放的量 (仅参考, 配额归 PMS)
"is_last_day": false, "tdays_left": 3, "now": "10:31",
"day": {"price": 10.0, "vwap": 10.05, "...": "PMS 的当日快照, 可整个留空"},
"refs": {"support": 9.5, "pressure": 11.0, "stop": 9.2, "source": "bionic"},
"position": {"total_qty": 0, "avail_qty": 0, "avg_cost": null, "cushion_pct": null}
}
bionic 只用其中的 ts_code / side / day.price(现价缺失时自己从 db13 分钟线取),
其余字段留作排查与将来扩展;区间一律按 bionic 自己库里的昨夜结论算,不用 PMS 转送的参考位
(PMS 的参考位在结论停更时会换成它自己兜底算的值,那不能当决策系统的结论用)。
3.2 bionic 侧处理(pms_advisor.py,版本标记 daily_band_v1)
只有三步,全部是读现成的数、做比对,没有任何盘中判断:
第一步,读昨夜结论。strategy_daily_results 里当晚算好的支撑位、压力位、定性。结论缺失、
超过 PMS_EXEC_YSTRAT_MAX_AGE_DAYS(默认 5 个自然日)、或支撑压力倒挂 → 回 UNAVAILABLE,
PMS 退实现 B。由支撑压力推出两个执行区间(推导是算术,宽度是参数):
买入区间 = [ 支撑 × (1 − 外沿0.01), 支撑 + (压力 − 支撑) × 0.3 ]
卖出区间 = [ 压力 − (压力 − 支撑) × 0.3, 压力 × (1 + 外沿0.01) ]
只有单边参考位时, 区间宽度按该位的 3% 取, 另一侧回 UNAVAILABLE。
第二步,读当天监控。strategy_audit_log 今天对该股的裁决(盘前复审与盘中研判本来就在写
这张表;PMS_ 开头的行是我们替 PMS 写的,读的时候排除)。今天已有离场类结论
(REVERSAL_SELL / TAKE_PROFIT / FAIL)→ 买入一律等待、卖出直接放行。监控查询失败不拦
执行(监控是「为辅」),但应答的 missing 里必须写明 today_audit。
第三步,比对现价与区间:
| 情形 | 应答 |
|---|---|
| 买入,现价在买入区间内 | FIRE,限价 = 区间上沿(委托能一直活到价格离开区间,不再是贴着现价的窄缝——2026-08-03 委托全部到期的病根即在此) |
| 买入,现价高于区间上沿 | WAIT「不追,接受买不上」 |
| 买入,现价低于区间下沿(支撑位下方) | WAIT「区间外不买入」 |
| 买入,今天已有离场结论 | WAIT,理由带上是哪条结论 |
| 卖出,现价到达卖出区间(或更高) | FIRE,限价不给(交回 PMS 按它自己的口径贴现价挂出) |
| 卖出,现价未到卖出区间 | WAIT「等高一点卖,当日收尾的强制完成仍由 PMS 兜底」 |
| 卖出,今天已有离场结论 | FIRE(与 PMS 已订阅的卖出信号同源,不会互相矛盾) |
3.3 应答(bionic → PMS)
{"verdict": "FIRE" | "WAIT" | "UNAVAILABLE",
"limit_price": 9.95, // 买入=区间上沿; 卖出与等待时为 null (PMS 用本地口径)
"valid_min": 10, "reason": "现价 9.7 在买入区间 [9.4, 9.95] 内 (支撑 9.5/压力 11.0), 限价挂区间上沿",
"confidence": null,
"observed": {"price": 9.7, "support": 9.5, "pressure": 11.0,
"buy_band": [9.4, 9.95], "sell_band": [10.55, 11.11],
"y_signal": "BUY", "today_exit": null},
"missing": [], "advisor": "daily_band_v1"}
PMS 侧对建议价有一道保护:偏离现价超过 PMS_EXEC_LIMIT_BAND(默认 10%,即 A 股单日
涨跌幅上限)视为异常数据,改回本地口径并在理由里说明。区间边缘离现价百分之几属正常,
正常联调不会触发这条。
3.4 下一个迭代(本轮明确不做)
凌晨管线加一步大模型推理,对每只票明示写出买入区间与卖出区间,盘中改为直接读它——届时
只换本模块第一步的取数来源和 advisor 版本标记,应答结构与 PMS 侧都不动。动凌晨管线
属于重大迭代,做之前会先出细方案确认(覆盖范围、存放位置、算不出来怎么办)。
4. 拿不到结论时两侧各自怎么办
| 情形 | bionic 回什么 | PMS 怎么办 | 人看哪里 |
|---|---|---|---|
| 研判超时/队列拥堵 | UNAVAILABLE(原因带超时) | 降级人工确认 + ERROR | PMS 日志 [研判闸];频繁出现 → §5.2 切独立队列 |
| 研判裁决解析失败/越界 | UNAVAILABLE | 同上 | bionic 日志 [PMS_JUDGE] |
| 该股没有昨夜结论 / 结论过期 | UNAVAILABLE(原因写明) | 本轮退实现 B + 冷却 5 分钟 | 指令 progress.exec_advice.error;last_decision.source 显示 B(实现A不可用: ...) |
| 收盘后 / 无实时价 | UNAVAILABLE「无实时价」 | 同上 | 同上 |
| 择时接口网络不通/超时 | —(HTTP 层异常) | 同上 | 同上 |
| 当天监控表查询失败 | 照常按区间应答,missing 带 today_audit |
正常执行 | 应答的 missing 字段;bionic 日志 |
PMS_API_ENABLED=False |
UNAVAILABLE(原因写明开关) | 各按上述降级 | bionic .env |
PMS 侧未配置(地址空 / PMS_EXEC_IMPL=B) |
不会发请求 | 研判闸=人工确认;择时=实现 B | PMS 参数设置页 |
5. bionic 侧部署(4090 机)
5.1 常规路径(本次交付走这条)
bionic 的源码是挂载卷(compose 里 .:/app),与 PMS 打进镜像正相反——不需要
build,git pull 后重启进程即可(backend-api 带自动重载,稳妥起见一起重启)。新增
配置全部有默认值,.env 不用动:
# 【4090 机 · bionic_trader 仓库根目录】
git pull
docker compose restart backend-api worker-brain
# 本机自测 (两接口, 不需要 PMS 参与; 研判走大模型要等 30~90 秒):
docker compose exec backend-api python scripts/pms_smoke.py
改动面:config/settings.py(P 段,全默认值)、app/api/main.py(两个接口)、
workers/tasks_intraday.py(PMS_JUDGE 分支,含同股同动作的重复研判缓存)、
app/services/pms_advisor.py(新)、scripts/pms_smoke.py(新)。
既有六个盘中裁决分支、既有 API、每晚判分、建仓仲裁的行为一行未动(PMS_JUDGE 是新增分支
且提前返回,不经过广播与重算;择时接口不写任何表)。
5.2 研判独立队列(预埋第二档,brain_queue 拥堵时再启用)
判据:PMS 侧 [研判闸] 请求失败 ... Timeout 成为常态(偶发超时降级人工确认是设计内
行为,不用切)。启用:bionic .env 加 PMS_JUDGE_QUEUE=pms_queue,compose 加一个
专属 worker 后 docker compose up -d worker-pms && docker compose restart backend-api:
worker-pms:
build: {context: ., dockerfile: docker/Dockerfile}
container_name: trader_worker_pms
command: celery -A workers.celery_app worker -Q pms_queue -l info -c 2 --prefetch-multiplier=1
env_file: .env
environment: [TZ=Asia/Shanghai]
volumes: ["/etc/localtime:/etc/localtime:ro", ".:/app"]
depends_on: [redis]
networks: [trader_net]
restart: always
(astock-kg 公告链 2026-07-23 单队列把交互请求饿死的同款处方:交互请求与长任务分开排队。)
5.3 bionic 侧新增配置一览(均有默认值)
| 键 | 默认 | 说明 |
|---|---|---|
PMS_API_ENABLED |
True | 总开关,False = 两接口一律回 UNAVAILABLE |
PMS_JUDGE_QUEUE |
brain_queue | 研判任务队列 |
PMS_JUDGE_TASK_TIMEOUT |
75 | API 等任务上限(秒),必须小于 PMS 侧的 90 |
PMS_JUDGE_RETRY_TTL |
1800 | 同股同动作重复研判的间隔(秒),间隔内回上次结论 |
PMS_EXEC_BUY_BAND_RATIO |
0.3 | 买入区间上沿 = 支撑 + (压力−支撑)×此比例 |
PMS_EXEC_SELL_BAND_RATIO |
0.3 | 卖出区间下沿 = 压力 − (压力−支撑)×此比例 |
PMS_EXEC_BAND_EDGE |
0.01 | 区间外沿的放宽(买入下沿=支撑×(1−此值),卖出上沿对称) |
PMS_EXEC_ONE_SIDE_BAND |
0.03 | 只有单边参考位时区间宽度按该位的此比例取 |
PMS_EXEC_VALID_MIN |
10 | 应答建议有效期(交易分钟) |
PMS_EXEC_YSTRAT_MAX_AGE_DAYS |
5 | 昨夜结论超此自然日龄按过期处理 |
区间比例是初值,联调后按应答里的 observed(区间与现价的实际关系)回看再调。
6. 联调步骤与判收
前提:factorevaluation 能路由到 4090 机的 38000 端口(同网段应当直通;不通先
curl http://<4090内网IP>:38000/health 排网络)。以下 <BIONIC> 代指
http://<4090内网IP>:38000。
# 1.【4090 机】部署与本机自测 —— §5.1 的三条命令
# 2.【factorevaluation · tradingSystem 根目录】跨机探活 (只读, 不落表不产生指令):
docker compose run --rm --no-deps pms-web python scripts/probe_bionic.py --base <BIONIC>
# 判读: pms_exec 回 FIRE/WAIT 即通, 看应答里的区间对不对得上该股昨夜的支撑压力;
# 回 UNAVAILABLE 看原因 (无实时价=收盘后正常; 没有昨夜结论=该股不在每晚分析
# 范围, 换一只探)。pms_judge 回 PASS/REJECT 即通, 30~90 秒正常。
# 3.【factorevaluation】页面「参数设置」填 PMS_JUDGE_API_BASE=<BIONIC> → 研判闸生效。
# 验收: 下一轮自主提议扫描里, 需研判的动作 (FILL/ADD/DCA) 的评审账本出现
# arbiter=judge 的 PASS/REJECT 行 (make t-gate 看), 而不再是"降级人工确认"。
# 4.【factorevaluation】要切择时A: 页面把 PMS_EXEC_IMPL 改 A (随时可改回 B)。
# 验收: make watch 里在途指令的 last_decision 出现 source=A / A缓存;
# make t-ins 里 progress.exec_advice 有结论、区间理由与有效期。
# 5. 盘中观察一天: 每次咨询 bionic 侧日志有 [PMS 择时] 行; 对比实现A按区间给出的
# 出手/等待与实现B的差别 (关注限价挂区间上沿后, 委托是否不再成批到期)。
判收纪律照旧:代码就绪 ≠ 接通,接通 ≠ 判收。#10 判收 = 一笔真实提议走完
「规则闸 → 研判通过/驳回 → 档位分流」并在 PMS 评审账本留下 arbiter=judge 的行;
#9 判收 = 盘中一条指令按区间出手或等待、且对端故障时当轮自动退实现 B 有留痕
(source=B(实现A不可用))。
7. 变更记录
| 版本 | 日期 | 内容 |
|---|---|---|
| V1.0 | 2026-08-03 | 首版定稿并双侧落码。择时部分是一套盘中判定规则(资金阈值、动量追买等) |
| V1.1 | 2026-08-03 | 按用户审核意见重做择时:删掉 V1.0 的盘中判定规则(违反「提前计算为主、盘中监控为辅、不另做盘中判断、可以接受买不上」),改为由昨夜支撑压力推出执行区间、盘中只做区间比对与当日监控核对;研判留痕不再写 decision_ledger(每晚判分全表扫描,已核实);文档与代码注释清理生造词 |