From 0ff719ad0b09d6e91fd8fce6d4ae99312c58b3f7 Mon Sep 17 00:00:00 2001 From: zlt Date: Wed, 12 Aug 2026 14:51:22 +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 | 52 ++++++ app/web/main.py | 12 ++ app/web/static/index.html | 287 ++++++++++++++++++++++--------- 3 files changed, 265 insertions(+), 86 deletions(-) diff --git a/app/services/proposal_service.py b/app/services/proposal_service.py index 774cac4..4238e36 100644 --- a/app/services/proposal_service.py +++ b/app/services/proposal_service.py @@ -209,6 +209,58 @@ def _scan_open(view, params, stock_params, skip, mkt, out) -> list: return res["candidates"] +def disposition_snapshot(now=None) -> dict: + """候选池处置快照 (只读, 无副作用): 候选池里每只票今天为什么下单 / 没下单。 + + 页面「系统在盯的候选」原来只铺上游榜单, 看不出每只票的去向。这里补上去向与原因: + 名额满 / 可投金额不足 / 超上限 / 不足一手 / 在途 / 已持有 / 今日已拒 —— 这些原因每分钟的 + scan_and_route 都算了, 但算完即弃 (评审账本只留规则闸/研判闸的驳回, 这一层查不到)。 + + 实现上**直接复用** `_scan_open`: 把它写 out / mkt 的两个收集器换成丢弃用的临时容器, + 一个字都不改扫描逻辑, 口径与实盘扫描逐字一致 (不另写一份, 免得日后分叉)。_scan_open 内部 + 只读库与行情、不落任何表, 所以这样调用是安全的。 + 「已下单 / 已生成提议」交给前端用它已在手的 instructions / proposals 按股票对齐, 这里不重复查。 + """ + now = now or datetime.now() + res = {"ok": True, "ready": True, "at": now.isoformat(timespec="seconds"), + "by_code": {}, "notes": [], + "autonomy": param_store.get("PMS_AUTONOMY", AUTONOMY_PROPOSE), + "open_autonomy": param_store.get("PMS_OPEN_AUTONOMY", AUTONOMY_FULL)} + if res["autonomy"] == AUTONOMY_OFF: + res["notes"].append("自主档位 off —— 本轮不扫描候选池 (off 是总闸)") + return res + if param_store.get_bool("PMS_GLOBAL_EXEC_HALT", False): + res["notes"].append("全局暂停执行 (休假模式) —— 本轮不扫描候选池") + return res + if res["open_autonomy"] == AUTONOMY_OFF: + res["notes"].append("新建仓档位 off —— 不扫描候选池") + return res + try: + view = portfolio.positions_view() + params = _scan_params(view) + stock_params = command_service.effective_stock_params() + # 与实盘扫描同一份 skip: 在途 / 今日已被规则闸拒 / 今日已被研判闸驳回的新建仓 + skip = {**_judge_rejected_open_keys(), **_rejected_today_keys(), **_inflight_keys()} + sink = {"skipped": []} # 丢弃用: _scan_open 只往里 append/extend, 不读它 + would = _scan_open(view, params, stock_params, skip, {}, sink) + except Exception as e: + logger.exception("候选处置快照失败") + return {"ok": False, "ready": False, "error": f"{type(e).__name__}: {e}", + "by_code": {}, "notes": []} + for c in would: + res["by_code"][c["ts_code"]] = { + "disp": "would", "why": "名额与资金都够, 本轮将建底仓 (下一跳落单)"} + for s in sink["skipped"]: + code = s.get("ts_code") + if not code or code == "*": # 池级原因 (名额满/资金为零/候选池空…) 挂到 notes + if s.get("why"): + res["notes"].append(s["why"]) + else: # 单票原因; 不覆盖已判 would 的 + res["by_code"].setdefault( + code, {"disp": "deny", "why": s.get("why") or "未产出候选"}) + return res + + def _route_one(c, view, params, stock_params, brake_active, now, dry_run, out, *, deadline=None): code, action, side = c["ts_code"], c["action"], c["side"] diff --git a/app/web/main.py b/app/web/main.py index 23fd0cb..aad8f36 100644 --- a/app/web/main.py +++ b/app/web/main.py @@ -486,6 +486,18 @@ def api_clear_resync(): # ================================================================ 上游选股计划 +@app.get("/api/open-scan") +def api_open_scan(): + """候选池处置快照 (只读): 每只候选今天为什么下单 / 没下单。 + + 复用每分钟那条自主扫描的 scan_open 口径, 但止步于规则闸之前、不送研判、不落任何表 —— + 补上「名额/资金/上限/不足一手/在途/已持有」这些算完即弃、评审账本里查不到的原因。 + 「已下单/已生成提议」由前端拿它已在手的 instructions/proposals 按股票对齐, 这里不重复查。 + """ + from app.services import proposal_service + return ok(proposal_service.disposition_snapshot) + + @app.get("/api/upstream/plan") def api_upstream_plan(limit: int = Query(30), date: str = Query(None), bucket: str = Query("main")): diff --git a/app/web/static/index.html b/app/web/static/index.html index 442bbf4..40f7cdd 100644 --- a/app/web/static/index.html +++ b/app/web/static/index.html @@ -1,79 +1,128 @@ - + PMS 持仓管理系统 -
-

PMS 持仓管理系统

- v{{ health.version || '-' }} · {{ health.now || '' }} - 交易日 - 非交易日 - - - 交易员 - 运维 - +

PMS 持仓管理v{{ health.version || '-' }} · {{ health.now || '' }}

+ {{ health.trade_day ? '交易日':'非交易日' }} + 买入 {{ ov.buy_halt ? '已暂停':'正常' }} + 执行 {{ ov.exec_halt ? '已暂停':'正常' }} + 自主 {{ tx('autonomy', ov.autonomy) }}? + 计划 {{ planStatus.ok===false ? '不可用' : (planStatus.date || '-') }} + 下发 {{ dm.mode || '-' }}?
- - 买入 {{ ov.buy_halt ? '已暂停':'正常' }} - - 执行 {{ ov.exec_halt ? '已暂停':'正常' }} - 自主档位 {{ tx('autonomy', ov.autonomy) }} - - 下发 {{ dm.mode || '-' }} - - 计划 {{ planStatus.ok===false ? '不可用' : (planStatus.date || '-') }} - 上游计划 - 日报 - 运维 - 刷新 +
+ + +
+ + + +
自主档位
{{ tx('autonomy', ov.autonomy) }}
+
+ 颜色约定: + 红=涨/盈 + 绿=跌/亏 + 琥珀=需注意 + 橙=被拦下 + 蓝=可操作/正常 + ? +
+

需要注意

· {{ a }}
@@ -216,19 +275,42 @@

今日在办

- - - - - - - - +
今天下出去的买卖单,按状态分三档:还在跑的、已成交的、已经结束的。
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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

@@ -237,7 +319,7 @@

我的策略 · {{ strategies.length }} 个 - + {{ stratEnabled ? '策略层已启用' : '策略层总开关未开(PMS_STRATEGY_ENABLED)' }}

- +