from __future__ import annotations import pytest from app.domain.portfolio_math import resolve_portfolio_spot_from_quote from app.models.portfolio import PortfolioConfig from app.services.alerts import build_portfolio_alert_context 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) spot_price, source, updated_at = resolve_portfolio_spot_from_quote( config, { "symbol": "GLD", "price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, ) portfolio = build_portfolio_alert_context( config, spot_price=spot_price, source=source, updated_at=updated_at, ) # 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 updated_at == "2026-03-24T00:00:00+00:00" # Gold value = 220 oz * spot_price 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 def test_overview_fails_closed_to_configured_entry_price_for_unsupported_quote_symbol() -> None: 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( config, { "symbol": "SLV", "price": 28.50, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, ) assert spot_price == 4400.0 assert source == "configured_entry_price" assert updated_at == "" 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) spot_price, source, updated_at = resolve_portfolio_spot_from_quote( config, {"price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00"}, fallback_symbol="GLD", ) # 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 updated_at == "2026-03-24T00:00:00+00:00" def test_overview_falls_back_when_quote_price_is_missing_or_invalid() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) missing_price = resolve_portfolio_spot_from_quote( config, {"symbol": "GLD", "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00"}, fallback_symbol="GLD", ) zero_price = resolve_portfolio_spot_from_quote( config, { "symbol": "GLD", "price": 0.0, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, fallback_symbol="GLD", ) bad_string_price = resolve_portfolio_spot_from_quote( config, { "symbol": "GLD", "price": "not-a-number", "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, fallback_symbol="GLD", ) assert missing_price == (4400.0, "configured_entry_price", "") assert zero_price == (4400.0, "configured_entry_price", "") assert bad_string_price == (4400.0, "configured_entry_price", "") def test_overview_falls_back_when_quote_unit_metadata_is_missing() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) result = resolve_portfolio_spot_from_quote( config, {"symbol": "GLD", "price": 404.19, "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00"}, fallback_symbol="GLD", ) assert result == (4400.0, "configured_entry_price", "")