DASH数据看板
-三户当日总览 · 分户明细 · 平仓流水 · SSE 推送更新
+三户当日总览 · 有仓持仓表 · 平仓流水 · SSE 推送更新
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 `
| 来源 | 合约 | 方向 | 张数 | 浮盈 | +
|---|