Show options position source (纯期权/永期/期期) on holdings cards.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4430,6 +4430,52 @@ html[data-theme="light"] .options-stats-pnl-summary .options-stat-item {
|
|||||||
font-size: 0.62rem;
|
font-size: 0.62rem;
|
||||||
line-height: 1;
|
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 {
|
.opt-pos-bar-meta {
|
||||||
font-size: 0.66rem;
|
font-size: 0.66rem;
|
||||||
color: #8b95b0;
|
color: #8b95b0;
|
||||||
|
|||||||
@@ -473,6 +473,28 @@
|
|||||||
return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
|
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 '<span class="' + cls + '" title="持仓来源">' + sourceText(p) + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
function expLabel(ms) {
|
function expLabel(ms) {
|
||||||
try {
|
try {
|
||||||
const dt = new Date(Number(ms));
|
const dt = new Date(Number(ms));
|
||||||
@@ -1158,11 +1180,14 @@
|
|||||||
return (
|
return (
|
||||||
'<div class="pos-card-head">' +
|
'<div class="pos-card-head">' +
|
||||||
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + '</strong>' +
|
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + '</strong>' +
|
||||||
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span></div>" +
|
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span>" +
|
||||||
|
sourceBadgeHtml(p) +
|
||||||
|
"</div>" +
|
||||||
'<div class="pos-head-actions">' +
|
'<div class="pos-head-actions">' +
|
||||||
'<button type="button" class="btn-primary opt-close-btn" data-inst="' + p.inst_id + '" data-sheets="' + closeSheets + '">买一平仓</button>' +
|
'<button type="button" class="btn-primary opt-close-btn" data-inst="' + p.inst_id + '" data-sheets="' + closeSheets + '">买一平仓</button>' +
|
||||||
"</div></div>" +
|
"</div></div>" +
|
||||||
'<div class="pos-meta">' +
|
'<div class="pos-meta">' +
|
||||||
|
'<span class="pos-meta-item">持仓来源: ' + sourceText(p) + "</span>" +
|
||||||
'<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
|
'<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
|
||||||
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
||||||
(expAttr
|
(expAttr
|
||||||
@@ -1320,6 +1345,7 @@
|
|||||||
'<span class="opt-pos-bar-id-group">' +
|
'<span class="opt-pos-bar-id-group">' +
|
||||||
'<strong class="opt-pos-bar-title" title="' + inst + '">' + inst + "</strong>" +
|
'<strong class="opt-pos-bar-title" title="' + inst + '">' + inst + "</strong>" +
|
||||||
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span>" +
|
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span>" +
|
||||||
|
sourceBadgeHtml(p) +
|
||||||
"</span>" +
|
"</span>" +
|
||||||
'<span class="opt-pos-bar-meta">行权 ' + fmt(p.strike, 0) + " · " + fmt(p.pos, 0) + "张</span>" +
|
'<span class="opt-pos-bar-meta">行权 ' + fmt(p.strike, 0) + " · " + fmt(p.pos, 0) + "张</span>" +
|
||||||
"</span>" +
|
"</span>" +
|
||||||
|
|||||||
@@ -38,6 +38,28 @@
|
|||||||
return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
|
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 '<span class="' + cls + '" title="持仓来源">' + sourceText(p) + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
function pnlCls(upl, hub) {
|
function pnlCls(upl, hub) {
|
||||||
if (upl > 0) return hub ? "pnl-pos" : "pos-pnl-profit";
|
if (upl > 0) return hub ? "pnl-pos" : "pos-pnl-profit";
|
||||||
if (upl < 0) return hub ? "pnl-neg" : "pos-pnl-loss";
|
if (upl < 0) return hub ? "pnl-neg" : "pos-pnl-loss";
|
||||||
@@ -147,10 +169,13 @@
|
|||||||
return (
|
return (
|
||||||
'<div class="pos-card-head">' +
|
'<div class="pos-card-head">' +
|
||||||
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + "</strong>" +
|
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + "</strong>" +
|
||||||
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span></div>" +
|
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span>" +
|
||||||
|
sourceBadgeHtml(p) +
|
||||||
|
"</div>" +
|
||||||
headActions +
|
headActions +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
'<div class="pos-meta">' +
|
'<div class="pos-meta">' +
|
||||||
|
'<span class="pos-meta-item">持仓来源: ' + sourceText(p) + "</span>" +
|
||||||
'<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
|
'<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
|
||||||
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
||||||
(expAttr
|
(expAttr
|
||||||
|
|||||||
@@ -87,14 +87,15 @@ OPTIONS_SOURCE_LABELS = {
|
|||||||
HEDGE_ACTIVE_STATUSES = frozenset({"opening", "active", "partial"})
|
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"):
|
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:
|
try:
|
||||||
row = conn.execute(
|
row = conn.execute(
|
||||||
"""
|
"""
|
||||||
SELECT p.plan_type
|
SELECT p.plan_type, p.id
|
||||||
FROM hedge_plans p
|
FROM hedge_plans p
|
||||||
JOIN hedge_plan_legs l ON l.plan_id = p.id
|
JOIN hedge_plan_legs l ON l.plan_id = p.id
|
||||||
WHERE p.status IN ('opening', 'active', 'partial')
|
WHERE p.status IN ('opening', 'active', 'partial')
|
||||||
@@ -106,13 +107,18 @@ def _resolve_options_source(conn, inst_id: str) -> tuple[str, str]:
|
|||||||
(inst_id,),
|
(inst_id,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
except Exception:
|
except Exception:
|
||||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
return default
|
||||||
if not row:
|
if not row:
|
||||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
return default
|
||||||
pt = str((_row_dict(row).get("plan_type") if isinstance(row, dict) else row[0]) or "").strip()
|
d = _row_dict(row)
|
||||||
if pt in OPTIONS_SOURCE_LABELS:
|
pt = str(d.get("plan_type") or "").strip()
|
||||||
return pt, OPTIONS_SOURCE_LABELS[pt]
|
try:
|
||||||
return "option", OPTIONS_SOURCE_LABELS["option"]
|
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:
|
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
|
exp_ms = int(float(exp_ms)) if exp_ms not in (None, "") else None
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
exp_ms = None
|
exp_ms = None
|
||||||
source_key, source_label = (
|
if conn is not None:
|
||||||
_resolve_options_source(conn, inst) if conn is not None else ("option", OPTIONS_SOURCE_LABELS["option"])
|
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 {
|
return {
|
||||||
"id": inst,
|
"id": inst,
|
||||||
"kind": "options",
|
"kind": "options",
|
||||||
@@ -166,6 +173,7 @@ def _format_options_item(p: dict[str, Any], *, conn=None) -> dict[str, Any]:
|
|||||||
"opt_type_label": label,
|
"opt_type_label": label,
|
||||||
"source": source_key,
|
"source": source_key,
|
||||||
"source_label": source_label,
|
"source_label": source_label,
|
||||||
|
"source_plan_id": source_plan_id,
|
||||||
"pos": pos,
|
"pos": pos,
|
||||||
"exp_time_ms": exp_ms,
|
"exp_time_ms": exp_ms,
|
||||||
"target_monitor": _format_options_target(p),
|
"target_monitor": _format_options_target(p),
|
||||||
|
|||||||
@@ -52,12 +52,14 @@ def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
inst = str(p.get("inst_id") or "")
|
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"] = source_key
|
||||||
p["source_label"] = source_label
|
p["source_label"] = source_label
|
||||||
|
p["source_plan_id"] = source_plan_id
|
||||||
p["target_monitor_text"] = _format_options_target(p)
|
p["target_monitor_text"] = _format_options_target(p)
|
||||||
except Exception:
|
except Exception:
|
||||||
p.setdefault("source_label", "—")
|
p.setdefault("source_label", "—")
|
||||||
|
p.setdefault("source_plan_id", None)
|
||||||
p.setdefault("target_monitor_text", "—")
|
p.setdefault("target_monitor_text", "—")
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -763,6 +763,17 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
|||||||
hedge_target = hedge_target_map.get(inst)
|
hedge_target = hedge_target_map.get(inst)
|
||||||
if hedge_target:
|
if hedge_target:
|
||||||
row["hedge_plan_target"] = 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)
|
rows.append(row)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -274,4 +274,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/static/options_panel.js?v=39"></script>
|
<script src="/static/options_panel.js?v=40"></script>
|
||||||
|
|||||||
@@ -1391,7 +1391,7 @@
|
|||||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||||
<script src="/assets/time_close_ui.js?v=3"></script>
|
<script src="/assets/time_close_ui.js?v=3"></script>
|
||||||
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/assets/options_position_cards.js?v=1"></script>
|
<script src="/assets/options_position_cards.js?v=2"></script>
|
||||||
<script src="/assets/backup.js?v=1"></script>
|
<script src="/assets/backup.js?v=1"></script>
|
||||||
<script src="/assets/app.js?v=20260717-quotes-feed"></script>
|
<script src="/assets/app.js?v=20260717-quotes-feed"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user