diff --git a/manual_trading_hub/hub_dashboard.py b/manual_trading_hub/hub_dashboard.py index 5b86654..6194509 100644 --- a/manual_trading_hub/hub_dashboard.py +++ b/manual_trading_hub/hub_dashboard.py @@ -38,6 +38,29 @@ def _account_capital_base(ac: dict) -> Optional[float]: return None +def _options_layout_enabled(ac: dict) -> bool: + if str(ac.get("key") or "").lower() != "okx": + return False + snap = ac.get("options_snapshot") + if isinstance(snap, dict) and snap.get("enabled") is not False: + return True + return ac.get("options_funding_usdt") is not None or ac.get("options_trading_usdt") is not None + + +def _perpetual_float_pnl_u(ac: dict) -> Optional[float]: + try: + total = float(ac.get("float_pnl_u") or 0) + except (TypeError, ValueError): + total = 0.0 + opt = ac.get("options_float_pnl_u") + if opt is None: + return round(total, 4) + try: + return round(total - float(opt), 4) + except (TypeError, ValueError): + return round(total, 4) + + def _enrich_account_row(ac: dict) -> dict: st = ac.get("trade_stats") or {} capital = _account_capital_base(ac) @@ -61,6 +84,8 @@ def _enrich_account_row(ac: dict) -> dict: "options_trading_usdt": ac.get("options_trading_usdt"), "options_float_pnl_u": ac.get("options_float_pnl_u"), "options_open_position_count": ac.get("options_open_position_count"), + "options_layout": _options_layout_enabled(ac), + "perpetual_float_pnl_u": _perpetual_float_pnl_u(ac), "capital_total_usdt": round(capital, 4) if capital is not None else None, "available_trading_usdt": ac.get("available_trading_usdt"), "pnl_u": st.get("total_pnl_u"), diff --git a/manual_trading_hub/static/dashboard.css b/manual_trading_hub/static/dashboard.css index 3b95eb8..be719bd 100644 --- a/manual_trading_hub/static/dashboard.css +++ b/manual_trading_hub/static/dashboard.css @@ -276,6 +276,71 @@ body.hub-page-dashboard .page#page-dashboard { font-size: 0.76rem; } +.dash-ac-metrics-3col { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.dash-ac-section-label { + grid-column: 1 / -1; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 0.06em; + color: var(--dash-accent); + margin-top: 2px; + padding-bottom: 2px; + border-bottom: 1px dashed color-mix(in srgb, var(--dash-card-border) 80%, transparent); +} + +.dash-ac-section-label:not(:first-child) { + margin-top: 8px; +} + +.dash-ac-metric-empty { + visibility: hidden; + min-height: 0; + padding: 0; + margin: 0; +} + +.dash-ac-total-row { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin-top: 10px; + padding: 8px 10px; + border-radius: 8px; + border: 1px solid var(--dash-card-border); + background: color-mix(in srgb, var(--dash-accent) 6%, var(--dash-card-bg)); + font-family: JetBrains Mono, monospace; + font-size: 0.76rem; +} + +.dash-ac-total-row span { + color: var(--dash-muted); + font-size: 0.68rem; +} + +.dash-ac-total-row strong { + color: var(--dash-text); + font-size: 0.88rem; +} + +.dash-ac-card-options .dash-ac-remark { + margin-top: 8px; + padding-top: 8px; + border-top: 1px dashed color-mix(in srgb, var(--dash-card-border) 80%, transparent); +} + +.dash-ac-metrics-3col .dash-ac-metric { + text-align: center; +} + +.dash-ac-metrics-3col .dash-ac-metric span, +.dash-ac-metrics-3col .dash-ac-metric strong { + text-align: center; +} + .dash-ac-metric span { display: block; color: var(--dash-muted); diff --git a/manual_trading_hub/static/dashboard.js b/manual_trading_hub/static/dashboard.js index 8b7cf5b..1218a26 100644 --- a/manual_trading_hub/static/dashboard.js +++ b/manual_trading_hub/static/dashboard.js @@ -170,8 +170,57 @@ }); } + function dashMetric(label, value, valCls) { + return `