From 8605efa2ed5ae7c15f6d9a5ceab4d566810df81f Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 12 Aug 2026 10:37:31 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BF=BB=E5=80=8D=E5=87=BA=E5=9C=BA=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E4=B8=AD=E6=8C=89=E9=92=AE=E6=94=B9=E4=B8=BA=E5=8F=96?= =?UTF-8?q?=E6=B6=88=EF=BC=9B=E4=B8=AD=E6=8E=A7=E7=9B=AE=E6=A0=87=E7=9B=91?= =?UTF-8?q?=E6=8E=A7=E5=88=97=E6=98=BE=E7=A4=BA=E5=80=8D=E6=95=B0=E5=A6=82?= =?UTF-8?q?1=E5=80=8D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- lib/common/static/options_panel.js | 22 +++++++--- lib/instance/instance_dashboard_lib.py | 54 ++++++++++++++++++++++-- lib/options/options_hub_lib.py | 18 ++++++++ lib/options/templates/options_panel.html | 2 +- manual_trading_hub/static/app.js | 41 +++++++++++++++--- manual_trading_hub/static/index.html | 2 +- 6 files changed, 121 insertions(+), 18 deletions(-) diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 87d9e23..6f22a04 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -1500,6 +1500,13 @@ ); } + function formatProfitExitMultLabel(mult) { + const n = Number(mult); + if (!Number.isFinite(n) || n <= 0) return "1倍"; + if (Math.abs(n - Math.round(n)) < 1e-9) return String(Math.round(n)) + "倍"; + return fmt(n, 2) + "倍"; + } + function renderProfitExitRow(p) { const inst = p.inst_id || ""; if (p.hedge_plan_target) { @@ -1511,10 +1518,9 @@ : 1; const state = String(p.profit_exit_state || (enabled ? "active" : "idle")); const req = p.profit_exit_required_recycle; - let statusTxt = enabled - ? ("监控中 · " + fmt(mult, 2) + "倍") - : "未开启"; - if (enabled && state === "closing") statusTxt = "平仓挂单中 · " + fmt(mult, 2) + "倍"; + const multLabel = formatProfitExitMultLabel(mult); + let statusTxt = enabled ? ("监控中 · " + multLabel) : "未开启"; + if (enabled && state === "closing") statusTxt = "平仓挂单中 · " + multLabel; return ( '
' + '翻倍' + @@ -1523,7 +1529,8 @@ '" + '' + + inst + '" data-mode="' + (enabled ? "cancel" : "apply") + '">' + + (enabled ? "取消" : "应用") + "" + '' + statusTxt + "" + '' + (enabled @@ -1853,7 +1860,10 @@ const row = card ? card.querySelector(".opt-profit-exit-pos-row") : null; const enabledEl = row ? row.querySelector(".opt-pos-profit-exit-enabled") : null; const multEl = row ? row.querySelector(".opt-pos-profit-exit-mult") : null; - const enabled = !!(enabledEl && enabledEl.checked); + const mode = btn && btn.getAttribute("data-mode"); + let enabled = !!(enabledEl && enabledEl.checked); + if (mode === "cancel") enabled = false; + if (mode === "apply") enabled = true; let mult = 1; if (enabled) { mult = parseFloat(multEl ? multEl.value : "1"); diff --git a/lib/instance/instance_dashboard_lib.py b/lib/instance/instance_dashboard_lib.py index 39c96c4..f4c4c29 100644 --- a/lib/instance/instance_dashboard_lib.py +++ b/lib/instance/instance_dashboard_lib.py @@ -121,20 +121,40 @@ def _resolve_options_source(conn, inst_id: str) -> tuple[str, str, int | None]: return default +def _format_profit_exit_mult(mult: Any) -> str: + try: + n = float(mult) + except (TypeError, ValueError): + return "1倍" + if n <= 0: + return "1倍" + if abs(n - round(n)) < 1e-9: + return f"{int(round(n))}倍" + return f"{n:g}倍" + + def _format_options_target(p: dict[str, Any]) -> str: hedge = p.get("hedge_plan_target") if isinstance(p.get("hedge_plan_target"), dict) else None opt_type = str(p.get("opt_type") or p.get("optType") or "").upper() if hedge: + rr = _safe_float(hedge.get("profit_rr")) + pid = hedge.get("plan_id") + if rr is not None and rr > 0: + return f"对冲#{pid} 盈亏比 {rr:g}" if pid is not None else f"盈亏比 {rr:g}" ot = str(hedge.get("opt_type") or opt_type).upper() side = "Put ≤" if ot == "P" else "Call ≥" tgt = _safe_float(hedge.get("target_index")) - pid = hedge.get("plan_id") if tgt is not None: return f"对冲#{pid} {side} {tgt:g}" if pid is not None else f"{side} {tgt:g}" + parts: list[str] = [] tgt = _safe_float(p.get("target_index")) if tgt is not None and tgt > 0: side = "Put ≤" if opt_type == "P" else "Call ≥" - return f"{side} {tgt:g}" + parts.append(f"{side} {tgt:g}") + if p.get("profit_exit_enabled"): + parts.append(_format_profit_exit_mult(p.get("profit_exit_mult"))) + if parts: + return " · ".join(parts) return "—" @@ -350,11 +370,39 @@ def collect_options_items( raw = fetch_options_positions() or [] except Exception: return [] + pe_map: dict[str, dict[str, Any]] = {} + tgt_map: dict[str, dict[str, Any]] = {} + hedge_map: dict[str, dict[str, Any]] = {} + if conn is not None: + try: + from lib.options.options_profit_exit_lib import profit_exit_by_inst + from lib.options.options_target_lib import targets_by_inst + from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst + + pe_map = profit_exit_by_inst(conn) + tgt_map = targets_by_inst(conn) + hedge_map = active_options_targets_by_inst(conn) + except Exception: + pe_map, tgt_map, hedge_map = {}, {}, {} out: list[dict[str, Any]] = [] for p in raw: if not isinstance(p, dict): continue - out.append(_format_options_item(p, conn=conn)) + row = dict(p) + inst = str(row.get("inst_id") or row.get("instId") or "").strip() + mon = tgt_map.get(inst) + if mon: + row["target_index"] = mon.get("target_index") + pe = pe_map.get(inst) + if pe: + row["profit_exit_enabled"] = pe.get("profit_exit_enabled") + row["profit_exit_mult"] = pe.get("profit_exit_mult") + hedge = hedge_map.get(inst) + if hedge: + row["hedge_plan_target"] = hedge + if not mon: + row["target_index"] = hedge.get("target_index") + out.append(_format_options_item(row, conn=conn)) return out diff --git a/lib/options/options_hub_lib.py b/lib/options/options_hub_lib.py index cbe0d9b..76b4406 100644 --- a/lib/options/options_hub_lib.py +++ b/lib/options/options_hub_lib.py @@ -28,18 +28,36 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]: conn = cfg["get_db"]() try: from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst + from lib.options.options_profit_exit_lib import profit_exit_by_inst from lib.options.options_target_lib import list_active_targets, list_closing_targets, targets_by_inst target_monitors = list_active_targets(conn) + list_closing_targets(conn) tgt_map = targets_by_inst(conn) hedge_target_map = active_options_targets_by_inst(conn) + profit_exit_map = profit_exit_by_inst(conn) target_monitors.extend(hedge_target_map.values()) + for pe in profit_exit_map.values(): + if pe.get("profit_exit_enabled"): + target_monitors.append( + { + "inst_id": pe.get("inst_id"), + "exit_mode": "profit_exit", + "profit_exit_mult": pe.get("profit_exit_mult"), + "profit_exit_enabled": True, + } + ) for p in positions: mon = tgt_map.get(str(p.get("inst_id") or "")) if mon: p["target_index"] = mon.get("target_index") p["target_monitor_id"] = mon.get("id") p["target_monitor"] = mon + pe = profit_exit_map.get(str(p.get("inst_id") or "")) + if pe: + p["profit_exit_enabled"] = pe.get("profit_exit_enabled") + p["profit_exit_mult"] = pe.get("profit_exit_mult") + p["profit_exit_state"] = pe.get("profit_exit_state") + p["profit_exit_required_recycle"] = pe.get("required_recycle") hedge_target = hedge_target_map.get(str(p.get("inst_id") or "")) if hedge_target: p["hedge_plan_target"] = hedge_target diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index a1014c5..fffcd1d 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -350,4 +350,4 @@
- + diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 62db67e..3374237 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -3928,9 +3928,15 @@ ); } - function renderOptionsTargetCell(target) { - if (!target) return "—"; - if (target.managed_by === "hedge_plan") { + function formatProfitExitMultLabel(mult) { + const n = Number(mult); + if (!Number.isFinite(n) || n <= 0) return "1倍"; + if (Math.abs(n - Math.round(n)) < 1e-9) return String(Math.round(n)) + "倍"; + return fmt(n, 2) + "倍"; + } + + function renderOptionsTargetCell(target, pos) { + if (target && target.managed_by === "hedge_plan") { const rr = target.profit_rr != null ? Number(target.profit_rr) : null; if (rr != null && rr > 0) { return `对冲#${esc(target.plan_id)} 盈亏比 ${esc(fmt(rr, 2))}`; @@ -3939,9 +3945,30 @@ const px = target.target_index != null ? fmt(target.target_index, 1) : "—"; return `对冲#${esc(target.plan_id)} ${esc(side)} ${esc(px)}`; } - const side = String(target.opt_type || "").toUpperCase() === "P" ? "Put≤" : "Call≥"; - const px = target.target_index != null ? fmt(target.target_index, 1) : "—"; - return `${esc(side)} ${esc(px)}`; + const parts = []; + const hasIndex = + target && + target.exit_mode !== "profit_exit" && + target.target_index != null && + Number(target.target_index) > 0; + if (hasIndex) { + const side = String(target.opt_type || (pos && pos.opt_type) || "").toUpperCase() === "P" ? "Put≤" : "Call≥"; + parts.push(side + " " + fmt(target.target_index, 1)); + } + const peOn = + !!(pos && pos.profit_exit_enabled) || + !!(target && (target.exit_mode === "profit_exit" || target.profit_exit_enabled)); + if (peOn) { + const mult = + pos && pos.profit_exit_mult != null + ? pos.profit_exit_mult + : target && target.profit_exit_mult != null + ? target.profit_exit_mult + : 1; + parts.push(formatProfitExitMultLabel(mult)); + } + if (!parts.length) return "—"; + return `${esc(parts.join(" · "))}`; } function renderOptionsPositionsTable(pos, targets) { @@ -3970,7 +3997,7 @@ ${esc(optType)} ${esc(p.pos)} ${optionsExpiryCdHtml(p.exp_time_ms != null ? p.exp_time_ms : p.exp_time)} - ${renderOptionsTargetCell(target)}`; + ${renderOptionsTargetCell(target, p)}`; if (showPnl) { html += `${net == null ? "—" : fmt(net, 2)} ${roi == null ? "—" : esc(Number(roi).toFixed(2)) + "%"}`; diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 2e5808b..c6c381d 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -1767,6 +1767,6 @@ - +