diff --git a/app/services/param_store.py b/app/services/param_store.py index d50a5e6..df8c824 100644 --- a/app/services/param_store.py +++ b/app/services/param_store.py @@ -203,6 +203,8 @@ DESC = { "仅展示不出提议 (上游行无 verdict 或本开关关着时按档位, 与旧行为一致)", "PMS_OPEN_AUTO_EXEC_ON_VERDICT": "propose_only 档位下, 判决为候选 + 决策系统研判通过 + 规则闸通过 + " "上游风险列表为空 的新建仓提议直接落指令 (默认关; 其余仍入队等人裁决)", + "PMS_JUDGE_REJECT_CONF_MIN": "新建仓被决策系统驳回时, 把握度低于这个数 (0~100) 的驳回按「证据不足」" + "交人裁决而不是记驳回 (默认 60; 只对新建仓; 一次定死, 不按复盘读数回调)", "PMS_JUDGE_TICK_BUDGET_SEC": "单轮提议扫描用于研判的时间预算 (秒), 用尽则剩下的候选留到下一跳", "PMS_JUDGE_ENABLED": "研判闸开关", "PMS_JUDGE_ACTIONS": "需过研判闸的动作", "PMS_JUDGE_TIMEOUT": "研判超时 (秒) → 降级 propose_only", diff --git a/app/services/proposal_service.py b/app/services/proposal_service.py index 7c7876c..c802def 100644 --- a/app/services/proposal_service.py +++ b/app/services/proposal_service.py @@ -505,6 +505,17 @@ def _route_one(c, view, params, stock_params, brake_active, now, dry_run, out, return verdict = judge.request(c, context={"position": _judge_ctx(pos), "recent_ledger": _recent_ledger(code)}) + # 新建仓的低把握驳回改交人 (2026-09-07, 第二道保险)。择时决策系统的提示词已改成 + # 「证据不足以判断 → 不可用」, 但模型未必每次守得住; 它给的把握度是现成的读数, + # 低于阈值的驳回按「不可用」处理 —— 进人的待确认队列, 不记驳回、不杀提议。 + # 只对新建仓: 加仓类的驳回口径不动。阈值一次定死, 不按复盘读数回调。 + if is_open and verdict["verdict"] == judge.REJECT: + conf = verdict.get("confidence") + floor_conf = param_store.get_int("PMS_JUDGE_REJECT_CONF_MIN", 60) + if conf is not None and float(conf) < floor_conf: + verdict = {**verdict, "verdict": judge.UNAVAILABLE, "degraded": True, + "reason": (f"决策系统驳回但把握度只有 {int(conf)} (低于 {floor_conf}), " + f"按证据不足交人: {verdict.get('reason') or ''}")[:500]} if verdict["verdict"] == judge.REJECT: out["rejected"].append({"ts_code": code, "action": action, "by": "judge", "failed": [verdict.get("reason") or "决策系统驳回"]}) diff --git a/scripts/run_tests.py b/scripts/run_tests.py index dcfc23d..82cb390 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -50,7 +50,8 @@ 含会话密钥/研判键放行与必答题/置信度进硬数字/ 仅展示的跳过原因写出这一只自己的判决依据/ 候选一只不剩时的说明不打印计数字典/ - 页面静态守卫 (17 例) + 新建仓低把握驳回按证据不足交人/ + 页面静态守卫 (18 例) test_batch21_units.py 减持的自动执行边界 (2026-09-03 安全修复): 保垫减仓照旧自动/ 强制入队对卖出同样有效/研判不可用的减持入队/研究证据走弱 来源必定交人裁决/风控高置信卖出与命令清仓不经提议分流/ @@ -61,9 +62,9 @@ test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) + 目标价到价必定入队 (档位 full 也不自动卖) + 用户设的止损价与目标价单独成列显示 (70 例) - 共 660 例 + 共 661 例 (总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条 - 减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 现为 660) + 减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 现为 661) 任一子集失败即整体失败 (退出码 1)。 哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了): diff --git a/scripts/test_batch20_units.py b/scripts/test_batch20_units.py index 544d1f0..1c9a74c 100644 --- a/scripts/test_batch20_units.py +++ b/scripts/test_batch20_units.py @@ -325,6 +325,31 @@ def _(): assert led["hard_numbers"]["verdict"] == "候选" and led["ref_id"] == ins["instruction_id"] +@case("低把握驳回交人·新建仓被决策系统驳回但把握度低于阈值 → 按证据不足入队, 不记驳回 (2026-09-07 第二件)") +def _(): + low = {"verdict": "REJECT", "reason": "资金面偏弱, 说不准驱动是否仍成立", "degraded": False, + "raw": {"verdict": "REJECT", "confidence": 40}, "confidence": 40.0} + out, got = _route(_open_cand(), values={}, judge_resp=low) + assert not out["rejected"] and len(out["queued"]) == 1, out + assert "把握度只有 40" in out["queued"][0]["why"] and "交人" in out["queued"][0]["why"], out["queued"] + assert out["degraded"] is True + hn = got["proposals"][0]["hard_numbers"] + assert hn["judge_verdict"] == "UNAVAILABLE" and hn["judge_conf"] == 40.0, hn + assert not [l for l in got["ledger"] if l["verdict"] == "REJECT"], got["ledger"] + # 把握度够高的驳回照旧记驳回、杀提议 (老行为) + high = {**low, "raw": {"verdict": "REJECT", "confidence": 85}, "confidence": 85.0} + out, got = _route(_open_cand(), values={}, judge_resp=high) + assert len(out["rejected"]) == 1 and not out["queued"] and not got["proposals"], out + assert got["ledger"][0]["verdict"] == "REJECT" and got["ledger"][0]["arbiter"] == "judge" + # 没带把握度的驳回不猜, 照旧驳回 + none = {**low, "raw": {"verdict": "REJECT"}, "confidence": None} + out, got = _route(_open_cand(), values={}, judge_resp=none) + assert len(out["rejected"]) == 1 and not out["queued"], out + # 阈值是参数, 调到 30 后把握度 40 的驳回就照旧驳回 + out, got = _route(_open_cand(), values={"PMS_JUDGE_REJECT_CONF_MIN": 30}, judge_resp=low) + assert len(out["rejected"]) == 1 and not out["queued"], out + + @case("自动执行开关·任一条件不满足仍入队 (开关关 / 关注 / 研判不可用 / 没问过决策系统的放行 / 有风险)") def _(): # ① 开关关着 (默认): 四条其余全满足也入队, 硬数字带研判结论与置信度