处理现有问题
This commit is contained in:
parent
680210ebcc
commit
d0398b9331
31
Makefile
31
Makefile
|
|
@ -23,8 +23,8 @@ RUN := docker compose run --rm --no-deps pms-web
|
|||
|
||||
.PHONY: help deploy deploy-local build up down ps logs test stale initdb check health \
|
||||
probe changes industry ws-status rebuild rebuild-accept \
|
||||
t-plan t-pre t-cmd t-plans t-mat t-dry t-tick t-ins t-book t-gate \
|
||||
reset-ledger shell
|
||||
t-pool t-plan t-pre t-issue t-cmd t-cmds t-catalog t-plans t-mat t-dry \
|
||||
t-tick t-ins t-book t-gate reset-ledger shell
|
||||
|
||||
help: ## 列出所有目标
|
||||
@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
||||
|
|
@ -128,16 +128,37 @@ rebuild-accept: ## 只看判收: 安全垫分布 / 批次账 / 行业集中度
|
|||
A ?= http://127.0.0.1:38100
|
||||
J := python3 -m json.tool
|
||||
|
||||
t-plan: ## A1 拉候选池 (= 08:40 调度位)
|
||||
# t-pool 与 t-plans 是**两个完全不同的东西**, 别被名字带偏 (原名 t-plan/t-plans 只差一个 s,
|
||||
# 实测把人绕进去过):
|
||||
# t-pool = 上游选股计划 = **候选池** = "我可以买哪些票" (400 只, 来自 /plan 接口)
|
||||
# t-plans = PMS 自己排的 **方案** = "买哪只、多少股、分几批" (来自你下的命令)
|
||||
# 候选池是命令的**原料**, 方案是命令的**产物**。候选池满了不等于会有方案 —— 中间要有命令。
|
||||
t-pool: ## A1 拉**候选池**(上游选股计划="能买哪些"; = 08:40 调度位)
|
||||
@curl -s -X POST '$(A)/api/ops/plan-refresh' | $(J)
|
||||
|
||||
t-plan: t-pool ## (t-pool 的旧名; 别跟 t-plans 混, 那个是 PMS 排的方案)
|
||||
|
||||
t-pre: ## A2 盘前准备: T+1 可卖重置 / 参考位 / 刹车结算 (= 08:50 调度位)
|
||||
@curl -s -X POST '$(A)/api/ops/premarket' | $(J)
|
||||
|
||||
t-cmd: ## A3 命令轮询: 新命令 → 方案生成 (先下任务命令! 参数命令不产生方案)
|
||||
t-issue: ## A3a 下一条命令 (例: make t-issue T=INCREASE_EXPOSURE P='{"pct":0.3}')
|
||||
@test -n "$(T)" || { echo "用法: make t-issue T=<命令类型> P='<参数JSON>'"; \
|
||||
echo " 升仓三成: make t-issue T=INCREASE_EXPOSURE P='{\"pct\":0.3}'"; \
|
||||
echo " 单股建仓: make t-issue T=OPEN_TARGET P='{\"ts_code\":\"600000.SH\",\"target_pct\":0.06}'"; \
|
||||
echo " 看全部命令类型: make t-catalog"; exit 1; }
|
||||
@curl -s -X POST '$(A)/api/commands' -H 'Content-Type: application/json' \
|
||||
-d '{"cmd_type":"$(T)","params":$(if $(P),$(P),{}),"by":"make"}' | $(J)
|
||||
|
||||
t-cmd: ## A3b 命令轮询: 把**已下达**的命令排成方案 (它自己不产生命令, 先 t-issue)
|
||||
@curl -s -X POST '$(A)/api/ops/plan-pending' | $(J)
|
||||
|
||||
t-plans: ## A4 看方案 (方案是"买什么、多少股、分几批", 还没成指令)
|
||||
t-cmds: ## A3c 看命令台: 现在有哪些命令、什么状态
|
||||
@curl -s '$(A)/api/commands?limit=30' | $(J)
|
||||
|
||||
t-catalog: ## 看命令目录: 27 类命令各要什么参数 (行业源没配时相关命令会置灰)
|
||||
@curl -s '$(A)/api/commands/catalog' | $(J)
|
||||
|
||||
t-plans: ## A4 看**方案**("买哪只、多少股、分几批", 还没成指令; 与 t-pool 不是一回事)
|
||||
@curl -s '$(A)/api/plans?limit=50' | $(J)
|
||||
|
||||
t-mat: ## A5 方案 → 指令 (先记账后动作: 指令先落表)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ scripts/
|
|||
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 32 例
|
||||
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 60 例
|
||||
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 47 例
|
||||
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 38 例
|
||||
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 40 例
|
||||
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) 58 例
|
||||
init_db.py 建表 (应用 ddl_pms_v1.sql, 幂等, 默认演练; 含 DDL 体检)
|
||||
check_db.py 实机连通性与表结构自检 (需真实 .env)
|
||||
|
|
@ -302,7 +302,7 @@ make t-gate # 随时: 规则闸/研判闸拒了什么、为什么
|
|||
|
||||
## 已实现 / 待开发
|
||||
|
||||
**已实现**:建表 DDL 与建表脚本;配置与运行参数中心;仓位规划器与安全垫账;命令系统(27 类命令全目录 + 双状态机 + 冲突识别);方案生成器(降仓凑额四档、升仓、建仓分批、清仓/减至、行业清仓与限额、暂停买入撤单);账本回放与对账引擎(成交认领、外部成交并入 BASE 告警、以下游为准修正、除权检测、T+1 可用量、连续不一致升级);规则闸终检;择时执行器实现 B(分日配额、分笔、VWAP/回踩/不追高、14:45 兜底、停牌一字板顺延、窗口耗尽收口、挂单有效期);动作引擎四类自主动作 + 研判闸客户端 + 提议分流;决策系统信号消化(两条流独立消费组订阅、置信度分档转清仓指令或提议);管理页面四块 + 运维/日报抽屉;调度器九个调度位;**上游选股计划接口接入**(`/plan` 取候选池、交易日龄硬校验、`theme` 灌行业映射表、页面预览抽屉与不可用横幅);**榜单变化提示**(名册快照 + 新进/掉榜/档位升降/覆盖翻转/名次跳变,持仓票单列,榜尾截断噪音闸);**ws 直连通道的连接层**(常驻进程 + 出口队列 + 签名 + seq 水位与累积确认,见下);**单测 389 例**。
|
||||
**已实现**:建表 DDL 与建表脚本;配置与运行参数中心;仓位规划器与安全垫账;命令系统(27 类命令全目录 + 双状态机 + 冲突识别);方案生成器(降仓凑额四档、升仓、建仓分批、清仓/减至、行业清仓与限额、暂停买入撤单);账本回放与对账引擎(成交认领、外部成交并入 BASE 告警、以下游为准修正、除权检测、T+1 可用量、连续不一致升级);规则闸终检;择时执行器实现 B(分日配额、分笔、VWAP/回踩/不追高、14:45 兜底、停牌一字板顺延、窗口耗尽收口、挂单有效期);动作引擎四类自主动作 + 研判闸客户端 + 提议分流;决策系统信号消化(两条流独立消费组订阅、置信度分档转清仓指令或提议);管理页面四块 + 运维/日报抽屉;调度器九个调度位;**上游选股计划接口接入**(`/plan` 取候选池、交易日龄硬校验、`theme` 灌行业映射表、页面预览抽屉与不可用横幅);**榜单变化提示**(名册快照 + 新进/掉榜/档位升降/覆盖翻转/名次跳变,持仓票单列,榜尾截断噪音闸);**ws 直连通道的连接层**(常驻进程 + 出口队列 + 签名 + seq 水位与累积确认,见下);**单测 391 例**。
|
||||
|
||||
### 静默失败专项(2026-07-31,八条已修)
|
||||
|
||||
|
|
@ -335,7 +335,10 @@ make t-gate # 随时: 规则闸/研判闸拒了什么、为什么
|
|||
|
||||
实机踩到这条的路径是清账:账本里躺过 1100 股(市值 10461)→ 高水位记成 10461 → 清空账本 → 市值归 0 → 回撤 100%。改按总资产之后钱还在账户里,回撤是 0。另外 `PMS_HIGH_WATER` 本来就是账本的派生量,`reset_ledger` 现在把 `PMS_RECON_STREAK` / `PMS_RECON_STREAK_YMD` / `PMS_HIGH_WATER` / `PMS_BRAKE_UNTIL` 四个一起归零(`RESET_PARAMS`),[D3]~[D4] 四条单测钉住。
|
||||
|
||||
**另一头是「没事长得像出事」,同一种病的反面**,两处:
|
||||
**另一头是「没事长得像出事」,同一种病的反面**,三处:
|
||||
|
||||
`make t-cmd` 返回 `{"planned": [], "failed": []}` 读起来像「排方案失败了」,实际绝大多数时候是**根本没有命令可排**。这一步只是把**你已下达的**命令排成方案,它自己不产生命令;而 `reset_ledger` 默认连 `pms_command` 一起清,清完账就真的一条命令都没有了。现在多了 `scanned` / `note` / `commands_by_status`,没命令时直接告诉你去下一条。顺带把 Makefile 里那对害人的名字拆开了:`t-pool`(原 `t-plan`,单数)= 拉**候选池**「能买哪些」,`t-plans`(复数)= 看 PMS 排的**方案**「买哪只、多少股、分几批」——差一个字母,是两个完全不同的东西,候选池是命令的原料、方案是命令的产物,中间必须有一条命令。新增 `t-issue` / `t-cmds` / `t-catalog` 三个目标,命令行就能下命令、看命令、查命令目录。
|
||||
|
||||
|
||||
`make t-mat` 三个列表全空时读起来像「一条都没转成」,实际多半是「早就转完了」——它只吃 `PENDING` 状态的方案,已转指令的是 `EXECUTING`、等解锁的批是 `GATED`。现在输出多了 `scanned` 与 `note`,把当前方案分布直接写出来。
|
||||
|
||||
|
|
|
|||
|
|
@ -423,13 +423,41 @@ def cancel(command_id: str) -> dict:
|
|||
|
||||
|
||||
def plan_pending(limit: int = 20) -> dict:
|
||||
"""调度器每分钟一跳: 把 PENDING 的任务命令推进到 EXECUTING (幂等)。"""
|
||||
"""调度器每分钟一跳: 把 PENDING 的任务命令推进到 EXECUTING (幂等)。
|
||||
|
||||
**两个列表都空时要说清是哪一种空** (2026-08-03): 本函数只吃 `PENDING` 的**任务**命令,
|
||||
它是「把已下达的命令排成方案」的泵, **自己不产生命令**。而 `{"planned": [], "failed": []}`
|
||||
读起来像"排方案失败了", 实际绝大多数时候是"根本没有命令可排" —— 命令得先由人在页面
|
||||
命令台下达 (或 POST /api/commands)。参数命令 (总规模、上限这类) 也不产出方案, 它们
|
||||
是 CLS_PARAM, 压根不进这个查询。同 executor.materialize_plans 的毛病, 一并治。
|
||||
"""
|
||||
done, errs = [], []
|
||||
for c in pms_repo.list_commands(statuses=[cs.ST_PENDING], cmd_class=cs.CLS_TASK,
|
||||
limit=limit):
|
||||
rows = pms_repo.list_commands(statuses=[cs.ST_PENDING], cmd_class=cs.CLS_TASK,
|
||||
limit=limit)
|
||||
for c in rows:
|
||||
r = plan_command(c)
|
||||
(done if r.get("ok") else errs).append(c["command_id"])
|
||||
return {"planned": done, "failed": errs}
|
||||
out = {"planned": done, "failed": errs, "scanned": len(rows), "note": ""}
|
||||
if not rows:
|
||||
by = {}
|
||||
for st in (cs.ST_PLANNING, cs.ST_EXECUTING, cs.ST_PARTIAL, cs.ST_DONE,
|
||||
cs.ST_CANCELLED):
|
||||
try:
|
||||
by[st] = len(pms_repo.list_commands(statuses=[st], cmd_class=cs.CLS_TASK,
|
||||
limit=200))
|
||||
except Exception:
|
||||
pass
|
||||
out["commands_by_status"] = by
|
||||
live = sum(v for k, v in by.items()
|
||||
if k in (cs.ST_PLANNING, cs.ST_EXECUTING, cs.ST_PARTIAL))
|
||||
out["note"] = (
|
||||
"没有 PENDING 的任务命令可排 —— 这**不是**失败。本步只是把**你已经下达的**命令"
|
||||
"排成方案, 它自己不产生命令。" +
|
||||
(f"当前在途任务命令 {live} 条 (已排过方案, 看 make t-plans)。"
|
||||
if live else
|
||||
"一条在途任务命令都没有 —— 先去页面「命令台」下一条 (升仓/建仓/降仓…), "
|
||||
"或 POST /api/commands。参数命令 (总规模、上限这类) 不产出方案, 不算在内。"))
|
||||
return out
|
||||
|
||||
|
||||
def refresh_progress(command_id=None) -> dict:
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 (32 例)
|
||||
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 例)
|
||||
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (47 例)
|
||||
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (38 例)
|
||||
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (40 例)
|
||||
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) (58 例)
|
||||
共 389 例
|
||||
共 391 例
|
||||
任一子集失败即整体失败 (退出码 1)。
|
||||
"""
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -749,6 +749,35 @@ def _():
|
|||
ls.positions_source = orig
|
||||
|
||||
|
||||
@case("[L8] 一条命令都没下时, t-cmd 要说破是「没命令可排」而不是「排方案失败」")
|
||||
def _():
|
||||
from test_wiring import install_fakes
|
||||
from app.services import command_service
|
||||
install_fakes(prices={})
|
||||
r = command_service.plan_pending()
|
||||
assert r["planned"] == [] and r["failed"] == [], r
|
||||
assert r["scanned"] == 0, r
|
||||
assert "不**是**失败" in r["note"] or "不**不是**" in r["note"] or "不是" in r["note"], r
|
||||
assert "命令台" in r["note"], r["note"] # 要给出下一步怎么做
|
||||
assert r["commands_by_status"] == {"PLANNING": 0, "EXECUTING": 0, "PARTIAL": 0,
|
||||
"DONE": 0, "CANCELLED": 0}, r
|
||||
|
||||
|
||||
@case("[L9] 有在途命令但没 PENDING 的 → note 要指向 t-plans, 别让人以为没命令")
|
||||
def _():
|
||||
from test_wiring import install_fakes
|
||||
from app.services import command_service
|
||||
from app.core import command_spec as cs
|
||||
fake = install_fakes(prices={})
|
||||
fake.insert_command(command_id="CMD_X", cmd_class=cs.CLS_TASK,
|
||||
cmd_type="INCREASE_EXPOSURE", ts_code=None, params={"pct": 0.3},
|
||||
status=cs.ST_EXECUTING)
|
||||
r = command_service.plan_pending()
|
||||
assert r["scanned"] == 0, r
|
||||
assert "在途任务命令 1 条" in r["note"], r["note"]
|
||||
assert "t-plans" in r["note"], r["note"]
|
||||
|
||||
|
||||
@case("[L6] 规模参数远大于账户资金 → 盘前就说破 (别等规则闸刷一屏 INSUFFICIENT_CASH)")
|
||||
def _():
|
||||
from test_wiring import install_fakes
|
||||
|
|
|
|||
Loading…
Reference in New Issue