Dashboard OKX card: 3-column perpetual/options split when options enabled.

Show perpetual and options funding metrics separately, total funds row, then positions; other exchanges keep the 2-column layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 15:22:11 +08:00
parent 9027708a61
commit 7bbb5663c9
4 changed files with 148 additions and 33 deletions
+25
View File
@@ -38,6 +38,29 @@ def _account_capital_base(ac: dict) -> Optional[float]:
return None
def _options_layout_enabled(ac: dict) -> bool:
if str(ac.get("key") or "").lower() != "okx":
return False
snap = ac.get("options_snapshot")
if isinstance(snap, dict) and snap.get("enabled") is not False:
return True
return ac.get("options_funding_usdt") is not None or ac.get("options_trading_usdt") is not None
def _perpetual_float_pnl_u(ac: dict) -> Optional[float]:
try:
total = float(ac.get("float_pnl_u") or 0)
except (TypeError, ValueError):
total = 0.0
opt = ac.get("options_float_pnl_u")
if opt is None:
return round(total, 4)
try:
return round(total - float(opt), 4)
except (TypeError, ValueError):
return round(total, 4)
def _enrich_account_row(ac: dict) -> dict:
st = ac.get("trade_stats") or {}
capital = _account_capital_base(ac)
@@ -61,6 +84,8 @@ def _enrich_account_row(ac: dict) -> dict:
"options_trading_usdt": ac.get("options_trading_usdt"),
"options_float_pnl_u": ac.get("options_float_pnl_u"),
"options_open_position_count": ac.get("options_open_position_count"),
"options_layout": _options_layout_enabled(ac),
"perpetual_float_pnl_u": _perpetual_float_pnl_u(ac),
"capital_total_usdt": round(capital, 4) if capital is not None else None,
"available_trading_usdt": ac.get("available_trading_usdt"),
"pnl_u": st.get("total_pnl_u"),
+65
View File
@@ -276,6 +276,71 @@ body.hub-page-dashboard .page#page-dashboard {
font-size: 0.76rem;
}
.dash-ac-metrics-3col {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.dash-ac-section-label {
grid-column: 1 / -1;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.06em;
color: var(--dash-accent);
margin-top: 2px;
padding-bottom: 2px;
border-bottom: 1px dashed color-mix(in srgb, var(--dash-card-border) 80%, transparent);
}
.dash-ac-section-label:not(:first-child) {
margin-top: 8px;
}
.dash-ac-metric-empty {
visibility: hidden;
min-height: 0;
padding: 0;
margin: 0;
}
.dash-ac-total-row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-top: 10px;
padding: 8px 10px;
border-radius: 8px;
border: 1px solid var(--dash-card-border);
background: color-mix(in srgb, var(--dash-accent) 6%, var(--dash-card-bg));
font-family: JetBrains Mono, monospace;
font-size: 0.76rem;
}
.dash-ac-total-row span {
color: var(--dash-muted);
font-size: 0.68rem;
}
.dash-ac-total-row strong {
color: var(--dash-text);
font-size: 0.88rem;
}
.dash-ac-card-options .dash-ac-remark {
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed color-mix(in srgb, var(--dash-card-border) 80%, transparent);
}
.dash-ac-metrics-3col .dash-ac-metric {
text-align: center;
}
.dash-ac-metrics-3col .dash-ac-metric span,
.dash-ac-metrics-3col .dash-ac-metric strong {
text-align: center;
}
.dash-ac-metric span {
display: block;
color: var(--dash-muted);
+56 -31
View File
@@ -170,8 +170,57 @@
});
}
function dashMetric(label, value, valCls) {
return `<div class="dash-ac-metric"><span>${esc(label)}</span><strong class="${valCls || ""}">${value}</strong></div>`;
}
function dashSectionLabel(text) {
return `<div class="dash-ac-section-label">${esc(text)}</div>`;
}
function renderOkxOptionsMetrics(ac) {
const pnl = Number(ac.pnl_u);
const perpFloat = Number(
ac.perpetual_float_pnl_u != null ? ac.perpetual_float_pnl_u : ac.float_pnl_u
);
const optFloat = Number(ac.options_float_pnl_u);
const pf =
ac.perpetual_funding_usdt != null ? ac.perpetual_funding_usdt : ac.funding_usdt;
const pt =
ac.perpetual_trading_usdt != null ? ac.perpetual_trading_usdt : ac.trading_usdt;
return `<div class="dash-ac-metrics dash-ac-metrics-3col">
${dashSectionLabel("永续账户")}
${dashMetric("资金账户", `${fmt(pf, 2)}U`)}
${dashMetric("交易账户", `${fmt(pt, 2)}U`)}
${dashMetric("今日盈亏", pnlSigned(pnl, 2), pnlClass(pnl))}
${dashMetric("平仓笔数", String(Number(ac.closed_count) || 0))}
${dashMetric("浮盈亏", pnlSigned(perpFloat, 2), pnlClass(perpFloat))}
<div class="dash-ac-metric dash-ac-metric-empty" aria-hidden="true"></div>
${dashSectionLabel("期权账户")}
${dashMetric("资金账户", `${fmt(ac.options_funding_usdt, 2)}U`)}
${dashMetric("交易账户", `${fmt(ac.options_trading_usdt, 2)}U`)}
${dashMetric("期权浮盈亏", pnlSigned(optFloat, 2), pnlClass(optFloat))}
</div>
<div class="dash-ac-total-row">
<span>总资金</span>
<strong>${fmt(ac.capital_total_usdt, 2)}U</strong>
</div>`;
}
function renderStandardMetrics(ac) {
const pnl = Number(ac.pnl_u);
const floatPnl = Number(ac.float_pnl_u);
return `<div class="dash-ac-metrics">
${dashMetric("资金账户", `${fmt(ac.funding_usdt, 2)}U`)}
${dashMetric("交易账户", `${fmt(ac.trading_usdt, 2)}U`)}
${dashMetric("资金合计", `${fmt(ac.capital_total_usdt, 2)}U`)}
${dashMetric("今日盈亏", pnlSigned(pnl, 2), pnlClass(pnl))}
${dashMetric("平仓笔数", String(Number(ac.closed_count) || 0))}
${dashMetric("浮盈亏", pnlSigned(floatPnl, 2), pnlClass(floatPnl))}
</div>`;
}
function renderAccounts(accounts, threshold) {
if (!elAccounts) return;
const rows = Array.isArray(accounts) ? accounts : [];
if (!rows.length) {
elAccounts.innerHTML = '<div class="dash-empty">暂无账户数据</div>';
@@ -181,8 +230,6 @@
.map((ac) => {
const alert = !!ac.loss_alert;
const unmon = !ac.monitored;
const pnl = Number(ac.pnl_u);
const floatPnl = Number(ac.float_pnl_u);
const lossPct = Number(ac.daily_loss_pct);
const barW =
alert && Number.isFinite(lossPct)
@@ -195,38 +242,16 @@
alert && barW > 0
? `<div class="dash-loss-bar" title="占资金合计 ${fmt(lossPct, 2)}%"><i style="width:${barW}%"></i></div>`
: "";
const optFunding = ac.options_funding_usdt;
const optTrading = ac.options_trading_usdt;
const optFloat = Number(ac.options_float_pnl_u);
const optPos = Number(ac.options_open_position_count) || 0;
const optMetrics =
optFunding != null || optTrading != null || optPos > 0
? `<div class="dash-ac-metric"><span>期权资金</span><strong>${fmt(
optFunding != null && optTrading != null
? Number(optFunding) + Number(optTrading)
: null,
2
)}U</strong></div>
<div class="dash-ac-metric"><span>期权持仓</span><strong>${optPos} 仓</strong></div>
<div class="dash-ac-metric"><span>期权浮盈</span><strong class="${pnlClass(optFloat)}">${pnlSigned(
optFloat,
2
)}</strong></div>`
: "";
return `<article class="dash-ac-card${alert ? " is-alert" : ""}${unmon ? " is-unmon" : ""}">
const metricsHtml = ac.options_layout
? renderOkxOptionsMetrics(ac)
: renderStandardMetrics(ac);
const cardCls = ac.options_layout ? " dash-ac-card-options" : "";
return `<article class="dash-ac-card${cardCls}${alert ? " is-alert" : ""}${unmon ? " is-unmon" : ""}">
<div class="dash-ac-top">
<div class="dash-ac-name">${esc(ac.name || "—")}</div>
${badge}
</div>
<div class="dash-ac-metrics">
<div class="dash-ac-metric"><span>资金账户</span><strong>${fmt(ac.funding_usdt, 2)}U</strong></div>
<div class="dash-ac-metric"><span>交易账户</span><strong>${fmt(ac.trading_usdt, 2)}U</strong></div>
<div class="dash-ac-metric"><span>资金合计</span><strong>${fmt(ac.capital_total_usdt, 2)}U</strong></div>
<div class="dash-ac-metric"><span>今日盈亏</span><strong class="${pnlClass(pnl)}">${pnlSigned(pnl, 2)}</strong></div>
<div class="dash-ac-metric"><span>平仓笔数</span><strong>${Number(ac.closed_count) || 0}</strong></div>
<div class="dash-ac-metric"><span>浮盈亏</span><strong class="${pnlClass(floatPnl)}">${pnlSigned(floatPnl, 2)}</strong></div>
${optMetrics}
</div>
${metricsHtml}
${lossBar}
${renderAccountDetail(ac)}
</article>`;
+2 -2
View File
@@ -19,7 +19,7 @@
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=3" />
<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=20260612-dash-monitor-count" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260707-dash-okx-options" />
</head>
<body>
<div class="app-bg" aria-hidden="true"></div>
@@ -1146,7 +1146,7 @@
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260626-archive-layout"></script>
<script src="/assets/funds.js?v=20260707-hub-options-funds"></script>
<script src="/assets/dashboard.js?v=20260707-hub-options-funds"></script>
<script src="/assets/dashboard.js?v=20260707-dash-okx-options"></script>
<script src="/assets/strategy.js?v=3"></script>
<script src="/assets/ai_review_render.js?v=3"></script>
<script src="/assets/time_close_ui.js?v=3"></script>