添加自动建仓逻辑
This commit is contained in:
parent
1efd7fb40f
commit
1f7b3e1e82
|
|
@ -0,0 +1,65 @@
|
|||
# 开发节点记录(三系统量化交易)
|
||||
|
||||
> 这份文件的用途只有一个:**让下一次交接不必重新查一遍**。
|
||||
> 每个重要节点写一条,写完就补,不攒到最后。一条节点包含五样东西:
|
||||
> 做了什么、动了哪些文件、部署方式、真机判收结果、还欠着什么。
|
||||
>
|
||||
> 三个系统的部署方式(改错等于白改):
|
||||
> - **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。
|
||||
> 容器时钟是 UTC,看时间戳先加八小时。
|
||||
> - **akg-factor-bridge(因子桥)** 在 `factorevaluation`,容器名 `akg_factor_bridge`,
|
||||
> API 在 8300,常驻 API 改码要 compose restart。容器时钟是 UTC。
|
||||
>
|
||||
> 记录纪律:判收状态只写「真机见过」或「未判收」两种,不写「应该没问题」。
|
||||
> 时间一律写北京时间,引用桥或决策系统的日志时间时先加八小时并标注已换算。
|
||||
|
||||
---
|
||||
|
||||
## 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 项目目录。
|
||||
|
||||
**部署方式**
|
||||
不涉及。
|
||||
|
||||
**真机判收**
|
||||
未判收(尚未动码)。
|
||||
|
||||
**还欠着什么**
|
||||
方案第十节的四件待拍板:新建仓的档位怎么给、每天最多新开几只、研判闸这一轮做不做、
|
||||
盘中参考位漂移要不要加防护。拍板之后才动手。
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
下一条节点从这里往下写,格式照抄上面:
|
||||
## YYYY-MM-DD · 一句话标题
|
||||
**做了什么** / **动了哪些文件** / **部署方式** / **真机判收** / **还欠着什么**
|
||||
-->
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
# 交接:三系统量化交易 · 2026-08-06 收盘前
|
||||
|
||||
> 这份东西是写给下一个对话开头的。可以整段贴过去,也可以留在仓库里当阶段存档。
|
||||
|
||||
我们在推进三个相互衔接的量化交易系统。请先读项目记忆里的 **tradingsystem-bionic-bridge**、
|
||||
**clock-and-timezones**、**tradingsystem-silent-failure** 三篇(都在 08-06 更新过),
|
||||
背景需要时再读 tradingsystem-ws-channel 和 quant-factor-synthesis,工作方式看
|
||||
feedback-plan-first 和 writing-style-readability。读完再看下文,然后等我给任务,不要自行开工。
|
||||
|
||||
---
|
||||
|
||||
## 一、三系统与部署方式(改错部署方式等于白改)
|
||||
|
||||
**tradingSystem(PMS,持仓管理)** 跑在 `factor@factorevaluation`。源码是打进镜像的,
|
||||
所以改码必须 `make deploy`,跑完再 `make test` 看到 ALL SUITES PASS;盯盘用 `make watch`。
|
||||
**容器时钟是北京时间。**
|
||||
|
||||
**bionic_trader(决策系统)** 跑在 `192.168.16.188:38000`(tlaipro 机)。挂载卷,
|
||||
`git pull` 之后 `docker compose restart backend-api worker-brain` 即可生效;
|
||||
但**新增 env 必须 `docker compose up -d --force-recreate`,`restart` 不重读 env**。
|
||||
**容器时钟是 UTC。**
|
||||
|
||||
**akg-factor-bridge(因子桥)** 在桥机 `factorevaluation`,容器名 `akg_factor_bridge`,
|
||||
API 在 8300。常驻 API 改码要 compose restart。**容器时钟是 UTC。**
|
||||
|
||||
线上运行中的系统只做加法。
|
||||
|
||||
## 二、规矩(不变)
|
||||
|
||||
先出方案再动手;中文可读性第一,写完整自然句、不自造缩写;只做加法;宁可空池;
|
||||
关键路径禁止丢弃返回值;全程 Docker,不给宿主机直跑 pip/python/psql;每条命令必须标注
|
||||
运行在哪台机器,一块代码里只放一台机器;判收的标准是真机各见一次;写回 Mac 的文件以
|
||||
written 回执加 md5 为准;**看桥和 bionic 的任何时间戳,先加八小时再和对话里的时间对齐。**
|
||||
|
||||
测试脚本由我在实机上运行,跑完把结果发给你,你不用跑复杂测试。里程碑的代码和文档
|
||||
请提示我及时创建或上传到项目目录。
|
||||
|
||||
## 三、下一个任务:给动作引擎加「新建仓」动作
|
||||
|
||||
**要解决的问题**:不能每天靠人手工走一遍下命令、排方案那一串。正常应该是上游选股计划
|
||||
出现之后,PMS 按规则自己执行。
|
||||
|
||||
**已经查清的现状,不用重查**:整条链除了「下命令」这一步,其余全是自动的。调度表里
|
||||
08:40 拉候选池,08:50 盘前准备,**每分钟**跑命令轮询(把已下达的命令排成方案)、盘中执行
|
||||
(方案转指令 + 自主提议扫描 + 择时出手)、成交入账加轻对账、消化决策系统的盘中信号,
|
||||
14:50 T 仓平回,15:10 日终结算加窗口收口,15:30 日报。`make t-*` 只是这些调度位的手动
|
||||
等价物,是排查用的,不是日常流程。
|
||||
|
||||
**唯一要人的就是下命令,而且是刻意的**:计划回答「能买哪些」,命令回答「今天打算投多少」。
|
||||
|
||||
**三条路,性质完全不同**:
|
||||
|
||||
一是把下命令挂成定时,每天自动下一条升仓命令。改动最小,加一个调度位;代价是仓位单向
|
||||
增长到撞上组合上限,而且不看市场状态。
|
||||
|
||||
二是把自主档位调到 full。**这条解决不了问题**——它只让已有持仓的增持类提议不用人确认,
|
||||
而动作引擎产出的只有补足底仓、回踩补仓、盈利加仓、减持这四类,**根本没有「新建仓」
|
||||
这个动作**。
|
||||
|
||||
三是给动作引擎加新建仓动作,让自主提议除了管已有持仓,也能从候选池里挑新票,走同样的
|
||||
规则闸、研判闸、档位分流。这才对得上要解决的问题,是新逻辑不是新调度位。
|
||||
|
||||
**我选第三条。先出方案,我拍板之后再动手。**
|
||||
|
||||
**开工前要知道的约束**:择时读的那张 `strategy_daily_results` 表,在盘中跑 push-pool
|
||||
触发补扫时会被就地改写(详见第六节)。开发期靠「盘中不跑 push-pool」这条纪律规避,
|
||||
没有改代码。设计新建仓动作时要把这件事放进考虑,别让无人值守的建仓建在会漂移的输入上。
|
||||
|
||||
## 四、当前系统状态(2026-08-06 盘中,约 11:15)
|
||||
|
||||
账户总资产 2,000,288.86,可用 1,881,764.86,组合仓位 5.92%。参数:总规模 200 万,
|
||||
自主档位 propose_only,下发模式 ws,择时档位 A,组合上限 70%,单股上限 8%,最多 20 只,
|
||||
执行窗口默认 3 个交易日。
|
||||
|
||||
**持仓三只**:600150.SH 一千七百股、可卖一千二、摊薄成本 34.625、安全垫 +0.16%,参考位
|
||||
支撑 34 压力 37 止损 34(来源 bionic);002335.SZ 一千九百股、可卖零、摊薄成本 30.984、
|
||||
安全垫 +0.41%;000035.SZ 一百股、可卖零、摊薄成本 5.028、安全垫 -1.55%,**决策系统对它
|
||||
的定性是 SELL**。
|
||||
|
||||
**在途四条**:000035 的减持一百股(来源自主提议,11:13 我在页面人工放行);002335 的清仓
|
||||
一千九百股(来源决策系统风控信号,置信度 95%);002518 的买入九百股(**被新装的定性闸
|
||||
拦着**,窗口今天到期);000063 的买入一千七百股(现价高于买入区间上沿,不追,窗口今天到期)。
|
||||
前两条都要等明天 T+1 才能出手。
|
||||
|
||||
## 五、08-05 到 08-06 做完的事
|
||||
|
||||
**买入侧补上了一道闸,两侧都已部署,真机判收成立。** 起因是发现候选层的结论对执行层
|
||||
完全不可见:桥用 `strategy_daily_results.signal_type` 落在 SELL、AVOID、DROPPED 就把票请出
|
||||
候选池,而决策系统的择时应答读同一个字段、放进 `observed.y_signal` 原样回传却从不用它判断,
|
||||
PMS 又只解析 verdict、limit_price、reason。同一条结论一边当判决一边当摆设。
|
||||
600841 就差一根 3.3% 的阴线会被买进来。
|
||||
|
||||
改动:决策系统 `app/services/pms_advisor.py` 加 `BAD_Y_SIGNALS` 常量和买入分支的闸,
|
||||
`config/settings.py` 加开关 `PMS_EXEC_BLOCK_BAD_Y_SIGNAL`;PMS 侧 `app/core/rule_gate.py`
|
||||
加 `BAD_SIGNAL` 检查项并把定性无条件写进 hard_numbers,`app/services/exec_advisor.py`
|
||||
把 `observed.y_signal` 存进 advice 并挂到决策上,`app/services/executor.py` 传进规则闸 flags,
|
||||
`scripts/test_batch3_units.py` 加一条用例。**08-06 真机拦下第一笔:002518 停在 PROPOSED、
|
||||
成交 0、理由是新写的那句。** PMS 那道 `BAD_SIGNAL` 至今没真的触发过(决策系统先回 WAIT
|
||||
就走不到规则闸),符合当初「第二道闸平时空转、只为留痕」的预期。
|
||||
|
||||
其余:600841 的在途买入指令已撤(回执是 `cancelled:0` 加「仅本地置撤销」,PROPOSED 从没
|
||||
下发过子单时走的就是这个分支);freeze 的 manifest.json 判收通过,08-03 和 08-04 两天
|
||||
都在;XXL 例行调度的根因查实——从来没有自动拉起过,日志里只有手动触发的行,配置已重新设计。
|
||||
|
||||
## 六、悬而未决(按优先级)
|
||||
|
||||
1. **主线**:动作引擎加新建仓动作,见第三节。
|
||||
2. **昨夜结论会被盘中补扫就地改写**。push-pool 跑完触发增量补扫,补扫用 `trade_date=昨日`
|
||||
就地覆盖已有那行,而 `fetch_yesterday_strategy` 是取最新一行、不按日期过滤。实证:
|
||||
000035 在 09:53 成交时定性是 WATCH、压力 5.2,补扫之后变 SELL、压力 5.15;002335 的支撑
|
||||
从 31.27 变成 29.00,差 7.3%。**开发期靠「盘中不跑 push-pool」规避,代码未改。**
|
||||
真要修,最小加法是给 `fetch_yesterday_strategy` 加「只取今天之前」的过滤,但那样盘中
|
||||
补扫的产出就完全用不上了,是不是想要的要先拍板。
|
||||
3. **卖出侧的镜像缺口**。桥对「持仓且形态恶化」只警示、明确不出池,而 PMS 没有任何机制
|
||||
会因为决策系统的定性去减仓——研判闸只管买入侧的补足、加仓、补仓,规则闸的冻结和黑名单
|
||||
只挡增持。买入侧补上了,卖出侧还是空的。000035 现在就是这个状态。
|
||||
4. **总资产与持仓市值的口径**。总资产来自 QMT 的 ws 资金快照,被组合刹车当权益基准用
|
||||
(高水位与回撤,15:10 跑);持仓市值是 PMS 拿 Redis 分钟线自算的。08-05 两边差 564 元,
|
||||
08-06 盘中只差 51 元。倾向是取数时点差不是口径差,收盘后价格静止时再对一次才能定案。
|
||||
5. **时区显示错位八小时**。`_today_exit_verdict` 把 created_at 直接截成「01:35」拼进理由,
|
||||
传到 PMS 页面和北京时间戳并排显示。给 bionic 容器设 `TZ=Asia/Shanghai`(新增 env,
|
||||
必须 force-recreate)或者格式化时显式转。低优先。
|
||||
6. **600150 最后五百股买在区间上沿**。区间连续四个交易日都是 [33.66, 34.9] 没变过,
|
||||
今天现价正好等于上沿,闭区间判定放行,成交在 34.89,摊薄成本被从 34.514 抬到 34.625、
|
||||
安全垫从峰值 +1.99% 压到 +0.01%。这是分批建仓遇上区间长期不变的固有副作用。
|
||||
要不要改(现价等于上沿时不买,或者区间连续 N 天不变时降档)是策略取舍,未拍板。
|
||||
7. **XXL 例行调度已重新配置**,验证点是看桥的 `data/xxl_build.log` 有没有在北京时间 07:10
|
||||
自动长出新行(日志是 UTC,会写成前一天的 23:10)。
|
||||
8. `trade_no` 格式核验(关系 S3 收尾函第一节的阻断项):
|
||||
`docker compose run --rm --no-deps pms-web python scripts/ws_smoke.py inbox --type trade --width 600`。
|
||||
是 `SHADOW-xxxx#1` 这种确定式就闭环,还是 T-SHADOW 随机串就继续追。这条一直没做。
|
||||
9. 命令目录里 `FREEZE_STOCK` 的说明写着「在途买入撤销」,与实现不符(它是参数类命令、
|
||||
没有 planner,只让规则闸的冻结项拦),文案要补账。
|
||||
10. 文档补账:`WS_INTEGRATION_STATUS.md` 停在 07-30;`BIONIC_PMS_INTERFACE.md` 第三节要
|
||||
加一句新闸;README 待办 9 和 10 标判收。
|
||||
11. 老悬项不变:分歧票候选层拦截未拍板;AKG_PLAN 并入 TECH_POOL 的共管规矩未拍板;
|
||||
07-29 那 19 条 SIG_INVALID 未查明。
|
||||
|
||||
## 七、这轮我犯过的三个误判,别重犯
|
||||
|
||||
**一是看见巧合就当线索。** 600150 的现价 34.900 恰好等于两笔委托的限价,我据此断定取价
|
||||
有问题,还推出「安全垫其实是负的」。实际行情源是活的、分钟线连续,34.900 就是当时的
|
||||
真实价。**先验证数据源是否在更新,再怀疑取值。**
|
||||
|
||||
**二是没换算时区。** 审计行写着 01:35,我当成北京时间,于是把一个正常的四分钟赛跑
|
||||
(09:31 咨询干净、09:32 成交、09:35:51 结论落库、09:36 信号到达)判成了「老闸漏读」的 bug,
|
||||
差点去改代码。**桥和 bionic 的时间戳一律先加八小时。**
|
||||
|
||||
**三是推理没落地就当结论。** 我推定 `audit_date` 会因为容器在 UTC 而错位一天,实际写入侧
|
||||
用的是北京日期,洞根本不存在。**时间相关的猜测必须先查实际数据再下判断。**
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
# 方案:给动作引擎加「新建仓」动作
|
||||
|
||||
> 状态:**待拍板**,2026-08-06 收盘后出。全部依据来自实际代码,不引用设计文档的结论。
|
||||
> 涉及系统:tradingSystem(PMS,主要改动)、bionic_trader(决策系统,可选的第二步)。
|
||||
> 部署方式:PMS 源码打进镜像,改码必须 `make deploy`,跑完 `make test` 见到 ALL SUITES PASS。
|
||||
|
||||
---
|
||||
|
||||
## 一、先说清现状:管道其实已经铺好了,缺的只有一段
|
||||
|
||||
读代码之后,一件事和交接信里的判断略有出入,而且是好消息:**`OPEN` 这个动作名在
|
||||
PMS 里已经存在,而且从方案一路到下单的整条管道都是通的**,缺的只是「自主提议」这一段。
|
||||
|
||||
已经存在的部分:
|
||||
|
||||
- `app/core/planner.py` 第 31 行 `A_OPEN, A_FILL, A_ADD, A_DCA = "OPEN", "FILL", "ADD", "DCA"`,
|
||||
建仓命令拆出来的底仓批产出的就是 `OPEN`。
|
||||
- `app/services/executor.py` 第 41 行 `BUY_ACTIONS = {"OPEN", "FILL", "ADD", "DCA"}`,
|
||||
动作是 `OPEN` 的指令已经能自动转成买入方向、走择时、过规则闸、下发。
|
||||
- `app/services/command_service.py` 第 647 行在候选被规划期拦下时,已经在往评审账本里
|
||||
写 `action="OPEN"` 的记录。
|
||||
- 择时接口 `exec_advisor._advice` 把 `action` 原样透传给决策系统,动作名叫什么它都不挑。
|
||||
|
||||
缺的部分,只有两处:
|
||||
|
||||
- `app/core/action_engine.py` 第 27 行的自主动作词表 `A_FILL, A_ADD, A_DCA, A_TRIM`
|
||||
里没有 `OPEN`,第 182 行的 `EVALUATORS` 也没有对应的求值器。
|
||||
- `action_engine.scan()` 第 197 行是 `for p in positions or []`,输入是
|
||||
`portfolio.positions_view()["held"]` —— **只遍历已有持仓,结构上就不可能引入新标的**。
|
||||
|
||||
所以这次要做的事可以说得很准确:**给动作引擎补一个以候选池为输入的求值器,让它产出
|
||||
`OPEN` 候选,然后并进现有的那条「规则闸 → 研判闸 → 档位分流」的路。** 下游一行都不用动。
|
||||
|
||||
---
|
||||
|
||||
## 二、选票规则:动作引擎只管名额、钱、批次,不管时机
|
||||
|
||||
候选池那边已经过完一整套筛选(`plan_feed.select_candidates`):按分数降序、档位白名单、
|
||||
分数与来源数与预期空间的下限、同主题限额、剔除 ST、剔除已持仓、剔除黑名单。
|
||||
排在它后面的规则闸又会管:一手整百、冻结、全局暂停买入、黑名单、决策系统昨夜定性
|
||||
`BAD_SIGNAL`、涨停不追、当日涨幅不追高、距 MA5 不追高、组合与单股与持仓数与行业集中度
|
||||
的上限、预留现金、真实可用资金。再后面的择时又会管:现价在不在买入区间内。
|
||||
|
||||
`action_engine.py` 第 17 行的注释已经把职责边界钉死了 ——「上限/一手/冻结等硬约束**不在
|
||||
这里重复判**,统一由规则闸终检(职责单一,口径唯一)」。所以新建仓求值器只回答三个问题:
|
||||
|
||||
1. **还能开几只**:`min(最大持仓数 − 当前持仓数, 每日新开上限 − 今日已开)`。
|
||||
2. **还有多少钱可投**:`总仓上限 × 总规模 − 当前组合市值`,与单只的目标金额取小。
|
||||
3. **这一批买多少股**:按 `PMS_BATCH_SPLIT` 拆批,**只提底仓那一批**(默认 50%),
|
||||
数量 `lot_qty(金额, 现价)`,不足一手就不提。
|
||||
|
||||
**只提底仓批**这一条是刻意的:后续的回踩补足与盈利加仓,等持仓真的建立起来之后,由已有的
|
||||
`eval_fill` 与 `eval_add` 自然接管——它们本来就是干这个的。这样不需要命令驱动那套
|
||||
`GATED` 方案的解锁机制,新建仓这条路上一条 GATED 都不产生。
|
||||
|
||||
**入场时机不在这里判。** 现价合不合适是择时的活;定性好不好是规则闸 `BAD_SIGNAL` 的活。
|
||||
动作引擎在这里另加一套判断,就违背了「提前计算为主、盘中监控为辅」,而且会和已有的闸重复。
|
||||
|
||||
**价格必须是实时价。** `market.plan_price` 在拿不到实时价时会回落昨收,它自己的注释写着
|
||||
「拿昨收当现价去做不追高这类判断会出错,只给规划期定量用」。自主建仓是要真下单的,
|
||||
所以走 `market.get_price`,取不到就整只跳过并写进 `skipped`——「什么都没发生」和
|
||||
「明着跳过了」在页面上必须是两回事,这是 `scan()` 入口处已经立过的规矩。
|
||||
|
||||
---
|
||||
|
||||
## 三、要动的文件与改法(PMS 侧,全部是加法)
|
||||
|
||||
### 1. `app/core/action_engine.py`
|
||||
|
||||
- 加常量 `A_OPEN = "OPEN"`,把它加进 `JUDGE_ACTIONS`。
|
||||
- 新增 `eval_open(c, params, mkt)`:输入是一条候选(代码、现价、分数、行业),
|
||||
输出与现有四个求值器同构的候选结构。
|
||||
- 新增 `scan_open(*, candidates, params, market, room, slots, skip)`:遍历候选,
|
||||
名额与金额是全局的,所以边遍历边扣减;每一条不产出候选都要写进 `skipped` 并说明原因。
|
||||
- **现有的 `scan()` 一个字不动**,既有单测继续全绿。
|
||||
|
||||
### 2. `app/services/proposal_service.py`
|
||||
|
||||
- `scan_and_route` 在 `ae.scan(...)` 之后追加一段:取候选池、取名额与可投金额、
|
||||
调 `ae.scan_open(...)`,把两批候选拼起来走同一个 `_route_one`。
|
||||
- 候选池取数失败(`PlanFeedError`)时**不产生任何新建仓候选,并把原因写进 `skipped`**。
|
||||
已有持仓的四类动作不受影响,照常扫描——一条外部接口的故障不该让整轮扫描停摆。
|
||||
- `_route_one` 里给 `OPEN` 三处特判:
|
||||
- 现价取候选自带的,不能取 `_pos_of` 的(没持仓的票那里没有 `price`,会是 0,
|
||||
规则闸直接 `PRICE_MISSING` 拒掉)。
|
||||
- 组合上下文改成 `portfolio.caps_ctx(view, ts_code=code, is_new_name=True,
|
||||
sector=industry.get(code))`。**必须显式传行业**:`caps_ctx` 只在
|
||||
`view["positions"]` 里找得到该票时才会带出行业,新票找不到,不传的话行业集中度
|
||||
整段静默跳过(`sizer.check_caps` 对 `sector` 为空是直接跳过的)。
|
||||
- 落指令时先 `pms_repo.ensure_position(code)` 建一行 `status='PLANNED'` 的持仓行,
|
||||
再写上 `target_pct`。不建这行的话 `bump_once_guards` 那类更新会影响 0 行。
|
||||
建了也不会污染上限计算——`positions_view` 的 `held` 过滤 `total_qty > 0`。
|
||||
- 档位分流:见第四节。
|
||||
|
||||
### 3. `app/services/exec_advisor.py` 与 `app/services/executor.py`(漂移防护)
|
||||
|
||||
见第五节。
|
||||
|
||||
### 4. `app/repo/pms_repo.py`
|
||||
|
||||
- 新增一个只读函数,统计今日已经开过或正在提议中的新仓票数(`pms_instruction` 与
|
||||
`pms_proposal` 里今天 `action='OPEN'` 的 distinct 股票数)。每日节流要用。
|
||||
|
||||
### 5. `app/services/param_store.py` 与 `config/settings.py`
|
||||
|
||||
新增参数(见第六节),并同步补 `DESC` 中文说明与 `_RANGES` 取值校验。
|
||||
`param_store.py` 第 46 到 48 行有一条血的教训写在那里:加参数表时不同步加白名单,
|
||||
`set_param` 只回 `ok=False` 不抛异常,调用方又把返回值丢了,于是那条纪律静默失效。
|
||||
这次一并照做。
|
||||
|
||||
### 6. `scripts/test_batch3_units.py`(或新增一个测试文件)
|
||||
|
||||
至少覆盖:名额已满、组合钱不够、买不足一手、取不到实时价、每日节流已用完、
|
||||
候选池取数失败、参考位漂移超阈值。**这些用例都是纯逻辑,不连库。**
|
||||
|
||||
---
|
||||
|
||||
## 四、档位分流:这是要你拍板的第一件事
|
||||
|
||||
现有分流在 `proposal_service._route_one` 第 132 行:
|
||||
|
||||
```
|
||||
auto_exec = (side == "sell") or (autonomy == AUTONOMY_FULL and not force_queue)
|
||||
```
|
||||
|
||||
新建仓是买入方向,所以在当前的 `propose_only` 档位下,它必然落进待确认队列。
|
||||
这意味着人力从「下命令 + 排方案 + 核方案」降到「点一下确认」,但仍然不是无人值守。
|
||||
|
||||
三条路:
|
||||
|
||||
- **甲:不动档位。** 新建仓入队待确认。改动最小,但没达到「不要每天靠人」的目标。
|
||||
- **乙:把 `PMS_AUTONOMY` 调成 `full`。** 新建仓自动了,但补足底仓、盈利加仓、
|
||||
浅档补仓也一起自动了——那是三个不同的决定,不该被一个开关捆在一起。
|
||||
- **丙(我的建议):给新建仓单独一个档位参数 `PMS_OPEN_AUTONOMY`。**
|
||||
取值 `inherit`(跟随全局,默认)/ `full`(自动执行)/ `propose_only`(待确认)。
|
||||
在 `auto_exec` 的计算里加一个针对 `OPEN` 的分支,不动既有那行的语义。
|
||||
这样可以做到「新建仓自动、其余仍待人确认」,也可以随时单独关掉新建仓而不影响别的。
|
||||
深档补仓的强制确认(`needs_user_confirm`)与研判不可用时的降级入队仍然无条件优先。
|
||||
|
||||
---
|
||||
|
||||
## 五、盘中输入漂移:这是要你拍板的第二件事
|
||||
|
||||
你点名的约束:择时读的 `strategy_daily_results` 在盘中跑 push-pool 触发补扫时会被就地改写。
|
||||
实证是 000035 的压力从 5.2 变 5.15、002335 的支撑从 31.27 变 29.00(差 7.3%)。
|
||||
|
||||
对新建仓的具体危害比对已有持仓大得多。已有持仓有摊薄成本和安全垫做锚,支撑压力漂一点,
|
||||
补仓补足的判断不会翻转;**而新建仓的买入区间完全由支撑压力推出来**,支撑变 7.3%,
|
||||
区间整体平移,一只上午判为「现价高于上沿、不追」的票,下午可能变成「在区间内、出手」——
|
||||
而这时候没有人在看。
|
||||
|
||||
两条路:
|
||||
|
||||
- **甲:这一轮不做防护,靠「盘中不跑 push-pool」的纪律。**
|
||||
改动最小,但把一条口头纪律放进了无人值守的关键路径。哪天忘了,或者哪天 XXL 例行调度
|
||||
真的自动跑起来了,没有任何东西会拦。
|
||||
|
||||
- **乙(我的建议):首答锁定 + 偏离即停,只作用于新建仓。**
|
||||
一条 `OPEN` 指令当天第一次拿到择时应答时,把应答 `observed` 里的支撑、压力、买入区间
|
||||
存进 `progress.ref_lock`。之后每次应答都跟它比,偏离超过 `PMS_OPEN_REF_DRIFT_MAX`
|
||||
(默认 3%)就把本轮判定改成等待,理由写明「昨夜结论盘中被改写:支撑 31.27 → 29.00,
|
||||
偏离 7.3%,该票新建仓当日暂停」,并在评审账本落一条 `WARN`。
|
||||
|
||||
三个好处:纯加法、只在 PMS 侧、不需要动桥和决策系统;不需要先拍板悬项二那个
|
||||
「要不要给 `fetch_yesterday_strategy` 加日期过滤」的取舍;而且**它顺带把漂移变成了
|
||||
可统计的数据**——漂了几次、每次漂多少,都会留在账本里,将来真要改上游时有实证依据。
|
||||
|
||||
代价是 `exec_advisor` 与 `executor` 各要动几行,而这两个文件是买入侧新闸刚改过的地方。
|
||||
|
||||
---
|
||||
|
||||
## 六、新增参数(全部有默认值,页面可调,默认值等于「不改变现有行为」)
|
||||
|
||||
| 键 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `PMS_OPEN_ENABLED` | `False` | 自主新建仓总开关。**默认关**,部署后行为与今天一字不差 |
|
||||
| `PMS_OPEN_AUTONOMY` | `inherit` | 新建仓单独档位:inherit 跟随全局 / full 自动执行 / propose_only 待确认 |
|
||||
| `PMS_OPEN_MAX_PER_DAY` | `1` | 每个交易日最多自动新开几只 |
|
||||
| `PMS_OPEN_MAX_NAMES` | `0` | 自主新建仓能占的持仓数上限,0 = 不额外限制(仍受 `PMS_MAX_NAMES` 约束) |
|
||||
| `PMS_OPEN_TARGET_PCT` | `0` | 自主新建仓的单股目标仓位,0 = 用 `PMS_STOCK_TARGET_DEFAULT` |
|
||||
| `PMS_OPEN_MIN_SCORE` | `0` | 在候选池已有过滤之上,自主建仓额外的分数下限,0 = 不额外过滤 |
|
||||
| `PMS_OPEN_REF_DRIFT_MAX` | `0.03` | 参考位盘中改写的容忍幅度,超过则该票当日暂停新建仓(选了第五节的乙才需要) |
|
||||
|
||||
`PMS_OPEN_ENABLED` 建议同时加进 `param_store.FAIL_CLOSED`,取值 `False`——参数表读不到时
|
||||
安全方向是不建仓。
|
||||
|
||||
**每日只开一只**这个默认值需要你确认。理由:候选池 top 30、持仓上限 20 只,如果不限,
|
||||
第一天就会把组合一次性建满,所有仓位同一天建立、同涨同跌,而且撞上的还是交接信里
|
||||
「仓位单向增长」的另一个版本。一天一只,二十个交易日建满,节奏上更像人在做。
|
||||
|
||||
---
|
||||
|
||||
## 七、研判闸:这是要你拍板的第三件事
|
||||
|
||||
`judge.request` 的第一行是:动作不在 `PMS_JUDGE_ACTIONS`(页面可调,默认
|
||||
`FILL,ADD,DCA,SWITCH`)里就直接回 `PASS`。而决策系统那侧按动作类型给定性判据的那张表,
|
||||
只有 FILL、ADD、DCA、SWITCH 四行,没有 `OPEN`。契约写明「越界与解析失败一律回
|
||||
`UNAVAILABLE`」,而 PMS 收到 `UNAVAILABLE` 会 `degraded=True` → 强制入队待人确认。
|
||||
|
||||
**也就是说,如果现在就把 OPEN 送研判,新建仓会 100% 落回人工确认——正好把这次要解决的
|
||||
问题原样退回去。**
|
||||
|
||||
两条路:
|
||||
|
||||
- **甲(我的建议):这一轮 PMS 侧把口子留好,实际先不送研判。**
|
||||
`action_engine.JUDGE_ACTIONS` 加上 `A_OPEN`(让它有资格),但页面参数
|
||||
`PMS_JUDGE_ACTIONS` 不加 `OPEN`(实际不送)。下一轮决策系统侧补上 OPEN 的判据、
|
||||
联调通过之后,在页面参数里加一个词就生效,**零部署、随时可退**。
|
||||
这一轮新建仓只过规则闸——规则闸已经有 `BAD_SIGNAL` 那道定性闸,加上候选池本身就是
|
||||
决策系统每晚推理覆盖过的池子,不是裸奔。
|
||||
|
||||
- **乙:这一轮双侧一起改。** 决策系统 `workers/tasks_intraday.py` 的 `PMS_JUDGE` 分支加
|
||||
OPEN 的判据(核心问题大致是「现在从零建这只票,逻辑成立吗」),
|
||||
`app/services/pms_advisor.py` 与判据表同步。跨机改动,部署方式是挂载卷 `git pull` 后
|
||||
`docker compose restart backend-api worker-brain`;如果加了新的 env 就必须
|
||||
`docker compose up -d --force-recreate`。
|
||||
|
||||
---
|
||||
|
||||
## 八、两个已知的交互,先摆出来
|
||||
|
||||
1. **命令驱动的 OPEN 拒绝记录会挡住自主 OPEN。**
|
||||
`command_service._ledger_rejects` 写的是 `action="OPEN"`、`arbiter="rule"`、
|
||||
`verdict="REJECT"`,而 `proposal_service._rejected_today_keys` 读的正是这张表的
|
||||
`(ts_code, action)`。所以某只票今天因为一条升仓命令在规划期被上限拦过,
|
||||
自主新建仓当天就不会再提它。**多数情况下这是对的**(同一组约束),但两边算的目标金额
|
||||
不一样,一手不足这类拒绝可能口径不同。先记下来,观察一天再看要不要分开。
|
||||
|
||||
2. **升仓命令的候选没有排除已有在途方案的票。**
|
||||
`command_service._dispatch_planner` 算出了 `exclude = _codes_with_live_plans()`,
|
||||
但只传给了降仓分支,升仓分支没传。这是既有的现象,不属于本次改动范围,
|
||||
但自主新建仓上线后两条路会同时挑票,撞车概率上升。
|
||||
自主这条路有 `_inflight_keys` 兜底(同票同动作有在途提议或指令就不重复提),
|
||||
命令那条路没有。**建议这次不动它,先观察**。
|
||||
|
||||
---
|
||||
|
||||
## 九、分步上线与判收
|
||||
|
||||
判收纪律照旧:代码就绪不等于接通,接通不等于判收,标准是真机各见一次。
|
||||
|
||||
| 步 | 动作 | 判收标准 |
|
||||
|---|---|---|
|
||||
| 1 | `make deploy` + `make test`,`PMS_OPEN_ENABLED=False` | ALL SUITES PASS,且盘中行为与今天一字不差(`make watch` 看不出变化) |
|
||||
| 2 | 页面开 `PMS_OPEN_ENABLED=True`,档位 `propose_only`,每日 1 只 | 提议队列出现一条 OPEN,理由读得懂,硬数字里有名额、可投金额、批次与分数 |
|
||||
| 3 | 人为把 `PMS_MAX_NAMES` 调到当前持仓数 | 评审账本出现 `MAX_NAMES` 的 OPEN 拒绝行(`make t-gate` 看得到) |
|
||||
| 4 | 档位改 `full` | 一条 OPEN 提议直接落指令,`make t-ins` 看到 `origin_type=proposal`、`action=OPEN`、`is_command=false` |
|
||||
| 5 | 盘中观察那条指令 | 现价在买入区间内时 FIRE,`last_decision.source=A`,限价等于区间上沿 |
|
||||
| 6 | 成交之后 | `pms_position` 出现该票、`opened_date` 有值、`status=HOLDING`;次日起 `eval_fill` 能看见它 |
|
||||
| 7 | 漂移防护(若选了第五节的乙) | **建议只靠单测判收,不在生产人为制造**:盘中跑 push-pool 触发补扫风险太大。生产侧只观察账本里有没有 `REF_DRIFT` 的 WARN 行 |
|
||||
|
||||
---
|
||||
|
||||
## 十、需要你拍板的四件事(其余我按上面的建议做)
|
||||
|
||||
1. **档位**:给新建仓单独一个 `PMS_OPEN_AUTONOMY`(建议),还是直接用全局 `PMS_AUTONOMY`?
|
||||
2. **每日节流**:一天最多新开几只?(建议 1)
|
||||
3. **研判闸**:这一轮 PMS 侧留口子、决策系统侧下一轮再做(建议),还是这一轮双侧一起改?
|
||||
4. **漂移防护**:做首答锁定加偏离即停(建议),还是这一轮先不做、继续靠「盘中不跑
|
||||
push-pool」的纪律?
|
||||
Loading…
Reference in New Issue