Show hedge plan id in options type column; drop group box UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-20 12:43:51 +08:00
parent 0f5fe801e2
commit 913c7d5be6
3 changed files with 27 additions and 120 deletions
-36
View File
@@ -290,42 +290,6 @@ body.hub-page-dashboard .page#page-dashboard {
color: #ff6b7a;
}
.dash-opt-group-head td {
background: color-mix(in srgb, var(--dash-accent) 10%, var(--dash-card-bg));
border-top: 1px solid color-mix(in srgb, var(--dash-accent) 35%, var(--dash-card-border));
padding-top: 10px;
padding-bottom: 8px;
}
.dash-opt-group-label {
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 700;
color: var(--dash-accent);
letter-spacing: 0.02em;
}
.dash-opt-group-label::before {
content: "一组";
font-size: 0.62rem;
font-weight: 700;
padding: 1px 6px;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--dash-accent) 45%, transparent);
color: var(--dash-accent);
background: color-mix(in srgb, var(--dash-accent) 12%, transparent);
}
.dash-opt-group-row td {
background: color-mix(in srgb, var(--dash-accent) 4%, transparent);
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--dash-accent) 55%, transparent);
}
.dash-opt-group-row.is-last td {
border-bottom-color: color-mix(in srgb, var(--dash-accent) 28%, var(--dash-card-border));
}
.dash-empty-inline {
padding: 8px 0 4px;
font-size: 0.78rem;
+25 -82
View File
@@ -259,6 +259,16 @@
return "solo:" + ex + ":" + String((p && p.inst_id) || Math.random());
}
function optionsTypeLabel(p) {
const source = String((p && (p.source_label || p.source)) || "纯期权").trim() || "纯期权";
const planId = optionsHedgePlanId(p);
if (planId && (source.indexOf("对冲") >= 0 || /对冲#/.test(String((p && p.target_monitor_text) || "")))) {
const base = source.indexOf("对冲") >= 0 ? source.replace(/\s*#\s*\d+\s*$/, "") : "对冲";
return base + "#" + planId;
}
return source;
}
function optionsRoiPct(p) {
if (!p || typeof p !== "object") return null;
const preview = p.close_preview || {};
@@ -273,19 +283,19 @@
return null;
}
function renderOptionsLegRow(ac, p, groupCls) {
function renderOptionsLegRow(ac, p) {
const optType =
(p.opt_type || "").toUpperCase() === "C"
? "Call"
: (p.opt_type || "").toUpperCase() === "P"
? "Put"
: p.opt_type || "—";
const source = String(p.source_label || p.source || "纯期权");
const source = optionsTypeLabel(p);
const target = String(p.target_monitor_text || "—");
const targetCls = target && target !== "—" ? "dash-target-monitor is-on" : "dash-target-monitor";
const net = optionsNetPnl(p);
const roi = optionsRoiPct(p);
return `<tr class="${groupCls || ""}">
return `<tr>
<td>${exchangeLinkCell(ac)}</td>
<td>${sourceTypeCell(source)}</td>
<td title="${esc(p.inst_id || "")}">${esc(shortDashInst(p.inst_id))}</td>
@@ -300,86 +310,19 @@
function renderUnifiedOptionsTable(rows) {
if (!rows.length) return "";
const groups = [];
const byKey = new Map();
rows.forEach((item) => {
const key = optionsGroupKey(item.ac, item.p);
if (!byKey.has(key)) {
const planId = optionsHedgePlanId(item.p);
const source = String((item.p && (item.p.source_label || item.p.source)) || "期权");
const isHedge = key.indexOf("hedge:") === 0;
const g = {
key: key,
isHedge: isHedge,
planId: planId,
source: source,
items: [],
};
byKey.set(key, g);
groups.push(g);
}
byKey.get(key).items.push(item);
});
// 对冲组内 Call 在前 Put 在后
groups.forEach((g) => {
if (!g.isHedge || g.items.length < 2) return;
g.items.sort((a, b) => {
const ta = String((a.p && a.p.opt_type) || "").toUpperCase();
const tb = String((b.p && b.p.opt_type) || "").toUpperCase();
if (ta === tb) return 0;
if (ta === "C") return -1;
if (tb === "C") return 1;
return ta.localeCompare(tb);
});
});
let body = "";
groups.forEach((g) => {
if (g.isHedge && g.items.length >= 1) {
let sum = 0;
let paidSum = 0;
let hasSum = false;
let hasPaid = false;
g.items.forEach(({ p }) => {
const n = optionsNetPnl(p);
if (n != null) {
sum += n;
hasSum = true;
}
const paid = Number(p && p.premium_paid);
if (Number.isFinite(paid) && paid > 0) {
paidSum += paid;
hasPaid = true;
}
});
const groupRoi = hasSum && hasPaid && paidSum > 0 ? (sum / paidSum) * 100 : null;
const title =
(g.source && g.source !== "—" ? g.source : "对冲") +
(g.planId ? " #" + g.planId : "") +
" · " +
g.items.length +
" 腿";
body += `<tr class="dash-opt-group-head">
<td colspan="7"><span class="dash-opt-group-label">${esc(title)}</span></td>
<td class="${hasSum ? pnlClass(sum) : ""}">${hasSum ? pnlSigned(sum, 2) : "—"}</td>
<td class="${groupRoi != null ? pnlClass(groupRoi) : ""}">${
groupRoi != null ? esc(Number(groupRoi).toFixed(2)) + "%" : "—"
}</td>
</tr>`;
g.items.forEach(({ ac, p }, idx) => {
const cls =
"dash-opt-group-row" +
(idx === 0 ? " is-first" : "") +
(idx === g.items.length - 1 ? " is-last" : "");
body += renderOptionsLegRow(ac, p, cls);
});
} else {
g.items.forEach(({ ac, p }) => {
body += renderOptionsLegRow(ac, p, "");
});
}
// 同所同计划相邻,Call 在前 Put 在后;不额外画分组框
const sorted = rows.slice().sort((a, b) => {
const ka = optionsGroupKey(a.ac, a.p);
const kb = optionsGroupKey(b.ac, b.p);
if (ka !== kb) return ka.localeCompare(kb);
const ta = String((a.p && a.p.opt_type) || "").toUpperCase();
const tb = String((b.p && b.p.opt_type) || "").toUpperCase();
if (ta === tb) return 0;
if (ta === "C") return -1;
if (tb === "C") return 1;
return ta.localeCompare(tb);
});
const body = sorted.map(({ ac, p }) => renderOptionsLegRow(ac, p)).join("");
return `<div class="dash-pos-block dash-options-block">
<div class="dash-ac-section-label">期权持仓</div>
+2 -2
View File
@@ -20,7 +20,7 @@
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
<script src="/assets/account_risk_badge.js?v=4"></script>
<link rel="stylesheet" href="/assets/dashboard.css?v=20260720-dash-tp-roi" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260720-dash-hedge-type" />
</head>
<body>
<div class="app-bg" aria-hidden="true"></div>
@@ -1385,7 +1385,7 @@
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
<script src="/assets/dashboard.js?v=20260720-dash-tp-roi"></script>
<script src="/assets/dashboard.js?v=20260720-dash-hedge-type"></script>
<script src="/assets/strategy.js?v=8"></script>
<script src="/assets/help.js?v=1"></script>
<script src="/assets/logs.js?v=1"></script>