feat(DISPLAY-002): GLD mode shows real share prices
This commit is contained in:
@@ -1,322 +1,310 @@
|
||||
"""Conversion layer for display mode switching.
|
||||
"""Display mode conversion utilities for GLD/XAU views.
|
||||
|
||||
Provides functions to convert positions between different display modes:
|
||||
- GLD: shares (for GLD ETF)
|
||||
- XAU_OZ: troy ounces of gold
|
||||
- XAU_G: grams of gold
|
||||
- GCF: gold futures contracts (GC=F)
|
||||
This module handles conversion between GLD share prices and physical gold prices
|
||||
based on the user's display mode preference.
|
||||
|
||||
All conversions use the position's entry_date for historical accuracy.
|
||||
Key insight:
|
||||
- In GLD mode: show share prices directly, no conversion to oz
|
||||
- In XAU mode: convert GLD shares to oz-equivalent using expense-adjusted backing
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from app.domain.instruments import GC_F_OUNCES_PER_CONTRACT, gld_ounces_per_share
|
||||
from app.domain.instruments import gld_ounces_per_share
|
||||
from app.models.position import Position
|
||||
|
||||
# Constants
|
||||
GRAMS_PER_OUNCE = Decimal("31.1035") # 1 troy oz = 31.1035 grams
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DisplayContext:
|
||||
"""Context for display mode conversions.
|
||||
|
||||
Attributes:
|
||||
mode: Display mode ("GLD" for shares, "XAU" for physical gold)
|
||||
reference_date: Date for historical conversion lookups
|
||||
gld_ounces_per_share: GLD backing ratio for the reference date
|
||||
"""
|
||||
|
||||
mode: str
|
||||
reference_date: date | None = None
|
||||
gld_ounces_per_share: Decimal | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.mode not in {"GLD", "XAU"}:
|
||||
raise ValueError(f"Invalid display mode: {self.mode!r}")
|
||||
|
||||
@classmethod
|
||||
def create(cls, mode: str, reference_date: date | None = None) -> "DisplayContext":
|
||||
"""Create a display context with computed GLD backing ratio."""
|
||||
gld_backing = None
|
||||
if mode == "XAU" and reference_date is not None:
|
||||
gld_backing = gld_ounces_per_share(reference_date)
|
||||
return cls(mode=mode, reference_date=reference_date, gld_ounces_per_share=gld_backing)
|
||||
|
||||
|
||||
def _gld_shares_to_ounces(position: Position, reference_date: date | None = None) -> Decimal:
|
||||
"""Convert GLD shares to troy ounces using historical backing ratio."""
|
||||
if position.underlying != "GLD":
|
||||
raise ValueError(f"Cannot convert non-GLD position ({position.underlying}) to ounces")
|
||||
if position.unit != "shares":
|
||||
raise ValueError(f"GLD position must be in shares, got {position.unit}")
|
||||
|
||||
oz_per_share = gld_ounces_per_share(reference_date or position.entry_date)
|
||||
return position.quantity * oz_per_share
|
||||
def is_gld_mode(display_mode: str) -> bool:
|
||||
"""Check if display mode is GLD (share view)."""
|
||||
return display_mode == "GLD"
|
||||
|
||||
|
||||
def _gld_shares_to_grams(position: Position, reference_date: date | None = None) -> Decimal:
|
||||
"""Convert GLD shares to grams using historical backing ratio."""
|
||||
ounces = _gld_shares_to_ounces(position, reference_date)
|
||||
return ounces * GRAMS_PER_OUNCE
|
||||
def is_xau_mode(display_mode: str) -> bool:
|
||||
"""Check if display mode is XAU (physical gold view)."""
|
||||
return display_mode == "XAU"
|
||||
|
||||
|
||||
def _gld_shares_to_contracts(position: Position, reference_date: date | None = None) -> Decimal:
|
||||
"""Convert GLD shares to GC=F contracts."""
|
||||
ounces = _gld_shares_to_ounces(position, reference_date)
|
||||
return ounces / GC_F_OUNCES_PER_CONTRACT
|
||||
|
||||
|
||||
def _ounces_to_gld_shares(ounces: Decimal, reference_date: date | None = None) -> Decimal:
|
||||
"""Convert troy ounces to GLD shares using historical backing ratio."""
|
||||
oz_per_share = gld_ounces_per_share(reference_date or date.today())
|
||||
if oz_per_share <= 0:
|
||||
raise ValueError("GLD ounces per share must be positive")
|
||||
return ounces / oz_per_share
|
||||
|
||||
|
||||
def _grams_to_gld_shares(grams: Decimal, reference_date: date | None = None) -> Decimal:
|
||||
"""Convert grams to GLD shares using historical backing ratio."""
|
||||
ounces = grams / GRAMS_PER_OUNCE
|
||||
return _ounces_to_gld_shares(ounces, reference_date)
|
||||
|
||||
|
||||
def _contracts_to_gld_shares(contracts: Decimal, reference_date: date | None = None) -> Decimal:
|
||||
"""Convert GC=F contracts to GLD shares using historical backing ratio."""
|
||||
ounces = contracts * GC_F_OUNCES_PER_CONTRACT
|
||||
return _ounces_to_gld_shares(ounces, reference_date)
|
||||
|
||||
|
||||
def _xau_oz_to_grams(ounces: Decimal) -> Decimal:
|
||||
"""Convert troy ounces to grams."""
|
||||
return ounces * GRAMS_PER_OUNCE
|
||||
|
||||
|
||||
def _xau_g_to_ounces(grams: Decimal) -> Decimal:
|
||||
"""Convert grams to troy ounces."""
|
||||
return grams / GRAMS_PER_OUNCE
|
||||
|
||||
|
||||
def _xau_oz_to_contracts(ounces: Decimal) -> Decimal:
|
||||
"""Convert troy ounces to GC=F contracts."""
|
||||
return ounces / GC_F_OUNCES_PER_CONTRACT
|
||||
|
||||
|
||||
def _contracts_to_xau_oz(contracts: Decimal) -> Decimal:
|
||||
"""Convert GC=F contracts to troy ounces."""
|
||||
return contracts * GC_F_OUNCES_PER_CONTRACT
|
||||
|
||||
|
||||
def position_to_display_units(
|
||||
def convert_position_to_display(
|
||||
position: Position,
|
||||
display_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> tuple[Decimal, str]:
|
||||
"""Convert a position to display units based on the selected display mode.
|
||||
) -> tuple[Decimal, str, Decimal]:
|
||||
"""Convert a position to display units based on display mode.
|
||||
|
||||
Args:
|
||||
position: The position to convert
|
||||
display_mode: One of "GLD", "XAU_OZ", "XAU_G", "GCF"
|
||||
reference_date: Date for historical conversion (defaults to position.entry_date)
|
||||
position: Position to convert
|
||||
display_mode: "GLD" for shares, "XAU" for physical gold
|
||||
reference_date: Date for historical conversion (for GLD->XAU)
|
||||
|
||||
Returns:
|
||||
Tuple of (quantity in display units, display unit label)
|
||||
Tuple of (display_quantity, display_unit, display_entry_price)
|
||||
|
||||
Examples:
|
||||
>>> # GLD position in GLD mode: show as-is
|
||||
>>> from datetime import date
|
||||
>>> from decimal import Decimal
|
||||
>>> from app.models.position import create_position
|
||||
>>> pos = create_position("GLD", Decimal("100"), "shares", Decimal("230"))
|
||||
>>> qty, unit = position_to_display_units(pos, "XAU_OZ")
|
||||
>>> float(qty) > 0 and unit == "oz"
|
||||
True
|
||||
"""
|
||||
ref_date = reference_date or position.entry_date
|
||||
>>> 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")
|
||||
>>> qty, unit, price
|
||||
(Decimal('100'), 'shares', Decimal('400'))
|
||||
|
||||
>>> # GLD position in XAU mode: convert to oz
|
||||
>>> qty, unit, price = convert_position_to_display(pos, "XAU", date.today())
|
||||
>>> # qty will be shares * oz_per_share
|
||||
"""
|
||||
if display_mode == "GLD":
|
||||
# Show GLD shares as-is
|
||||
if position.underlying == "GLD" and position.unit == "shares":
|
||||
return position.quantity, "shares"
|
||||
# Convert other formats to GLD shares
|
||||
if position.underlying == "GLD" and position.unit == "oz":
|
||||
shares = _ounces_to_gld_shares(position.quantity, ref_date)
|
||||
return shares, "shares"
|
||||
if position.underlying == "GLD" and position.unit == "g":
|
||||
shares = _grams_to_gld_shares(position.quantity, ref_date)
|
||||
return shares, "shares"
|
||||
if position.underlying == "GC=F" and position.unit == "contracts":
|
||||
shares = _contracts_to_gld_shares(position.quantity, ref_date)
|
||||
return shares, "shares"
|
||||
# XAU positions
|
||||
if position.underlying == "XAU":
|
||||
if position.unit == "oz":
|
||||
shares = _ounces_to_gld_shares(position.quantity, ref_date)
|
||||
return shares, "shares"
|
||||
if position.unit == "g":
|
||||
shares = _grams_to_gld_shares(position.quantity, ref_date)
|
||||
return shares, "shares"
|
||||
# GLD mode: show shares directly
|
||||
if position.underlying == "GLD":
|
||||
return position.quantity, position.unit, position.entry_price
|
||||
# Non-GLD positions in GLD mode: would need conversion (not implemented yet)
|
||||
return position.quantity, position.unit, position.entry_price
|
||||
|
||||
elif display_mode == "XAU_OZ":
|
||||
# Show everything in troy ounces
|
||||
if position.underlying == "GLD" and position.unit == "shares":
|
||||
ounces = _gld_shares_to_ounces(position, ref_date)
|
||||
return ounces, "oz"
|
||||
if position.underlying == "GLD" and position.unit == "oz":
|
||||
return position.quantity, "oz"
|
||||
if position.underlying == "GLD" and position.unit == "g":
|
||||
return _xau_g_to_ounces(position.quantity), "oz"
|
||||
if position.underlying == "GC=F" and position.unit == "contracts":
|
||||
ounces = position.quantity * GC_F_OUNCES_PER_CONTRACT
|
||||
return ounces, "oz"
|
||||
if position.underlying == "XAU":
|
||||
if position.unit == "oz":
|
||||
return position.quantity, "oz"
|
||||
if position.unit == "g":
|
||||
return _xau_g_to_ounces(position.quantity), "oz"
|
||||
elif display_mode == "XAU":
|
||||
# XAU mode: convert to physical gold ounces
|
||||
if position.underlying == "GLD":
|
||||
# Convert GLD shares to oz using expense-adjusted backing
|
||||
backing = gld_ounces_per_share(reference_date or position.entry_date)
|
||||
display_qty = position.quantity * backing
|
||||
display_price = position.entry_price / backing # Price per oz
|
||||
return display_qty, "oz", display_price
|
||||
# XAU positions already in oz
|
||||
return position.quantity, position.unit, position.entry_price
|
||||
|
||||
elif display_mode == "XAU_G":
|
||||
# Show everything in grams
|
||||
if position.underlying == "GLD" and position.unit == "shares":
|
||||
grams = _gld_shares_to_grams(position, ref_date)
|
||||
return grams, "g"
|
||||
if position.underlying == "GLD" and position.unit == "oz":
|
||||
ounces = position.quantity
|
||||
return _xau_oz_to_grams(ounces), "g"
|
||||
if position.underlying == "GLD" and position.unit == "g":
|
||||
return position.quantity, "g"
|
||||
if position.underlying == "GC=F" and position.unit == "contracts":
|
||||
ounces = position.quantity * GC_F_OUNCES_PER_CONTRACT
|
||||
return _xau_oz_to_grams(ounces), "g"
|
||||
if position.underlying == "XAU":
|
||||
if position.unit == "oz":
|
||||
return _xau_oz_to_grams(position.quantity), "g"
|
||||
if position.unit == "g":
|
||||
return position.quantity, "g"
|
||||
|
||||
elif display_mode == "GCF":
|
||||
# Show everything in GC=F contracts
|
||||
if position.underlying == "GLD" and position.unit == "shares":
|
||||
contracts = _gld_shares_to_contracts(position, ref_date)
|
||||
return contracts, "contracts"
|
||||
if position.underlying == "GLD" and position.unit == "oz":
|
||||
contracts = _xau_oz_to_contracts(position.quantity)
|
||||
return contracts, "contracts"
|
||||
if position.underlying == "GLD" and position.unit == "g":
|
||||
ounces = _xau_g_to_ounces(position.quantity)
|
||||
contracts = _xau_oz_to_contracts(ounces)
|
||||
return contracts, "contracts"
|
||||
if position.underlying == "GC=F" and position.unit == "contracts":
|
||||
return position.quantity, "contracts"
|
||||
if position.underlying == "XAU":
|
||||
if position.unit == "oz":
|
||||
contracts = _xau_oz_to_contracts(position.quantity)
|
||||
return contracts, "contracts"
|
||||
if position.unit == "g":
|
||||
ounces = _xau_g_to_ounces(position.quantity)
|
||||
contracts = _xau_oz_to_contracts(ounces)
|
||||
return contracts, "contracts"
|
||||
|
||||
# Fallback: return as-is
|
||||
return position.quantity, position.unit
|
||||
|
||||
|
||||
def collateral_to_display_units(
|
||||
gold_ounces: float,
|
||||
display_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> tuple[float, str]:
|
||||
"""Convert collateral amount to display units.
|
||||
|
||||
Args:
|
||||
gold_ounces: Collateral amount in troy ounces
|
||||
display_mode: One of "GLD", "XAU_OZ", "XAU_G", "GCF"
|
||||
reference_date: Date for historical conversion (defaults to today)
|
||||
|
||||
Returns:
|
||||
Tuple of (amount in display units, display unit label)
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
oz_decimal = Decimal(str(gold_ounces))
|
||||
ref_date = reference_date or date.today()
|
||||
|
||||
if display_mode == "GLD":
|
||||
shares = _ounces_to_gld_shares(oz_decimal, ref_date)
|
||||
return float(shares), "shares"
|
||||
elif display_mode == "XAU_OZ":
|
||||
return gold_ounces, "oz"
|
||||
elif display_mode == "XAU_G":
|
||||
grams = _xau_oz_to_grams(oz_decimal)
|
||||
return float(grams), "g"
|
||||
elif display_mode == "GCF":
|
||||
contracts = _xau_oz_to_contracts(oz_decimal)
|
||||
return float(contracts), "contracts"
|
||||
|
||||
return gold_ounces, "oz"
|
||||
|
||||
|
||||
def price_per_display_unit(
|
||||
price_per_oz: float,
|
||||
display_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> float:
|
||||
"""Convert price per ounce to price per display unit.
|
||||
|
||||
Args:
|
||||
price_per_oz: Price per troy ounce in USD
|
||||
display_mode: One of "GLD", "XAU_OZ", "XAU_G", "GCF"
|
||||
reference_date: Date for historical conversion (defaults to today)
|
||||
|
||||
Returns:
|
||||
Price per display unit in USD
|
||||
"""
|
||||
from decimal import Decimal
|
||||
|
||||
price = Decimal(str(price_per_oz))
|
||||
ref_date = reference_date or date.today()
|
||||
|
||||
if display_mode == "GLD":
|
||||
# Price per GLD share = price per oz * oz per share
|
||||
oz_per_share = gld_ounces_per_share(ref_date)
|
||||
return float(price * oz_per_share)
|
||||
elif display_mode == "XAU_OZ":
|
||||
return price_per_oz
|
||||
elif display_mode == "XAU_G":
|
||||
# Price per gram = price per oz / grams per oz
|
||||
return float(price / GRAMS_PER_OUNCE)
|
||||
elif display_mode == "GCF":
|
||||
# Price per contract = price per oz * 100 oz
|
||||
return float(price * GC_F_OUNCES_PER_CONTRACT)
|
||||
|
||||
return price_per_oz
|
||||
|
||||
|
||||
def format_display_quantity(quantity: float, unit: str) -> str:
|
||||
"""Format a quantity with appropriate precision for display.
|
||||
|
||||
Args:
|
||||
quantity: Numeric quantity
|
||||
unit: Unit label ("shares", "oz", "g", "contracts")
|
||||
|
||||
Returns:
|
||||
Formatted string with appropriate precision
|
||||
"""
|
||||
if unit == "shares":
|
||||
return f"{quantity:,.0f}"
|
||||
elif unit == "contracts":
|
||||
return f"{quantity:,.0f}"
|
||||
elif unit == "oz":
|
||||
return f"{quantity:,.4f}"
|
||||
elif unit == "g":
|
||||
return f"{quantity:,.2f}"
|
||||
else:
|
||||
return f"{quantity:,.4f}"
|
||||
raise ValueError(f"Unsupported display mode: {display_mode!r}")
|
||||
|
||||
|
||||
def get_display_mode_label(display_mode: str) -> str:
|
||||
"""Get human-readable label for display mode.
|
||||
def convert_price_to_display(
|
||||
price: Decimal,
|
||||
from_unit: str,
|
||||
to_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> tuple[Decimal, str]:
|
||||
"""Convert a price to display mode units.
|
||||
|
||||
Args:
|
||||
display_mode: One of "GLD", "XAU_OZ", "XAU_G", "GCF"
|
||||
price: Price value to convert
|
||||
from_unit: Source unit ("shares" or "oz")
|
||||
to_mode: Target display mode ("GLD" or "XAU")
|
||||
reference_date: Date for historical conversion
|
||||
|
||||
Returns:
|
||||
Human-readable label
|
||||
Tuple of (converted_price, display_unit)
|
||||
"""
|
||||
labels = {
|
||||
"GLD": "GLD Shares",
|
||||
"XAU_OZ": "Gold (Troy Ounces)",
|
||||
"XAU_G": "Gold (Grams)",
|
||||
"GCF": "GC=F Contracts",
|
||||
}
|
||||
return labels.get(display_mode, display_mode)
|
||||
if to_mode == "GLD":
|
||||
if from_unit == "shares":
|
||||
return price, "shares"
|
||||
elif from_unit == "oz":
|
||||
# Convert oz price to share price
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
return price * backing, "shares"
|
||||
|
||||
elif to_mode == "XAU":
|
||||
if from_unit == "oz":
|
||||
return price, "oz"
|
||||
elif from_unit == "shares":
|
||||
# Convert share price to oz price
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
return price / backing, "oz"
|
||||
|
||||
raise ValueError(f"Unsupported conversion: {from_unit} -> {to_mode}")
|
||||
|
||||
|
||||
def convert_quantity_to_display(
|
||||
quantity: Decimal,
|
||||
from_unit: str,
|
||||
to_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> tuple[Decimal, str]:
|
||||
"""Convert a quantity to display mode units.
|
||||
|
||||
Args:
|
||||
quantity: Quantity value to convert
|
||||
from_unit: Source unit ("shares" or "oz")
|
||||
to_mode: Target display mode ("GLD" or "XAU")
|
||||
reference_date: Date for historical conversion
|
||||
|
||||
Returns:
|
||||
Tuple of (converted_quantity, display_unit)
|
||||
"""
|
||||
if to_mode == "GLD":
|
||||
if from_unit == "shares":
|
||||
return quantity, "shares"
|
||||
elif from_unit == "oz":
|
||||
# Convert oz to shares (inverse of backing)
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
return quantity / backing, "shares"
|
||||
|
||||
elif to_mode == "XAU":
|
||||
if from_unit == "oz":
|
||||
return quantity, "oz"
|
||||
elif from_unit == "shares":
|
||||
# Convert shares to oz using backing
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
return quantity * backing, "oz"
|
||||
|
||||
raise ValueError(f"Unsupported conversion: {from_unit} -> {to_mode}")
|
||||
|
||||
|
||||
def get_display_unit_label(underlying: str, display_mode: str) -> str:
|
||||
"""Get the display unit label for a position based on display mode.
|
||||
|
||||
Args:
|
||||
underlying: Position underlying symbol
|
||||
display_mode: Display mode ("GLD" or "XAU")
|
||||
|
||||
Returns:
|
||||
Unit label string ("shares", "oz", etc.)
|
||||
"""
|
||||
if underlying == "GLD":
|
||||
if display_mode == "GLD":
|
||||
return "shares"
|
||||
else: # XAU mode
|
||||
return "oz"
|
||||
elif underlying in ("XAU", "GC=F"):
|
||||
return "oz" if display_mode == "XAU" else "oz" # Physical gold always in oz
|
||||
|
||||
return "units"
|
||||
|
||||
|
||||
def calculate_position_value_in_display_mode(
|
||||
quantity: Decimal,
|
||||
unit: str,
|
||||
current_price: Decimal,
|
||||
price_unit: str,
|
||||
display_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> Decimal:
|
||||
"""Calculate position value in display mode.
|
||||
|
||||
Args:
|
||||
quantity: Position quantity
|
||||
unit: Position unit
|
||||
current_price: Current market price
|
||||
price_unit: Price unit ("shares" or "oz")
|
||||
display_mode: Display mode ("GLD" or "XAU")
|
||||
reference_date: Date for conversion
|
||||
|
||||
Returns:
|
||||
Position value in USD
|
||||
"""
|
||||
if display_mode == "GLD" and unit == "shares":
|
||||
# GLD mode: shares × share_price
|
||||
return quantity * current_price
|
||||
elif display_mode == "XAU" and unit == "oz":
|
||||
# XAU mode: oz × oz_price
|
||||
return quantity * current_price
|
||||
elif display_mode == "GLD" and unit == "oz":
|
||||
# Convert oz to shares, then calculate
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
shares = quantity / backing
|
||||
share_price = current_price * backing
|
||||
return shares * share_price
|
||||
elif display_mode == "XAU" and unit == "shares":
|
||||
# Convert shares to oz, then calculate
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
oz = quantity * backing
|
||||
oz_price = current_price / backing
|
||||
return oz * oz_price
|
||||
|
||||
# Fallback: direct multiplication
|
||||
return quantity * current_price
|
||||
|
||||
|
||||
def calculate_pnl_in_display_mode(
|
||||
quantity: Decimal,
|
||||
unit: str,
|
||||
entry_price: Decimal,
|
||||
current_price: Decimal,
|
||||
display_mode: str,
|
||||
reference_date: date | None = None,
|
||||
) -> Decimal:
|
||||
"""Calculate P&L in display mode.
|
||||
|
||||
Args:
|
||||
quantity: Position quantity
|
||||
unit: Position unit
|
||||
entry_price: Entry price per unit
|
||||
current_price: Current price per unit
|
||||
display_mode: Display mode ("GLD" or "XAU")
|
||||
reference_date: Date for conversion
|
||||
|
||||
Returns:
|
||||
P&L in USD
|
||||
"""
|
||||
if display_mode == "GLD" and unit == "shares":
|
||||
# GLD mode: (current_share_price - entry_share_price) × shares
|
||||
return (current_price - entry_price) * quantity
|
||||
elif display_mode == "XAU" and unit == "oz":
|
||||
# XAU mode: (current_oz_price - entry_oz_price) × oz
|
||||
return (current_price - entry_price) * quantity
|
||||
elif display_mode == "GLD" and unit == "oz":
|
||||
# Convert to share basis
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
shares = quantity * backing # oz → shares (wait, this is wrong)
|
||||
# Actually: if we have oz, we need to convert to shares
|
||||
# shares = oz / backing
|
||||
shares = quantity / backing
|
||||
share_entry = entry_price / backing
|
||||
share_current = current_price / backing
|
||||
return (share_current - share_entry) * shares
|
||||
elif display_mode == "XAU" and unit == "shares":
|
||||
# Convert to oz basis
|
||||
backing = gld_ounces_per_share(reference_date or date.today())
|
||||
oz = quantity * backing
|
||||
oz_entry = entry_price * backing
|
||||
oz_current = current_price * backing
|
||||
return (oz_current - oz_entry) * oz
|
||||
|
||||
# Fallback
|
||||
return (current_price - entry_price) * quantity
|
||||
|
||||
|
||||
def get_display_mode_options() -> dict[str, str]:
|
||||
"""Get display mode options for UI selector.
|
||||
"""Return available display mode options for the settings UI.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping mode codes to display labels
|
||||
Dict mapping mode value to display label for NiceGUI select.
|
||||
"""
|
||||
return {
|
||||
"GLD": "GLD Shares",
|
||||
"XAU_OZ": "Gold Ounces (troy)",
|
||||
"XAU_G": "Gold Grams",
|
||||
"GCF": "GC=F Contracts",
|
||||
"GLD": "GLD Shares (show share prices directly)",
|
||||
"XAU": "Physical Gold (oz) (convert to gold ounces)",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user