Files
vault-dash/tests/test_display_mode.py

302 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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"