"""Tests for DISPLAY-002: GLD Mode Shows Real GLD Pricing.""" from __future__ import annotations from datetime import date from decimal import Decimal from app.domain.conversions import ( convert_position_to_display, convert_price_to_display, convert_quantity_to_display, get_display_unit_label, is_gld_mode, is_xau_mode, ) from app.domain.instruments import gld_ounces_per_share from app.domain.portfolio_math import ( build_alert_context, portfolio_snapshot_from_config, resolve_portfolio_spot_from_quote, ) from app.models.portfolio import PortfolioConfig from app.models.position import create_position class TestDisplayModeHelpers: """Test display mode helper functions.""" def test_is_gld_mode(self) -> None: assert is_gld_mode("GLD") is True assert is_gld_mode("XAU") is False assert is_gld_mode("gld") is False # Case sensitive def test_is_xau_mode(self) -> None: assert is_xau_mode("XAU") is True assert is_xau_mode("GLD") is False class TestConvertPositionToDisplay: """Test position conversion based on display mode.""" def test_gld_position_in_gld_mode_shows_shares(self) -> None: """GLD position in GLD mode: show as-is in shares.""" pos = create_position( underlying="GLD", quantity=Decimal("100"), unit="shares", entry_price=Decimal("400"), entry_date=date.today(), ) qty, unit, price = convert_position_to_display(pos, "GLD") assert qty == Decimal("100") assert unit == "shares" assert price == Decimal("400") def test_gld_position_in_xau_mode_converts_to_oz(self) -> None: """GLD position in XAU mode: convert to oz using expense-adjusted backing.""" pos = create_position( underlying="GLD", quantity=Decimal("100"), unit="shares", entry_price=Decimal("400"), entry_date=date.today(), ) qty, unit, price = convert_position_to_display(pos, "XAU", date.today()) # Should convert to oz assert unit == "oz" backing = gld_ounces_per_share(date.today()) expected_qty = Decimal("100") * backing assert abs(float(qty) - float(expected_qty)) < 0.0001 # Price should be per oz (share price / backing) expected_price = Decimal("400") / backing assert abs(float(price) - float(expected_price)) < 0.01 def test_xau_position_in_xau_mode_shows_oz(self) -> None: """Physical gold position in XAU mode: show as-is in oz.""" pos = create_position( underlying="XAU", quantity=Decimal("50"), unit="oz", entry_price=Decimal("2000"), entry_date=date.today(), ) qty, unit, price = convert_position_to_display(pos, "XAU") assert qty == Decimal("50") assert unit == "oz" assert price == Decimal("2000") class TestConvertPriceToDisplay: """Test price conversion based on display mode.""" def test_share_price_to_gld_mode(self) -> None: """Share price in GLD mode: no conversion.""" price, unit = convert_price_to_display(Decimal("400"), "shares", "GLD") assert price == Decimal("400") assert unit == "shares" def test_oz_price_to_gld_mode(self) -> None: """Oz price in GLD mode: convert to share price.""" oz_price = Decimal("4400") price, unit = convert_price_to_display(oz_price, "oz", "GLD", date.today()) backing = gld_ounces_per_share(date.today()) # Share price = oz price × backing expected = oz_price * backing assert abs(float(price) - float(expected)) < 0.01 assert unit == "shares" def test_share_price_to_xau_mode(self) -> None: """Share price in XAU mode: convert to oz price.""" share_price = Decimal("400") price, unit = convert_price_to_display(share_price, "shares", "XAU", date.today()) backing = gld_ounces_per_share(date.today()) # Oz price = share price / backing expected = share_price / backing assert abs(float(price) - float(expected)) < 1.0 assert unit == "oz" class TestConvertQuantityToDisplay: """Test quantity conversion based on display mode.""" def test_shares_to_gld_mode(self) -> None: """Shares in GLD mode: no conversion.""" qty, unit = convert_quantity_to_display(Decimal("100"), "shares", "GLD") assert qty == Decimal("100") assert unit == "shares" def test_shares_to_xau_mode(self) -> None: """Shares in XAU mode: convert to oz.""" shares = Decimal("100") qty, unit = convert_quantity_to_display(shares, "shares", "XAU", date.today()) backing = gld_ounces_per_share(date.today()) expected = shares * backing assert abs(float(qty) - float(expected)) < 0.0001 assert unit == "oz" class TestGetDisplayUnitLabel: """Test display unit label helper.""" def test_gld_underlying_in_gld_mode(self) -> None: assert get_display_unit_label("GLD", "GLD") == "shares" def test_gld_underlying_in_xau_mode(self) -> None: assert get_display_unit_label("GLD", "XAU") == "oz" def test_xau_underlying(self) -> None: assert get_display_unit_label("XAU", "GLD") == "oz" assert get_display_unit_label("XAU", "XAU") == "oz" class TestPortfolioSnapshotWithDisplayMode: """Test portfolio snapshot respects display mode.""" def test_gld_mode_snapshot_uses_shares(self) -> None: """GLD mode: snapshot shows shares and share price.""" config = PortfolioConfig( entry_price=2150.0, gold_ounces=100.0, entry_basis_mode="weight", loan_amount=145000.0, display_mode="GLD", ) snapshot = portfolio_snapshot_from_config(config, runtime_spot_price=400.0) # In GLD mode, spot_price should be the share price (400.0) assert snapshot["spot_price"] == 400.0 # gold_units should be in shares (converted from oz using backing) backing = float(gld_ounces_per_share(date.today())) expected_shares = 100.0 / backing assert abs(snapshot["gold_units"] - expected_shares) < 0.01 # gold_value = shares × share_price expected_value = expected_shares * 400.0 assert abs(snapshot["gold_value"] - expected_value) < 0.01 assert snapshot["display_mode"] == "GLD" def test_xau_mode_snapshot_uses_oz(self) -> None: """XAU mode: snapshot shows oz and oz price.""" config = PortfolioConfig( entry_price=2150.0, gold_ounces=100.0, entry_basis_mode="weight", loan_amount=145000.0, display_mode="XAU", ) snapshot = portfolio_snapshot_from_config(config, runtime_spot_price=2150.0) # In XAU mode, spot_price should be oz price (2150.0) assert snapshot["spot_price"] == 2150.0 # gold_units should be in oz assert snapshot["gold_units"] == 100.0 # gold_value = oz × oz_price assert snapshot["gold_value"] == 215000.0 assert snapshot["display_mode"] == "XAU" class TestResolvePortfolioSpotFromQuote: """Test spot price resolution respects display mode.""" def test_gld_mode_returns_share_price_directly(self) -> None: """GLD mode: return GLD share price without conversion.""" config = PortfolioConfig( entry_price=2150.0, gold_ounces=100.0, display_mode="GLD", ) quote = { "symbol": "GLD", "price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", } spot, source, updated_at = resolve_portfolio_spot_from_quote(config, quote) # In GLD mode, should return share price directly assert spot == 404.19 assert source == "yfinance" def test_xau_mode_converts_to_oz_price(self) -> None: """XAU mode: convert GLD share price to oz-equivalent.""" config = PortfolioConfig( entry_price=2150.0, gold_ounces=100.0, display_mode="XAU", ) quote = { "symbol": "GLD", "price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", } spot, source, updated_at = resolve_portfolio_spot_from_quote(config, quote) # In XAU mode, should convert to oz price backing = float(gld_ounces_per_share(date.today())) expected_spot = 404.19 / backing assert abs(spot - expected_spot) < 0.01 assert source == "yfinance" class TestBuildAlertContextWithDisplayMode: """Test alert context respects display mode.""" def test_gld_mode_context_uses_shares(self) -> None: """GLD mode: alert context shows shares.""" config = PortfolioConfig( entry_price=2150.0, gold_ounces=100.0, loan_amount=145000.0, display_mode="GLD", ) context = build_alert_context( config, spot_price=400.0, # Share price source="yfinance", updated_at="2026-03-24T00:00:00+00:00", ) # Should show shares, not oz backing = float(gld_ounces_per_share(date.today())) expected_shares = 100.0 / backing assert abs(context["gold_units"] - expected_shares) < 0.01 assert context["display_mode"] == "GLD" def test_xau_mode_context_uses_oz(self) -> None: """XAU mode: alert context shows oz.""" config = PortfolioConfig( entry_price=2150.0, gold_ounces=100.0, loan_amount=145000.0, display_mode="XAU", ) context = build_alert_context( config, spot_price=2150.0, source="yfinance", updated_at="2026-03-24T00:00:00+00:00", ) assert context["gold_units"] == 100.0 assert context["display_mode"] == "XAU"