"""Tests for hedge contract count calculation using true GLD backing.""" from __future__ import annotations import math from datetime import date import pytest from app.domain.instruments import gld_ounces_per_share from app.models.portfolio import LombardPortfolio from app.strategies.base import StrategyConfig from app.strategies.protective_put import ProtectivePutSpec, ProtectivePutStrategy class TestGLDBacking: """Test GLD backing calculation.""" def test_gld_backing_2026_is_approx_0_0919(self) -> None: """GLD backing in 2026 should be ~0.0919 oz/share (8.1% decay from 0.10).""" backing = gld_ounces_per_share(date(2026, 1, 1)) assert 0.0915 <= float(backing) <= 0.0925 def test_gld_backing_decays_over_time(self) -> None: """GLD backing should decay as years pass.""" backing_2004 = gld_ounces_per_share(date(2004, 11, 18)) # GLD launch date backing_2026 = gld_ounces_per_share(date(2026, 1, 1)) assert float(backing_2004) == 0.10 # At launch, exactly 0.10 oz/share assert float(backing_2026) < float(backing_2004) class TestContractCountCalculation: """Test contract count formula uses corrected GLD backing.""" @pytest.fixture def sample_portfolio(self) -> LombardPortfolio: return LombardPortfolio( gold_ounces=919.0, gold_price_per_ounce=2300.0, loan_amount=1500000.0, initial_ltv=0.71, margin_call_ltv=0.75, ) @pytest.fixture def strategy_config(self, sample_portfolio: LombardPortfolio) -> StrategyConfig: return StrategyConfig( portfolio=sample_portfolio, spot_price=2300.0, volatility=0.16, risk_free_rate=0.045, ) def test_contract_count_uses_gld_backing_not_naive_10_to_1(self, strategy_config: StrategyConfig) -> None: """Contract count should use gld_ounces_per_share(), not naive 10:1 ratio.""" strategy = ProtectivePutStrategy( strategy_config, ProtectivePutSpec(label="ATM", strike_pct=1.0, months=12), ) # At backing ~0.091576: 919 / (100 * 0.091576) = 100.35... → ceil = 101 # Naive 10:1 would give: ceil(919 / 10) = 92 contracts (WRONG) naive_count = math.ceil(919.0 / 10) assert strategy.contract_count != naive_count, "Should not use naive 10:1 ratio" # Verify formula: ceil(gold_ounces / (100 * backing)) expected = math.ceil(919.0 / (100 * strategy.gld_backing)) assert strategy.contract_count == expected def test_contract_count_rounds_up(self, strategy_config: StrategyConfig) -> None: """Contract count should round up to ensure full coverage.""" strategy = ProtectivePutStrategy( strategy_config, ProtectivePutSpec(label="ATM", strike_pct=1.0, months=12), ) # Verify rounding behavior assert strategy.contract_count == math.ceil( strategy_config.portfolio.gold_ounces / (100 * strategy.gld_backing) ) def test_contract_notional_equals_gold_ounces(self, strategy_config: StrategyConfig) -> None: """Contract notional (quantity * contract_size) should cover portfolio gold ounces.""" strategy = ProtectivePutStrategy( strategy_config, ProtectivePutSpec(label="ATM", strike_pct=1.0, months=12), ) contract = strategy.build_contract() # notional_units = quantity * contract_size notional = contract.notional_units # Should be >= gold_ounces (may slightly over-hedge due to rounding) assert notional >= strategy.hedge_units # But not excessively over-hedged (within one contract) max_overhedge = 100 * strategy.gld_backing assert notional - strategy.hedge_units < max_overhedge class TestHedgeCostWithCorrectedBacking: """Test hedge cost calculations use corrected backing.""" @pytest.fixture def portfolio(self) -> LombardPortfolio: return LombardPortfolio( gold_ounces=919.0, gold_price_per_ounce=2300.0, loan_amount=1500000.0, initial_ltv=0.71, margin_call_ltv=0.75, ) @pytest.fixture def config(self, portfolio: LombardPortfolio) -> StrategyConfig: return StrategyConfig( portfolio=portfolio, spot_price=2300.0, volatility=0.16, risk_free_rate=0.045, ) def test_total_cost_scales_with_corrected_contract_count(self, config: StrategyConfig) -> None: """Total hedge cost should reflect corrected contract count.""" strategy = ProtectivePutStrategy( config, ProtectivePutSpec(label="ATM", strike_pct=1.0, months=12), ) cost_info = strategy.calculate_cost() # Total cost should be premium * notional_units contract = strategy.build_contract() assert cost_info["total_cost"] > 0 assert abs(contract.total_premium - cost_info["total_cost"]) < 0.01