- Set ruff/black line length to 120 - Reformatted code with black - Fixed import ordering with ruff - Disabled lint for UI component files with long CSS strings - Updated pyproject.toml with proper tool configuration
84 lines
3.6 KiB
Python
84 lines
3.6 KiB
Python
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;",
|
|
)
|