From 3ed8dabc769d0b6b51ae81b9899afb91ed5cad1e Mon Sep 17 00:00:00 2001 From: dekun Date: Mon, 20 Jul 2026 12:34:09 +0800 Subject: [PATCH] Align dashboard PnL columns with monitor; add back-to-dashboard button. Co-authored-by: Cursor --- manual_trading_hub/hub_ai/context.py | 26 +++++++++++++++++++++++++ manual_trading_hub/static/app.js | 12 ++++++++++++ manual_trading_hub/static/dashboard.css | 5 +++++ manual_trading_hub/static/dashboard.js | 24 +++++++++++------------ manual_trading_hub/static/index.html | 6 +++--- 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/manual_trading_hub/hub_ai/context.py b/manual_trading_hub/hub_ai/context.py index bc96493..f444d85 100644 --- a/manual_trading_hub/hub_ai/context.py +++ b/manual_trading_hub/hub_ai/context.py @@ -1059,6 +1059,30 @@ def resolve_position_monitor_source(pos: dict, hub_mon: Optional[dict]) -> str: return candidates[0][1] +def resolve_position_reward_at_tp(pos: dict, hub_mon: Optional[dict]) -> Optional[float]: + """与监控区「盈利金额」一致:优先匹配下单监控 reward_at_tp_usdt,否则仓位自身字段.""" + sym = str(pos.get("symbol") or "") + side = str(pos.get("side") or "") + if isinstance(hub_mon, dict) and hub_mon.get("ok") is not False and sym: + # 与前端 findMonitorOrder 一致:先扫 orders + for o in hub_mon.get("orders") or []: + if isinstance(o, dict) and _monitor_item_matches_position(o, sym, side): + v = _safe_float(o.get("reward_at_tp_usdt")) + if v is not None: + return v + for r in hub_mon.get("rolls") or []: + if isinstance(r, dict) and _monitor_item_matches_position(r, sym, side): + v = _safe_float(r.get("reward_at_tp_usdt")) + if v is not None: + return v + for t in hub_mon.get("trends") or []: + if isinstance(t, dict) and _monitor_item_matches_position(t, sym, side): + v = _safe_float(t.get("reward_at_tp_usdt")) + if v is not None: + return v + return _safe_float(pos.get("reward_at_tp_usdt")) + + def _options_source_label(p: dict) -> str: """看板期权来源:期期/永期对冲,其余为纯期权.""" source = str(p.get("source") or "").strip() @@ -1105,6 +1129,7 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]: notional = _safe_float(p.get("notional_usdt")) if notional is None: notional = _safe_float(p.get("notional")) + reward_tp = resolve_position_reward_at_tp(p, hub_mon) position_lines.append( { "kind": "position", @@ -1117,6 +1142,7 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]: "mark_price": mark, "mark_price_fmt": p.get("mark_price_fmt"), "notional_usdt": notional, + "reward_at_tp_usdt": round(reward_tp, 4) if reward_tp is not None else None, "text": f"{sym} {side}", "pnl": round(upnl, 4), } diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index a2013f3..50a8f62 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -2528,6 +2528,17 @@ renderMonitorGrid(lastMonitorRows); }; }); + fsInner.querySelectorAll(".btn-expand-dashboard").forEach((btn) => { + btn.onclick = (ev) => { + ev.stopPropagation(); + closeExchangeFullscreen(); + if (window.hubNavigateTo) window.hubNavigateTo("/dashboard"); + else { + history.pushState({}, "", "/dashboard"); + setActiveNav(); + } + }; + }); } catch (err) { console.error("renderFullscreenExchange", err); closeExchangeFullscreen(); @@ -4000,6 +4011,7 @@
+ ${flaskOpen ? `打开实例` : ""} ${flaskOpen ? `下单` : ""} ${flaskOpen ? `监控位` : ""} diff --git a/manual_trading_hub/static/dashboard.css b/manual_trading_hub/static/dashboard.css index 6312062..4f99578 100644 --- a/manual_trading_hub/static/dashboard.css +++ b/manual_trading_hub/static/dashboard.css @@ -273,6 +273,11 @@ body.hub-page-dashboard .page#page-dashboard { color: color-mix(in srgb, var(--dash-accent) 80%, #fff); } +.dash-tp-profit { + color: var(--dash-accent); + font-variant-numeric: tabular-nums; +} + .dash-empty-inline { padding: 8px 0 4px; font-size: 0.78rem; diff --git a/manual_trading_hub/static/dashboard.js b/manual_trading_hub/static/dashboard.js index b42b108..bd997cb 100644 --- a/manual_trading_hub/static/dashboard.js +++ b/manual_trading_hub/static/dashboard.js @@ -130,14 +130,16 @@ 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 tpProfitCell(ln) { + const n = ln && ln.reward_at_tp_usdt != null ? Number(ln.reward_at_tp_usdt) : NaN; + if (!Number.isFinite(n)) return "—"; + return `${fmt(n, 2)}U`; + } + + function floatPnlCell(ln) { + const pnl = ln && ln.pnl != null ? Number(ln.pnl) : NaN; + if (!Number.isFinite(pnl)) return "—"; + return `${fmt(pnl, 2)}`; } function collectUnifiedPositions(accounts) { @@ -184,8 +186,6 @@ 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; - const pct = floatPctText(pnl, ln && ln.notional_usdt); return ` ${exchangeLinkCell(ac)} ${sourceTypeCell(source)} @@ -194,8 +194,8 @@ ${priceCell(ln && ln.entry_price_fmt, ln && ln.entry_price)} ${priceCell(ln && ln.mark_price_fmt, ln && ln.mark_price)} ${contracts} - ${Number.isFinite(pnl) ? pnlSigned(pnl, 2) : "—"} - ${pct} + ${tpProfitCell(ln)} + ${floatPnlCell(ln)} `; }) .join(""); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index efdeb15..ffdeb61 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -20,7 +20,7 @@ - + @@ -1385,7 +1385,7 @@ - + @@ -1394,6 +1394,6 @@ - +