Initial commit: Vault Dashboard for options hedging
- 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
This commit is contained in:
139
app/strategies/protective_put.py
Normal file
139
app/strategies/protective_put.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, timedelta
|
||||
|
||||
from app.core.pricing.black_scholes import BlackScholesInputs, black_scholes_price_and_greeks
|
||||
from app.models.option import Greeks, OptionContract
|
||||
from app.models.strategy import HedgingStrategy
|
||||
from app.strategies.base import BaseStrategy, StrategyConfig
|
||||
|
||||
DEFAULT_SCENARIO_CHANGES = (-0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProtectivePutSpec:
|
||||
label: str
|
||||
strike_pct: float
|
||||
months: int = 12
|
||||
|
||||
|
||||
class ProtectivePutStrategy(BaseStrategy):
|
||||
"""Single-leg protective put strategy using ATM or configurable OTM strikes."""
|
||||
|
||||
def __init__(self, config: StrategyConfig, spec: ProtectivePutSpec) -> None:
|
||||
super().__init__(config)
|
||||
self.spec = spec
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return f"protective_put_{self.spec.label.lower()}"
|
||||
|
||||
@property
|
||||
def hedge_units(self) -> float:
|
||||
return self.config.portfolio.gold_value / self.config.spot_price
|
||||
|
||||
@property
|
||||
def strike(self) -> float:
|
||||
return self.config.spot_price * self.spec.strike_pct
|
||||
|
||||
@property
|
||||
def term_years(self) -> float:
|
||||
return self.spec.months / 12.0
|
||||
|
||||
def build_contract(self) -> OptionContract:
|
||||
pricing = black_scholes_price_and_greeks(
|
||||
BlackScholesInputs(
|
||||
spot=self.config.spot_price,
|
||||
strike=self.strike,
|
||||
time_to_expiry=self.term_years,
|
||||
risk_free_rate=self.config.risk_free_rate,
|
||||
volatility=self.config.volatility,
|
||||
option_type="put",
|
||||
)
|
||||
)
|
||||
return OptionContract(
|
||||
option_type="put",
|
||||
strike=self.strike,
|
||||
expiry=date.today() + timedelta(days=max(1, round(365 * self.term_years))),
|
||||
premium=pricing.price,
|
||||
quantity=1.0,
|
||||
contract_size=self.hedge_units,
|
||||
underlying_price=self.config.spot_price,
|
||||
greeks=Greeks(
|
||||
delta=pricing.delta,
|
||||
gamma=pricing.gamma,
|
||||
theta=pricing.theta,
|
||||
vega=pricing.vega,
|
||||
rho=pricing.rho,
|
||||
),
|
||||
)
|
||||
|
||||
def build_hedging_strategy(self) -> HedgingStrategy:
|
||||
return HedgingStrategy(
|
||||
strategy_type="single_put",
|
||||
long_contracts=(self.build_contract(),),
|
||||
description=f"{self.spec.label} protective put",
|
||||
)
|
||||
|
||||
def calculate_cost(self) -> dict:
|
||||
contract = self.build_contract()
|
||||
total_cost = contract.total_premium
|
||||
return {
|
||||
"strategy": self.name,
|
||||
"label": self.spec.label,
|
||||
"strike": round(contract.strike, 2),
|
||||
"strike_pct": self.spec.strike_pct,
|
||||
"premium_per_share": round(contract.premium, 4),
|
||||
"total_cost": round(total_cost, 2),
|
||||
"cost_pct_of_portfolio": round(total_cost / self.config.portfolio.gold_value, 6),
|
||||
"term_months": self.spec.months,
|
||||
"annualized_cost": round(total_cost / self.term_years, 2),
|
||||
"annualized_cost_pct": round((total_cost / self.term_years) / self.config.portfolio.gold_value, 6),
|
||||
}
|
||||
|
||||
def calculate_protection(self) -> dict:
|
||||
contract = self.build_contract()
|
||||
threshold_price = self.config.portfolio.margin_call_price()
|
||||
payoff_at_threshold = contract.payoff(threshold_price)
|
||||
hedged_value_at_threshold = self.config.portfolio.gold_value_at_price(threshold_price) + payoff_at_threshold
|
||||
protected_ltv = self.config.portfolio.loan_amount / hedged_value_at_threshold
|
||||
floor_value = contract.strike * self.hedge_units
|
||||
return {
|
||||
"strategy": self.name,
|
||||
"threshold_price": round(threshold_price, 2),
|
||||
"strike": round(contract.strike, 2),
|
||||
"portfolio_floor_value": round(floor_value, 2),
|
||||
"unhedged_ltv_at_threshold": round(self.config.portfolio.ltv_at_price(threshold_price), 6),
|
||||
"hedged_ltv_at_threshold": round(protected_ltv, 6),
|
||||
"payoff_at_threshold": round(payoff_at_threshold, 2),
|
||||
"maintains_margin_call_buffer": protected_ltv < self.config.portfolio.margin_call_ltv,
|
||||
}
|
||||
|
||||
def get_scenarios(self) -> list[dict]:
|
||||
strategy = self.build_hedging_strategy()
|
||||
scenarios: list[dict] = []
|
||||
for change in DEFAULT_SCENARIO_CHANGES:
|
||||
price = self.config.spot_price * (1 + change)
|
||||
if price <= 0:
|
||||
continue
|
||||
gold_value = self.config.portfolio.gold_value_at_price(price)
|
||||
option_payoff = strategy.gross_payoff(price)
|
||||
hedged_collateral = gold_value + option_payoff
|
||||
scenarios.append(
|
||||
{
|
||||
"price_change_pct": round(change, 2),
|
||||
"gld_price": round(price, 2),
|
||||
"gold_value": round(gold_value, 2),
|
||||
"option_payoff": round(option_payoff, 2),
|
||||
"hedge_cost": round(strategy.hedge_cost, 2),
|
||||
"net_portfolio_value": round(gold_value + option_payoff - strategy.hedge_cost, 2),
|
||||
"unhedged_ltv": round(self.config.portfolio.loan_amount / gold_value, 6),
|
||||
"hedged_ltv": round(self.config.portfolio.loan_amount / hedged_collateral, 6),
|
||||
"margin_call_without_hedge": (self.config.portfolio.loan_amount / gold_value)
|
||||
>= self.config.portfolio.margin_call_ltv,
|
||||
"margin_call_with_hedge": (self.config.portfolio.loan_amount / hedged_collateral)
|
||||
>= self.config.portfolio.margin_call_ltv,
|
||||
}
|
||||
)
|
||||
return scenarios
|
||||
Reference in New Issue
Block a user