from __future__ import annotations from nicegui import ui from app.pages.common import ( dashboard_page, demo_spot_price, strategy_catalog, strategy_metrics, ) def _cost_benefit_options(metrics: dict) -> dict: return { "tooltip": {"trigger": "axis"}, "xAxis": { "type": "category", "data": [f"${point['price']:.0f}" for point in metrics["scenario_series"]], "name": "GLD spot", }, "yAxis": {"type": "value", "name": "Net benefit / oz"}, "series": [ { "type": "bar", "data": [point["benefit"] for point in metrics["scenario_series"]], "itemStyle": { "color": "#0ea5e9", }, } ], } def _waterfall_options(metrics: dict) -> dict: steps = metrics["waterfall_steps"] running = 0.0 base: list[float] = [] values: list[float] = [] for index, (_, amount) in enumerate(steps): if index == 0: base.append(0) values.append(amount) running = amount elif index == len(steps) - 1: base.append(0) values.append(amount) else: base.append(running) values.append(amount) running += amount return { "tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}}, "xAxis": {"type": "category", "data": [label for label, _ in steps]}, "yAxis": {"type": "value", "name": "USD"}, "series": [ { "type": "bar", "stack": "total", "data": base, "itemStyle": {"color": "rgba(0,0,0,0)"}, }, { "type": "bar", "stack": "total", "data": values, "itemStyle": { "color": "#22c55e", }, }, ], } @ui.page("/hedge") def hedge_page() -> None: strategies = strategy_catalog() strategy_map = {strategy["label"]: strategy["name"] for strategy in strategies} selected = {"strategy": strategies[0]["name"], "label": strategies[0]["label"], "scenario_pct": 0} with dashboard_page( "Hedge Analysis", "Compare hedge structures across scenarios, visualize cost-benefit tradeoffs, and inspect net equity impacts.", "hedge", ): with ui.row().classes("w-full gap-6 max-lg:flex-col"): 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 Controls").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") selector = ui.select(strategy_map, value=selected["label"], label="Strategy selector").classes("w-full") slider_value = ui.label("Scenario move: +0%").classes("text-sm text-slate-500 dark:text-slate-400") slider = ui.slider(min=-25, max=25, value=0, step=5).classes("w-full") ui.label(f"Current spot reference: ${demo_spot_price():,.2f}").classes( "text-sm text-slate-500 dark:text-slate-400" ) summary = ui.card().classes( "w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900" ) charts_row = ui.row().classes("w-full gap-6 max-lg:flex-col") with charts_row: cost_chart = ui.echart( _cost_benefit_options(strategy_metrics(selected["strategy"], selected["scenario_pct"])) ).classes( "h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900" ) waterfall_chart = ui.echart( _waterfall_options(strategy_metrics(selected["strategy"], selected["scenario_pct"])) ).classes( "h-96 w-full rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900" ) def render_summary() -> None: metrics = strategy_metrics(selected["strategy"], selected["scenario_pct"]) strategy = metrics["strategy"] summary.clear() with summary: ui.label("Scenario Summary").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") with ui.grid(columns=2).classes("w-full gap-4 max-sm:grid-cols-1"): cards = [ ("Scenario spot", f"${metrics['scenario_price']:,.2f}"), ("Hedge cost", f"${strategy['estimated_cost']:,.2f}/oz"), ("Unhedged equity", f"${metrics['unhedged_equity']:,.0f}"), ("Hedged equity", f"${metrics['hedged_equity']:,.0f}"), ] for label, value in cards: 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 text-slate-500 dark:text-slate-400") ui.label(value).classes("text-2xl font-bold text-slate-900 dark:text-slate-100") ui.label(strategy["description"]).classes("text-sm text-slate-600 dark:text-slate-300") cost_chart.options.clear() cost_chart.options.update(_cost_benefit_options(metrics)) cost_chart.update() waterfall_chart.options.clear() waterfall_chart.options.update(_waterfall_options(metrics)) waterfall_chart.update() def refresh_from_selector(event) -> None: selected["label"] = str(event.value) selected["strategy"] = strategy_map[selected["label"]] render_summary() def refresh_from_slider(event) -> None: selected["scenario_pct"] = int(event.value) sign = "+" if selected["scenario_pct"] >= 0 else "" slider_value.set_text(f"Scenario move: {sign}{selected['scenario_pct']}%") render_summary() selector.on_value_change(refresh_from_selector) slider.on_value_change(refresh_from_slider) render_summary()