From df0f1dbec56d6f98c3153d4d1cc9799fe8a6120b Mon Sep 17 00:00:00 2001 From: zlt Date: Wed, 12 Aug 2026 15:31:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=A1=B5=E9=9D=A2=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/proposal_service.py | 48 ++++++++++++++++++++++++++++---- app/web/static/index.html | 14 +++++++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/app/services/proposal_service.py b/app/services/proposal_service.py index 89fe837..0901849 100644 --- a/app/services/proposal_service.py +++ b/app/services/proposal_service.py @@ -209,6 +209,41 @@ def _scan_open(view, params, stock_params, skip, mkt, out) -> list: return res["candidates"] +def _plain_check(c) -> str: + """规则闸的 failed 串形如 "T1_UNAVAILABLE: 卖出…" —— 去掉给运维看的大写代码前缀, 只留中文说明。""" + s = str(c) + head, sep, tail = s.partition(":") + if sep and head.strip() and all(ch.isupper() or ch.isdigit() or ch == "_" for ch in head.strip()): + return tail.strip() + return s.strip() + + +def _today_open_reject_reasons(now): + """今天各票**新建仓**被驳回的实因, 供候选处置显示真正的「为什么」: + 判 = 决策系统给的原话 (评审账本 arbiter=judge 那行的 reason); + 合规 = 规则闸具体未过的检查 (failed_checks, 去掉大写代码前缀)。 + 只读评审账本、按票取当天最新一条 (list_ledger 按 id 倒序); 读不到就返回空, 调用方退回概述。""" + judge, rule = {}, {} + today = now.strftime("%Y-%m-%d") + try: + for r in pms_repo.list_ledger(limit=500): + if r.get("verdict") != "REJECT" or r.get("action") != ae.A_OPEN: + continue + if str(r.get("decided_at"))[:10] != today: + continue + code = r.get("ts_code") + if not code: + continue + if r.get("arbiter") == "judge" and code not in judge: + judge[code] = (r.get("reason") or "").strip() + elif r.get("arbiter") == "rule" and code not in rule: + fc = r.get("failed_checks") or [] + rule[code] = ";".join(_plain_check(x) for x in fc) if fc else (r.get("reason") or "").strip() + except Exception: + logger.exception("读当日新建仓驳回实因失败 (退回概述)") + return judge, rule + + def disposition_snapshot(now=None) -> dict: """候选池处置快照 (只读, 无副作用): 候选池里每只票今天为什么下单 / 没下单。 @@ -239,14 +274,17 @@ def disposition_snapshot(now=None) -> dict: view = portfolio.positions_view() params = _scan_params(view) stock_params = command_service.effective_stock_params() - # 给交易员的白话原因: 用同一套去重键 (口径与实盘扫描一致), 但值不含内部闸门术语、 - # 也不指引任何终端命令 —— 原来的 _*_keys() 串带「研判闸/规则闸/看 make t-gate」, 那是 - # 给日志与运维看的, 不该出现在交易员页面。落库顺序同实盘 (在途最后, 覆盖被拒)。 + # 给交易员的白话原因: 用同一套去重键 (口径与实盘扫描一致), 但值不含内部闸门术语、也不指引 + # 任何终端命令。能取到实因就带上实因 (决策系统原话 / 合规具体未过项), 取不到才退回概述。 + # 落库顺序同实盘 (在途最后, 覆盖被拒)。 + _jr, _rr = _today_open_reject_reasons(now) skip = {} for _k in _judge_rejected_open_keys(): - skip[_k] = "决策系统今天判过暂不建仓,明天开盘会重新评估" + _w = _jr.get(_k[0]) + skip[_k] = ("决策系统判暂不建仓:" + _w) if _w else "决策系统今天判过暂不建仓,明天开盘会重新评估" for _k in _rejected_today_keys(): - skip[_k] = "今天没通过合规检查,明天开盘会重新评估" + _w = _rr.get(_k[0]) + skip[_k] = ("未通过合规检查:" + _w) if _w else "今天没通过合规检查,明天开盘会重新评估" for _k in _inflight_keys(): # 在途; 前端多半已按指令/提议标成「已下单/待你确认」 skip[_k] = "系统已经在处理这只票(见『等我拍板』或『今日在办』)" sink = {"skipped": []} # 丢弃用: _scan_open 只往里 append/extend, 不读它 diff --git a/app/web/static/index.html b/app/web/static/index.html index 37738db..3311d87 100644 --- a/app/web/static/index.html +++ b/app/web/static/index.html @@ -305,6 +305,14 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius: + + +
今天考察过、但没建仓的票,连原因一起摆出来。
+ + + + +

今天发生了什么 · {{ ledger.length }} 条 {{ showLedger?'▲ 收起':'▼ 展开' }}