diff --git a/manual_trading_hub/hub_ai/context.py b/manual_trading_hub/hub_ai/context.py index b6f8031..bc96493 100644 --- a/manual_trading_hub/hub_ai/context.py +++ b/manual_trading_hub/hub_ai/context.py @@ -1100,6 +1100,11 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]: contracts = p.get("size") upnl = _position_float_pnl(p) source = resolve_position_monitor_source(p, hub_mon) + entry = _safe_float(p.get("entry_price")) + mark = _safe_float(p.get("mark_price")) + notional = _safe_float(p.get("notional_usdt")) + if notional is None: + notional = _safe_float(p.get("notional")) position_lines.append( { "kind": "position", @@ -1107,6 +1112,11 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]: "symbol": sym, "side": side, "contracts": contracts, + "entry_price": entry, + "entry_price_fmt": p.get("entry_price_fmt"), + "mark_price": mark, + "mark_price_fmt": p.get("mark_price_fmt"), + "notional_usdt": notional, "text": f"{sym} {side}", "pnl": round(upnl, 4), } diff --git a/manual_trading_hub/static/dashboard.css b/manual_trading_hub/static/dashboard.css index 6579fcc..6312062 100644 --- a/manual_trading_hub/static/dashboard.css +++ b/manual_trading_hub/static/dashboard.css @@ -247,11 +247,43 @@ body.hub-page-dashboard .page#page-dashboard { .dash-ac-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); + grid-template-columns: 1fr; gap: 12px; padding: 14px; } +.dash-pos-unified-card { + width: 100%; +} + +.dash-ex-link { + appearance: none; + border: 0; + background: transparent; + color: var(--dash-accent); + font: inherit; + font-weight: 600; + padding: 0; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; +} + +.dash-ex-link:hover { + color: color-mix(in srgb, var(--dash-accent) 80%, #fff); +} + +.dash-empty-inline { + padding: 8px 0 4px; + font-size: 0.78rem; +} + +.dash-pos-alert-note { + margin-top: 4px; + font-size: 0.75rem; + color: var(--dash-warn); +} + .dash-ac-card { position: relative; padding: 14px 16px; diff --git a/manual_trading_hub/static/dashboard.js b/manual_trading_hub/static/dashboard.js index d87064a..6e23f7d 100644 --- a/manual_trading_hub/static/dashboard.js +++ b/manual_trading_hub/static/dashboard.js @@ -88,22 +88,6 @@ `; } - function renderMonitorCountChips(counts) { - const mc = counts || {}; - const chips = []; - const keys = Number(mc.keys) || 0; - const orders = Number(mc.orders) || 0; - const trends = Number(mc.trends) || 0; - const rolls = Number(mc.rolls) || 0; - if (keys > 0) chips.push(`关键位 ${keys}`); - if (orders > 0) { - chips.push(`下单监控 ${orders}`); - } - if (trends > 0) chips.push(`趋势回调 ${trends}`); - if (rolls > 0) chips.push(`顺势加仓 ${rolls}`); - return chips; - } - function dashOptionsExpiryCd(expMs) { const ms = expMs != null && expMs !== "" ? String(expMs) : ""; if (!ms) return "—"; @@ -130,34 +114,75 @@ return perp.length > 0 || (ac && ac.options_layout && optionsPositions.length > 0); } - function sourceBadgeClass(source) { - const s = String(source || ""); - if (s.indexOf("对冲") >= 0) return "is-hedge"; - if (s.indexOf("纯期权") >= 0 || s === "期权") return "is-opt"; - if (s.indexOf("顺势") >= 0) return "is-roll"; - if (s.indexOf("趋势") >= 0) return "is-trend"; - if (s.indexOf("关键位") >= 0) return "is-key"; - if (s.indexOf("下单") >= 0) return "is-order"; - return "is-none"; + function exchangeLinkCell(ac) { + const name = esc((ac && ac.name) || "—"); + const exId = ac && ac.id != null ? String(ac.id) : ""; + if (!exId) return name; + return ( + `` + ); } - function renderDashboardPerpTable(lines) { - const rows = Array.isArray(lines) ? lines : []; - if (!rows.length) return ""; + function priceCell(fmtVal, raw) { + if (fmtVal != null && String(fmtVal).trim() !== "") return esc(String(fmtVal)); + if (raw != null && Number.isFinite(Number(raw))) return esc(fmt(raw, 4).replace(/\.?0+$/, "")); + return "—"; + } + + function floatPctText(pnl, notional) { + const n = Number(pnl); + const notion = Number(notional); + if (!Number.isFinite(n) || !Number.isFinite(notion) || Math.abs(notion) < 1e-8) return "—"; + const pct = (n / Math.abs(notion)) * 100; + const abs = Math.abs(pct).toFixed(2); + if (Math.abs(pct) < 1e-9) return "0.00%"; + return `${pct > 0 ? "+" : "-"}${abs}%`; + } + + function collectUnifiedPositions(accounts) { + const perp = []; + const options = []; + (Array.isArray(accounts) ? accounts : []).forEach((ac) => { + if (!accountHasOpenPositions(ac)) return; + accountPerpLines(ac).forEach((ln) => { + if (!ln) return; + perp.push({ ac: ac, ln: ln }); + }); + if (ac && ac.options_layout) { + (Array.isArray(ac.options_positions) ? ac.options_positions : []).forEach((p) => { + if (!p) return; + options.push({ ac: ac, p: p }); + }); + } + }); + return { perp: perp, options: options }; + } + + function renderUnifiedPerpTable(rows) { + if (!rows.length) { + return `
| 来源 | 合约 | 方向 | 张数 | 浮盈 | +交易所 | 合约 | 方向 | 开仓价 | 标记价 | 张数 | 盈利金额 | 浮盈 |
|---|