"""
greche.py - Calcolo delle Greche di primo e secondo ordine
==========================================================
Companion code per "Trading con le Opzioni - Strategie Operative"
di Pierpaolo Marturano (Core Matrix S.r.l.)

Calcola: Delta, Gamma, Theta, Vega, Rho, Vanna, Volga (Vomma), Charm

Requisiti: numpy, scipy
Compatibile con Python 3.10+
"""

import numpy as np
from scipy.stats import norm


def d1(S: float, K: float, T: float, r: float, sigma: float, q: float = 0.0) -> float:
    """Calcola d1 della formula Black-Scholes."""
    return (np.log(S / K) + (r - q + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))


def d2(S: float, K: float, T: float, r: float, sigma: float, q: float = 0.0) -> float:
    """Calcola d2 della formula Black-Scholes."""
    return d1(S, K, T, r, sigma, q) - sigma * np.sqrt(T)


# =============================================================================
# GRECHE DI PRIMO ORDINE
# =============================================================================

def delta(S: float, K: float, T: float, r: float, sigma: float,
          option_type: str = "call", q: float = 0.0) -> float:
    """
    Delta: sensibilità del prezzo dell'opzione al movimento del sottostante.

    Parameters
    ----------
    S : float - Prezzo del sottostante
    K : float - Strike price
    T : float - Tempo a scadenza (in anni)
    r : float - Tasso risk-free (es. 0.05 per 5%)
    sigma : float - Volatilità implicita (es. 0.20 per 20%)
    option_type : str - "call" o "put"
    q : float - Dividend yield continuo (default 0)

    Returns
    -------
    float : Delta dell'opzione
    """
    d1_val = d1(S, K, T, r, sigma, q)
    if option_type == "call":
        return np.exp(-q * T) * norm.cdf(d1_val)
    else:
        return np.exp(-q * T) * (norm.cdf(d1_val) - 1)


def gamma(S: float, K: float, T: float, r: float, sigma: float,
          q: float = 0.0) -> float:
    """
    Gamma: tasso di variazione del delta rispetto al sottostante.
    Identico per call e put.
    """
    d1_val = d1(S, K, T, r, sigma, q)
    return np.exp(-q * T) * norm.pdf(d1_val) / (S * sigma * np.sqrt(T))


def theta(S: float, K: float, T: float, r: float, sigma: float,
          option_type: str = "call", q: float = 0.0) -> float:
    """
    Theta: decadimento temporale (per giorno di calendario).
    Restituisce un valore negativo per opzioni long.
    """
    d1_val = d1(S, K, T, r, sigma, q)
    d2_val = d2(S, K, T, r, sigma, q)

    term1 = -(S * np.exp(-q * T) * norm.pdf(d1_val) * sigma) / (2 * np.sqrt(T))

    if option_type == "call":
        term2 = q * S * np.exp(-q * T) * norm.cdf(d1_val)
        term3 = -r * K * np.exp(-r * T) * norm.cdf(d2_val)
    else:
        term2 = -q * S * np.exp(-q * T) * norm.cdf(-d1_val)
        term3 = r * K * np.exp(-r * T) * norm.cdf(-d2_val)

    # Diviso 365 per ottenere theta giornaliero
    return (term1 + term2 + term3) / 365


def vega(S: float, K: float, T: float, r: float, sigma: float,
         q: float = 0.0) -> float:
    """
    Vega: sensibilità a variazioni di 1 punto percentuale di IV.
    Identico per call e put. Restituisce il valore per 1% di variazione.
    """
    d1_val = d1(S, K, T, r, sigma, q)
    return S * np.exp(-q * T) * norm.pdf(d1_val) * np.sqrt(T) / 100


def rho(S: float, K: float, T: float, r: float, sigma: float,
        option_type: str = "call", q: float = 0.0) -> float:
    """
    Rho: sensibilità a variazioni di 1% del tasso risk-free.
    """
    d2_val = d2(S, K, T, r, sigma, q)
    if option_type == "call":
        return K * T * np.exp(-r * T) * norm.cdf(d2_val) / 100
    else:
        return -K * T * np.exp(-r * T) * norm.cdf(-d2_val) / 100


# =============================================================================
# GRECHE DI SECONDO ORDINE
# =============================================================================

def vanna(S: float, K: float, T: float, r: float, sigma: float,
          q: float = 0.0) -> float:
    """
    Vanna: sensibilità del delta alla volatilità (dDelta/dSigma).
    Equivale anche a dVega/dS.
    """
    d1_val = d1(S, K, T, r, sigma, q)
    d2_val = d2(S, K, T, r, sigma, q)
    return -np.exp(-q * T) * norm.pdf(d1_val) * d2_val / sigma


def volga(S: float, K: float, T: float, r: float, sigma: float,
          q: float = 0.0) -> float:
    """
    Volga (Vomma): sensibilità del vega alla volatilità (dVega/dSigma).
    """
    d1_val = d1(S, K, T, r, sigma, q)
    d2_val = d2(S, K, T, r, sigma, q)
    v = vega(S, K, T, r, sigma, q) * 100  # vega non scalato
    return v * d1_val * d2_val / sigma


def charm(S: float, K: float, T: float, r: float, sigma: float,
          option_type: str = "call", q: float = 0.0) -> float:
    """
    Charm (Delta Decay): variazione del delta nel tempo (dDelta/dT).
    Restituisce la variazione giornaliera.
    """
    d1_val = d1(S, K, T, r, sigma, q)
    d2_val = d2(S, K, T, r, sigma, q)

    term1 = q * np.exp(-q * T) * norm.cdf(d1_val) if option_type == "call" \
        else -q * np.exp(-q * T) * norm.cdf(-d1_val)

    term2 = np.exp(-q * T) * norm.pdf(d1_val) * (
        2 * (r - q) * T - d2_val * sigma * np.sqrt(T)
    ) / (2 * T * sigma * np.sqrt(T))

    # Per giorno di calendario
    if option_type == "call":
        return -(term1 + term2) / 365
    else:
        return -(term1 - term2) / 365


# =============================================================================
# FUNZIONE RIASSUNTIVA
# =============================================================================

def all_greeks(S: float, K: float, T: float, r: float, sigma: float,
               option_type: str = "call", q: float = 0.0) -> dict:
    """
    Calcola tutte le greche in un'unica chiamata.

    Returns
    -------
    dict con tutte le greche di primo e secondo ordine
    """
    return {
        "delta": delta(S, K, T, r, sigma, option_type, q),
        "gamma": gamma(S, K, T, r, sigma, q),
        "theta": theta(S, K, T, r, sigma, option_type, q),
        "vega": vega(S, K, T, r, sigma, q),
        "rho": rho(S, K, T, r, sigma, option_type, q),
        "vanna": vanna(S, K, T, r, sigma, q),
        "volga": volga(S, K, T, r, sigma, q),
        "charm": charm(S, K, T, r, sigma, option_type, q),
    }


# =============================================================================
# BLACK-SCHOLES PRICING
# =============================================================================

def bs_price(S: float, K: float, T: float, r: float, sigma: float,
             option_type: str = "call", q: float = 0.0) -> float:
    """Prezzo Black-Scholes di un'opzione europea."""
    d1_val = d1(S, K, T, r, sigma, q)
    d2_val = d2(S, K, T, r, sigma, q)

    if option_type == "call":
        return S * np.exp(-q * T) * norm.cdf(d1_val) - K * np.exp(-r * T) * norm.cdf(d2_val)
    else:
        return K * np.exp(-r * T) * norm.cdf(-d2_val) - S * np.exp(-q * T) * norm.cdf(-d1_val)


# =============================================================================
# ESEMPIO D'USO
# =============================================================================

if __name__ == "__main__":
    # Parametri esempio: SPX call ATM, 45 DTE
    S = 5500       # Prezzo SPX
    K = 5500       # Strike ATM
    T = 45 / 365   # 45 giorni
    r = 0.045      # Risk-free rate 4.5%
    sigma = 0.16   # IV 16%

    print("=" * 60)
    print("GRECHE - Esempio: SPX 5500 Call, 45 DTE, IV=16%")
    print("=" * 60)

    price = bs_price(S, K, T, r, sigma, "call")
    greeks = all_greeks(S, K, T, r, sigma, "call")

    print(f"\nPrezzo Black-Scholes: ${price:.2f}")
    print(f"\n--- Greche di Primo Ordine ---")
    print(f"  Delta:  {greeks['delta']:+.4f}")
    print(f"  Gamma:  {greeks['gamma']:.6f}")
    print(f"  Theta:  {greeks['theta']:+.4f} $/giorno")
    print(f"  Vega:   {greeks['vega']:+.4f} $/1% IV")
    print(f"  Rho:    {greeks['rho']:+.4f} $/1% rate")
    print(f"\n--- Greche di Secondo Ordine ---")
    print(f"  Vanna:  {greeks['vanna']:+.6f}")
    print(f"  Volga:  {greeks['volga']:+.4f}")
    print(f"  Charm:  {greeks['charm']:+.6f} /giorno")

    print(f"\n--- Confronto Call vs Put (stessi parametri) ---")
    put_greeks = all_greeks(S, K, T, r, sigma, "put")
    print(f"  Put Delta:  {put_greeks['delta']:+.4f}")
    print(f"  Put Theta:  {put_greeks['theta']:+.4f} $/giorno")
    print(f"  Put Rho:    {put_greeks['rho']:+.4f} $/1% rate")
