Files
vault-dash/tests/test_hedge_contract_count.py

133 lines
4.9 KiB
Python

"""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, 1, 1))
backing_2026 = gld_ounces_per_share(date(2026, 1, 1))
assert float(backing_2004) == 0.10
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