- FastAPI + NiceGUI web application - QuantLib-based Black-Scholes pricing with Greeks - Protective put, laddered, and LEAPS strategies - Real-time WebSocket updates - TradingView-style charts via Lightweight-Charts - Docker containerization - GitLab CI/CD pipeline for VPS deployment - VPN-only access configuration
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
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
|