from __future__ import annotations from typing import Any from nicegui import ui class PortfolioOverview: """Portfolio summary card with LTV risk coloring and margin warning.""" def __init__(self, *, margin_call_ltv: float = 0.75) -> None: self.margin_call_ltv = margin_call_ltv with ui.card().classes( "w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900" ): with ui.row().classes("w-full items-center justify-between"): ui.label("Portfolio Overview").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") self.warning_badge = ui.label("Monitoring").classes( "rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700 dark:bg-amber-500/15 dark:text-amber-300" ) with ui.grid(columns=2).classes("w-full gap-4 max-sm:grid-cols-1"): self.gold_value = self._metric_card("Current Gold Value") self.loan_amount = self._metric_card("Loan Amount") self.ltv = self._metric_card("Current LTV") self.net_equity = self._metric_card("Net Equity") def _metric_card(self, label: str) -> ui.label: with ui.card().classes( "rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-none dark:border-slate-800 dark:bg-slate-950" ): ui.label(label).classes("text-sm font-medium text-slate-500 dark:text-slate-400") return ui.label("--").classes("text-2xl font-bold text-slate-900 dark:text-slate-50") def update(self, portfolio: dict[str, Any], *, margin_call_ltv: float | None = None) -> None: threshold = ( margin_call_ltv if margin_call_ltv is not None else portfolio.get("margin_call_ltv", self.margin_call_ltv) ) gold_value = float(portfolio.get("gold_value", portfolio.get("portfolio_value", 0.0))) loan_amount = float(portfolio.get("loan_amount", 0.0)) current_ltv = float(portfolio.get("ltv_ratio", portfolio.get("current_ltv", 0.0))) net_equity = float(portfolio.get("net_equity", gold_value - loan_amount)) self.gold_value.set_text(self._money(gold_value)) self.loan_amount.set_text(self._money(loan_amount)) self.net_equity.set_text(self._money(net_equity)) self.ltv.set_text(f"{current_ltv * 100:.1f}%") self.ltv.style(f"color: {self._ltv_color(current_ltv, threshold)}") badge_text, badge_style = self._warning_state(current_ltv, threshold) self.warning_badge.set_text(badge_text) self.warning_badge.style(badge_style) @staticmethod def _money(value: float) -> str: return f"${value:,.2f}" @staticmethod def _ltv_color(ltv: float, threshold: float) -> str: if ltv >= threshold: return "#f43f5e" if ltv >= threshold * 0.9: return "#f59e0b" return "#22c55e" @staticmethod def _warning_state(ltv: float, threshold: float) -> tuple[str, str]: base = "border-radius: 9999px; padding: 0.25rem 0.75rem; font-size: 0.75rem; font-weight: 600;" if ltv >= threshold: return ( "Margin call risk", base + " background: rgba(244, 63, 94, 0.14); color: #f43f5e;", ) if ltv >= threshold * 0.9: return ( "Approaching threshold", base + " background: rgba(245, 158, 11, 0.14); color: #f59e0b;", ) return ( "Healthy collateral", base + " background: rgba(34, 197, 94, 0.14); color: #22c55e;", )