from __future__ import annotations from decimal import Decimal from typing import Any from app.domain.units import BaseCurrency, Money, PricePerWeight, Weight, WeightUnit, decimal_from_float from app.models.portfolio import PortfolioConfig _DEFAULT_CASH_BUFFER = 18_500.0 _DECIMAL_ZERO = Decimal("0") _DECIMAL_ONE = Decimal("1") _DECIMAL_HUNDRED = Decimal("100") def _decimal_ratio(numerator: Decimal, denominator: Decimal) -> Decimal: if denominator == 0: return _DECIMAL_ZERO return numerator / denominator def _pct_factor(pct: int) -> Decimal: return _DECIMAL_ONE + (Decimal(pct) / _DECIMAL_HUNDRED) def _money_to_float(value: Money) -> float: return float(value.amount) def _decimal_to_float(value: Decimal) -> float: return float(value) def _spot_price(spot_price: float) -> PricePerWeight: return PricePerWeight( amount=decimal_from_float(spot_price), currency=BaseCurrency.USD, per_unit=WeightUnit.OUNCE_TROY, ) def _gold_weight(gold_ounces: float) -> Weight: return Weight(amount=decimal_from_float(gold_ounces), unit=WeightUnit.OUNCE_TROY) def portfolio_snapshot_from_config(config: PortfolioConfig | None = None) -> dict[str, float]: if config is None: gold_weight = Weight(amount=Decimal("1000"), unit=WeightUnit.OUNCE_TROY) spot = PricePerWeight(amount=Decimal("215"), currency=BaseCurrency.USD, per_unit=WeightUnit.OUNCE_TROY) loan_amount = Money(amount=Decimal("145000"), currency=BaseCurrency.USD) margin_call_ltv = Decimal("0.75") hedge_budget = Money(amount=Decimal("8000"), currency=BaseCurrency.USD) else: gold_weight = _gold_weight(float(config.gold_ounces or 0.0)) spot = _spot_price(float(config.entry_price or 0.0)) loan_amount = Money(amount=decimal_from_float(float(config.loan_amount)), currency=BaseCurrency.USD) margin_call_ltv = decimal_from_float(float(config.margin_threshold)) hedge_budget = Money(amount=decimal_from_float(float(config.monthly_budget)), currency=BaseCurrency.USD) gold_value = gold_weight * spot net_equity = gold_value - loan_amount ltv_ratio = _decimal_ratio(loan_amount.amount, gold_value.amount) margin_call_price = loan_amount.amount / (margin_call_ltv * gold_weight.amount) return { "gold_value": _money_to_float(gold_value), "loan_amount": _money_to_float(loan_amount), "ltv_ratio": _decimal_to_float(ltv_ratio), "net_equity": _money_to_float(net_equity), "spot_price": _decimal_to_float(spot.amount), "gold_units": _decimal_to_float(gold_weight.amount), "margin_call_ltv": _decimal_to_float(margin_call_ltv), "margin_call_price": _decimal_to_float(margin_call_price), "cash_buffer": _DEFAULT_CASH_BUFFER, "hedge_budget": _money_to_float(hedge_budget), } def build_alert_context( config: PortfolioConfig, *, spot_price: float, source: str, updated_at: str, ) -> dict[str, float | str]: gold_weight = _gold_weight(float(config.gold_ounces or 0.0)) live_spot = _spot_price(spot_price) gold_value = gold_weight * live_spot loan_amount = Money(amount=decimal_from_float(float(config.loan_amount)), currency=BaseCurrency.USD) margin_call_ltv = decimal_from_float(float(config.margin_threshold)) margin_call_price = ( loan_amount.amount / (margin_call_ltv * gold_weight.amount) if gold_weight.amount > 0 else _DECIMAL_ZERO ) return { "spot_price": _decimal_to_float(live_spot.amount), "gold_units": _decimal_to_float(gold_weight.amount), "gold_value": _money_to_float(gold_value), "loan_amount": _money_to_float(loan_amount), "ltv_ratio": _decimal_to_float(_decimal_ratio(loan_amount.amount, gold_value.amount)), "net_equity": _money_to_float(gold_value - loan_amount), "margin_call_ltv": _decimal_to_float(margin_call_ltv), "margin_call_price": _decimal_to_float(margin_call_price), "quote_source": source, "quote_updated_at": updated_at, } def strategy_metrics_from_snapshot( strategy: dict[str, Any], scenario_pct: int, snapshot: dict[str, Any] ) -> dict[str, Any]: spot = decimal_from_float(float(snapshot["spot_price"])) gold_weight = _gold_weight(float(snapshot["gold_units"])) current_spot = PricePerWeight(amount=spot, currency=BaseCurrency.USD, per_unit=WeightUnit.OUNCE_TROY) loan_amount = Money(amount=decimal_from_float(float(snapshot["loan_amount"])), currency=BaseCurrency.USD) base_equity = Money(amount=decimal_from_float(float(snapshot["net_equity"])), currency=BaseCurrency.USD) default_floor = spot * Decimal("0.95") floor = ( decimal_from_float(float(strategy["max_drawdown_floor"])) if "max_drawdown_floor" in strategy else default_floor ) cap_decimal = ( decimal_from_float(float(strategy["upside_cap"])) if isinstance(strategy.get("upside_cap"), (int, float)) else None ) cost = decimal_from_float(float(strategy["estimated_cost"])) scenario_prices = [spot * _pct_factor(pct) for pct in range(-25, 30, 5)] benefits: list[float] = [] for price in scenario_prices: payoff = max(floor - price, _DECIMAL_ZERO) if cap_decimal is not None and price > cap_decimal: payoff -= price - cap_decimal benefits.append(round(float(payoff - cost), 2)) scenario_price = spot * _pct_factor(scenario_pct) scenario_gold_value = gold_weight * PricePerWeight( amount=scenario_price, currency=BaseCurrency.USD, per_unit=WeightUnit.OUNCE_TROY, ) current_gold_value = gold_weight * current_spot unhedged_equity = scenario_gold_value - loan_amount scenario_payoff_per_unit = max(floor - scenario_price, _DECIMAL_ZERO) capped_upside_per_unit = _DECIMAL_ZERO if cap_decimal is not None and scenario_price > cap_decimal: capped_upside_per_unit = -(scenario_price - cap_decimal) option_payoff_cash = Money(amount=gold_weight.amount * scenario_payoff_per_unit, currency=BaseCurrency.USD) capped_upside_cash = Money(amount=gold_weight.amount * capped_upside_per_unit, currency=BaseCurrency.USD) hedge_cost_cash = Money(amount=gold_weight.amount * cost, currency=BaseCurrency.USD) hedged_equity = unhedged_equity + option_payoff_cash + capped_upside_cash - hedge_cost_cash waterfall_steps = [ ("Base equity", round(_money_to_float(base_equity), 2)), ("Spot move", round(_money_to_float(scenario_gold_value - current_gold_value), 2)), ("Option payoff", round(_money_to_float(option_payoff_cash), 2)), ("Call cap", round(_money_to_float(capped_upside_cash), 2)), ("Hedge cost", round(_money_to_float(-hedge_cost_cash), 2)), ("Net equity", round(_money_to_float(hedged_equity), 2)), ] return { "strategy": strategy, "scenario_pct": scenario_pct, "scenario_price": round(float(scenario_price), 2), "scenario_series": [ {"price": round(float(price), 2), "benefit": benefit} for price, benefit in zip(scenario_prices, benefits, strict=True) ], "waterfall_steps": waterfall_steps, "unhedged_equity": round(_money_to_float(unhedged_equity), 2), "hedged_equity": round(_money_to_float(hedged_equity), 2), }