From 89148bb119f04e154d631ee0299cb9f0dc602785 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 17 Jul 2026 14:52:05 +0800 Subject: [PATCH] Merge dashboard KPIs into one compact card. Put status badge and expand on one row; drop funds footnote, win/loss, and separate options float KPI. Co-authored-by: Cursor --- manual_trading_hub/hub_ai/context.py | 3 + manual_trading_hub/static/dashboard.css | 57 ++++++++++++++---- manual_trading_hub/static/dashboard.js | 77 ++++++++++--------------- manual_trading_hub/static/index.html | 4 +- 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/manual_trading_hub/hub_ai/context.py b/manual_trading_hub/hub_ai/context.py index efd2005..b6f8031 100644 --- a/manual_trading_hub/hub_ai/context.py +++ b/manual_trading_hub/hub_ai/context.py @@ -689,6 +689,9 @@ def build_daily_context( "float_pnl_u": round(total_float, 4), "open_position_count": total_open_positions, "options_open_position_count": total_options_open_positions, + "perpetual_open_position_count": max( + 0, int(total_open_positions) - int(total_options_open_positions) + ), "options_float_pnl_u": round(total_options_float, 4) if options_float_known else None, "total_funding_usdt": round(total_funding, 4) if total_funding is not None else None, "total_trading_usdt": round(total_trading, 4) if total_trading is not None else None, diff --git a/manual_trading_hub/static/dashboard.css b/manual_trading_hub/static/dashboard.css index 35c1893..47902b7 100644 --- a/manual_trading_hub/static/dashboard.css +++ b/manual_trading_hub/static/dashboard.css @@ -114,9 +114,38 @@ body.hub-page-dashboard .page#page-dashboard { } .dash-kpi-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); - gap: 12px; + display: block; +} + +.dash-kpi-summary { + display: flex; + flex-wrap: wrap; + align-items: stretch; + gap: 0; + padding: 10px 6px; + border-radius: 12px; + background: var(--dash-card-bg); + border: 1px solid var(--dash-card-border); + box-shadow: var(--dash-card-glow); + overflow: hidden; +} + +.dash-kpi-item { + flex: 1 1 88px; + min-width: 84px; + max-width: 140px; + padding: 4px 12px; + position: relative; +} + +.dash-kpi-item + .dash-kpi-item::before { + content: ""; + position: absolute; + left: 0; + top: 18%; + bottom: 18%; + width: 1px; + background: color-mix(in srgb, var(--dash-card-border) 85%, transparent); } .dash-kpi, @@ -135,18 +164,20 @@ body.hub-page-dashboard .page#page-dashboard { } .dash-kpi-label { - font-size: 0.72rem; + font-size: 0.65rem; color: var(--dash-muted); - letter-spacing: 0.06em; - margin-bottom: 8px; + letter-spacing: 0.04em; + margin-bottom: 4px; + white-space: nowrap; } .dash-kpi-value { font-family: JetBrains Mono, monospace; - font-size: clamp(1.25rem, 2.2vw, 1.55rem); + font-size: 0.92rem; font-weight: 600; - line-height: 1.2; + line-height: 1.25; color: var(--dash-text); + white-space: nowrap; } .dash-kpi-value.pos { @@ -236,7 +267,7 @@ body.hub-page-dashboard .page#page-dashboard { .dash-ac-top { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; @@ -248,6 +279,13 @@ body.hub-page-dashboard .page#page-dashboard { color: var(--dash-text); } +.dash-ac-top-actions { + display: inline-flex; + align-items: center; + gap: 8px; + flex-shrink: 0; +} + .dash-ac-badge { font-size: 0.65rem; font-weight: 700; @@ -534,7 +572,6 @@ body.hub-page-dashboard .page#page-dashboard { } .dash-ac-expand-btn { - margin-left: auto; display: inline-flex; align-items: center; justify-content: center; diff --git a/manual_trading_hub/static/dashboard.js b/manual_trading_hub/static/dashboard.js index a69bbef..d87064a 100644 --- a/manual_trading_hub/static/dashboard.js +++ b/manual_trading_hub/static/dashboard.js @@ -60,45 +60,31 @@ const floating = Number(totals.float_pnl_u); const funding = totals.total_funding_usdt; const trading = totals.total_trading_usdt; - const optionsFloat = Number(totals.options_float_pnl_u); - const optionsPos = totals.options_open_position_count; - elKpi.innerHTML = [ - kpiCard("交易日", esc(totals.trading_day || "—"), ""), - kpiCard("平仓盈亏", pnlSigned(closed, 2), pnlClass(closed)), - kpiCard( - "平仓笔数", - `${totals.closed_count || 0}`, - "", - `胜 ${totals.win_count || 0} / 负 ${totals.loss_count || 0}` - ), - kpiCard("浮盈亏", pnlSigned(floating, 2), pnlClass(floating)), - kpiCard( - "资金合计", - funding != null && trading != null - ? `${fmt(Number(funding) + Number(trading), 2)}U` - : "—", - "", - `永续+期权 USDT 等价 · 资金 ${fmt(funding, 2)} + 交易 ${fmt(trading, 2)}` - ), - kpiCard( - "实盘持仓", - `${totals.open_position_count || 0} 仓`, - "", - Number.isFinite(optionsPos) && optionsPos > 0 ? `含期权 ${optionsPos} 仓` : "" - ), - Number.isFinite(optionsFloat) && Math.abs(optionsFloat) > 1e-9 - ? kpiCard("期权浮盈", pnlSigned(optionsFloat, 2), pnlClass(optionsFloat)) - : "", - ] - .filter(Boolean) - .join(""); + const funds = + funding != null && trading != null ? Number(funding) + Number(trading) : NaN; + const totalPos = Number(totals.open_position_count) || 0; + const optPos = Number(totals.options_open_position_count) || 0; + const perpPos = + totals.perpetual_open_position_count != null + ? Number(totals.perpetual_open_position_count) || 0 + : Math.max(0, totalPos - optPos); + const items = [ + kpiItem("交易日", esc(totals.trading_day || "—")), + kpiItem("资金合计", Number.isFinite(funds) ? `${fmt(funds, 2)}U` : "—"), + kpiItem("总持仓数量", `${totalPos}`), + kpiItem("期权持仓", `${optPos}`), + kpiItem("永续持仓", `${perpPos}`), + kpiItem("平仓数量", `${totals.closed_count || 0}`), + kpiItem("平仓盈亏", pnlSigned(closed, 2), pnlClass(closed)), + kpiItem("浮盈亏", pnlSigned(floating, 2), pnlClass(floating)), + ]; + elKpi.innerHTML = `
${items.join("")}
`; } - function kpiCard(label, value, valCls, sub) { - return `
+ function kpiItem(label, value, valCls) { + return `
${esc(label)}
${value}
- ${sub ? `
${esc(sub)}
` : ""}
`; } @@ -245,17 +231,10 @@ const perpLines = accountPerpLines(ac); 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 expandBtn = exId - ? `` - : ""; const chips = renderMonitorCountChips((ac && ac.monitor_counts) || {}); - const monitorRow = - chips.length || expandBtn - ? `
${chips.join("")}${expandBtn}
` - : ""; + const monitorRow = chips.length + ? `
${chips.join("")}
` + : ""; const perpHtml = renderDashboardPerpTable(perpLines); const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : ""; const issueHtml = issues @@ -294,6 +273,12 @@ const badge = alert ? `单日亏损 ≥${threshold}%` : `${esc(ac.status || "—")}`; + const exId = ac && ac.id != null ? String(ac.id) : ""; + const expandBtn = exId + ? `` + : ""; const lossBar = alert && barW > 0 ? `
` @@ -302,7 +287,7 @@ return `
${esc(ac.name || "—")}
- ${badge} +
${badge}${expandBtn}
${lossBar}
${renderAccountPositions(ac)}
diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 0f1130b..079d350 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -19,7 +19,7 @@ - + @@ -1373,7 +1373,7 @@ - +