feat(EXEC-001A): add named strategy templates
This commit is contained in:
@@ -6,6 +6,8 @@ from typing import Any
|
||||
|
||||
from nicegui import ui
|
||||
|
||||
from app.services.strategy_templates import StrategyTemplateService
|
||||
|
||||
NAV_ITEMS: list[tuple[str, str, str]] = [
|
||||
("overview", "/", "Overview"),
|
||||
("hedge", "/hedge", "Hedge Analysis"),
|
||||
@@ -38,33 +40,7 @@ def portfolio_snapshot() -> dict[str, float]:
|
||||
|
||||
|
||||
def strategy_catalog() -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"name": "protective_put",
|
||||
"label": "Protective Put",
|
||||
"description": "Full downside protection below the hedge strike with uncapped upside.",
|
||||
"estimated_cost": 6.25,
|
||||
"max_drawdown_floor": 210.0,
|
||||
"coverage": "High",
|
||||
},
|
||||
{
|
||||
"name": "collar",
|
||||
"label": "Collar",
|
||||
"description": "Lower premium by financing puts with covered call upside caps.",
|
||||
"estimated_cost": 2.10,
|
||||
"max_drawdown_floor": 208.0,
|
||||
"upside_cap": 228.0,
|
||||
"coverage": "Balanced",
|
||||
},
|
||||
{
|
||||
"name": "laddered_puts",
|
||||
"label": "Laddered Puts",
|
||||
"description": "Multiple maturities and strikes reduce roll concentration and smooth protection.",
|
||||
"estimated_cost": 4.45,
|
||||
"max_drawdown_floor": 205.0,
|
||||
"coverage": "Layered",
|
||||
},
|
||||
]
|
||||
return StrategyTemplateService().catalog_items()
|
||||
|
||||
|
||||
def quick_recommendations(portfolio: dict[str, Any] | None = None) -> list[dict[str, str]]:
|
||||
@@ -73,7 +49,7 @@ def quick_recommendations(portfolio: dict[str, Any] | None = None) -> list[dict[
|
||||
return [
|
||||
{
|
||||
"title": "Balanced hedge favored",
|
||||
"summary": "A collar keeps the current LTV comfortably below the margin threshold while limiting upfront spend.",
|
||||
"summary": "A 95% protective put balances margin-call protection with a lower upfront hedge cost.",
|
||||
"tone": "positive",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ def _waterfall_options(metrics: dict) -> dict:
|
||||
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}
|
||||
selected = {"strategy": strategies[0]["name"], "label": strategies[0]["label"], "scenario_pct": 0}
|
||||
|
||||
with dashboard_page(
|
||||
"Hedge Analysis",
|
||||
@@ -87,9 +87,7 @@ def hedge_page() -> None:
|
||||
"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"
|
||||
)
|
||||
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(
|
||||
@@ -134,13 +132,16 @@ def hedge_page() -> None:
|
||||
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.options.clear()
|
||||
cost_chart.options.update(_cost_benefit_options(metrics))
|
||||
cost_chart.update()
|
||||
waterfall_chart.options = _waterfall_options(metrics)
|
||||
waterfall_chart.options.clear()
|
||||
waterfall_chart.options.update(_waterfall_options(metrics))
|
||||
waterfall_chart.update()
|
||||
|
||||
def refresh_from_selector(event) -> None:
|
||||
selected["strategy"] = event.value
|
||||
selected["label"] = str(event.value)
|
||||
selected["strategy"] = strategy_map[selected["label"]]
|
||||
render_summary()
|
||||
|
||||
def refresh_from_slider(event) -> None:
|
||||
|
||||
Reference in New Issue
Block a user