Add OKX options expiry and close breakeven to hub monitor and dashboard.

Expose bePx-based expiry balance and mark-to-close breakeven on positions so monitor and dashboard can show both labels per contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 16:10:03 +08:00
parent 7bbb5663c9
commit 3570a6900e
8 changed files with 269 additions and 9 deletions
+4 -2
View File
@@ -3475,7 +3475,7 @@
function renderOptionsPositionsTable(pos) {
if (!pos.length) return '<div class="empty-hint">暂无期权持仓</div>';
let html = '<div class="table-wrap hub-options-table-wrap"><table class="hub-options-table"><thead><tr>';
html += "<th>合约</th><th>类型</th><th>张数</th><th>标记</th><th>浮盈</th><th>浮盈%</th>";
html += "<th>合约</th><th>类型</th><th>张数</th><th>指数</th><th>到期平衡</th><th>平掉回本</th><th>浮盈</th><th>浮盈%</th>";
html += "</tr></thead><tbody>";
pos.forEach((p) => {
const optType = (p.opt_type || "").toUpperCase() === "C" ? "Call" : (p.opt_type || "").toUpperCase() === "P" ? "Put" : (p.opt_type || "—");
@@ -3483,7 +3483,9 @@
<td><code class="hub-options-inst" title="${esc(p.inst_id || "")}">${esc(shortOptionsInst(p.inst_id))}</code></td>
<td>${esc(optType)}</td>
<td>${esc(p.pos)}</td>
<td>${fmt(p.mark_px, 4)}</td>
<td>${p.idx_px != null ? fmt(p.idx_px, 0) : "—"}</td>
<td>${p.expiry_be_px != null ? fmt(p.expiry_be_px, 0) : "—"}</td>
<td>${p.close_be_px != null ? fmt(p.close_be_px, 0) : "—"}</td>
<td class="${pnlCls(p.upl)}">${fmt(p.upl, 4)}</td>
<td class="${pnlCls(p.upl)}">${p.upl_ratio_pct != null ? esc(p.upl_ratio_pct) + "%" : "—"}</td>
</tr>`;
+18
View File
@@ -332,6 +332,24 @@ body.hub-page-dashboard .page#page-dashboard {
border-top: 1px dashed color-mix(in srgb, var(--dash-card-border) 80%, transparent);
}
.dash-options-block {
margin-top: 8px;
}
.dash-options-block .dash-ac-section-label {
margin-bottom: 4px;
}
.dash-options-table-wrap {
overflow-x: auto;
}
.dash-options-table th,
.dash-options-table td {
font-size: 0.68rem;
white-space: nowrap;
}
.dash-ac-metrics-3col .dash-ac-metric {
text-align: center;
}
+49 -4
View File
@@ -118,9 +118,50 @@
return chips;
}
function renderDashboardOptionsTable(positions) {
const pos = Array.isArray(positions) ? positions : [];
if (!pos.length) return "";
const rows = pos
.map((p) => {
const optType =
(p.opt_type || "").toUpperCase() === "C"
? "Call"
: (p.opt_type || "").toUpperCase() === "P"
? "Put"
: p.opt_type || "—";
return `<tr>
<td title="${esc(p.inst_id || "")}">${esc(shortDashInst(p.inst_id))}</td>
<td>${esc(optType)}</td>
<td>${p.idx_px != null ? fmt(p.idx_px, 0) : "—"}</td>
<td>${p.expiry_be_px != null ? fmt(p.expiry_be_px, 0) : "—"}</td>
<td>${p.close_be_px != null ? fmt(p.close_be_px, 0) : "—"}</td>
<td class="${pnlClass(p.upl)}">${p.upl != null ? pnlSigned(p.upl, 2) : "—"}</td>
</tr>`;
})
.join("");
return `<div class="dash-options-block">
<div class="dash-ac-section-label">期权持仓</div>
<div class="dash-table-wrap dash-options-table-wrap">
<table class="dash-table dash-options-table">
<thead><tr>
<th>合约</th><th></th><th></th><th></th><th></th><th></th>
</tr></thead>
<tbody>${rows}</tbody>
</table>
</div>
</div>`;
}
function shortDashInst(instId) {
const s = String(instId || "");
if (s.length <= 18) return s;
return s.slice(0, 8) + "…" + s.slice(-6);
}
function renderAccountDetail(ac) {
const counts = (ac && ac.monitor_counts) || {};
const positions = Array.isArray(ac && ac.position_lines) ? ac.position_lines : [];
const optionsPositions = Array.isArray(ac && ac.options_positions) ? ac.options_positions : [];
const issues = Array.isArray(ac && ac.issues) ? ac.issues : [];
const exId = ac && ac.id != null ? String(ac.id) : "";
const chips = renderMonitorCountChips(counts);
@@ -134,8 +175,11 @@
? `<div class="dash-ac-monitor-row">${chips.join("")}${expandBtn}</div>`
: "";
let posHtml = "";
if (positions.length) {
posHtml = positions
const perpLines = ac && ac.options_layout
? positions.filter((ln) => (ln && ln.kind) !== "options")
: positions;
if (perpLines.length) {
posHtml = perpLines
.map((ln) => {
const text = esc((ln && ln.text) || "");
if (ln.pnl != null && Number.isFinite(Number(ln.pnl))) {
@@ -149,13 +193,14 @@
return `<div class="dash-ac-remark-line dash-ac-remark-pos">${text}</div>`;
})
.join("");
} else if (!chips.length && !issues.length) {
} else if (!chips.length && !issues.length && !(ac && ac.options_layout && optionsPositions.length)) {
posHtml = `<div class="dash-ac-remark-line dash-ac-remark-empty">无持仓</div>`;
}
const issueHtml = issues
.map((text) => `<div class="dash-ac-remark-line dash-ac-remark-issue">${esc(text)}</div>`)
.join("");
return `<div class="dash-ac-remark">${monitorRow}<div class="dash-ac-positions">${posHtml}</div>${issueHtml}</div>`;
const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : "";
return `<div class="dash-ac-remark">${monitorRow}<div class="dash-ac-positions">${posHtml}</div>${optionsHtml}${issueHtml}</div>`;
}
function bindDashboardExpand() {