Files
vault-dash/app/models/strategy.py
Bu5hm4nn 00a68bc767 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
2026-03-21 19:21:40 +01:00

102 lines
4.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Literal
from .option import OptionContract
StrategyType = Literal["single_put", "laddered_put", "collar"]
@dataclass(frozen=True)
class ScenarioResult:
"""Scenario output for a hedging strategy."""
underlying_price: float
gross_option_payoff: float
hedge_cost: float
net_option_benefit: float
@dataclass(frozen=True)
class HedgingStrategy:
"""Collection of option positions representing a hedge.
Notes:
Premiums on long positions are positive cash outflows. Premiums on
short positions are handled through ``short_contracts`` and reduce the
total hedge cost.
"""
strategy_type: StrategyType
long_contracts: tuple[OptionContract, ...] = field(default_factory=tuple)
short_contracts: tuple[OptionContract, ...] = field(default_factory=tuple)
description: str = ""
def __post_init__(self) -> None:
if self.strategy_type not in {"single_put", "laddered_put", "collar"}:
raise ValueError("unsupported strategy_type")
if not self.long_contracts and not self.short_contracts:
raise ValueError("at least one option contract is required")
if self.strategy_type == "single_put":
if len(self.long_contracts) != 1 or self.long_contracts[0].option_type != "put":
raise ValueError("single_put requires exactly one long put contract")
if self.short_contracts:
raise ValueError("single_put cannot include short contracts")
if self.strategy_type == "laddered_put":
if len(self.long_contracts) < 2:
raise ValueError("laddered_put requires at least two long put contracts")
if any(contract.option_type != "put" for contract in self.long_contracts):
raise ValueError("laddered_put supports only long put contracts")
if self.short_contracts:
raise ValueError("laddered_put cannot include short contracts")
if self.strategy_type == "collar":
if not self.long_contracts or not self.short_contracts:
raise ValueError("collar requires both long and short contracts")
if any(contract.option_type != "put" for contract in self.long_contracts):
raise ValueError("collar long leg must be put options")
if any(contract.option_type != "call" for contract in self.short_contracts):
raise ValueError("collar short leg must be call options")
@property
def hedge_cost(self) -> float:
"""Net upfront hedge cost."""
long_cost = sum(contract.total_premium for contract in self.long_contracts)
short_credit = sum(contract.total_premium for contract in self.short_contracts)
return long_cost - short_credit
def gross_payoff(self, underlying_price: float) -> float:
"""Gross expiry payoff from all option legs."""
if underlying_price <= 0:
raise ValueError("underlying_price must be positive")
long_payoff = sum(contract.payoff(underlying_price) for contract in self.long_contracts)
short_payoff = sum(contract.payoff(underlying_price) for contract in self.short_contracts)
return long_payoff - short_payoff
def net_benefit(self, underlying_price: float) -> float:
"""Net value added by the hedge after premium cost."""
return self.gross_payoff(underlying_price) - self.hedge_cost
def scenario_analysis(self, underlying_prices: list[float] | tuple[float, ...]) -> list[ScenarioResult]:
"""Evaluate the hedge across alternative underlying-price scenarios."""
if not underlying_prices:
raise ValueError("underlying_prices must not be empty")
results: list[ScenarioResult] = []
for price in underlying_prices:
if price <= 0:
raise ValueError("scenario prices must be positive")
gross_payoff = self.gross_payoff(price)
results.append(
ScenarioResult(
underlying_price=price,
gross_option_payoff=gross_payoff,
hedge_cost=self.hedge_cost,
net_option_benefit=gross_payoff - self.hedge_cost,
)
)
return results