处理问题

This commit is contained in:
zlt 2026-08-03 10:45:18 +08:00
parent f03de08ae0
commit 2f1dcf3373
4 changed files with 99 additions and 7 deletions

View File

@ -67,7 +67,7 @@ scripts/
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 32 例
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 60 例
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 49 例
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 46
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 48
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) 58 例
init_db.py 建表 (应用 ddl_pms_v1.sql, 幂等, 默认演练; 含 DDL 体检)
check_db.py 实机连通性与表结构自检 (需真实 .env)
@ -304,7 +304,7 @@ make t-gate # 随时: 规则闸/研判闸拒了什么、为什么
## 已实现 / 待开发
**已实现**:建表 DDL 与建表脚本配置与运行参数中心仓位规划器与安全垫账命令系统27 类命令全目录 + 双状态机 + 冲突识别);方案生成器(降仓凑额四档、升仓、建仓分批、清仓/减至、行业清仓与限额、暂停买入撤单);账本回放与对账引擎(成交认领、外部成交并入 BASE 告警、以下游为准修正、除权检测、T+1 可用量、连续不一致升级);规则闸终检;择时执行器实现 B分日配额、分笔、VWAP/回踩/不追高、14:45 兜底、停牌一字板顺延、窗口耗尽收口、挂单有效期);动作引擎四类自主动作 + 研判闸客户端 + 提议分流;决策系统信号消化(两条流独立消费组订阅、置信度分档转清仓指令或提议);管理页面四块 + 运维/日报抽屉;调度器九个调度位;**上游选股计划接口接入**`/plan` 取候选池、交易日龄硬校验、`theme` 灌行业映射表、页面预览抽屉与不可用横幅);**榜单变化提示**(名册快照 + 新进/掉榜/档位升降/覆盖翻转/名次跳变,持仓票单列,榜尾截断噪音闸);**ws 直连通道的连接层**(常驻进程 + 出口队列 + 签名 + seq 水位与累积确认,见下);**单测 399 例**。
**已实现**:建表 DDL 与建表脚本配置与运行参数中心仓位规划器与安全垫账命令系统27 类命令全目录 + 双状态机 + 冲突识别);方案生成器(降仓凑额四档、升仓、建仓分批、清仓/减至、行业清仓与限额、暂停买入撤单);账本回放与对账引擎(成交认领、外部成交并入 BASE 告警、以下游为准修正、除权检测、T+1 可用量、连续不一致升级);规则闸终检;择时执行器实现 B分日配额、分笔、VWAP/回踩/不追高、14:45 兜底、停牌一字板顺延、窗口耗尽收口、挂单有效期);动作引擎四类自主动作 + 研判闸客户端 + 提议分流;决策系统信号消化(两条流独立消费组订阅、置信度分档转清仓指令或提议);管理页面四块 + 运维/日报抽屉;调度器九个调度位;**上游选股计划接口接入**`/plan` 取候选池、交易日龄硬校验、`theme` 灌行业映射表、页面预览抽屉与不可用横幅);**榜单变化提示**(名册快照 + 新进/掉榜/档位升降/覆盖翻转/名次跳变,持仓票单列,榜尾截断噪音闸);**ws 直连通道的连接层**(常驻进程 + 出口队列 + 签名 + seq 水位与累积确认,见下);**单测 401 例**。
### 静默失败专项2026-07-31八条已修

View File

@ -22,7 +22,7 @@ from app.core import command_spec as cs
from app.core import exec_timing as et
from app.core import rule_gate
from app.core import tradedays as td
from app.repo import pms_repo
from app.repo import pms_repo, qmt_repo
from app.services import (command_service, dispatcher, industry, market, param_store,
portfolio)
@ -153,8 +153,7 @@ def run_tick(*, now=None, dry_run: bool = False) -> dict:
pos = _pos_of(view, code)
quota = et.daily_quota(remaining, tdays_left,
allow_odd_tail=(ins.get("action") == "EXIT"))
fired_today = sum(int(c.get("qty") or 0) for c in children
if int(c.get("ymd") or 0) == ymd_today)
fired_today = _consumed_today(children, ymd_today)
day_ctx = {**day, "support": pos.get("support_ref"),
"limit_up": _limit_up(day), "limit_down": _limit_down(day),
"halted": not day or not day.get("price")}
@ -351,6 +350,49 @@ def _child_id(instruction_id: str, n: int) -> str:
return f"{instruction_id}_D{n:02d}"
def _consumed_today(children: list, ymd_today: int) -> int:
"""今日已投放量 —— **到期/被撤/被拒且一股没成的分片不算数**。
2026-08-03 实机: 002518.SZ 的分片挂到 TTL 到期没成交, 当天配额却显示已出完,
一股没买到而当日不再尝试这跟 `exec_timing.slice_deadline` 自己写的设计本意直接
冲突 那里说得很清楚:普通分片只给 ttl_min 个交易分钟, run_tick 每分钟重评一次,
**撤掉重下比挂着更好**可只要作废的分片仍占着当日额度, 重下就永远轮不上
配额的语义是今天投放多少, **已经作废且零成交的投放不该占额度** 那笔钱没花
出去, 敞口也没建立, 它什么都没发生所以按委托的真实结局算:
* 还在途 (未终态) 按下发量算, 它随时可能成交
* 已终态 只算真正成交的 cum_qty, 未成交部分释放回配额
* 查不到出口行 按下发量算 (影子模式没有出口行; 宁可少投也不重复投)
"""
total = 0
for c in children or []:
if int(c.get("ymd") or 0) != ymd_today:
continue
qty = int(c.get("qty") or 0)
ref = c.get("ref")
if not ref or str(ref).startswith("manual:"):
total += qty # 影子模式: 没有出口行可查
continue
try:
o = qmt_repo.get_order(ref)
except Exception:
logger.warning("[配额] 反查出口行失败 %s, 本轮按下发量计入", ref)
total += qty
continue
if not o:
total += qty
continue
if str(o.get("status") or "").upper() in qmt_repo.FINAL:
done = int(o.get("cum_qty") or 0)
if done < qty:
logger.info("[配额] %s 已终态 %s, 成交 %s/%s —— 未成交部分释放回当日配额",
ref, o.get("status"), done, qty)
total += done
else:
total += qty
return total
def _limit_up(day: dict) -> bool:
"""一字板粗判: 当日最高=最低=现价 且 涨幅为正 (无涨跌停价字段时的兜底口径)。"""
if not day:

View File

@ -14,9 +14,9 @@
test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫 (32 )
test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 )
test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 )
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (46 )
test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 (48 )
test_wiring.py 装配自检: 服务层核心落表 全链路 (内存桩) (58 )
399
401
任一子集失败即整体失败 (退出码 1)
"""
import os

View File

@ -910,6 +910,56 @@ def _():
assert "已下发但父指令未更新" in src, "下发成功但更新影响 0 行时没有留出口"
# ================================================================
# [N] 当日配额: 作废且零成交的分片不该占额度
# ================================================================
@case("[N1] 分片到期未成交 → 未成交部分释放回当日配额 (否则「撤掉重下」永远轮不上)")
def _():
from test_wiring import install_fakes
from app.services import executor
from app.repo import qmt_repo
install_fakes(prices={})
orders = {"P_D01": {"status": "EXPIRED", "cum_qty": 0},
"P_D02": {"status": "SUBMITTED", "cum_qty": 0},
"P_D03": {"status": "FILLED", "cum_qty": 100},
"P_D04": {"status": "EXPIRED", "cum_qty": 40}}
orig = qmt_repo.get_order
try:
qmt_repo.get_order = lambda ref: orders.get(ref)
kids = [{"ymd": 20260803, "qty": 100, "ref": r} for r in
("P_D01", "P_D02", "P_D03", "P_D04")]
# D01 到期零成交 → 0; D02 在途 → 按下发量 100; D03 全成 → 100; D04 到期成 40 → 40
assert executor._consumed_today(kids, 20260803) == 240
# 不是今天的分片一概不算
assert executor._consumed_today(
[{"ymd": 20260731, "qty": 999, "ref": "P_D01"}], 20260803) == 0
finally:
qmt_repo.get_order = orig
@case("[N2] 影子模式 / 查不到出口行 → 按下发量算 (宁可少投也不重复投)")
def _():
from test_wiring import install_fakes
from app.services import executor
from app.repo import qmt_repo
install_fakes(prices={})
orig = qmt_repo.get_order
try:
qmt_repo.get_order = lambda ref: None
assert executor._consumed_today(
[{"ymd": 20260803, "qty": 100, "ref": "manual:INS_X"}], 20260803) == 100
assert executor._consumed_today(
[{"ymd": 20260803, "qty": 100, "ref": "P_D01"}], 20260803) == 100
def boom(ref):
raise RuntimeError("库挂了")
qmt_repo.get_order = boom
assert executor._consumed_today(
[{"ymd": 20260803, "qty": 100, "ref": "P_D01"}], 20260803) == 100
finally:
qmt_repo.get_order = orig
# ================================================================
def main():
ok = fail = 0