from __future__ import annotations from app.domain.portfolio_math import resolve_portfolio_spot_from_quote from app.models.portfolio import PortfolioConfig from app.pages.common import strategy_metrics from app.pages.hedge import _cost_benefit_options, _waterfall_options def test_protective_put_atm_minus_20pct_improves_equity() -> None: metrics = strategy_metrics("protective_put_atm", -20) assert metrics["scenario_price"] == 172.0 assert metrics["unhedged_equity"] == 27_000.0 assert metrics["hedged_equity"] == 63_750.0 assert metrics["hedged_equity"] > metrics["unhedged_equity"] assert metrics["waterfall_steps"] == [ ("Base equity", 70_000.0), ("Spot move", -43_000.0), ("Option payoff", 43_000.0), ("Call cap", 0.0), ("Hedge cost", -6_250.0), ("Net equity", 63_750.0), ] def test_hedge_waterfall_uses_zero_based_contribution_bars() -> None: options = _waterfall_options(strategy_metrics("protective_put_atm", -20)) assert len(options["series"]) == 1 assert options["series"][0]["type"] == "bar" assert options["series"][0]["label"]["show"] is True values = options["series"][0]["data"] assert values[2]["value"] == 43_000.0 assert values[2]["itemStyle"]["color"] == "#22c55e" def test_cost_benefit_chart_shows_positive_downside_benefit_when_puts_are_in_the_money() -> None: metrics = strategy_metrics( "protective_put_atm", -20, portfolio={ **{ "gold_value": 968000.0, "loan_amount": 145000.0, "ltv_ratio": 145000.0 / 968000.0, "net_equity": 823000.0, "spot_price": 4400.0, "gold_units": 220.0, "margin_call_ltv": 0.75, "margin_call_price": 878.79, "cash_buffer": 18500.0, "hedge_budget": 8000.0, } }, ) options = _cost_benefit_options(metrics) assert options["xAxis"]["name"] == "Collateral spot" assert options["yAxis"]["name"] == "Net hedge benefit / oz" assert options["series"][0]["data"][0] > 0 assert options["series"][0]["data"][1] > 0 def test_hedge_quote_resolution_converts_gld_share_price_to_ozt_spot() -> None: """Hedge page should convert GLD share quotes to USD/ozt using expense-adjusted backing.""" from datetime import date from app.domain.instruments import gld_ounces_per_share config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) share_quote = { "symbol": "GLD", "price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-25T00:00:00+00:00", } spot, source, updated_at = resolve_portfolio_spot_from_quote(config, share_quote) # With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71 current_backing = float(gld_ounces_per_share(date.today())) expected_spot = 404.19 / current_backing assert abs(spot - expected_spot) < 0.01 assert source == "yfinance" def test_hedge_quote_resolution_fails_closed_when_quote_unit_missing() -> None: """Hedge page should fall back to configured price when quote_unit is missing.""" config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) legacy_quote = { "symbol": "GLD", "price": 404.19, "source": "cache", } spot, source, _ = resolve_portfolio_spot_from_quote(config, legacy_quote) assert spot == 4400.0 assert source == "configured_entry_price" def test_hedge_quote_resolution_fails_closed_for_unsupported_instrument() -> None: """Hedge page should fall back when instrument metadata is unavailable.""" config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) slv_quote = { "symbol": "SLV", "price": 28.50, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-25T00:00:00+00:00", } spot, source, _ = resolve_portfolio_spot_from_quote(config, slv_quote) assert spot == 4400.0 assert source == "configured_entry_price"