- Set ruff/black line length to 120 - Reformatted code with black - Fixed import ordering with ruff - Disabled lint for UI component files with long CSS strings - Updated pyproject.toml with proper tool configuration
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import date
|
|
from typing import Literal
|
|
|
|
OptionType = Literal["call", "put"]
|
|
OptionMoneyness = Literal["ITM", "ATM", "OTM"]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Greeks:
|
|
"""Option Greeks container."""
|
|
|
|
delta: float = 0.0
|
|
gamma: float = 0.0
|
|
theta: float = 0.0
|
|
vega: float = 0.0
|
|
rho: float = 0.0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class OptionContract:
|
|
"""Vanilla option contract used in hedging strategies.
|
|
|
|
Attributes:
|
|
option_type: Contract type, either ``"put"`` or ``"call"``.
|
|
strike: Strike price.
|
|
expiry: Expiration date.
|
|
premium: Premium paid or received per unit of underlying.
|
|
quantity: Number of contracts or units.
|
|
contract_size: Underlying units per contract.
|
|
underlying_price: Current underlying spot price for classification.
|
|
greeks: Stored option Greeks.
|
|
"""
|
|
|
|
option_type: OptionType
|
|
strike: float
|
|
expiry: date
|
|
premium: float
|
|
quantity: float = 1.0
|
|
contract_size: float = 1.0
|
|
underlying_price: float | None = None
|
|
greeks: Greeks = Greeks()
|
|
|
|
def __post_init__(self) -> None:
|
|
option = self.option_type.lower()
|
|
if option not in {"call", "put"}:
|
|
raise ValueError("option_type must be either 'call' or 'put'")
|
|
object.__setattr__(self, "option_type", option)
|
|
|
|
if self.strike <= 0:
|
|
raise ValueError("strike must be positive")
|
|
if self.premium < 0:
|
|
raise ValueError("premium must be non-negative")
|
|
if self.quantity <= 0:
|
|
raise ValueError("quantity must be positive")
|
|
if self.contract_size <= 0:
|
|
raise ValueError("contract_size must be positive")
|
|
if self.expiry <= date.today():
|
|
raise ValueError("expiry must be in the future")
|
|
if self.underlying_price is not None and self.underlying_price <= 0:
|
|
raise ValueError("underlying_price must be positive when provided")
|
|
|
|
@property
|
|
def notional_units(self) -> float:
|
|
"""Underlying units covered by the contract position."""
|
|
return self.quantity * self.contract_size
|
|
|
|
@property
|
|
def total_premium(self) -> float:
|
|
"""Total premium paid or received for the position."""
|
|
return self.premium * self.notional_units
|
|
|
|
def classify_moneyness(
|
|
self, underlying_price: float | None = None, *, atm_tolerance: float = 0.01
|
|
) -> OptionMoneyness:
|
|
"""Classify the contract as ITM, ATM, or OTM.
|
|
|
|
Args:
|
|
underlying_price: Spot price used for classification. Falls back to
|
|
``self.underlying_price``.
|
|
atm_tolerance: Relative tolerance around strike treated as at-the-money.
|
|
"""
|
|
spot = self.underlying_price if underlying_price is None else underlying_price
|
|
if spot is None:
|
|
raise ValueError("underlying_price must be provided for strategy classification")
|
|
if spot <= 0:
|
|
raise ValueError("underlying_price must be positive")
|
|
if atm_tolerance < 0:
|
|
raise ValueError("atm_tolerance must be non-negative")
|
|
|
|
relative_gap = abs(spot - self.strike) / self.strike
|
|
if relative_gap <= atm_tolerance:
|
|
return "ATM"
|
|
|
|
if self.option_type == "put":
|
|
return "ITM" if self.strike > spot else "OTM"
|
|
return "ITM" if self.strike < spot else "OTM"
|
|
|
|
def intrinsic_value(self, underlying_price: float) -> float:
|
|
"""Intrinsic value per underlying unit at a given spot price."""
|
|
if underlying_price <= 0:
|
|
raise ValueError("underlying_price must be positive")
|
|
if self.option_type == "put":
|
|
return max(self.strike - underlying_price, 0.0)
|
|
return max(underlying_price - self.strike, 0.0)
|
|
|
|
def payoff(self, underlying_price: float) -> float:
|
|
"""Gross payoff of the option position at expiry."""
|
|
return self.intrinsic_value(underlying_price) * self.notional_units
|