feat(DISPLAY-002): GLD mode shows real share prices

This commit is contained in:
Bu5hm4nn
2026-03-28 21:45:00 +01:00
parent 24c74cacbd
commit dac0463d55
8 changed files with 800 additions and 40 deletions

301
tests/test_display_mode.py Normal file
View File

@@ -0,0 +1,301 @@
"""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"