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 <cursoragent@cursor.com>
This commit is contained in:
@@ -689,6 +689,9 @@ def build_daily_context(
|
|||||||
"float_pnl_u": round(total_float, 4),
|
"float_pnl_u": round(total_float, 4),
|
||||||
"open_position_count": total_open_positions,
|
"open_position_count": total_open_positions,
|
||||||
"options_open_position_count": total_options_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,
|
"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_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,
|
"total_trading_usdt": round(total_trading, 4) if total_trading is not None else None,
|
||||||
|
|||||||
@@ -114,9 +114,38 @@ body.hub-page-dashboard .page#page-dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dash-kpi-row {
|
.dash-kpi-row {
|
||||||
display: grid;
|
display: block;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
}
|
||||||
gap: 12px;
|
|
||||||
|
.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,
|
.dash-kpi,
|
||||||
@@ -135,18 +164,20 @@ body.hub-page-dashboard .page#page-dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dash-kpi-label {
|
.dash-kpi-label {
|
||||||
font-size: 0.72rem;
|
font-size: 0.65rem;
|
||||||
color: var(--dash-muted);
|
color: var(--dash-muted);
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.04em;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-kpi-value {
|
.dash-kpi-value {
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
font-size: clamp(1.25rem, 2.2vw, 1.55rem);
|
font-size: 0.92rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.2;
|
line-height: 1.25;
|
||||||
color: var(--dash-text);
|
color: var(--dash-text);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-kpi-value.pos {
|
.dash-kpi-value.pos {
|
||||||
@@ -236,7 +267,7 @@ body.hub-page-dashboard .page#page-dashboard {
|
|||||||
|
|
||||||
.dash-ac-top {
|
.dash-ac-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@@ -248,6 +279,13 @@ body.hub-page-dashboard .page#page-dashboard {
|
|||||||
color: var(--dash-text);
|
color: var(--dash-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dash-ac-top-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.dash-ac-badge {
|
.dash-ac-badge {
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -534,7 +572,6 @@ body.hub-page-dashboard .page#page-dashboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dash-ac-expand-btn {
|
.dash-ac-expand-btn {
|
||||||
margin-left: auto;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -60,45 +60,31 @@
|
|||||||
const floating = Number(totals.float_pnl_u);
|
const floating = Number(totals.float_pnl_u);
|
||||||
const funding = totals.total_funding_usdt;
|
const funding = totals.total_funding_usdt;
|
||||||
const trading = totals.total_trading_usdt;
|
const trading = totals.total_trading_usdt;
|
||||||
const optionsFloat = Number(totals.options_float_pnl_u);
|
const funds =
|
||||||
const optionsPos = totals.options_open_position_count;
|
funding != null && trading != null ? Number(funding) + Number(trading) : NaN;
|
||||||
elKpi.innerHTML = [
|
const totalPos = Number(totals.open_position_count) || 0;
|
||||||
kpiCard("交易日", esc(totals.trading_day || "—"), ""),
|
const optPos = Number(totals.options_open_position_count) || 0;
|
||||||
kpiCard("平仓盈亏", pnlSigned(closed, 2), pnlClass(closed)),
|
const perpPos =
|
||||||
kpiCard(
|
totals.perpetual_open_position_count != null
|
||||||
"平仓笔数",
|
? Number(totals.perpetual_open_position_count) || 0
|
||||||
`${totals.closed_count || 0}`,
|
: Math.max(0, totalPos - optPos);
|
||||||
"",
|
const items = [
|
||||||
`胜 ${totals.win_count || 0} / 负 ${totals.loss_count || 0}`
|
kpiItem("交易日", esc(totals.trading_day || "—")),
|
||||||
),
|
kpiItem("资金合计", Number.isFinite(funds) ? `${fmt(funds, 2)}U` : "—"),
|
||||||
kpiCard("浮盈亏", pnlSigned(floating, 2), pnlClass(floating)),
|
kpiItem("总持仓数量", `${totalPos}`),
|
||||||
kpiCard(
|
kpiItem("期权持仓", `${optPos}`),
|
||||||
"资金合计",
|
kpiItem("永续持仓", `${perpPos}`),
|
||||||
funding != null && trading != null
|
kpiItem("平仓数量", `${totals.closed_count || 0}`),
|
||||||
? `${fmt(Number(funding) + Number(trading), 2)}U`
|
kpiItem("平仓盈亏", pnlSigned(closed, 2), pnlClass(closed)),
|
||||||
: "—",
|
kpiItem("浮盈亏", pnlSigned(floating, 2), pnlClass(floating)),
|
||||||
"",
|
];
|
||||||
`永续+期权 USDT 等价 · 资金 ${fmt(funding, 2)} + 交易 ${fmt(trading, 2)}`
|
elKpi.innerHTML = `<div class="dash-kpi-summary">${items.join("")}</div>`;
|
||||||
),
|
|
||||||
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("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function kpiCard(label, value, valCls, sub) {
|
function kpiItem(label, value, valCls) {
|
||||||
return `<div class="dash-kpi">
|
return `<div class="dash-kpi-item">
|
||||||
<div class="dash-kpi-label">${esc(label)}</div>
|
<div class="dash-kpi-label">${esc(label)}</div>
|
||||||
<div class="dash-kpi-value ${valCls || ""}">${value}</div>
|
<div class="dash-kpi-value ${valCls || ""}">${value}</div>
|
||||||
${sub ? `<div class="dash-kpi-sub">${esc(sub)}</div>` : ""}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,16 +231,9 @@
|
|||||||
const perpLines = accountPerpLines(ac);
|
const perpLines = accountPerpLines(ac);
|
||||||
const optionsPositions = Array.isArray(ac && ac.options_positions) ? ac.options_positions : [];
|
const optionsPositions = Array.isArray(ac && ac.options_positions) ? ac.options_positions : [];
|
||||||
const issues = Array.isArray(ac && ac.issues) ? ac.issues : [];
|
const issues = Array.isArray(ac && ac.issues) ? ac.issues : [];
|
||||||
const exId = ac && ac.id != null ? String(ac.id) : "";
|
|
||||||
const expandBtn = exId
|
|
||||||
? `<button type="button" class="dash-ac-expand-btn" data-dash-ex-id="${esc(exId)}" title="放大查看监控详情" aria-label="放大查看监控详情">` +
|
|
||||||
`<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M15 3h6v6h-2V6.41l-7.29 7.3-1.42-1.42 7.3-7.29H15V3zM3 9h2v10h10v2H3V9z"/></svg>` +
|
|
||||||
`</button>`
|
|
||||||
: "";
|
|
||||||
const chips = renderMonitorCountChips((ac && ac.monitor_counts) || {});
|
const chips = renderMonitorCountChips((ac && ac.monitor_counts) || {});
|
||||||
const monitorRow =
|
const monitorRow = chips.length
|
||||||
chips.length || expandBtn
|
? `<div class="dash-ac-monitor-row">${chips.join("")}</div>`
|
||||||
? `<div class="dash-ac-monitor-row">${chips.join("")}${expandBtn}</div>`
|
|
||||||
: "";
|
: "";
|
||||||
const perpHtml = renderDashboardPerpTable(perpLines);
|
const perpHtml = renderDashboardPerpTable(perpLines);
|
||||||
const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : "";
|
const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : "";
|
||||||
@@ -294,6 +273,12 @@
|
|||||||
const badge = alert
|
const badge = alert
|
||||||
? `<span class="dash-ac-badge alert">单日亏损 ≥${threshold}%</span>`
|
? `<span class="dash-ac-badge alert">单日亏损 ≥${threshold}%</span>`
|
||||||
: `<span class="dash-ac-badge ok">${esc(ac.status || "—")}</span>`;
|
: `<span class="dash-ac-badge ok">${esc(ac.status || "—")}</span>`;
|
||||||
|
const exId = ac && ac.id != null ? String(ac.id) : "";
|
||||||
|
const expandBtn = exId
|
||||||
|
? `<button type="button" class="dash-ac-expand-btn" data-dash-ex-id="${esc(exId)}" title="放大查看监控详情" aria-label="放大查看监控详情">` +
|
||||||
|
`<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path fill="currentColor" d="M15 3h6v6h-2V6.41l-7.29 7.3-1.42-1.42 7.3-7.29H15V3zM3 9h2v10h10v2H3V9z"/></svg>` +
|
||||||
|
`</button>`
|
||||||
|
: "";
|
||||||
const lossBar =
|
const lossBar =
|
||||||
alert && barW > 0
|
alert && barW > 0
|
||||||
? `<div class="dash-loss-bar" title="占资金合计 ${fmt(lossPct, 2)}%"><i style="width:${barW}%"></i></div>`
|
? `<div class="dash-loss-bar" title="占资金合计 ${fmt(lossPct, 2)}%"><i style="width:${barW}%"></i></div>`
|
||||||
@@ -302,7 +287,7 @@
|
|||||||
return `<article class="dash-ac-card dash-ac-card-pos-only${cardCls}${alert ? " is-alert" : ""}${unmon ? " is-unmon" : ""}">
|
return `<article class="dash-ac-card dash-ac-card-pos-only${cardCls}${alert ? " is-alert" : ""}${unmon ? " is-unmon" : ""}">
|
||||||
<div class="dash-ac-top">
|
<div class="dash-ac-top">
|
||||||
<div class="dash-ac-name">${esc(ac.name || "—")}</div>
|
<div class="dash-ac-name">${esc(ac.name || "—")}</div>
|
||||||
${badge}
|
<div class="dash-ac-top-actions">${badge}${expandBtn}</div>
|
||||||
</div>
|
</div>
|
||||||
${lossBar}
|
${lossBar}
|
||||||
<div class="dash-ac-pos-body">${renderAccountPositions(ac)}</div>
|
<div class="dash-ac-pos-body">${renderAccountPositions(ac)}</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
<link rel="stylesheet" href="/assets/dashboard.css?v=20260717-dash-opt-net" />
|
<link rel="stylesheet" href="/assets/dashboard.css?v=20260717-dash-kpi-merge" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-bg" aria-hidden="true"></div>
|
<div class="app-bg" aria-hidden="true"></div>
|
||||||
@@ -1373,7 +1373,7 @@
|
|||||||
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
|
<script src="/assets/archive.js?v=20260717-archive-cal-chart"></script>
|
||||||
<script src="/assets/quotes.js?v=20260717-quotes-feed"></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/funds.js?v=20260717-funds-scroll-fix"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260717-dash-opt-net"></script>
|
<script src="/assets/dashboard.js?v=20260717-dash-kpi-merge"></script>
|
||||||
<script src="/assets/strategy.js?v=3"></script>
|
<script src="/assets/strategy.js?v=3"></script>
|
||||||
<script src="/assets/help.js?v=1"></script>
|
<script src="/assets/help.js?v=1"></script>
|
||||||
<script src="/assets/logs.js?v=1"></script>
|
<script src="/assets/logs.js?v=1"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user