feat: prioritize lazy options loading and live overview wiring
- queue OPS-001 Caddy route for vd1.uncloud.vpn - lazy-load options expirations/chains per expiry - wire overview to live quote data and persisted portfolio config - extend browser test to verify live quote metadata
This commit is contained in:
@@ -1,48 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from nicegui import ui
|
||||
|
||||
from app.components import PortfolioOverview
|
||||
from app.pages.common import (
|
||||
dashboard_page,
|
||||
portfolio_snapshot,
|
||||
quick_recommendations,
|
||||
recommendation_style,
|
||||
strategy_catalog,
|
||||
)
|
||||
from app.models.portfolio import PortfolioConfig, get_portfolio_repository
|
||||
from app.pages.common import dashboard_page, quick_recommendations, recommendation_style, strategy_catalog
|
||||
from app.services.runtime import get_data_service
|
||||
|
||||
_REFERENCE_SPOT_PRICE = 215.0
|
||||
_DEFAULT_CASH_BUFFER = 18_500.0
|
||||
|
||||
|
||||
def _format_timestamp(value: str | None) -> str:
|
||||
if not value:
|
||||
return "Unavailable"
|
||||
try:
|
||||
timestamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
return value
|
||||
return timestamp.astimezone(UTC).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
|
||||
|
||||
def _build_live_portfolio(config: PortfolioConfig, quote: dict[str, object]) -> dict[str, float | str]:
|
||||
spot_price = float(quote.get("price", _REFERENCE_SPOT_PRICE))
|
||||
configured_gold_value = float(config.gold_value)
|
||||
estimated_units = configured_gold_value / _REFERENCE_SPOT_PRICE if _REFERENCE_SPOT_PRICE > 0 else 0.0
|
||||
live_gold_value = estimated_units * spot_price
|
||||
loan_amount = float(config.loan_amount)
|
||||
margin_call_ltv = float(config.margin_threshold)
|
||||
ltv_ratio = loan_amount / live_gold_value if live_gold_value > 0 else 0.0
|
||||
|
||||
return {
|
||||
"spot_price": spot_price,
|
||||
"gold_units": estimated_units,
|
||||
"gold_value": live_gold_value,
|
||||
"loan_amount": loan_amount,
|
||||
"ltv_ratio": ltv_ratio,
|
||||
"net_equity": live_gold_value - loan_amount,
|
||||
"margin_call_ltv": margin_call_ltv,
|
||||
"margin_call_price": loan_amount / (margin_call_ltv * estimated_units) if estimated_units > 0 else 0.0,
|
||||
"cash_buffer": max(live_gold_value - configured_gold_value, 0.0) + _DEFAULT_CASH_BUFFER,
|
||||
"hedge_budget": float(config.monthly_budget),
|
||||
"quote_source": str(quote.get("source", "unknown")),
|
||||
"quote_updated_at": str(quote.get("updated_at", "")),
|
||||
}
|
||||
|
||||
|
||||
@ui.page("/")
|
||||
@ui.page("/overview")
|
||||
def overview_page() -> None:
|
||||
portfolio = portfolio_snapshot()
|
||||
async def overview_page() -> None:
|
||||
config = get_portfolio_repository().load()
|
||||
data_service = get_data_service()
|
||||
symbol = data_service.default_symbol
|
||||
quote = await data_service.get_quote(symbol)
|
||||
portfolio = _build_live_portfolio(config, quote)
|
||||
quote_status = (
|
||||
f"Live quote source: {portfolio['quote_source']} · "
|
||||
f"Last updated {_format_timestamp(str(portfolio['quote_updated_at']))}"
|
||||
)
|
||||
|
||||
with dashboard_page(
|
||||
"Overview",
|
||||
"Portfolio health, LTV risk, and quick strategy guidance for the current GLD-backed loan.",
|
||||
"overview",
|
||||
):
|
||||
with ui.row().classes("w-full items-center justify-between gap-4 max-md:flex-col max-md:items-start"):
|
||||
ui.label(quote_status).classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
ui.label(
|
||||
f"Configured collateral baseline: ${config.gold_value:,.0f} · Loan ${config.loan_amount:,.0f}"
|
||||
).classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
|
||||
with ui.grid(columns=4).classes("w-full gap-4 max-lg:grid-cols-2 max-sm:grid-cols-1"):
|
||||
summary_cards = [
|
||||
(
|
||||
"Spot Price",
|
||||
f"${portfolio['spot_price']:,.2f}",
|
||||
"GLD reference price",
|
||||
f"{symbol} live quote via {portfolio['quote_source']}",
|
||||
),
|
||||
(
|
||||
"Margin Call Price",
|
||||
f"${portfolio['margin_call_price']:,.2f}",
|
||||
"Implied trigger level",
|
||||
"Implied trigger level from persisted portfolio settings",
|
||||
),
|
||||
(
|
||||
"Cash Buffer",
|
||||
f"${portfolio['cash_buffer']:,.0f}",
|
||||
"Available liquidity",
|
||||
"Base liquidity plus unrealized gain cushion vs configured baseline",
|
||||
),
|
||||
(
|
||||
"Hedge Budget",
|
||||
f"${portfolio['hedge_budget']:,.0f}",
|
||||
"Approved premium budget",
|
||||
"Monthly budget from saved settings",
|
||||
),
|
||||
]
|
||||
for title, value, caption in summary_cards:
|
||||
@@ -53,7 +103,7 @@ def overview_page() -> None:
|
||||
ui.label(value).classes("text-3xl font-bold text-slate-900 dark:text-slate-50")
|
||||
ui.label(caption).classes("text-sm text-slate-500 dark:text-slate-400")
|
||||
|
||||
portfolio_view = PortfolioOverview(margin_call_ltv=portfolio["margin_call_ltv"])
|
||||
portfolio_view = PortfolioOverview(margin_call_ltv=float(portfolio["margin_call_ltv"]))
|
||||
portfolio_view.update(portfolio)
|
||||
|
||||
with ui.row().classes("w-full gap-6 max-lg:flex-col"):
|
||||
@@ -62,15 +112,15 @@ def overview_page() -> None:
|
||||
):
|
||||
with ui.row().classes("w-full items-center justify-between"):
|
||||
ui.label("Current LTV Status").classes("text-lg font-semibold text-slate-900 dark:text-slate-100")
|
||||
ui.label(f"Threshold {portfolio['margin_call_ltv'] * 100:.0f}%").classes(
|
||||
ui.label(f"Threshold {float(portfolio['margin_call_ltv']) * 100:.0f}%").classes(
|
||||
"rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700 dark:bg-rose-500/15 dark:text-rose-300"
|
||||
)
|
||||
ui.linear_progress(
|
||||
value=portfolio["ltv_ratio"] / portfolio["margin_call_ltv"],
|
||||
value=float(portfolio["ltv_ratio"]) / max(float(portfolio["margin_call_ltv"]), 0.01),
|
||||
show_value=False,
|
||||
).props("color=warning track-color=grey-3 rounded")
|
||||
ui.label(
|
||||
f"Current LTV is {portfolio['ltv_ratio'] * 100:.1f}% with a margin buffer of {(portfolio['margin_call_ltv'] - portfolio['ltv_ratio']) * 100:.1f} percentage points."
|
||||
f"Current LTV is {float(portfolio['ltv_ratio']) * 100:.1f}% with a margin buffer of {(float(portfolio['margin_call_ltv']) - float(portfolio['ltv_ratio'])) * 100:.1f} percentage points."
|
||||
).classes("text-sm text-slate-600 dark:text-slate-300")
|
||||
ui.label(
|
||||
"Warning: if GLD approaches the margin-call price, collateral remediation or hedge monetization will be required."
|
||||
@@ -93,7 +143,7 @@ def overview_page() -> None:
|
||||
|
||||
ui.label("Quick Strategy Recommendations").classes("text-xl font-semibold text-slate-900 dark:text-slate-100")
|
||||
with ui.grid(columns=3).classes("w-full gap-4 max-lg:grid-cols-1"):
|
||||
for rec in quick_recommendations():
|
||||
for rec in quick_recommendations(portfolio):
|
||||
with ui.card().classes(f"rounded-2xl border shadow-sm {recommendation_style(rec['tone'])}"):
|
||||
ui.label(rec["title"]).classes("text-base font-semibold text-slate-900 dark:text-slate-100")
|
||||
ui.label(rec["summary"]).classes("text-sm text-slate-600 dark:text-slate-300")
|
||||
|
||||
Reference in New Issue
Block a user