feat(PRICING-001): add GLD expense ratio decay correction

This commit is contained in:
Bu5hm4nn
2026-03-28 09:04:35 +01:00
parent ff251b5ace
commit 894d88f72f
7 changed files with 208 additions and 26 deletions

View File

@@ -1,11 +1,49 @@
from __future__ import annotations from __future__ import annotations
import math
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date
from decimal import Decimal from decimal import Decimal
from app.domain.backtesting_math import AssetQuantity, PricePerAsset from app.domain.backtesting_math import AssetQuantity, PricePerAsset
from app.domain.units import BaseCurrency, PricePerWeight, Weight, WeightUnit from app.domain.units import BaseCurrency, PricePerWeight, Weight, WeightUnit
# GLD expense ratio decay parameters (from docs/GLD_BASIS_RESEARCH.md)
# Formula: ounces_per_share = 0.10 * e^(-0.004 * years_since_2004)
GLD_INITIAL_OUNCES_PER_SHARE = Decimal("0.10")
GLD_EXPENSE_DECAY_RATE = Decimal("0.004") # 0.4% annual decay
GLD_LAUNCH_YEAR = 2004
def gld_ounces_per_share(reference_date: date | None = None) -> Decimal:
"""
Calculate GLD's current gold backing per share based on expense ratio decay.
GLD's expense ratio (0.40% annually) causes the gold backing per share to
decay exponentially from the initial 0.10 oz/share at launch (2004).
Formula: ounces_per_share = 0.10 * e^(-0.004 * years_since_2004)
Args:
reference_date: Date to calculate backing for. Defaults to today.
Returns:
Decimal representing troy ounces of gold backing per GLD share.
Examples:
>>> # 2026 backing should be ~0.0919 oz/share (8.1% decay)
>>> from datetime import date
>>> result = gld_ounces_per_share(date(2026, 1, 1))
>>> float(result) # doctest: +SKIP
0.0919...
"""
if reference_date is None:
reference_date = date.today()
years_since_launch = Decimal(reference_date.year - GLD_LAUNCH_YEAR)
decay_factor = Decimal(str(math.exp(-float(GLD_EXPENSE_DECAY_RATE * years_since_launch))))
return GLD_INITIAL_OUNCES_PER_SHARE * decay_factor
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class InstrumentMetadata: class InstrumentMetadata:
@@ -64,7 +102,7 @@ class InstrumentMetadata:
_GLD = InstrumentMetadata( _GLD = InstrumentMetadata(
symbol="GLD", symbol="GLD",
quote_currency=BaseCurrency.USD, quote_currency=BaseCurrency.USD,
weight_per_share=Weight(amount=Decimal("0.1"), unit=WeightUnit.OUNCE_TROY), weight_per_share=Weight(amount=gld_ounces_per_share(), unit=WeightUnit.OUNCE_TROY),
) )
_INSTRUMENTS: dict[str, InstrumentMetadata] = { _INSTRUMENTS: dict[str, InstrumentMetadata] = {

View File

@@ -50,6 +50,11 @@ def test_asset_quantity_from_floats_matches_workspace_backtest_conversion() -> N
def test_asset_quantity_from_workspace_config_uses_instrument_weight_conversion_for_gld() -> None: def test_asset_quantity_from_workspace_config_uses_instrument_weight_conversion_for_gld() -> None:
"""GLD shares are calculated using expense-adjusted backing (~0.0916 oz/share in 2026)."""
from datetime import date
from app.domain.instruments import gld_ounces_per_share
config = PortfolioConfig( config = PortfolioConfig(
gold_ounces=220.0, gold_ounces=220.0,
entry_price=4400.0, entry_price=4400.0,
@@ -60,7 +65,12 @@ def test_asset_quantity_from_workspace_config_uses_instrument_weight_conversion_
quantity = asset_quantity_from_workspace_config(config, entry_spot=100.0, symbol="GLD") quantity = asset_quantity_from_workspace_config(config, entry_spot=100.0, symbol="GLD")
assert quantity == 2200.0 # 220 oz / 0.091576... oz/share ≈ 2402.37 shares (NOT 2200 with old 0.1 ratio)
current_backing = float(gld_ounces_per_share(date.today()))
expected_shares = 220.0 / current_backing
assert abs(quantity - expected_shares) < 0.0001
# Verify it's more than the old 2200 shares
assert quantity > 2200.0
def test_materialize_backtest_portfolio_state_uses_typed_asset_boundary() -> None: def test_materialize_backtest_portfolio_state_uses_typed_asset_boundary() -> None:

View File

@@ -62,7 +62,11 @@ def test_cost_benefit_chart_shows_positive_downside_benefit_when_puts_are_in_the
def test_hedge_quote_resolution_converts_gld_share_price_to_ozt_spot() -> None: def test_hedge_quote_resolution_converts_gld_share_price_to_ozt_spot() -> None:
"""Hedge page should convert GLD share quotes to USD/ozt for display.""" """Hedge page should convert GLD share quotes to USD/ozt using expense-adjusted backing."""
from datetime import date
from app.domain.instruments import gld_ounces_per_share
config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0)
share_quote = { share_quote = {
"symbol": "GLD", "symbol": "GLD",
@@ -74,7 +78,10 @@ def test_hedge_quote_resolution_converts_gld_share_price_to_ozt_spot() -> None:
spot, source, updated_at = resolve_portfolio_spot_from_quote(config, share_quote) spot, source, updated_at = resolve_portfolio_spot_from_quote(config, share_quote)
assert spot == 4041.9 # With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert abs(spot - expected_spot) < 0.01
assert source == "yfinance" assert source == "yfinance"

View File

@@ -1,42 +1,109 @@
from __future__ import annotations from __future__ import annotations
from datetime import date
from decimal import Decimal from decimal import Decimal
import pytest import pytest
from app.domain.backtesting_math import AssetQuantity, PricePerAsset from app.domain.backtesting_math import AssetQuantity, PricePerAsset
from app.domain.instruments import ( from app.domain.instruments import (
GLD_EXPENSE_DECAY_RATE,
GLD_INITIAL_OUNCES_PER_SHARE,
GLD_LAUNCH_YEAR,
asset_quantity_from_weight, asset_quantity_from_weight,
gld_ounces_per_share,
instrument_metadata,
price_per_weight_from_asset_price, price_per_weight_from_asset_price,
weight_from_asset_quantity, weight_from_asset_quantity,
) )
from app.domain.units import BaseCurrency, Weight, WeightUnit 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: 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") quantity = AssetQuantity(amount=Decimal("10"), symbol="GLD")
current_backing = gld_ounces_per_share()
weight = weight_from_asset_quantity(quantity) 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: 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) weight = Weight(amount=Decimal("1"), unit=WeightUnit.OUNCE_TROY)
current_backing = gld_ounces_per_share()
quantity = asset_quantity_from_weight("GLD", weight) 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: 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) 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.currency is BaseCurrency.USD
assert spot.per_unit is WeightUnit.OUNCE_TROY 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: def test_instrument_conversions_fail_closed_for_unsupported_symbols() -> None:

View File

@@ -1,11 +1,18 @@
from __future__ import annotations from __future__ import annotations
import pytest
from app.domain.portfolio_math import resolve_portfolio_spot_from_quote from app.domain.portfolio_math import resolve_portfolio_spot_from_quote
from app.models.portfolio import PortfolioConfig from app.models.portfolio import PortfolioConfig
from app.services.alerts import build_portfolio_alert_context from app.services.alerts import build_portfolio_alert_context
def test_overview_converts_gld_share_quote_to_ounce_equivalent_spot() -> None: def test_overview_converts_gld_share_quote_to_ounce_equivalent_spot() -> None:
"""Overview page converts GLD share quotes using expense-adjusted backing."""
from datetime import date
from app.domain.instruments import gld_ounces_per_share
config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0)
spot_price, source, updated_at = resolve_portfolio_spot_from_quote( spot_price, source, updated_at = resolve_portfolio_spot_from_quote(
@@ -25,11 +32,16 @@ def test_overview_converts_gld_share_quote_to_ounce_equivalent_spot() -> None:
updated_at=updated_at, updated_at=updated_at,
) )
assert spot_price == 4041.9 # With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert abs(spot_price - expected_spot) < 0.01
assert source == "yfinance" assert source == "yfinance"
assert updated_at == "2026-03-24T00:00:00+00:00" assert updated_at == "2026-03-24T00:00:00+00:00"
assert portfolio["gold_value"] == 889218.0 # Gold value = 220 oz * spot_price
assert portfolio["net_equity"] == 744218.0 expected_gold_value = 220.0 * spot_price
assert abs(portfolio["gold_value"] - expected_gold_value) < 0.01
assert portfolio["net_equity"] == pytest.approx(expected_gold_value - 145000.0, abs=0.01)
assert round(float(portfolio["margin_call_price"]), 2) == 878.79 assert round(float(portfolio["margin_call_price"]), 2) == 878.79
@@ -53,6 +65,11 @@ def test_overview_fails_closed_to_configured_entry_price_for_unsupported_quote_s
def test_overview_uses_fallback_symbol_when_quote_payload_omits_symbol() -> None: def test_overview_uses_fallback_symbol_when_quote_payload_omits_symbol() -> None:
"""GLD quote with fallback symbol uses expense-adjusted backing for conversion."""
from datetime import date
from app.domain.instruments import gld_ounces_per_share
config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0)
spot_price, source, updated_at = resolve_portfolio_spot_from_quote( spot_price, source, updated_at = resolve_portfolio_spot_from_quote(
@@ -61,7 +78,10 @@ def test_overview_uses_fallback_symbol_when_quote_payload_omits_symbol() -> None
fallback_symbol="GLD", fallback_symbol="GLD",
) )
assert spot_price == 4041.9 # With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert abs(spot_price - expected_spot) < 0.01
assert source == "yfinance" assert source == "yfinance"
assert updated_at == "2026-03-24T00:00:00+00:00" assert updated_at == "2026-03-24T00:00:00+00:00"

View File

@@ -2,6 +2,8 @@ from __future__ import annotations
from decimal import Decimal from decimal import Decimal
import pytest
from app.domain.backtesting_math import PricePerAsset from app.domain.backtesting_math import PricePerAsset
from app.domain.instruments import price_per_weight_from_asset_price from app.domain.instruments import price_per_weight_from_asset_price
from app.domain.portfolio_math import ( from app.domain.portfolio_math import (
@@ -45,10 +47,20 @@ def test_build_alert_context_uses_unit_safe_gold_value_calculation() -> None:
def test_build_alert_context_accepts_explicit_gld_share_quote_conversion() -> None: def test_build_alert_context_accepts_explicit_gld_share_quote_conversion() -> None:
"""GLD share quotes convert to spot using expense-adjusted backing."""
from datetime import date
from app.domain.instruments import gld_ounces_per_share
config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0)
share_quote = PricePerAsset(amount=Decimal("404.19"), currency=BaseCurrency.USD, symbol="GLD") share_quote = PricePerAsset(amount=Decimal("404.19"), currency=BaseCurrency.USD, symbol="GLD")
ounce_spot = price_per_weight_from_asset_price(share_quote, per_unit=WeightUnit.OUNCE_TROY) ounce_spot = price_per_weight_from_asset_price(share_quote, per_unit=WeightUnit.OUNCE_TROY)
# With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert abs(float(ounce_spot.amount) - expected_spot) < 0.01
context = build_alert_context( context = build_alert_context(
config, config,
spot_price=float(ounce_spot.amount), spot_price=float(ounce_spot.amount),
@@ -56,9 +68,9 @@ def test_build_alert_context_accepts_explicit_gld_share_quote_conversion() -> No
updated_at="2026-03-24T00:00:00+00:00", updated_at="2026-03-24T00:00:00+00:00",
) )
assert context["spot_price"] == 4041.9 assert abs(context["spot_price"] - expected_spot) < 0.01
assert context["gold_value"] == 889218.0 assert context["gold_value"] == pytest.approx(220.0 * expected_spot, abs=0.01)
assert context["net_equity"] == 744218.0 assert context["net_equity"] == pytest.approx(220.0 * expected_spot - 145000.0, abs=0.01)
assert context["quote_source"] == "yfinance" assert context["quote_source"] == "yfinance"
@@ -95,7 +107,10 @@ def test_strategy_metrics_from_snapshot_preserves_minus_20pct_protective_put_exa
def test_resolve_portfolio_spot_from_quote_converts_gld_share_to_ozt() -> None: def test_resolve_portfolio_spot_from_quote_converts_gld_share_to_ozt() -> None:
"""Hedge/runtime quote resolution should convert GLD share quotes to USD/ozt.""" """Hedge/runtime quote resolution should convert GLD share quotes to USD/ozt using expense-adjusted backing."""
from datetime import date
from app.domain.instruments import gld_ounces_per_share
from app.models.portfolio import PortfolioConfig from app.models.portfolio import PortfolioConfig
config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0)
@@ -109,7 +124,10 @@ def test_resolve_portfolio_spot_from_quote_converts_gld_share_to_ozt() -> None:
spot, source, updated_at = resolve_portfolio_spot_from_quote(config, share_quote) spot, source, updated_at = resolve_portfolio_spot_from_quote(config, share_quote)
assert spot == 4041.9 # 404.19 / 0.1 = 4041.9 USD/ozt # With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert abs(spot - expected_spot) < 0.01
assert source == "yfinance" assert source == "yfinance"
assert updated_at == "2026-03-25T00:00:00+00:00" assert updated_at == "2026-03-25T00:00:00+00:00"

View File

@@ -4,6 +4,7 @@ import json
import re import re
from uuid import uuid4 from uuid import uuid4
import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from app.main import app from app.main import app
@@ -109,9 +110,17 @@ def test_bootstrap_endpoint_requires_turnstile_and_creates_workspace_cookie_and_
assert response.cookies.get("workspace_id") == workspace_id assert response.cookies.get("workspace_id") == workspace_id
created = repo.load_portfolio_config(workspace_id) created = repo.load_portfolio_config(workspace_id)
assert created.entry_price == 4041.9 # GLD quote at $404.19/share converts to spot using expense-adjusted backing
from datetime import date
from app.domain.instruments import gld_ounces_per_share
current_backing = float(gld_ounces_per_share(date.today()))
expected_entry_price = 404.19 / current_backing # ~4413.71 with 2026 backing
expected_gold_value = expected_entry_price * 100.0
assert created.entry_price == pytest.approx(expected_entry_price, rel=1e-4)
assert created.gold_ounces == 100.0 assert created.gold_ounces == 100.0
assert created.gold_value == 404190.0 assert created.gold_value == pytest.approx(expected_gold_value, rel=1e-4)
def test_root_with_valid_workspace_cookie_redirects_to_workspace(tmp_path, monkeypatch) -> None: def test_root_with_valid_workspace_cookie_redirects_to_workspace(tmp_path, monkeypatch) -> None:
@@ -238,9 +247,11 @@ def test_workspace_routes_seed_page_defaults_from_workspace_portfolio_config(tmp
def test_hedge_page_upgrades_cached_gld_quote_and_uses_converted_spot(tmp_path, monkeypatch) -> None: def test_hedge_page_upgrades_cached_gld_quote_and_uses_converted_spot(tmp_path, monkeypatch) -> None:
"""Hedge page should reuse DataService cache normalization for legacy GLD quotes.""" """Hedge page should reuse DataService cache normalization for legacy GLD quotes with expense-adjusted backing."""
import asyncio import asyncio
from datetime import date
from app.domain.instruments import gld_ounces_per_share
from app.pages import hedge as hedge_module from app.pages import hedge as hedge_module
from app.services import runtime as runtime_module from app.services import runtime as runtime_module
@@ -269,10 +280,13 @@ def test_hedge_page_upgrades_cached_gld_quote_and_uses_converted_spot(tmp_path,
portfolio, source, _ = asyncio.run(hedge_module._resolve_hedge_spot(workspace_id)) portfolio, source, _ = asyncio.run(hedge_module._resolve_hedge_spot(workspace_id))
# With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert source == "cache" assert source == "cache"
assert portfolio["spot_price"] == 4041.9 assert portfolio["spot_price"] == pytest.approx(expected_spot, abs=0.01)
assert portfolio["gold_value"] == 889218.0 assert portfolio["gold_value"] == pytest.approx(220.0 * expected_spot, abs=0.01)
assert portfolio["net_equity"] == 667218.0 assert portfolio["net_equity"] == pytest.approx(220.0 * expected_spot - 222_000.0, abs=0.01)
def test_hedge_page_upgrades_legacy_default_workspace_footprint(tmp_path, monkeypatch) -> None: def test_hedge_page_upgrades_legacy_default_workspace_footprint(tmp_path, monkeypatch) -> None:
@@ -323,8 +337,16 @@ def test_hedge_page_upgrades_legacy_default_workspace_footprint(tmp_path, monkey
portfolio, source, _ = asyncio.run(hedge_module._resolve_hedge_spot(workspace_id)) portfolio, source, _ = asyncio.run(hedge_module._resolve_hedge_spot(workspace_id))
# With expense-adjusted backing (~0.0916 oz/share), spot = 404.19 / 0.091576... ≈ 4413.71
from datetime import date
from app.domain.instruments import gld_ounces_per_share
current_backing = float(gld_ounces_per_share(date.today()))
expected_spot = 404.19 / current_backing
assert source == "cache" assert source == "cache"
assert portfolio["gold_units"] == 100.0 assert portfolio["gold_units"] == 100.0
assert portfolio["margin_call_price"] == 1933.3333333333333 assert portfolio["margin_call_price"] == pytest.approx(1933.3333333333333, abs=0.01)
assert portfolio["gold_value"] == 404190.0 assert portfolio["gold_value"] == pytest.approx(100.0 * expected_spot, abs=0.01)
assert portfolio["net_equity"] == 259190.0 assert portfolio["net_equity"] == pytest.approx(100.0 * expected_spot - 145000.0, abs=0.01)