"""
Autograd-based Greeks computation.
Computes option sensitivities by running the pricing function through
the Tensor autograd engine and extracting gradients.
- Delta (dV/dS) via first-order autograd
- Vega (dV/dσ) via first-order autograd
- Gamma (d²V/dS²) via second-order autograd (gamma_autograd)
- Vanna (d²V/dS dσ) via second-order autograd (vanna_autograd)
- Volga (d²V/dσ²) via second-order autograd (volga_autograd)
"""
from __future__ import annotations
from collections.abc import Callable
import numpy as np
from tensorquantlib.core.second_order import (
second_order_greeks,
)
from tensorquantlib.core.tensor import Tensor
[docs]
def compute_greeks(
price_fn: Callable[..., Tensor],
S: float,
K: float,
T: float,
r: float,
sigma: float,
q: float = 0.0,
option_type: str = "call",
include_second_order: bool = True,
) -> dict[str, float]:
"""Compute Delta, Vega, Gamma (and optionally Vanna, Volga) using autograd.
First-order Greeks (Delta, Vega) come from a single backward pass.
Second-order Greeks (Gamma, Vanna, Volga) use the hybrid semi-analytic
method from ``tensorquantlib.core.second_order``: analytical gradients
differentiated once more by central differences (~1e-10 accuracy).
Args:
price_fn: Pricing function accepting
``(S, K, T, r, sigma, q, option_type)`` and returning a Tensor.
S: Spot price.
K: Strike price.
T: Time to expiry.
r: Risk-free rate.
sigma: Volatility.
q: Dividend yield.
option_type: ``'call'`` or ``'put'``.
include_second_order: If True (default), also compute Gamma, Vanna,
and Volga. Set False for speed when only
first-order Greeks are needed.
Returns:
Dict with keys ``'price'``, ``'delta'``, ``'vega'``, and (when
``include_second_order=True``) ``'gamma'``, ``'vanna'``, ``'volga'``.
"""
# ---- Delta & Vega via first-order autograd ----
S_t = Tensor(np.array(S, dtype=np.float64), requires_grad=True)
sigma_t = Tensor(np.array(sigma, dtype=np.float64), requires_grad=True)
price = price_fn(S_t, K, T, r, sigma_t, q, option_type)
price_val = float(price.data.item()) if price.data.size == 1 else float(price.data.sum())
if price.data.size > 1:
price.sum().backward()
else:
price.backward()
delta = (
float(S_t.grad.item())
if S_t.grad is not None and S_t.grad.size == 1
else (float(S_t.grad.sum()) if S_t.grad is not None else 0.0)
)
vega = (
float(sigma_t.grad.item())
if sigma_t.grad is not None and sigma_t.grad.size == 1
else (float(sigma_t.grad.sum()) if sigma_t.grad is not None else 0.0)
)
result: dict[str, float] = {
"price": price_val,
"delta": delta,
"vega": vega,
}
if include_second_order:
so = second_order_greeks(price_fn, S, K, T, r, sigma, q, option_type)
result["gamma"] = so["gamma"]
result["vanna"] = so["vanna"]
result["volga"] = so["volga"]
return result
[docs]
def compute_greeks_vectorized(
price_fn: Callable[..., Tensor],
S_array: np.ndarray,
K: float,
T: float,
r: float,
sigma: float,
q: float = 0.0,
option_type: str = "call",
) -> dict[str, np.ndarray]:
"""Compute Greeks for a vector of spot prices.
Args:
price_fn: Pricing function.
S_array: 1D array of spot prices.
K, T, r, sigma, q, option_type: Option parameters.
Returns:
Dict with 'price', 'delta', 'vega' as arrays matching S_array.
"""
S_t = Tensor(np.asarray(S_array, dtype=np.float64), requires_grad=True)
sigma_t = Tensor(np.array(sigma, dtype=np.float64), requires_grad=True)
price = price_fn(S_t, K, T, r, sigma_t, q, option_type)
if price.data.size > 1:
price.sum().backward()
else:
price.backward()
return {
"price": price.data.copy(),
"delta": S_t.grad.copy() if S_t.grad is not None else np.zeros_like(S_array),
"vega": np.full(
len(S_array),
float(sigma_t.grad.item()) if sigma_t.grad is not None else 0.0,
),
}