Files
vault-dash/app/services/storage_costs.py
2026-03-28 23:48:41 +01:00

106 lines
3.6 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.
"""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