from __future__ import annotations from dataclasses import dataclass from typing import Literal from app.core.pricing.black_scholes import ( DEFAULT_GLD_PRICE, DEFAULT_RISK_FREE_RATE, DEFAULT_VOLATILITY, ) from app.models.portfolio import LombardPortfolio from app.services.strategy_templates import StrategyTemplateService from app.strategies.base import BaseStrategy, StrategyConfig from app.strategies.lease import LeaseStrategy RiskProfile = Literal["conservative", "balanced", "cost_sensitive"] RESEARCH_PORTFOLIO_VALUE = 1_000_000.0 RESEARCH_LOAN_AMOUNT = 600_000.0 RESEARCH_MARGIN_CALL_THRESHOLD = 0.75 RESEARCH_GLD_SPOT = 460.0 RESEARCH_VOLATILITY = 0.16 RESEARCH_RISK_FREE_RATE = 0.045 @dataclass(frozen=True) class StrategySelectionEngine: """Compare paper strategies and recommend the best fit by risk profile.""" portfolio_value: float = RESEARCH_PORTFOLIO_VALUE loan_amount: float = RESEARCH_LOAN_AMOUNT margin_call_threshold: float = RESEARCH_MARGIN_CALL_THRESHOLD spot_price: float = RESEARCH_GLD_SPOT volatility: float = RESEARCH_VOLATILITY risk_free_rate: float = RESEARCH_RISK_FREE_RATE template_service: StrategyTemplateService | None = None def _config(self) -> StrategyConfig: portfolio = LombardPortfolio( gold_ounces=self.portfolio_value / self.spot_price, gold_price_per_ounce=self.spot_price, loan_amount=self.loan_amount, initial_ltv=self.loan_amount / self.portfolio_value, margin_call_ltv=self.margin_call_threshold, ) return StrategyConfig( portfolio=portfolio, spot_price=self.spot_price, volatility=self.volatility, risk_free_rate=self.risk_free_rate, ) def _strategies(self) -> list[BaseStrategy]: config = self._config() template_service = self.template_service or StrategyTemplateService() template_strategies = [ template_service.build_strategy_from_template(config, template) for template in template_service.list_active_templates("GLD") ] return [*template_strategies, LeaseStrategy(config)] def compare_all_strategies(self) -> list[dict]: comparisons: list[dict] = [] for strategy in self._strategies(): cost = strategy.calculate_cost() protection = strategy.calculate_protection() scenarios = strategy.get_scenarios() annual_cost = cost.get("annualized_cost", cost.get("lowest_annual_cost", 0.0)) protection_ltv = protection.get("hedged_ltv_at_threshold") if protection_ltv is None: duration_rows = protection.get("durations", []) protection_ltv = min( (row["hedged_ltv_at_threshold"] for row in duration_rows), default=1.0, ) comparisons.append( { "name": strategy.name, "cost": cost, "protection": protection, "scenarios": scenarios, "score_inputs": { "annual_cost": annual_cost, "hedged_ltv_at_threshold": protection_ltv, }, } ) return comparisons def recommend(self, risk_profile: RiskProfile = "balanced") -> dict: comparisons = self.compare_all_strategies() def score(item: dict) -> tuple[float, float]: annual_cost = item["score_inputs"]["annual_cost"] hedged_ltv = item["score_inputs"]["hedged_ltv_at_threshold"] if risk_profile == "conservative": return (hedged_ltv, annual_cost) if risk_profile == "cost_sensitive": return (annual_cost, hedged_ltv) return (hedged_ltv + (annual_cost / self.portfolio_value), annual_cost) recommended = min(comparisons, key=score) return { "risk_profile": risk_profile, "recommended_strategy": recommended["name"], "rationale": { "portfolio_value": self.portfolio_value, "loan_amount": self.loan_amount, "margin_call_threshold": self.margin_call_threshold, "spot_price": self.spot_price, "volatility": self.volatility, "risk_free_rate": self.risk_free_rate, }, "comparison_summary": [ { "name": item["name"], "annual_cost": round(item["score_inputs"]["annual_cost"], 2), "hedged_ltv_at_threshold": round(item["score_inputs"]["hedged_ltv_at_threshold"], 6), } for item in comparisons ], } def sensitivity_analysis(self) -> dict: results: dict[str, list[dict]] = {"volatility": [], "spot_price": []} for volatility in (0.12, 0.16, 0.20): engine = StrategySelectionEngine( portfolio_value=self.portfolio_value, loan_amount=self.loan_amount, margin_call_threshold=self.margin_call_threshold, spot_price=self.spot_price, volatility=volatility, risk_free_rate=self.risk_free_rate, template_service=self.template_service, ) recommendation = engine.recommend("balanced") results["volatility"].append( { "volatility": volatility, "recommended_strategy": recommendation["recommended_strategy"], } ) for spot_price in ( DEFAULT_GLD_PRICE * 0.9, DEFAULT_GLD_PRICE, DEFAULT_GLD_PRICE * 1.1, ): engine = StrategySelectionEngine( portfolio_value=self.portfolio_value, loan_amount=self.loan_amount, margin_call_threshold=self.margin_call_threshold, spot_price=spot_price, volatility=DEFAULT_VOLATILITY, risk_free_rate=DEFAULT_RISK_FREE_RATE, template_service=self.template_service, ) recommendation = engine.recommend("balanced") results["spot_price"].append( { "spot_price": round(spot_price, 2), "recommended_strategy": recommendation["recommended_strategy"], } ) return results