"""Storage cost calculation service for positions with physical storage requirements.""" from __future__ import annotations from decimal import Decimal from app.models.position import Position _DECIMAL_ZERO = Decimal("0") _DECIMAL_ONE = Decimal("1") _DECIMAL_HUNDRED = Decimal("100") _DECIMAL_TWELVE = Decimal("12") def calculate_annual_storage_cost(position: Position, current_value: Decimal) -> Decimal: """Calculate annual storage cost for a single position. Args: position: Position with optional storage_cost_basis and storage_cost_period current_value: Current market value of the position (quantity × current_price) Returns: Annual storage cost in position's storage_cost_currency (default USD) Notes: - If storage_cost_basis is None, returns 0 (no storage cost) - If storage_cost_period is "monthly", annualizes the cost (×12) - If storage_cost_basis is a percentage, applies it to current_value - If storage_cost_basis is a fixed amount, uses it directly """ if position.storage_cost_basis is None: return _DECIMAL_ZERO basis = position.storage_cost_basis period = position.storage_cost_period or "annual" # Determine if basis is a percentage (e.g., 0.12 for 0.12%) or fixed amount # Heuristic: if basis < 1, treat as percentage; otherwise as fixed amount if basis < _DECIMAL_ONE: # Percentage-based cost if period == "monthly": # Monthly percentage, annualize it annual_rate = basis * _DECIMAL_TWELVE else: # Already annual annual_rate = basis # Apply percentage to current value return (current_value * annual_rate) / _DECIMAL_HUNDRED else: # Fixed amount if period == "monthly": # Monthly fixed cost, annualize it return basis * _DECIMAL_TWELVE else: # Already annual fixed cost return basis def calculate_total_storage_cost( positions: list[Position], current_values: dict[str, Decimal], ) -> Decimal: """Calculate total annual storage cost across all positions. Args: positions: List of positions with optional storage costs current_values: Mapping of position ID (str) to current market value Returns: Total annual storage cost in USD (assumes all positions use USD) """ total = _DECIMAL_ZERO for position in positions: current_value = current_values.get(str(position.id), _DECIMAL_ZERO) cost = calculate_annual_storage_cost(position, current_value) total += cost return total def get_default_storage_cost_for_underlying(underlying: str) -> tuple[Decimal | None, str | None]: """Get default storage cost settings for a given underlying instrument. Args: underlying: Instrument symbol (e.g., "XAU", "GLD", "GC=F") Returns: Tuple of (storage_cost_basis, storage_cost_period) or (None, None) if no default Notes: - XAU (physical gold): 0.12% annual for allocated vault storage - GLD: None (expense ratio baked into share price) - GC=F: None (roll costs are the storage analog, handled separately) """ if underlying == "XAU": # Physical gold: 0.12% annual storage cost for allocated vault storage return Decimal("0.12"), "annual" elif underlying == "GLD": # GLD: expense ratio is implicit in share price, no separate storage cost return None, None elif underlying == "GC=F": # Futures: roll costs are the storage analog (deferred to GCF-001) return None, None else: return None, None