Show only positioned accounts on the dashboard.
Drop per-exchange fund metrics and render open positions as labeled tables while keeping closed-trade details. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -974,16 +974,23 @@ def format_account_remark(ac: dict) -> str:
|
||||
|
||||
|
||||
def format_dashboard_account_detail(ac: dict) -> dict[str, Any]:
|
||||
"""数据看板分户卡片:监控仅数量,持仓逐行(含浮盈亏)."""
|
||||
"""数据看板分户卡片:监控仅数量,持仓逐行(含浮盈亏与来源)."""
|
||||
mon = ac.get("monitor_lines") or {}
|
||||
position_lines: list[dict[str, Any]] = []
|
||||
for p in _filter_open_positions(ac.get("positions") or []):
|
||||
sym = p.get("symbol") or "?"
|
||||
side = p.get("side") or "?"
|
||||
contracts = p.get("contracts")
|
||||
if contracts is None:
|
||||
contracts = p.get("size")
|
||||
upnl = _position_float_pnl(p)
|
||||
position_lines.append(
|
||||
{
|
||||
"kind": "position",
|
||||
"source": "永续",
|
||||
"symbol": sym,
|
||||
"side": side,
|
||||
"contracts": contracts,
|
||||
"text": f"{sym} {side}",
|
||||
"pnl": round(upnl, 4),
|
||||
}
|
||||
@@ -1011,6 +1018,7 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]:
|
||||
text = f"{text} {be_line}"
|
||||
line: dict[str, Any] = {
|
||||
"kind": "options",
|
||||
"source": "期权",
|
||||
"text": text,
|
||||
}
|
||||
if upl is not None:
|
||||
|
||||
@@ -350,6 +350,47 @@ body.hub-page-dashboard .page#page-dashboard {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dash-ac-card-pos-only {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dash-ac-pos-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dash-pos-block .dash-ac-section-label {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dash-pos-source {
|
||||
display: inline-block;
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.66rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.dash-pos-source.is-perp {
|
||||
color: #93c5fd;
|
||||
background: rgba(59, 130, 246, 0.18);
|
||||
border: 1px solid rgba(59, 130, 246, 0.35);
|
||||
}
|
||||
|
||||
.dash-pos-source.is-opt {
|
||||
color: #c4b5fd;
|
||||
background: rgba(139, 92, 246, 0.18);
|
||||
border: 1px solid rgba(139, 92, 246, 0.35);
|
||||
}
|
||||
|
||||
.dash-pos-table th,
|
||||
.dash-pos-table td {
|
||||
font-size: 0.72rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dash-ac-metrics-3col .dash-ac-metric {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -124,6 +124,59 @@
|
||||
return `<span class="opt-expiry-cd" data-opt-exp-ms="${esc(ms)}">—</span>`;
|
||||
}
|
||||
|
||||
function shortDashInst(instId) {
|
||||
const s = String(instId || "");
|
||||
if (s.length <= 18) return s;
|
||||
return s.slice(0, 8) + "…" + s.slice(-6);
|
||||
}
|
||||
|
||||
function accountPerpLines(ac) {
|
||||
const positions = Array.isArray(ac && ac.position_lines) ? ac.position_lines : [];
|
||||
if (ac && ac.options_layout) {
|
||||
return positions.filter((ln) => (ln && ln.kind) !== "options");
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
function accountHasOpenPositions(ac) {
|
||||
const perp = accountPerpLines(ac);
|
||||
const optionsPositions = Array.isArray(ac && ac.options_positions) ? ac.options_positions : [];
|
||||
return perp.length > 0 || (ac && ac.options_layout && optionsPositions.length > 0);
|
||||
}
|
||||
|
||||
function renderDashboardPerpTable(lines) {
|
||||
const rows = Array.isArray(lines) ? lines : [];
|
||||
if (!rows.length) return "";
|
||||
const body = rows
|
||||
.map((ln) => {
|
||||
const source = esc((ln && ln.source) || "永续");
|
||||
const symbol = esc((ln && (ln.symbol || ln.text)) || "—");
|
||||
const side = esc((ln && ln.side) || "—");
|
||||
const contracts =
|
||||
ln && ln.contracts != null && ln.contracts !== "" ? esc(String(ln.contracts)) : "—";
|
||||
const pnl = ln && ln.pnl != null ? Number(ln.pnl) : NaN;
|
||||
return `<tr>
|
||||
<td><span class="dash-pos-source is-perp">${source}</span></td>
|
||||
<td>${symbol}</td>
|
||||
<td>${side}</td>
|
||||
<td>${contracts}</td>
|
||||
<td class="${pnlClass(pnl)}">${Number.isFinite(pnl) ? pnlSigned(pnl, 2) : "—"}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
return `<div class="dash-pos-block">
|
||||
<div class="dash-ac-section-label">永续持仓</div>
|
||||
<div class="dash-table-wrap">
|
||||
<table class="dash-table dash-pos-table">
|
||||
<thead><tr>
|
||||
<th>来源</th><th>合约</th><th>方向</th><th>张数</th><th>浮盈</th>
|
||||
</tr></thead>
|
||||
<tbody>${body}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderDashboardOptionsTable(positions) {
|
||||
const pos = Array.isArray(positions) ? positions : [];
|
||||
if (!pos.length) return "";
|
||||
@@ -136,6 +189,7 @@
|
||||
? "Put"
|
||||
: p.opt_type || "—";
|
||||
return `<tr>
|
||||
<td><span class="dash-pos-source is-opt">期权</span></td>
|
||||
<td title="${esc(p.inst_id || "")}">${esc(shortDashInst(p.inst_id))}</td>
|
||||
<td>${esc(optType)}</td>
|
||||
<td>${dashOptionsExpiryCd(p.exp_time_ms != null ? p.exp_time_ms : p.exp_time)}</td>
|
||||
@@ -146,12 +200,12 @@
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
return `<div class="dash-options-block">
|
||||
return `<div class="dash-pos-block 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><th>浮盈</th>
|
||||
<th>来源</th><th>合约</th><th>类型</th><th>到期倒计时</th><th>指数</th><th>到期平衡</th><th>平掉回本</th><th>浮盈</th>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
@@ -159,55 +213,27 @@
|
||||
</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 : [];
|
||||
function renderAccountPositions(ac) {
|
||||
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 chips = renderMonitorCountChips(counts);
|
||||
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 monitorRow =
|
||||
chips.length || expandBtn
|
||||
? `<div class="dash-ac-monitor-row">${chips.join("")}${expandBtn}</div>`
|
||||
: "";
|
||||
let posHtml = "";
|
||||
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))) {
|
||||
const pnl = Number(ln.pnl);
|
||||
return (
|
||||
`<div class="dash-ac-remark-line dash-ac-remark-pos">` +
|
||||
`${text} 浮<span class="${pnlClass(pnl)}">${pnlSigned(pnl, 2)}</span>` +
|
||||
`</div>`
|
||||
);
|
||||
}
|
||||
return `<div class="dash-ac-remark-line dash-ac-remark-pos">${text}</div>`;
|
||||
})
|
||||
.join("");
|
||||
} 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 perpHtml = renderDashboardPerpTable(perpLines);
|
||||
const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : "";
|
||||
const issueHtml = issues
|
||||
.map((text) => `<div class="dash-ac-remark-line dash-ac-remark-issue">${esc(text)}</div>`)
|
||||
.join("");
|
||||
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>`;
|
||||
return `${monitorRow}${perpHtml}${optionsHtml}${issueHtml}`;
|
||||
}
|
||||
|
||||
function bindDashboardExpand() {
|
||||
@@ -222,60 +248,10 @@
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const rows = Array.isArray(accounts) ? accounts : [];
|
||||
const rows = (Array.isArray(accounts) ? accounts : []).filter(accountHasOpenPositions);
|
||||
if (!rows.length) {
|
||||
elAccounts.innerHTML = '<div class="dash-empty">暂无账户数据</div>';
|
||||
elAccounts.innerHTML = '<div class="dash-empty">当前无持仓账户</div>';
|
||||
return;
|
||||
}
|
||||
elAccounts.innerHTML = rows
|
||||
@@ -294,18 +270,14 @@
|
||||
alert && barW > 0
|
||||
? `<div class="dash-loss-bar" title="占资金合计 ${fmt(lossPct, 2)}%"><i style="width:${barW}%"></i></div>`
|
||||
: "";
|
||||
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" : ""}">
|
||||
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-name">${esc(ac.name || "—")}</div>
|
||||
${badge}
|
||||
</div>
|
||||
${metricsHtml}
|
||||
${lossBar}
|
||||
${renderAccountDetail(ac)}
|
||||
<div class="dash-ac-pos-body">${renderAccountPositions(ac)}</div>
|
||||
</article>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
@@ -19,7 +19,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=20260707-dash-okx-options" />
|
||||
<link rel="stylesheet" href="/assets/dashboard.css?v=20260717-dash-pos-only" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-bg" aria-hidden="true"></div>
|
||||
@@ -609,7 +609,7 @@
|
||||
<div class="dash-head">
|
||||
<div>
|
||||
<h1><span class="dash-head-tag">DASH</span>数据看板</h1>
|
||||
<p class="page-desc">三户当日总览 · 分户明细 · 平仓流水 · SSE 推送更新</p>
|
||||
<p class="page-desc">三户当日总览 · 有仓持仓表 · 平仓流水 · SSE 推送更新</p>
|
||||
</div>
|
||||
<div class="dash-head-meta">
|
||||
<div><span class="dash-pulse-dot" aria-hidden="true"></span><strong>LIVE</strong> · 交易日 <span id="dash-trading-day">—</span></div>
|
||||
@@ -623,7 +623,7 @@
|
||||
</div>
|
||||
<div id="dash-kpi-row" class="dash-kpi-row"></div>
|
||||
<section class="dash-section">
|
||||
<div class="dash-section-head">分户明细 · ACCOUNTS</div>
|
||||
<div class="dash-section-head">持仓明细 · POSITIONS</div>
|
||||
<div class="dash-section-body"><div id="dash-accounts" class="dash-ac-grid"></div></div>
|
||||
</section>
|
||||
<section class="dash-section">
|
||||
@@ -1373,7 +1373,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=20260708-options-expiry-cd"></script>
|
||||
<script src="/assets/dashboard.js?v=20260717-dash-pos-only"></script>
|
||||
<script src="/assets/strategy.js?v=3"></script>
|
||||
<script src="/assets/help.js?v=1"></script>
|
||||
<script src="/assets/logs.js?v=1"></script>
|
||||
|
||||
Reference in New Issue
Block a user