diff --git a/manual_trading_hub/hub_ai/context.py b/manual_trading_hub/hub_ai/context.py index f0fa1e9..f360a38 100644 --- a/manual_trading_hub/hub_ai/context.py +++ b/manual_trading_hub/hub_ai/context.py @@ -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: diff --git a/manual_trading_hub/static/dashboard.css b/manual_trading_hub/static/dashboard.css index 3ecdafe..a86ee8e 100644 --- a/manual_trading_hub/static/dashboard.css +++ b/manual_trading_hub/static/dashboard.css @@ -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; } diff --git a/manual_trading_hub/static/dashboard.js b/manual_trading_hub/static/dashboard.js index 3f81760..e6ae5e6 100644 --- a/manual_trading_hub/static/dashboard.js +++ b/manual_trading_hub/static/dashboard.js @@ -124,6 +124,59 @@ return ``; } + 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 ` + ${source} + ${symbol} + ${side} + ${contracts} + ${Number.isFinite(pnl) ? pnlSigned(pnl, 2) : "—"} + `; + }) + .join(""); + return `
+
永续持仓
+
+ + + + + ${body} +
来源合约方向张数浮盈
+
+
`; + } + function renderDashboardOptionsTable(positions) { const pos = Array.isArray(positions) ? positions : []; if (!pos.length) return ""; @@ -136,6 +189,7 @@ ? "Put" : p.opt_type || "—"; return ` + 期权 ${esc(shortDashInst(p.inst_id))} ${esc(optType)} ${dashOptionsExpiryCd(p.exp_time_ms != null ? p.exp_time_ms : p.exp_time)} @@ -146,12 +200,12 @@ `; }) .join(""); - return `
+ return `
- + ${rows}
合约类型到期倒计时指数到期平衡平掉回本浮盈来源合约类型到期倒计时指数到期平衡平掉回本浮盈
@@ -159,55 +213,27 @@
`; } - 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 ? `` : ""; + const chips = renderMonitorCountChips((ac && ac.monitor_counts) || {}); const monitorRow = chips.length || expandBtn ? `
${chips.join("")}${expandBtn}
` : ""; - 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 ( - `
` + - `${text} 浮${pnlSigned(pnl, 2)}` + - `
` - ); - } - return `
${text}
`; - }) - .join(""); - } else if (!chips.length && !issues.length && !(ac && ac.options_layout && optionsPositions.length)) { - posHtml = `
无持仓
`; - } + const perpHtml = renderDashboardPerpTable(perpLines); + const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : ""; const issueHtml = issues .map((text) => `
${esc(text)}
`) .join(""); - const optionsHtml = ac && ac.options_layout ? renderDashboardOptionsTable(optionsPositions) : ""; - return `
${monitorRow}
${posHtml}
${optionsHtml}${issueHtml}
`; + return `${monitorRow}${perpHtml}${optionsHtml}${issueHtml}`; } function bindDashboardExpand() { @@ -222,60 +248,10 @@ }); } - function dashMetric(label, value, valCls) { - return `
${esc(label)}${value}
`; - } - - function dashSectionLabel(text) { - return ``; - } - - 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 `
- ${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))} - - ${dashSectionLabel("期权账户")} - ${dashMetric("资金账户", `${fmt(ac.options_funding_usdt, 2)}U`)} - ${dashMetric("交易账户", `${fmt(ac.options_trading_usdt, 2)}U`)} - ${dashMetric("期权浮盈亏", pnlSigned(optFloat, 2), pnlClass(optFloat))} -
-
- 总资金 - ${fmt(ac.capital_total_usdt, 2)}U -
`; - } - - function renderStandardMetrics(ac) { - const pnl = Number(ac.pnl_u); - const floatPnl = Number(ac.float_pnl_u); - return `
- ${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))} -
`; - } - function renderAccounts(accounts, threshold) { - const rows = Array.isArray(accounts) ? accounts : []; + const rows = (Array.isArray(accounts) ? accounts : []).filter(accountHasOpenPositions); if (!rows.length) { - elAccounts.innerHTML = '
暂无账户数据
'; + elAccounts.innerHTML = '
当前无持仓账户
'; return; } elAccounts.innerHTML = rows @@ -294,18 +270,14 @@ alert && barW > 0 ? `
` : ""; - const metricsHtml = ac.options_layout - ? renderOkxOptionsMetrics(ac) - : renderStandardMetrics(ac); const cardCls = ac.options_layout ? " dash-ac-card-options" : ""; - return `
+ return `
${esc(ac.name || "—")}
${badge}
- ${metricsHtml} ${lossBar} - ${renderAccountDetail(ac)} +
${renderAccountPositions(ac)}
`; }) .join(""); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 2b750e7..08991b8 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -19,7 +19,7 @@ - + @@ -609,7 +609,7 @@

DASH数据看板

-

三户当日总览 · 分户明细 · 平仓流水 · SSE 推送更新

+

三户当日总览 · 有仓持仓表 · 平仓流水 · SSE 推送更新

LIVE · 交易日
@@ -623,7 +623,7 @@
-
分户明细 · ACCOUNTS
+
持仓明细 · POSITIONS
@@ -1373,7 +1373,7 @@ - +