tradingSystem/BIONIC_PMS_INTERFACE.md

18 KiB
Raw Blame History

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.py P 段。 机器口径PMS 跑在 factorevaluationbionic 跑在 4090 机API 宿主端口 38000)。


1. 分工与原则

设计 POSITION_MGMT_DESIGN.md §1/§7/§8 的落点:持仓系统管「做什么、多少」,决策系统管 「该不该、何时」。2026-08-03 用户补充的三条原则,两个接口都必须守:

  1. 决策系统还在运行、还有其他职责,改造只做加法。 新功能全部走新增分支、新增接口、 带默认值的新配置;原有六个盘中裁决分支、原有 API、每晚判分、建仓仲裁的输入输出一律 不动。凡是「顺手写进共享表」这类会影响既有功能的副作用,都要在本文写明并给出处置。
  2. 提前计算为主、盘中监控为辅,不另做盘中判断。 大模型推理放在凌晨(每晚的分析本来 就在产支撑位、压力位、定性结论),盘中接口只读凌晨结论和当天已有的监控产出,做比对、 不做判断。可以接受买不上——现价跑出执行区间就等待,不追。
  3. 拿不到结论时,各按各的纪律降级。 对 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_logverdict 记 PMS_PASS / PMS_REJECT / PMS_UNAVAILABLE,该列 VARCHAR(20) 装得下,已核对建表语句),不写 decision_ledger 2026-08-03 复核 outcome_scorer.py 确认每晚判分对决策账本是全表扫描、不按裁决类型过滤, 建仓仲裁的历史注入也读整本账——PMS 的裁决写进去会混进这两个既有功能。PMS 自己的评审记录 本来就落在它侧的 pms_action_ledgerarbiter=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。三层超时必须保持 75bionic 等任务)< 90PMS 等 HTTP,否则 PMS 先断开、bionic 白算。 队列默认 brain_queue12 并发零部署改动若盘中全量重算把它塞住、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.errorlast_decision.source 显示 B(实现A不可用: ...)
收盘后 / 无实时价 UNAVAILABLE「无实时价」 同上 同上
择时接口网络不通/超时 HTTP 层异常) 同上 同上
当天监控表查询失败 照常按区间应答,missingtoday_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 打进镜像正相反——不需要 buildgit 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.pyP 段,全默认值)、app/api/main.py(两个接口)、 workers/tasks_intraday.pyPMS_JUDGE 分支,含同股同动作的重复研判缓存)、 app/services/pms_advisor.py(新)、scripts/pms_smoke.py(新)。 既有六个盘中裁决分支、既有 API、每晚判分、建仓仲裁的行为一行未动PMS_JUDGE 是新增分支 且提前返回,不经过广播与重算;择时接口不写任何表)。

5.2 研判独立队列预埋第二档brain_queue 拥堵时再启用)

判据PMS 侧 [研判闸] 请求失败 ... Timeout 成为常态(偶发超时降级人工确认是设计内 行为不用切。启用bionic .envPMS_JUDGE_QUEUE=pms_queuecompose 加一个 专属 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(每晚判分全表扫描,已核实);文档与代码注释清理生造词