diff --git a/lib/common/static/instance_theme.css b/lib/common/static/instance_theme.css index 8c9a274..18c6dc3 100644 --- a/lib/common/static/instance_theme.css +++ b/lib/common/static/instance_theme.css @@ -4430,6 +4430,52 @@ html[data-theme="light"] .options-stats-pnl-summary .options-stat-item { font-size: 0.62rem; line-height: 1; } +.opt-source-badge { + display: inline-flex; + align-items: center; + white-space: nowrap; + flex-shrink: 0; + padding: 2px 7px; + border-radius: 6px; + font-size: 0.68rem; + font-weight: 600; + line-height: 1.2; + border: 1px solid transparent; +} +.opt-source-badge--plain { + background: rgba(255, 255, 255, 0.06); + color: #9aa4b2; + border-color: rgba(255, 255, 255, 0.1); +} +.opt-source-badge--po { + background: rgba(100, 160, 255, 0.16); + color: #8ec0ff; + border-color: rgba(100, 160, 255, 0.35); +} +.opt-source-badge--oo { + background: rgba(0, 212, 255, 0.14); + color: #5ee4ff; + border-color: rgba(0, 212, 255, 0.35); +} +.opt-pos-bar .opt-source-badge { + padding: 2px 6px; + font-size: 0.6rem; +} +html[data-theme="light"] .opt-source-badge--plain { + background: rgba(15, 23, 42, 0.06); + color: #5a6578; + border-color: rgba(15, 23, 42, 0.12); +} +html[data-theme="light"] .opt-source-badge--po { + background: rgba(37, 99, 235, 0.1); + color: #1d4ed8; + border-color: rgba(37, 99, 235, 0.25); +} +html[data-theme="light"] .opt-source-badge--oo { + background: rgba(8, 145, 178, 0.1); + color: #0e7490; + border-color: rgba(8, 145, 178, 0.28); +} .opt-pos-bar-meta { font-size: 0.66rem; color: #8b95b0; diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 42b609c..4b4313e 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -473,6 +473,28 @@ return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call"; } + function sourceText(p) { + const lab = (p && p.source_label) || "纯期权"; + const src = (p && p.source) || "option"; + let pid = p && p.source_plan_id; + if (pid == null && p && p.hedge_plan_target && p.hedge_plan_target.plan_id != null) { + pid = p.hedge_plan_target.plan_id; + } + if (src !== "option" && pid != null && pid !== "") return lab + " #" + pid; + return lab; + } + + function sourceBadgeHtml(p) { + const src = (p && p.source) || "option"; + const cls = + src === "options_options" + ? "opt-source-badge opt-source-badge--oo" + : src === "perp_options" + ? "opt-source-badge opt-source-badge--po" + : "opt-source-badge opt-source-badge--plain"; + return '' + sourceText(p) + ""; + } + function expLabel(ms) { try { const dt = new Date(Number(ms)); @@ -1158,11 +1180,14 @@ return ( '
' + '
' + (p.inst_id || "") + '' + - '' + optTypeLabel(p.opt_type) + "
" + + '' + optTypeLabel(p.opt_type) + "" + + sourceBadgeHtml(p) + + "
" + '
' + '' + "
" + '
' + + '持仓来源: ' + sourceText(p) + "" + '行权价: ' + fmt(p.strike, 0) + "" + '张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "" + (expAttr @@ -1320,6 +1345,7 @@ '' + '' + inst + "" + '' + optTypeLabel(p.opt_type) + "" + + sourceBadgeHtml(p) + "" + '行权 ' + fmt(p.strike, 0) + " · " + fmt(p.pos, 0) + "张" + "" + diff --git a/lib/common/static/options_position_cards.js b/lib/common/static/options_position_cards.js index 943babe..f6f4999 100644 --- a/lib/common/static/options_position_cards.js +++ b/lib/common/static/options_position_cards.js @@ -38,6 +38,28 @@ return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call"; } + function sourceText(p) { + const lab = (p && p.source_label) || "纯期权"; + const src = (p && p.source) || "option"; + let pid = p && p.source_plan_id; + if (pid == null && p && p.hedge_plan_target && p.hedge_plan_target.plan_id != null) { + pid = p.hedge_plan_target.plan_id; + } + if (src !== "option" && pid != null && pid !== "") return lab + " #" + pid; + return lab; + } + + function sourceBadgeHtml(p) { + const src = (p && p.source) || "option"; + const cls = + src === "options_options" + ? "opt-source-badge opt-source-badge--oo" + : src === "perp_options" + ? "opt-source-badge opt-source-badge--po" + : "opt-source-badge opt-source-badge--plain"; + return '' + sourceText(p) + ""; + } + function pnlCls(upl, hub) { if (upl > 0) return hub ? "pnl-pos" : "pos-pnl-profit"; if (upl < 0) return hub ? "pnl-neg" : "pos-pnl-loss"; @@ -147,10 +169,13 @@ return ( '
' + '
' + (p.inst_id || "") + "" + - '' + optTypeLabel(p.opt_type) + "
" + + '' + optTypeLabel(p.opt_type) + "" + + sourceBadgeHtml(p) + + "
" + headActions + "
" + '
' + + '持仓来源: ' + sourceText(p) + "" + '行权价: ' + fmt(p.strike, 0) + "" + '张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "" + (expAttr diff --git a/lib/instance/instance_dashboard_lib.py b/lib/instance/instance_dashboard_lib.py index 58bece7..4cdb2e1 100644 --- a/lib/instance/instance_dashboard_lib.py +++ b/lib/instance/instance_dashboard_lib.py @@ -87,14 +87,15 @@ OPTIONS_SOURCE_LABELS = { HEDGE_ACTIVE_STATUSES = frozenset({"opening", "active", "partial"}) -def _resolve_options_source(conn, inst_id: str) -> tuple[str, str]: - """根据进行中对冲计划腿判定来源;默认纯期权.""" +def _resolve_options_source(conn, inst_id: str) -> tuple[str, str, int | None]: + """根据进行中对冲计划腿判定来源;默认纯期权. 返回 (source, label, plan_id).""" + default = ("option", OPTIONS_SOURCE_LABELS["option"], None) if not inst_id or not _table_exists(conn, "hedge_plans") or not _table_exists(conn, "hedge_plan_legs"): - return "option", OPTIONS_SOURCE_LABELS["option"] + return default try: row = conn.execute( """ - SELECT p.plan_type + SELECT p.plan_type, p.id FROM hedge_plans p JOIN hedge_plan_legs l ON l.plan_id = p.id WHERE p.status IN ('opening', 'active', 'partial') @@ -106,13 +107,18 @@ def _resolve_options_source(conn, inst_id: str) -> tuple[str, str]: (inst_id,), ).fetchone() except Exception: - return "option", OPTIONS_SOURCE_LABELS["option"] + return default if not row: - return "option", OPTIONS_SOURCE_LABELS["option"] - pt = str((_row_dict(row).get("plan_type") if isinstance(row, dict) else row[0]) or "").strip() - if pt in OPTIONS_SOURCE_LABELS: - return pt, OPTIONS_SOURCE_LABELS[pt] - return "option", OPTIONS_SOURCE_LABELS["option"] + return default + d = _row_dict(row) + pt = str(d.get("plan_type") or "").strip() + try: + plan_id = int(d["id"]) if d.get("id") is not None else None + except (TypeError, ValueError): + plan_id = None + if pt in OPTIONS_SOURCE_LABELS and pt != "option": + return pt, OPTIONS_SOURCE_LABELS[pt], plan_id + return default def _format_options_target(p: dict[str, Any]) -> str: @@ -152,9 +158,10 @@ def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]: exp_ms = int(float(exp_ms)) if exp_ms not in (None, "") else None except (TypeError, ValueError): exp_ms = None - source_key, source_label = ( - _resolve_options_source(conn, inst) if conn is not None else ("option", OPTIONS_SOURCE_LABELS["option"]) - ) + if conn is not None: + source_key, source_label, source_plan_id = _resolve_options_source(conn, inst) + else: + source_key, source_label, source_plan_id = "option", OPTIONS_SOURCE_LABELS["option"], None return { "id": inst, "kind": "options", @@ -166,6 +173,7 @@ def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]: "opt_type_label": label, "source": source_key, "source_label": source_label, + "source_plan_id": source_plan_id, "pos": pos, "exp_time_ms": exp_ms, "target_monitor": _format_options_target(p), diff --git a/lib/options/options_hub_lib.py b/lib/options/options_hub_lib.py index 48227a7..f70777d 100644 --- a/lib/options/options_hub_lib.py +++ b/lib/options/options_hub_lib.py @@ -52,12 +52,14 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]: ) inst = str(p.get("inst_id") or "") - source_key, source_label = _resolve_options_source(conn, inst) + source_key, source_label, source_plan_id = _resolve_options_source(conn, inst) p["source"] = source_key p["source_label"] = source_label + p["source_plan_id"] = source_plan_id p["target_monitor_text"] = _format_options_target(p) except Exception: p.setdefault("source_label", "—") + p.setdefault("source_plan_id", None) p.setdefault("target_monitor_text", "—") finally: conn.close() diff --git a/lib/options/options_register.py b/lib/options/options_register.py index 408516a..57ffe34 100644 --- a/lib/options/options_register.py +++ b/lib/options/options_register.py @@ -763,6 +763,17 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None: hedge_target = hedge_target_map.get(inst) if hedge_target: row["hedge_plan_target"] = hedge_target + try: + from lib.instance.instance_dashboard_lib import _resolve_options_source + + source_key, source_label, source_plan_id = _resolve_options_source(conn, inst) + row["source"] = source_key + row["source_label"] = source_label + row["source_plan_id"] = source_plan_id + except Exception: + row.setdefault("source", "option") + row.setdefault("source_label", "纯期权") + row.setdefault("source_plan_id", None) rows.append(row) finally: conn.close() diff --git a/lib/options/templates/options_panel.html b/lib/options/templates/options_panel.html index dea3d9d..639d81b 100644 --- a/lib/options/templates/options_panel.html +++ b/lib/options/templates/options_panel.html @@ -274,4 +274,4 @@
- + diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 5c1779e..2328eb6 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -1391,7 +1391,7 @@ - +