Files
vault-dash/tests/test_hedge_contract_count.py
Bu5hm4nn 2e2a832b31 fix(tests): use GLD launch date in decay test
Use date(2004, 11, 18) instead of date(2004, 1, 1) since GLD didn't
exist before November 18, 2004. The validation now correctly raises
ValueError for pre-launch dates.
2026-03-29 14:47:36 +02:00

133 lines
5.0 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, 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