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
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 @@
-
+