from __future__ import annotations from typing import Any from nicegui import ui class StrategyComparisonPanel: """Interactive strategy comparison with scenario slider and cost-benefit table.""" def __init__( self, strategies: list[dict[str, Any]] | None = None, *, current_spot: float = 100.0, ) -> None: self.strategies = strategies or [] self.current_spot = current_spot self.price_change_pct = 0 self.strategy_cards: list[ui.html] = [] with ui.card().classes( "w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900" ): ui.label("Strategy Comparison").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") with ui.row().classes("w-full items-center justify-between gap-4 max-sm:flex-col max-sm:items-start"): self.slider_label = ui.label(self._slider_text()).classes("text-sm text-slate-500 dark:text-slate-400") self.scenario_spot = ui.label(self._scenario_spot_text()).classes( "rounded-full bg-sky-100 px-3 py-1 text-sm font-medium text-sky-700 dark:bg-sky-500/15 dark:text-sky-300" ) ui.slider(min=-50, max=50, value=0, step=5, on_change=self._on_slider_change).classes("w-full") with ui.row().classes("w-full gap-4 max-lg:flex-col"): self.cards_container = ui.row().classes("w-full gap-4 max-lg:flex-col") ui.separator().classes("my-2") ui.label("Cost / Benefit Summary").classes( "text-sm font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400" ) self.table_html = ui.html("").classes("w-full") self.set_strategies(self.strategies, current_spot=current_spot) def _on_slider_change(self, event: Any) -> None: self.price_change_pct = int(event.value) self.slider_label.set_text(self._slider_text()) self.scenario_spot.set_text(self._scenario_spot_text()) self._render() def set_strategies(self, strategies: list[dict[str, Any]], *, current_spot: float | None = None) -> None: self.strategies = strategies if current_spot is not None: self.current_spot = current_spot self._render() def _render(self) -> None: self.cards_container.clear() self.strategy_cards.clear() with self.cards_container: for strategy in self.strategies: self.strategy_cards.append(ui.html(self._strategy_card_html(strategy)).classes("w-full")) self.table_html.content = self._table_html() self.table_html.update() def _scenario_spot(self) -> float: return self.current_spot * (1 + self.price_change_pct / 100) def _slider_text(self) -> str: sign = "+" if self.price_change_pct > 0 else "" return f"Scenario slider: {sign}{self.price_change_pct}% gold price change" def _scenario_spot_text(self) -> str: return f"Scenario spot: ${self._scenario_spot():,.2f}" def _strategy_card_html(self, strategy: dict[str, Any]) -> str: name = str(strategy.get("name", "strategy")).replace("_", " ").title() description = strategy.get("description", "") cost = float(strategy.get("estimated_cost", 0.0)) payoff = self._scenario_benefit(strategy) payoff_class = "text-emerald-600 dark:text-emerald-400" if payoff >= 0 else "text-rose-600 dark:text-rose-400" return f"""
{name}
{description}
Live Scenario
Est. Cost
${cost:,.2f}
Scenario Benefit
${payoff:,.2f}
""" def _table_html(self) -> str: rows = [] for strategy in self.strategies: name = str(strategy.get("name", "strategy")).replace("_", " ").title() cost = float(strategy.get("estimated_cost", 0.0)) floor = strategy.get("max_drawdown_floor", "—") cap = strategy.get("upside_cap", "—") scenario = self._scenario_benefit(strategy) scenario_class = ( "text-emerald-600 dark:text-emerald-400" if scenario >= 0 else "text-rose-600 dark:text-rose-400" ) rows.append(f""" {name} ${cost:,.2f} {self._fmt_optional_money(floor)} {self._fmt_optional_money(cap)} ${scenario:,.2f} """) return f"""
{''.join(rows) if rows else self._empty_row()}
Strategy Estimated Cost Protection Floor Upside Cap Scenario Benefit
""" def _scenario_benefit(self, strategy: dict[str, Any]) -> float: scenario_spot = self._scenario_spot() cost = float(strategy.get("estimated_cost", 0.0)) floor = strategy.get("max_drawdown_floor") cap = strategy.get("upside_cap") benefit = -cost if isinstance(floor, (int, float)) and scenario_spot < float(floor): benefit += float(floor) - scenario_spot if isinstance(cap, (int, float)) and scenario_spot > float(cap): benefit -= scenario_spot - float(cap) return benefit @staticmethod def _fmt_optional_money(value: Any) -> str: if isinstance(value, (int, float)): return f"${float(value):,.2f}" return "—" @staticmethod def _empty_row() -> str: return ( '' "No strategies loaded" "" )