feat(PRICING-001): add GLD expense ratio decay correction
This commit is contained in:
@@ -1,42 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from app.domain.backtesting_math import AssetQuantity, PricePerAsset
|
||||
from app.domain.instruments import (
|
||||
GLD_EXPENSE_DECAY_RATE,
|
||||
GLD_INITIAL_OUNCES_PER_SHARE,
|
||||
GLD_LAUNCH_YEAR,
|
||||
asset_quantity_from_weight,
|
||||
gld_ounces_per_share,
|
||||
instrument_metadata,
|
||||
price_per_weight_from_asset_price,
|
||||
weight_from_asset_quantity,
|
||||
)
|
||||
from app.domain.units import BaseCurrency, Weight, WeightUnit
|
||||
|
||||
|
||||
def test_gld_ounces_per_share_decay_formula_matches_research() -> None:
|
||||
"""Verify decay formula matches research examples from docs/GLD_BASIS_RESEARCH.md."""
|
||||
# Launch (2004): should be exactly 0.10 oz/share
|
||||
launch_backing = gld_ounces_per_share(date(2004, 1, 1))
|
||||
assert launch_backing == GLD_INITIAL_OUNCES_PER_SHARE
|
||||
assert launch_backing == Decimal("0.10")
|
||||
|
||||
# 2026: should be ~0.0916 oz/share (8.4% decay from 22 years)
|
||||
# Formula: 0.10 * e^(-0.004 * 22) = 0.10 * e^(-0.088) ≈ 0.091576
|
||||
years_2026 = 2026 - GLD_LAUNCH_YEAR # 22 years
|
||||
expected_2026_decay = Decimal("0.10") * Decimal(str(__import__("math").exp(-0.004 * years_2026)))
|
||||
actual_2026 = gld_ounces_per_share(date(2026, 1, 1))
|
||||
|
||||
# Check 2026 backing is approximately 0.0916 (within rounding tolerance)
|
||||
assert abs(float(actual_2026) - 0.0916) < 0.0001
|
||||
assert actual_2026 == expected_2026_decay
|
||||
|
||||
|
||||
def test_gld_ounces_per_share_uses_current_year_by_default() -> None:
|
||||
"""Verify default behavior uses today's date."""
|
||||
current_backing = gld_ounces_per_share()
|
||||
current_year = date.today().year
|
||||
expected_backing = gld_ounces_per_share(date(current_year, 1, 1))
|
||||
|
||||
# Should match the current year's calculation
|
||||
assert current_backing == expected_backing
|
||||
|
||||
|
||||
def test_gld_decay_rate_is_correct() -> None:
|
||||
"""Verify the decay rate constant is 0.4% annually."""
|
||||
assert GLD_EXPENSE_DECAY_RATE == Decimal("0.004")
|
||||
|
||||
|
||||
def test_gld_share_quantity_converts_to_troy_ounce_weight() -> None:
|
||||
"""GLD shares convert to weight using expense-adjusted backing (~0.0919 oz/share in 2026)."""
|
||||
quantity = AssetQuantity(amount=Decimal("10"), symbol="GLD")
|
||||
current_backing = gld_ounces_per_share()
|
||||
|
||||
weight = weight_from_asset_quantity(quantity)
|
||||
|
||||
assert weight == Weight(amount=Decimal("1.0"), unit=WeightUnit.OUNCE_TROY)
|
||||
expected_weight = current_backing * Decimal("10")
|
||||
assert weight == Weight(amount=expected_weight, unit=WeightUnit.OUNCE_TROY)
|
||||
# Verify it's NOT the old 1.0 oz (which would be wrong)
|
||||
assert weight != Weight(amount=Decimal("1.0"), unit=WeightUnit.OUNCE_TROY)
|
||||
|
||||
|
||||
def test_gld_troy_ounce_weight_converts_to_share_quantity() -> None:
|
||||
"""Convert 1 troy ounce to GLD shares using expense-adjusted backing."""
|
||||
# 1 oz should require more than 10 shares now (since each share backs <0.1 oz)
|
||||
weight = Weight(amount=Decimal("1"), unit=WeightUnit.OUNCE_TROY)
|
||||
current_backing = gld_ounces_per_share()
|
||||
|
||||
quantity = asset_quantity_from_weight("GLD", weight)
|
||||
|
||||
assert quantity == AssetQuantity(amount=Decimal("10"), symbol="GLD")
|
||||
expected_shares = Decimal("1") / current_backing
|
||||
assert quantity == AssetQuantity(amount=expected_shares, symbol="GLD")
|
||||
# Should be more than 10 shares (approximately 10.87 in 2026)
|
||||
assert quantity.amount > Decimal("10")
|
||||
|
||||
|
||||
def test_gld_share_quote_converts_to_ounce_equivalent_spot() -> None:
|
||||
quote = PricePerAsset(amount=Decimal("404.19"), currency=BaseCurrency.USD, symbol="GLD")
|
||||
"""GLD price converts to gold spot using expense-adjusted backing."""
|
||||
# At ~$423/GLD share with ~0.0919 oz backing, spot should be ~$4600/oz
|
||||
quote = PricePerAsset(amount=Decimal("422.73"), currency=BaseCurrency.USD, symbol="GLD")
|
||||
current_backing = gld_ounces_per_share()
|
||||
|
||||
spot = price_per_weight_from_asset_price(quote, per_unit=WeightUnit.OUNCE_TROY)
|
||||
|
||||
assert spot.amount == Decimal("4041.9")
|
||||
expected_spot = quote.amount / current_backing
|
||||
assert spot.amount == expected_spot
|
||||
assert spot.currency is BaseCurrency.USD
|
||||
assert spot.per_unit is WeightUnit.OUNCE_TROY
|
||||
# Spot should be higher than naive 10:1 conversion ($4227.3)
|
||||
assert spot.amount > Decimal("4227.3")
|
||||
|
||||
|
||||
def test_gld_metadata_uses_expense_adjusted_backing() -> None:
|
||||
"""Verify GLD metadata uses the dynamic expense-adjusted backing."""
|
||||
gld_meta = instrument_metadata("GLD")
|
||||
|
||||
expected_backing = gld_ounces_per_share()
|
||||
assert gld_meta.weight_per_share.amount == expected_backing
|
||||
assert gld_meta.weight_per_share.unit is WeightUnit.OUNCE_TROY
|
||||
# Verify it's not the old hardcoded 0.1
|
||||
assert gld_meta.weight_per_share.amount != Decimal("0.1")
|
||||
|
||||
|
||||
def test_instrument_conversions_fail_closed_for_unsupported_symbols() -> None:
|
||||
|
||||
Reference in New Issue
Block a user