Files
vault-dash/app/pages/hedge.py
Bu5hm4nn 7dc5b3d734 Fix type hints and dependency issues for CI
- Add -r requirements.txt to requirements-dev.txt
- Fix mypy errors:
  - Remove slots=True from Settings dataclass
  - Add explicit list[float] type annotations in hedge.py
  - Add type ignore comments for optional QuantLib imports
  - Use Sequence instead of list in GreeksTable for covariance
  - Fix dict type annotation in options.py
  - Add type ignore for nicegui attr-defined errors
- Disable attr-defined error code in mypy config
2026-03-22 10:36:58 +01:00

155 lines
6.1 KiB
Python

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"], "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["strategy"], 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 = _cost_benefit_options(metrics)
cost_chart.update()
waterfall_chart.options = _waterfall_options(metrics)
waterfall_chart.update()
def refresh_from_selector(event) -> None:
selected["strategy"] = event.value
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()