from __future__ import annotations from dataclasses import dataclass from app.strategies.base import BaseStrategy, StrategyConfig from app.strategies.protective_put import ProtectivePutSpec, ProtectivePutStrategy @dataclass(frozen=True) class LeaseAnalysisSpec: strike_pct: float = 1.0 durations_months: tuple[int, ...] = (3, 6, 12, 18, 24) class LeaseStrategy(BaseStrategy): """LEAPS duration analysis with roll timing and annualized cost comparison.""" def __init__(self, config: StrategyConfig, spec: LeaseAnalysisSpec | None = None) -> None: super().__init__(config) self.spec = spec or LeaseAnalysisSpec() @property def name(self) -> str: return "lease_duration_analysis" def _protective_put(self, months: int) -> ProtectivePutStrategy: return ProtectivePutStrategy( self.config, ProtectivePutSpec(label=f"LEAPS_{months}M", strike_pct=self.spec.strike_pct, months=months), ) def _duration_rows(self) -> list[dict]: rows: list[dict] = [] for months in self.spec.durations_months: strategy = self._protective_put(months) cost = strategy.calculate_cost() rolls_per_year = 12 / months rows.append( { "months": months, "strike": cost["strike"], "premium_per_share": cost["premium_per_share"], "total_cost": cost["total_cost"], "annualized_cost": cost["annualized_cost"], "annualized_cost_pct": cost["annualized_cost_pct"], "rolls_per_year": round(rolls_per_year, 4), "recommended_roll_month": max(1, months - 1), } ) return rows def calculate_cost(self) -> dict: rows = self._duration_rows() optimal = min(rows, key=lambda item: item["annualized_cost"]) return { "strategy": self.name, "comparison": rows, "optimal_duration_months": optimal["months"], "lowest_annual_cost": optimal["annualized_cost"], "lowest_annual_cost_pct": optimal["annualized_cost_pct"], } def calculate_protection(self) -> dict: threshold_price = self.config.portfolio.margin_call_price() rows: list[dict] = [] for months in self.spec.durations_months: strategy = self._protective_put(months) protection = strategy.calculate_protection() rows.append( { "months": months, "payoff_at_threshold": protection["payoff_at_threshold"], "hedged_ltv_at_threshold": protection["hedged_ltv_at_threshold"], "maintains_margin_call_buffer": protection["maintains_margin_call_buffer"], } ) return { "strategy": self.name, "threshold_price": round(threshold_price, 2), "durations": rows, } def get_scenarios(self) -> list[dict]: scenarios: list[dict] = [] for months in self.spec.durations_months: strategy = self._protective_put(months) scenarios.append( { "months": months, "annualized_cost": strategy.calculate_cost()["annualized_cost"], "annualized_cost_pct": strategy.calculate_cost()["annualized_cost_pct"], "sample_scenarios": strategy.get_scenarios(), } ) return scenarios