Skip to content

Techniques

The techniques package provides the various numerical and analytical methods for pricing options.

AmericanMonteCarloTechnique

AmericanMonteCarloTechnique(
    *,
    n_paths: int = 20000,
    n_steps: int = 100,
    antithetic: bool = True,
    seed: int | None = None,
    lsm_degree: int = 2,
)

Bases: BaseTechnique, GreekMixin, IVMixin

Prices American options by simulating full SDE paths and applying the Longstaff-Schwartz algorithm.

Source code in src/optpricing/techniques/american_monte_carlo.py
def __init__(
    self,
    *,
    n_paths: int = 20_000,
    n_steps: int = 100,
    antithetic: bool = True,
    seed: int | None = None,
    lsm_degree: int = 2,
):
    if antithetic and n_paths % 2 != 0:
        n_paths += 1
    self.n_paths = n_paths
    self.n_steps = n_steps
    self.antithetic = antithetic
    self.rng = np.random.default_rng(seed)
    self.lsm_degree = lsm_degree

BaseTechnique

Bases: ABC

Abstract base class for all pricing methodologies.

A technique defines the algorithm used to compute a price from the core 'atoms' (Option, Stock, Rate) and a given financial 'Model'.

price abstractmethod

price(
    option: Option | ndarray,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> PricingResult | np.ndarray

Calculate the price of an option.

Parameters:

Name Type Description Default
option Option | ndarray

The option contract(s) to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the calculation.

required
rate Rate

The risk-free rate structure.

required
**kwargs Any

Additional keyword arguments required by specific techniques or models.

{}

Returns:

Type Description
PricingResult

An object containing the calculated price and potentially other metrics.

Source code in src/optpricing/techniques/base/base_technique.py
@abstractmethod
def price(
    self,
    option: Option | np.ndarray,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> PricingResult | np.ndarray:
    """
    Calculate the price of an option.

    Parameters
    ----------
    option : Option | np.ndarray
        The option contract(s) to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the calculation.
    rate : Rate
        The risk-free rate structure.
    **kwargs : Any
        Additional keyword arguments required by specific techniques or models.

    Returns
    -------
    PricingResult
        An object containing the calculated price and potentially other metrics.
    """
    raise NotImplementedError

CRRTechnique

CRRTechnique(steps: int = 200, is_american: bool = False)

Bases: LatticeTechnique

Cox-Ross-Rubinstein binomial lattice technique.

Source code in src/optpricing/techniques/base/lattice_technique.py
def __init__(
    self,
    steps: int = 200,
    is_american: bool = False,
):
    """
    Initializes the lattice technique.

    Parameters
    ----------
    steps : int, optional
        The number of time steps in the lattice, by default 200.
    is_american : bool, optional
        True if pricing an American option, False for European. Defaults to False.
    """
    self.steps = int(steps)
    self.is_american = bool(is_american)
    self._cached_nodes: dict[str, Any] = {}
    self._cache_key: tuple | None = None

ClosedFormTechnique

ClosedFormTechnique(*, use_analytic_greeks: bool = True)

Bases: BaseTechnique, GreekMixin, IVMixin

A pricing technique for models that provide a closed-form solution.

This class acts as a generic wrapper. It calls the price_closed_form method on a given model. It also intelligently uses analytic Greeks if the model provides them, otherwise falling back to the finite-difference methods from GreekMixin.

Initializes the technique.

Parameters:

Name Type Description Default
use_analytic_greeks bool

If True, the technique will use the model's specific analytic Greek methods (e.g., delta_analytic) if they exist. If False or if the methods don't exist, it falls back to finite differences. Default is True.

True
Source code in src/optpricing/techniques/closed_form.py
def __init__(
    self,
    *,
    use_analytic_greeks: bool = True,
):
    """
    Initializes the technique.

    Parameters
    ----------
    use_analytic_greeks : bool, optional
        If True, the technique will use the model's specific analytic Greek
        methods (e.g., `delta_analytic`) if they exist. If False or if the
        methods don't exist, it falls back to finite differences.
        Default is True.
    """
    self.use_analytic_greeks = use_analytic_greeks

delta

delta(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float

Overrides GreekMixin to use analytic delta if available.

Parameters:

Name Type Description Default
option Option | ZeroCouponBond

The instrument to be priced.

required
stock Stock

The underlying asset's properties. For rate models, stock.spot is re-interpreted as the initial short rate r0.

required
model BaseModel

The financial model to use. Must have has_closed_form=True.

required
rate Rate

The risk-free rate structure.

required
Source code in src/optpricing/techniques/closed_form.py
def delta(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float:
    """Overrides GreekMixin to use analytic delta if available.

    Parameters
    ----------
    option : Option | ZeroCouponBond
        The instrument to be priced.
    stock : Stock
        The underlying asset's properties. For rate models, `stock.spot` is
        re-interpreted as the initial short rate `r0`.
    model : BaseModel
        The financial model to use. Must have `has_closed_form=True`.
    rate : Rate
        The risk-free rate structure.
    """
    if self.use_analytic_greeks and hasattr(model, "delta_analytic"):
        return model.delta_analytic(
            spot=stock.spot,
            strike=option.strike,
            r=rate.get_rate(option.maturity),
            q=stock.dividend,
            t=option.maturity,
            call=(option.option_type is OptionType.CALL),
        )
    return super().delta(option, stock, model, rate, **kwargs)

gamma

gamma(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float

Overrides GreekMixin to use analytic gamma if available.

Parameters:

Name Type Description Default
option Option | ZeroCouponBond

The instrument to be priced.

required
stock Stock

The underlying asset's properties. For rate models, stock.spot is re-interpreted as the initial short rate r0.

required
model BaseModel

The financial model to use. Must have has_closed_form=True.

required
rate Rate

The risk-free rate structure.

required
Source code in src/optpricing/techniques/closed_form.py
def gamma(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float:
    """Overrides GreekMixin to use analytic gamma if available.

    Parameters
    ----------
    option : Option | ZeroCouponBond
        The instrument to be priced.
    stock : Stock
        The underlying asset's properties. For rate models, `stock.spot` is
        re-interpreted as the initial short rate `r0`.
    model : BaseModel
        The financial model to use. Must have `has_closed_form=True`.
    rate : Rate
        The risk-free rate structure.
    """
    if self.use_analytic_greeks and hasattr(model, "gamma_analytic"):
        return model.gamma_analytic(
            spot=stock.spot,
            strike=option.strike,
            r=rate.get_rate(option.maturity),
            q=stock.dividend,
            t=option.maturity,
        )
    return super().gamma(option, stock, model, rate, **kwargs)

price

price(
    option: Option | ZeroCouponBond,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult

Prices the instrument using the model's closed-form solution.

This method dynamically builds the required parameters based on the type of instrument being priced (e.g., Option or ZeroCouponBond) and calls the model's price_closed_form method.

Parameters:

Name Type Description Default
option Option | ZeroCouponBond

The instrument to be priced.

required
stock Stock

The underlying asset's properties. For rate models, stock.spot is re-interpreted as the initial short rate r0.

required
model BaseModel

The financial model to use. Must have has_closed_form=True.

required
rate Rate

The risk-free rate structure.

required

Returns:

Type Description
PricingResult

An object containing the calculated price.

Raises:

Type Description
TypeError

If the model does not have a closed-form solution or if the instrument type is not supported.

Source code in src/optpricing/techniques/closed_form.py
def price(
    self,
    option: Option | ZeroCouponBond,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult:
    """
    Prices the instrument using the model's closed-form solution.

    This method dynamically builds the required parameters based on the
    type of instrument being priced (e.g., Option or ZeroCouponBond) and
    calls the model's `price_closed_form` method.

    Parameters
    ----------
    option : Option | ZeroCouponBond
        The instrument to be priced.
    stock : Stock
        The underlying asset's properties. For rate models, `stock.spot` is
        re-interpreted as the initial short rate `r0`.
    model : BaseModel
        The financial model to use. Must have `has_closed_form=True`.
    rate : Rate
        The risk-free rate structure.

    Returns
    -------
    PricingResult
        An object containing the calculated price.

    Raises
    ------
    TypeError
        If the model does not have a closed-form solution or if the
        instrument type is not supported.
    """
    if not model.has_closed_form:
        raise TypeError(f"{model.name} has no closed-form solver.")

    base_params: dict[str, Any] = {}

    if isinstance(option, Option):
        base_params = {
            "spot": stock.spot,
            "strike": option.strike,
            "r": rate.get_rate(option.maturity),
            "q": stock.dividend,
            "t": option.maturity,
            "call": (option.option_type is OptionType.CALL),
        }
    elif isinstance(option, ZeroCouponBond):
        # For rate models, 'spot' is re-interpreted as the initial short rate r0.
        base_params = {
            "spot": stock.spot,
            "t": option.maturity,
            # passed to satisfy the model signature but are ignored.
            "strike": option.face_value,
            "r": rate.get_rate(option.maturity),
            "q": stock.dividend,
        }
    else:
        raise TypeError(
            f"Unsupported asset type for ClosedFormTechnique: {type(option)}"
        )

    # Add extra model-specific kwargs
    for key in getattr(model, "cf_kwargs", []):
        if key in base_params:
            continue
        if hasattr(stock, key):
            base_params[key] = getattr(stock, key)
        elif key in kwargs:
            base_params[key] = kwargs[key]
        else:
            if not (
                isinstance(option, ZeroCouponBond)
                and key in ["call_price", "put_price"]
            ):
                raise ValueError(
                    f"{model.name} requires '{key}' for closed-form pricing."
                )

    # For ImpliedRateModel, pass these explicitly
    if "call_price" in kwargs:
        base_params["call_price"] = kwargs["call_price"]
    if "put_price" in kwargs:
        base_params["put_price"] = kwargs["put_price"]

    price = model.price_closed_form(**base_params)
    return PricingResult(price=price)

rho

rho(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float

Overrides GreekMixin to use analytic rho if available.

Parameters:

Name Type Description Default
option Option | ZeroCouponBond

The instrument to be priced.

required
stock Stock

The underlying asset's properties. For rate models, stock.spot is re-interpreted as the initial short rate r0.

required
model BaseModel

The financial model to use. Must have has_closed_form=True.

required
rate Rate

The risk-free rate structure.

required
Source code in src/optpricing/techniques/closed_form.py
def rho(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float:
    """Overrides GreekMixin to use analytic rho if available.

    Parameters
    ----------
    option : Option | ZeroCouponBond
        The instrument to be priced.
    stock : Stock
        The underlying asset's properties. For rate models, `stock.spot` is
        re-interpreted as the initial short rate `r0`.
    model : BaseModel
        The financial model to use. Must have `has_closed_form=True`.
    rate : Rate
        The risk-free rate structure.
    """
    if self.use_analytic_greeks and hasattr(model, "rho_analytic"):
        return model.rho_analytic(
            spot=stock.spot,
            strike=option.strike,
            r=rate.get_rate(option.maturity),
            q=stock.dividend,
            t=option.maturity,
            call=(option.option_type is OptionType.CALL),
        )
    return super().rho(option, stock, model, rate, **kwargs)

theta

theta(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float

Overrides GreekMixin to use analytic theta if available.

Parameters:

Name Type Description Default
option Option | ZeroCouponBond

The instrument to be priced.

required
stock Stock

The underlying asset's properties. For rate models, stock.spot is re-interpreted as the initial short rate r0.

required
model BaseModel

The financial model to use. Must have has_closed_form=True.

required
rate Rate

The risk-free rate structure.

required
Source code in src/optpricing/techniques/closed_form.py
def theta(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float:
    """Overrides GreekMixin to use analytic theta if available.

    Parameters
    ----------
    option : Option | ZeroCouponBond
        The instrument to be priced.
    stock : Stock
        The underlying asset's properties. For rate models, `stock.spot` is
        re-interpreted as the initial short rate `r0`.
    model : BaseModel
        The financial model to use. Must have `has_closed_form=True`.
    rate : Rate
        The risk-free rate structure.
    """
    if self.use_analytic_greeks and hasattr(model, "theta_analytic"):
        return model.theta_analytic(
            spot=stock.spot,
            strike=option.strike,
            r=rate.get_rate(option.maturity),
            q=stock.dividend,
            t=option.maturity,
            call=(option.option_type is OptionType.CALL),
        )
    return super().theta(option, stock, model, rate, **kwargs)

vega

vega(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float

Overrides GreekMixin to use analytic vega if available.

Parameters:

Name Type Description Default
option Option | ZeroCouponBond

The instrument to be priced.

required
stock Stock

The underlying asset's properties. For rate models, stock.spot is re-interpreted as the initial short rate r0.

required
model BaseModel

The financial model to use. Must have has_closed_form=True.

required
rate Rate

The risk-free rate structure.

required
Source code in src/optpricing/techniques/closed_form.py
def vega(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float:
    """Overrides GreekMixin to use analytic vega if available.

    Parameters
    ----------
    option : Option | ZeroCouponBond
        The instrument to be priced.
    stock : Stock
        The underlying asset's properties. For rate models, `stock.spot` is
        re-interpreted as the initial short rate `r0`.
    model : BaseModel
        The financial model to use. Must have `has_closed_form=True`.
    rate : Rate
        The risk-free rate structure.
    """
    if self.use_analytic_greeks and hasattr(model, "vega_analytic"):
        return model.vega_analytic(
            spot=stock.spot,
            strike=option.strike,
            r=rate.get_rate(option.maturity),
            q=stock.dividend,
            t=option.maturity,
        )
    return super().vega(option, stock, model, rate, **kwargs)

FFTTechnique

FFTTechnique(
    *,
    n: int = 12,
    eta: float = 0.25,
    alpha: float | None = None,
)

Bases: BaseTechnique, GreekMixin, IVMixin

Fast Fourier Transform (FFT) pricer based on the Carr-Madan formula, preserving the original tuned logic for grid and parameter selection.

Initializes the FFT solver.

Parameters:

Name Type Description Default
n int

The exponent for the number of grid points (N = 2^n), by default 12.

12
eta float

The spacing of the grid in the frequency domain, by default 0.25.

0.25
alpha float | None

The dampening parameter. If None, it is auto-tuned based on a volatility proxy from the model. Defaults to None.

None
Source code in src/optpricing/techniques/fft.py
def __init__(
    self,
    *,
    n: int = 12,
    eta: float = 0.25,
    alpha: float | None = None,
):
    """
    Initializes the FFT solver.

    Parameters
    ----------
    n : int, optional
        The exponent for the number of grid points (N = 2^n), by default 12.
    eta : float, optional
        The spacing of the grid in the frequency domain, by default 0.25.
    alpha : float | None, optional
        The dampening parameter. If None, it is auto-tuned based on a
        volatility proxy from the model. Defaults to None.
    """
    self.n = int(n)
    self.N = 1 << self.n
    self.base_eta = float(eta)
    self.alpha_user = alpha
    self._cached_results: dict[str, Any] = {}

price

price(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult

Calculates the option price using the FFT method.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use. Must support a characteristic function.

required
rate Rate

The risk-free rate structure.

required

Returns:

Type Description
PricingResult

An object containing the calculated price.

Source code in src/optpricing/techniques/fft.py
def price(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult:
    """
    Calculates the option price using the FFT method.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use. Must support a characteristic function.
    rate : Rate
        The risk-free rate structure.

    Returns
    -------
    PricingResult
        An object containing the calculated price.
    """
    self._cached_results = self._price_and_greeks(
        option, stock, model, rate, **kwargs
    )
    return PricingResult(price=self._cached_results["price"])

GreekMixin

Provides finite-difference calculations for Greeks.

This mixin is designed to be side-effect-free. It creates modified copies of the input objects for shifted calculations rather than mutating them in place. It also supports Common Random Numbers (CRN) for variance reduction in Monte Carlo-based calculations by checking for a self.rng attribute.

delta

delta(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h_frac: float = 0.001,
    **kwargs: Any,
) -> float

Calculates delta using a central difference formula.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the calculation.

required
rate Rate

The risk-free rate structure.

required
h_frac float

The fractional step size for shifting the spot price, by default 1e-3.

0.001

Returns:

Type Description
float

The calculated delta.

Source code in src/optpricing/techniques/base/greek_mixin.py
def delta(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h_frac: float = 1e-3,
    **kwargs: Any,
) -> float:
    """
    Calculates delta using a central difference formula.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the calculation.
    rate : Rate
        The risk-free rate structure.
    h_frac : float, optional
        The fractional step size for shifting the spot price, by default 1e-3.

    Returns
    -------
    float
        The calculated delta.
    """
    h = stock.spot * h_frac
    stock_up = replace(stock, spot=stock.spot + h)
    stock_dn = replace(stock, spot=stock.spot - h)

    rng = getattr(self, "rng", None)
    if isinstance(rng, np.random.Generator):
        with crn(rng):
            p_up = self.price(option, stock_up, model, rate, **kwargs).price
        with crn(rng):
            p_dn = self.price(option, stock_dn, model, rate, **kwargs).price
    else:
        p_up = self.price(option, stock_up, model, rate, **kwargs).price
        p_dn = self.price(option, stock_dn, model, rate, **kwargs).price

    return (p_up - p_dn) / (2 * h)

gamma

gamma(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h_frac: float = 0.001,
    **kw: Any,
) -> float

Calculates gamma using a central difference formula.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the calculation.

required
rate Rate

The risk-free rate structure.

required
h_frac float

The fractional step size for shifting the spot price, by default 1e-3.

0.001

Returns:

Type Description
float

The calculated gamma.

Source code in src/optpricing/techniques/base/greek_mixin.py
def gamma(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h_frac: float = 1e-3,
    **kw: Any,
) -> float:
    """
    Calculates gamma using a central difference formula.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the calculation.
    rate : Rate
        The risk-free rate structure.
    h_frac : float, optional
        The fractional step size for shifting the spot price, by default 1e-3.

    Returns
    -------
    float
        The calculated gamma.
    """
    h = stock.spot * h_frac
    stock_up = replace(stock, spot=stock.spot + h)
    stock_dn = replace(stock, spot=stock.spot - h)

    rng = getattr(self, "rng", None)
    if isinstance(rng, np.random.Generator):
        with crn(rng):
            p_up = self.price(option, stock_up, model, rate, **kw).price
        with crn(rng):
            p_0 = self.price(option, stock, model, rate, **kw).price
        with crn(rng):
            p_dn = self.price(option, stock_dn, model, rate, **kw).price
    else:
        p_up = self.price(option, stock_up, model, rate, **kw).price
        p_0 = self.price(option, stock, model, rate, **kw).price
        p_dn = self.price(option, stock_dn, model, rate, **kw).price

    return (p_up - 2 * p_0 + p_dn) / (h * h)

rho

rho(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h: float = 0.0001,
    **kw: Any,
) -> float

Calculates rho using a central difference formula.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the calculation.

required
rate Rate

The risk-free rate structure.

required
h float

The absolute step size for shifting the interest rate, by default 1e-4.

0.0001

Returns:

Type Description
float

The calculated rho.

Source code in src/optpricing/techniques/base/greek_mixin.py
def rho(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h: float = 1e-4,
    **kw: Any,
) -> float:
    """
    Calculates rho using a central difference formula.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the calculation.
    rate : Rate
        The risk-free rate structure.
    h : float, optional
        The absolute step size for shifting the interest rate, by default 1e-4.

    Returns
    -------
    float
        The calculated rho.
    """
    r0 = rate.get_rate(option.maturity)
    rate_up = replace(rate, rate=r0 + h)
    rate_dn = replace(rate, rate=r0 - h)

    rng = getattr(self, "rng", None)
    if isinstance(rng, np.random.Generator):
        with crn(rng):
            p_up = self.price(option, stock, model, rate_up, **kw).price
        with crn(rng):
            p_dn = self.price(option, stock, model, rate_dn, **kw).price
    else:
        p_up = self.price(option, stock, model, rate_up, **kw).price
        p_dn = self.price(option, stock, model, rate_dn, **kw).price

    return (p_up - p_dn) / (2 * h)

theta

theta(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h: float = 1e-05,
    **kw: Any,
) -> float

Calculates theta using a central difference formula.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the calculation.

required
rate Rate

The risk-free rate structure.

required
h float

The absolute step size for shifting maturity, by default 1e-5.

1e-05

Returns:

Type Description
float

The calculated theta.

Source code in src/optpricing/techniques/base/greek_mixin.py
def theta(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h: float = 1e-5,
    **kw: Any,
) -> float:
    """
    Calculates theta using a central difference formula.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the calculation.
    rate : Rate
        The risk-free rate structure.
    h : float, optional
        The absolute step size for shifting maturity, by default 1e-5.

    Returns
    -------
    float
        The calculated theta.
    """
    T0 = option.maturity
    opt_up = replace(option, maturity=T0 + h)
    opt_dn = replace(option, maturity=max(T0 - h, 1e-12))  # Avoid maturity

    rng = getattr(self, "rng", None)
    if isinstance(rng, np.random.Generator):
        with crn(rng):
            p_up = self.price(opt_up, stock, model, rate, **kw).price
        with crn(rng):
            p_dn = self.price(opt_dn, stock, model, rate, **kw).price
    else:
        p_up = self.price(opt_up, stock, model, rate, **kw).price
        p_dn = self.price(opt_dn, stock, model, rate, **kw).price

    return (p_dn - p_up) / (2 * h)

vega

vega(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h: float = 0.0001,
    **kw: Any,
) -> float

Calculates vega using a central difference formula.

Returns np.nan if the model does not have a 'sigma' parameter.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the calculation.

required
rate Rate

The risk-free rate structure.

required
h float

The absolute step size for shifting volatility, by default 1e-4.

0.0001

Returns:

Type Description
float

The calculated vega.

Source code in src/optpricing/techniques/base/greek_mixin.py
def vega(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    h: float = 1e-4,
    **kw: Any,
) -> float:
    """
    Calculates vega using a central difference formula.

    Returns `np.nan` if the model does not have a 'sigma' parameter.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the calculation.
    rate : Rate
        The risk-free rate structure.
    h : float, optional
        The absolute step size for shifting volatility, by default 1e-4.

    Returns
    -------
    float
        The calculated vega.
    """
    if "sigma" not in model.params:
        return np.nan

    sigma = model.params["sigma"]
    model_up = model.with_params(sigma=sigma + h)
    model_dn = model.with_params(sigma=sigma - h)

    rng = getattr(self, "rng", None)
    if isinstance(rng, np.random.Generator):
        with crn(rng):
            p_up = self.price(option, stock, model_up, rate, **kw).price
        with crn(rng):
            p_dn = self.price(option, stock, model_dn, rate, **kw).price
    else:
        p_up = self.price(option, stock, model_up, rate, **kw).price
        p_dn = self.price(option, stock, model_dn, rate, **kw).price

    return (p_up - p_dn) / (2 * h)

IVMixin

Calculates Black-Scholes implied volatility for a given price using a root-finding algorithm.

This implementation uses Brent's method for speed and precision, with a fallback to a more robust Secant method if the initial search fails.

implied_volatility

implied_volatility(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    target_price: float,
    low: float = 1e-06,
    high: float = 5.0,
    tol: float = 1e-06,
    **kwargs: Any,
) -> float

Calculates the implied volatility for a given option price.

Parameters:

Name Type Description Default
option Option

The option contract.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The model to use for pricing. Note: IV is always calculated relative to the Black-Scholes-Merton model.

required
rate Rate

The risk-free rate structure.

required
target_price float

The market price of the option for which to find the IV.

required
low float

The lower bound for the volatility search, by default 1e-6.

1e-06
high float

The upper bound for the volatility search, by default 5.0.

5.0
tol float

The tolerance for the root-finding algorithm, by default 1e-6.

1e-06

Returns:

Type Description
float

The implied volatility, or np.nan if the search fails.

Source code in src/optpricing/techniques/base/iv_mixin.py
def implied_volatility(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    target_price: float,
    low: float = 1e-6,
    high: float = 5.0,
    tol: float = 1e-6,
    **kwargs: Any,
) -> float:
    """
    Calculates the implied volatility for a given option price.

    Parameters
    ----------
    option : Option
        The option contract.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The model to use for pricing. Note: IV is always calculated
        relative to the Black-Scholes-Merton model.
    rate : Rate
        The risk-free rate structure.
    target_price : float
        The market price of the option for which to find the IV.
    low : float, optional
        The lower bound for the volatility search, by default 1e-6.
    high : float, optional
        The upper bound for the volatility search, by default 5.0.
    tol : float, optional
        The tolerance for the root-finding algorithm, by default 1e-6.

    Returns
    -------
    float
        The implied volatility, or `np.nan` if the search fails.
    """
    bsm_solver_model = BSMModel(params={"sigma": 0.3})

    def bsm_price_minus_target(vol: float) -> float:
        current_bsm_model = bsm_solver_model.with_params(sigma=vol)
        try:
            with np.errstate(all="ignore"):
                price = self.price(option, stock, current_bsm_model, rate).price
            if not np.isfinite(price):
                return 1e6
            return price - target_price
        except (ZeroDivisionError, OverflowError):
            return 1e6

    try:
        # First, try the fast and precise Brent's method
        iv = brentq(bsm_price_minus_target, low, high, xtol=tol, disp=False)
    except (ValueError, RuntimeError):
        try:
            # If brentq fails, fall back to the slower Secant method
            iv = self._secant_iv(bsm_price_minus_target, 0.2, tol, 100)
        except (ValueError, RuntimeError):
            iv = np.nan

    return iv

IntegrationTechnique

IntegrationTechnique(
    *,
    upper_bound: float = 200.0,
    limit: int = 200,
    epsabs: float = 1e-09,
    epsrel: float = 1e-09,
)

Bases: BaseTechnique, GreekMixin, IVMixin

Prices options using the Gil-Pelaez inversion formula via numerical quadrature.

This technique leverages the model's characteristic function (CF) to price options. It is particularly useful for models where a closed-form solution is unavailable but the CF is known (e.g., Heston, Bates, VG, NIG).

It provides an analytic delta as a "free" byproduct of the pricing calculation.

Initializes the numerical integration solver.

Parameters:

Name Type Description Default
upper_bound float

The upper limit of the integration, by default 200.0.

200.0
limit int

The maximum number of sub-intervals for the integration, by default 200.

200
epsabs float

The absolute error tolerance for the integration, by default 1e-9.

1e-09
epsrel float

The relative error tolerance for the integration, by default 1e-9.

1e-09
Source code in src/optpricing/techniques/integration.py
def __init__(
    self,
    *,
    upper_bound: float = 200.0,
    limit: int = 200,
    epsabs: float = 1e-9,
    epsrel: float = 1e-9,
):
    """
    Initializes the numerical integration solver.

    Parameters
    ----------
    upper_bound : float, optional
        The upper limit of the integration, by default 200.0.
    limit : int, optional
        The maximum number of sub-intervals for the integration, by default 200.
    epsabs : float, optional
        The absolute error tolerance for the integration, by default 1e-9.
    epsrel : float, optional
        The relative error tolerance for the integration, by default 1e-9.
    """
    self.upper_bound = upper_bound
    self.limit = limit
    self.epsabs = epsabs
    self.epsrel = epsrel
    self._cached_results: dict[str, Any] = {}

delta

delta(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float

Returns the 'free' delta calculated during the pricing call.

If the cache is empty or the analytic delta calculation failed, it falls back to the numerical finite difference method from GreekMixin.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use. Must support a characteristic function.

required
rate Rate

The risk-free rate structure.

required

Returns:

Type Description
float

Delta of the option.

Source code in src/optpricing/techniques/integration.py
def delta(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> float:
    """
    Returns the 'free' delta calculated during the pricing call.

    If the cache is empty or the analytic delta calculation failed, it
    falls back to the numerical finite difference method from `GreekMixin`.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use. Must support a characteristic function.
    rate : Rate
        The risk-free rate structure.

    Returns
    -------
    float
        Delta of the option.
    """
    if not self._cached_results:
        self.price(option, stock, model, rate, **kwargs)

    delta_val = self._cached_results.get("delta")
    if delta_val is not None and not np.isnan(delta_val):
        return delta_val
    else:
        # Fallback to finite difference if analytic delta failed
        return super().delta(option, stock, model, rate, **kwargs)

price

price(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult

Calculates the option price and caches the 'free' analytic delta.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use. Must support a characteristic function.

required
rate Rate

The risk-free rate structure.

required

Returns:

Type Description
PricingResult

An object containing the calculated price.

Source code in src/optpricing/techniques/integration.py
def price(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult:
    """
    Calculates the option price and caches the 'free' analytic delta.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use. Must support a characteristic function.
    rate : Rate
        The risk-free rate structure.

    Returns
    -------
    PricingResult
        An object containing the calculated price.
    """
    self._cached_results = self._price_and_delta(
        option, stock, model, rate, **kwargs
    )
    return PricingResult(price=self._cached_results["price"])

LeisenReimerTechnique

LeisenReimerTechnique(
    steps: int = 200, is_american: bool = False
)

Bases: LatticeTechnique

Leisen-Reimer binomial lattice technique with Peizer-Pratt inversion.

Source code in src/optpricing/techniques/base/lattice_technique.py
def __init__(
    self,
    steps: int = 200,
    is_american: bool = False,
):
    """
    Initializes the lattice technique.

    Parameters
    ----------
    steps : int, optional
        The number of time steps in the lattice, by default 200.
    is_american : bool, optional
        True if pricing an American option, False for European. Defaults to False.
    """
    self.steps = int(steps)
    self.is_american = bool(is_american)
    self._cached_nodes: dict[str, Any] = {}
    self._cache_key: tuple | None = None

MonteCarloTechnique

MonteCarloTechnique(
    *,
    n_paths: int = 20000,
    n_steps: int = 100,
    antithetic: bool = True,
    seed: int | None = None,
)

Bases: BaseTechnique, GreekMixin, IVMixin

A universal Monte Carlo engine that dispatches to specialized, JIT-compiled kernels for different model types.

Initializes the Monte Carlo engine.

Parameters:

Name Type Description Default
n_paths int

The number of simulation paths, by default 20_000.

20000
n_steps int

The number of time steps in each path, by default 100.

100
antithetic bool

Whether to use antithetic variates for variance reduction, by default True.

True
seed int | None

Seed for the random number generator for reproducibility, by default None.

None
Source code in src/optpricing/techniques/monte_carlo.py
def __init__(
    self,
    *,
    n_paths: int = 20_000,
    n_steps: int = 100,
    antithetic: bool = True,
    seed: int | None = None,
):
    """
    Initializes the Monte Carlo engine.

    Parameters
    ----------
    n_paths : int, optional
        The number of simulation paths, by default 20_000.
    n_steps : int, optional
        The number of time steps in each path, by default 100.
    antithetic : bool, optional
        Whether to use antithetic variates for variance reduction, by default True.
    seed : int | None, optional
        Seed for the random number generator for reproducibility, by default None.
    """
    if antithetic and n_paths % 2 != 0:
        n_paths += 1
    self.n_paths = n_paths
    self.n_steps = n_steps
    self.antithetic = antithetic
    self.rng = np.random.default_rng(seed)

price

price(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult

Prices an option using the appropriate Monte Carlo simulation method.

This method acts as a dispatcher, selecting the correct simulation strategy (SDE path, pure Levy, or exact sampler) based on the capabilities of the provided model.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use for the simulation.

required
rate Rate

The risk-free rate structure.

required

Returns:

Type Description
PricingResult

An object containing the calculated price.

Source code in src/optpricing/techniques/monte_carlo.py
def price(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs: Any,
) -> PricingResult:
    """
    Prices an option using the appropriate Monte Carlo simulation method.

    This method acts as a dispatcher, selecting the correct simulation
    strategy (SDE path, pure Levy, or exact sampler) based on the
    capabilities of the provided model.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use for the simulation.
    rate : Rate
        The risk-free rate structure.

    Returns
    -------
    PricingResult
        An object containing the calculated price.
    """
    if not (model.supports_sde or getattr(model, "is_pure_levy", False)):
        raise TypeError(f"Model '{model.name}' does not support simulation.")

    S0, K, T = stock.spot, option.strike, option.maturity
    r, q = rate.get_rate(T), stock.dividend

    # Dispatch to the correct simulation method
    if getattr(model, "has_exact_sampler", False):
        ST = model.sample_terminal_spot(S0, r, T, self.n_paths)
    elif getattr(model, "is_pure_levy", False):
        ST = self._simulate_levy_terminal(model, S0, r, q, T)
    else:
        ST = self._simulate_sde_path(model, S0, r, q, T, **kwargs)

    payoff = (
        np.maximum(ST - K, 0)
        if option.option_type is OptionType.CALL
        else np.maximum(K - ST, 0)
    )
    price = float(np.mean(payoff) * math.exp(-r * T))
    return PricingResult(price=price)

PDETechnique

PDETechnique(
    S_max_mult: float = 3.0, M: int = 200, N: int = 200
)

Bases: BaseTechnique, GreekMixin, IVMixin

Prices options by solving the Black-Scholes PDE with a Crank-Nicolson scheme.

This technique is optimized for the BSM model and calculates the price, delta, and gamma in a single pass by building a grid of asset prices and time steps.

Initializes the PDE solver.

Parameters:

Name Type Description Default
S_max_mult float

Multiplier for the initial spot price to set the maximum grid boundary, by default 3.0.

3.0
M int

Number of asset price steps (grid columns), by default 200.

200
N int

Number of time steps (grid rows), by default 200.

200
Source code in src/optpricing/techniques/pde.py
def __init__(self, S_max_mult: float = 3.0, M: int = 200, N: int = 200):
    """
    Initializes the PDE solver.

    Parameters
    ----------
    S_max_mult : float, optional
        Multiplier for the initial spot price to set the maximum grid boundary,
        by default 3.0.
    M : int, optional
        Number of asset price steps (grid columns), by default 200.
    N : int, optional
        Number of time steps (grid rows), by default 200.
    """
    self.S_max_mult = float(S_max_mult)
    self.M = int(M)
    self.N = int(N)
    self._cached_results: dict[str, Any] = {}

delta

delta(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> float

Returns the cached delta from the PDE grid.

If the cache is empty, it first runs the pricing calculation.

Source code in src/optpricing/techniques/pde.py
def delta(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> float:
    """
    Returns the cached delta from the PDE grid.

    If the cache is empty, it first runs the pricing calculation.
    """
    if not self._cached_results:
        self.price(option, stock, model, rate)
    return self._cached_results["delta"]

gamma

gamma(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> float

Returns the cached gamma from the PDE grid.

If the cache is empty, it first runs the pricing calculation.

Source code in src/optpricing/techniques/pde.py
def gamma(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> float:
    """
    Returns the cached gamma from the PDE grid.

    If the cache is empty, it first runs the pricing calculation.
    """
    if not self._cached_results:
        self.price(option, stock, model, rate)
    return self._cached_results["gamma"]

price

price(
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> PricingResult

Calculates the option price and caches grid-based Greeks.

Parameters:

Name Type Description Default
option Option

The option contract to be priced.

required
stock Stock

The underlying asset's properties.

required
model BaseModel

The financial model to use. Must be a BSMModel.

required
rate Rate

The risk-free rate structure.

required

Returns:

Type Description
PricingResult

An object containing the calculated price.

Source code in src/optpricing/techniques/pde.py
def price(
    self,
    option: Option,
    stock: Stock,
    model: BaseModel,
    rate: Rate,
    **kwargs,
) -> PricingResult:
    """
    Calculates the option price and caches grid-based Greeks.

    Parameters
    ----------
    option : Option
        The option contract to be priced.
    stock : Stock
        The underlying asset's properties.
    model : BaseModel
        The financial model to use. Must be a BSMModel.
    rate : Rate
        The risk-free rate structure.

    Returns
    -------
    PricingResult
        An object containing the calculated price.
    """
    self._cached_results = self._price_and_greeks(option, stock, model, rate)
    return PricingResult(price=self._cached_results["price"])

PricingResult dataclass

PricingResult(
    price: float,
    greeks: dict[str, float] = dict(),
    implied_vol: float | None = None,
)

A container for the results of a pricing operation.

Attributes:

Name Type Description
price float

The calculated price of the instrument.

greeks (dict[str, float], optional)

A dictionary containing calculated Greek values (e.g., 'delta', 'gamma').

implied_vol (float, optional)

The calculated Black-Scholes implied volatility.

TOPMTechnique

TOPMTechnique(steps: int = 200, is_american: bool = False)

Bases: LatticeTechnique

Kamrad-Ritchken trinomial lattice technique.

Source code in src/optpricing/techniques/base/lattice_technique.py
def __init__(
    self,
    steps: int = 200,
    is_american: bool = False,
):
    """
    Initializes the lattice technique.

    Parameters
    ----------
    steps : int, optional
        The number of time steps in the lattice, by default 200.
    is_american : bool, optional
        True if pricing an American option, False for European. Defaults to False.
    """
    self.steps = int(steps)
    self.is_american = bool(is_american)
    self._cached_nodes: dict[str, Any] = {}
    self._cache_key: tuple | None = None