from __future__ import annotations import pytest import app.core.pricing.black_scholes as black_scholes from app.strategies.base import StrategyConfig from app.strategies.laddered_put import LadderedPutStrategy, LadderSpec from app.strategies.protective_put import ProtectivePutSpec, ProtectivePutStrategy def _force_analytic_pricing(monkeypatch: pytest.MonkeyPatch) -> None: """Use deterministic analytical pricing for stable expected values.""" monkeypatch.setattr(black_scholes, "ql", None) def test_protective_put_costs( monkeypatch: pytest.MonkeyPatch, sample_strategy_config: StrategyConfig, ) -> None: _force_analytic_pricing(monkeypatch) strategy = ProtectivePutStrategy( sample_strategy_config, ProtectivePutSpec(label="ATM", strike_pct=1.0, months=12), ) cost = strategy.calculate_cost() assert cost["strategy"] == "protective_put_atm" assert cost["label"] == "ATM" assert cost["strike"] == 460.0 assert cost["premium_per_share"] == pytest.approx(19.6894, abs=1e-4) # Total cost uses corrected GLD backing (contract_count * contract_size * premium) assert cost["total_cost"] == pytest.approx(42913.36, abs=1e-2) assert cost["cost_pct_of_portfolio"] == pytest.approx(0.042913, abs=1e-6) assert cost["annualized_cost"] == pytest.approx(42913.36, abs=1e-2) assert cost["annualized_cost_pct"] == pytest.approx(0.042913, abs=1e-6) def test_laddered_strategy(sample_strategy_config: StrategyConfig, monkeypatch: pytest.MonkeyPatch) -> None: _force_analytic_pricing(monkeypatch) strategy = LadderedPutStrategy( sample_strategy_config, LadderSpec( label="50_50_ATM_OTM95", weights=(0.5, 0.5), strike_pcts=(1.0, 0.95), months=12, ), ) cost = strategy.calculate_cost() protection = strategy.calculate_protection() assert cost["strategy"] == "laddered_put_50_50_atm_otm95" assert len(cost["legs"]) == 2 assert cost["legs"][0]["weight"] == 0.5 assert cost["legs"][0]["strike"] == 460.0 assert cost["legs"][1]["strike"] == 437.0 # Costs updated to reflect corrected GLD backing assert cost["blended_cost"] == pytest.approx(34288.79, abs=1e-2) assert cost["cost_pct_of_portfolio"] == pytest.approx(0.034289, abs=1e-6) # Floor value uses notional_units (corrected backing) assert protection["portfolio_floor_value"] == pytest.approx(977510.63, rel=1e-6) assert protection["payoff_at_threshold"] == pytest.approx(175450.63, abs=1e-2) assert protection["hedged_ltv_at_threshold"] == pytest.approx(0.615100, rel=1e-6) assert protection["maintains_margin_call_buffer"] is True def test_scenario_analysis( monkeypatch: pytest.MonkeyPatch, sample_strategy_config: StrategyConfig, ) -> None: _force_analytic_pricing(monkeypatch) protective = ProtectivePutStrategy( sample_strategy_config, ProtectivePutSpec(label="ATM", strike_pct=1.0, months=12), ) ladder = LadderedPutStrategy( sample_strategy_config, LadderSpec( label="50_50_ATM_OTM95", weights=(0.5, 0.5), strike_pcts=(1.0, 0.95), months=12, ), ) protective_scenarios = protective.get_scenarios() ladder_scenarios = ladder.get_scenarios() assert len(protective_scenarios) == 12 assert len(ladder_scenarios) == 12 first_protective = protective_scenarios[0] assert first_protective["price_change_pct"] == -0.6 assert first_protective["gld_price"] == 184.0 # Option payoff uses corrected contract count and notional assert first_protective["option_payoff"] == pytest.approx(601545.00, abs=1e-2) assert first_protective["hedge_cost"] == pytest.approx(42913.36, abs=1e-2) assert first_protective["hedged_ltv"] == pytest.approx(0.599074, rel=1e-6) assert first_protective["margin_call_with_hedge"] is False first_ladder = ladder_scenarios[0] assert first_ladder["gld_price"] == 184.0 assert first_ladder["option_payoff"] == pytest.approx(576480.63, abs=1e-2) assert first_ladder["hedge_cost"] == pytest.approx(34288.79, abs=1e-2) assert first_ladder["hedged_ltv"] == pytest.approx(0.614452, rel=1e-6) worst_ladder = ladder_scenarios[-1] assert worst_ladder["gld_price"] == 690.0 assert worst_ladder["hedged_ltv"] == pytest.approx(0.4, rel=1e-12) assert worst_ladder["margin_call_with_hedge"] is False