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, n_jobs: int | None = None, chunk_size: int = DEFAULT_CHUNK_SIZE, variance_scheme: str = 'qe')

Bases: BaseTechnique, GreekMixin, IVMixin

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

Parameters:

Name Type Description Default
n_paths int

As for MonteCarloTechnique.

20000
n_steps int

As for MonteCarloTechnique.

20000
antithetic int

As for MonteCarloTechnique.

20000
seed int

As for MonteCarloTechnique.

20000
variance_scheme int

As for MonteCarloTechnique.

20000
lsm_degree int

Polynomial degree of the Longstaff-Schwartz regression, by default 2.

2
n_jobs int | None

Maximum number of threads simulating path chunks concurrently; results do not depend on it.

None
chunk_size int

Paths per chunk and random stream, by default 8192.

DEFAULT_CHUNK_SIZE
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,
    n_jobs: int | None = None,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
    variance_scheme: str = "qe",
):
    """
    Parameters
    ----------
    n_paths, n_steps, antithetic, seed, variance_scheme
        As for `MonteCarloTechnique`.
    lsm_degree : int, optional
        Polynomial degree of the Longstaff-Schwartz regression, by default 2.
    n_jobs : int | None, optional
        Maximum number of threads simulating path chunks concurrently;
        results do not depend on it.
    chunk_size : int, optional
        Paths per chunk and random stream, by default 8192.
    """
    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
    self.n_jobs = n_jobs
    self.chunk_size = int(chunk_size)
    if variance_scheme not in ("qe", "euler"):
        raise ValueError("variance_scheme must be 'qe' or 'euler'.")
    self.variance_scheme = variance_scheme

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

COSTechnique

COSTechnique(*, width: float = 10.0, n_terms: int | None = None, tol: float = 1e-12, max_terms: int = 1 << 16)

Bases: BaseTechnique, GreekMixin, IVMixin

Prices European options with the COS method (Fang & Oosterlee 2008).

Works for every model with a characteristic function. The series converges exponentially for smooth densities and, unlike Gil-Pelaez quadrature on a fixed grid, stays accurate when the characteristic function decays slowly (strong Feller violation in Heston, Variance Gamma and CGMY at short maturities), because the number of terms adapts to the decay. The delta is a by-product of the pricing series.

Parameters:

Name Type Description Default
width float

Half-width L of the truncation range in units of sqrt(c2 + sqrt(c4)), by default 10 (Fang & Oosterlee 2008).

10.0
n_terms int

Fixed number of cosine terms. By default it is chosen per option as the smallest power of two (at least 64) whose neglected tail is below tol (see cos_n_terms), capped at max_terms.

None
tol float

Bound on the neglected tail relative to the strike, by default 1e-12.

1e-12
max_terms int

Cap on the adaptive number of terms, by default 2**16.

1 << 16
Source code in src/optpricing/techniques/cos.py
def __init__(
    self,
    *,
    width: float = 10.0,
    n_terms: int | None = None,
    tol: float = 1e-12,
    max_terms: int = 1 << 16,
):
    """
    Parameters
    ----------
    width : float, optional
        Half-width ``L`` of the truncation range in units of
        ``sqrt(c2 + sqrt(c4))``, by default 10 (Fang & Oosterlee 2008).
    n_terms : int, optional
        Fixed number of cosine terms. By default it is chosen per option
        as the smallest power of two (at least 64) whose neglected tail is
        below ``tol`` (see `cos_n_terms`), capped at ``max_terms``.
    tol : float, optional
        Bound on the neglected tail relative to the strike, by default 1e-12.
    max_terms : int, optional
        Cap on the adaptive number of terms, by default 2**16.
    """
    if width <= 0:
        raise ValueError("width must be positive.")
    if n_terms is not None and n_terms < 2:
        raise ValueError("n_terms must be at least 2.")
    self.width = float(width)
    self.n_terms = None if n_terms is None else int(n_terms)
    self.tol = float(tol)
    self.max_terms = int(max_terms)
    self._cached_results: dict[str, float] = {}
    self._cache_key: tuple | None = None

delta

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

Returns the delta computed with the price, pricing first if the inputs differ from the last price call.

Source code in src/optpricing/techniques/cos.py
def delta(
    self, option: Option, stock: Stock, model: BaseModel, rate: Rate, **kwargs: Any
) -> float:
    """
    Returns the delta computed with the price, pricing first if the inputs
    differ from the last `price` call.
    """
    try:
        cached = self._cache_key == (option, stock, model, rate, kwargs)
    except ValueError:  # array-valued kwargs have no truth value
        cached = False
    if not cached:
        self.price(option, stock, model, rate, **kwargs)
    delta_val = self._cached_results.get("delta")
    if delta_val is not None and np.isfinite(delta_val):
        return delta_val
    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 series 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/cos.py
def price(
    self, option: Option, stock: Stock, model: BaseModel, rate: Rate, **kwargs: Any
) -> PricingResult:
    """
    Calculates the option price and caches the series 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
    )
    self._cache_key = (option, stock, model, rate, kwargs)
    return PricingResult(price=self._cached_results["price"])

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.
        # The models price a unit discount bond; strike, r and q are passed
        # to satisfy the signature and are ignored.
        base_params = {
            "spot": stock.spot,
            "t": option.maturity,
            "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 getattr(stock, key, None) is not None:
            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)
    if isinstance(option, ZeroCouponBond):
        price *= option.face_value
    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.125, alpha: float | None = None)

Bases: BaseTechnique, GreekMixin, IVMixin

Fast Fourier Transform (FFT) pricer based on the Carr-Madan formula.

Initializes the FFT solver.

Parameters:

Name Type Description Default
n int

The exponent for the minimum number of grid points (N = 2^n), by default 12. N is doubled (up to 2^18) until the damped characteristic function has decayed to 1e-12 at u = N eta, and the log-strike grid is centred on the strike, so no interpolation is needed.

12
eta float

The spacing of the grid in the frequency domain, by default 0.125 (0.25 left a 3e-5 discretisation error on ATM prices).

0.125
alpha float | None

The dampening parameter. If None, it is chosen per option by the Lord & Kahl (2007) rule inside the strip where E[S_T^(alpha+1)] is finite (negative values below -1 price the put), and eta is reduced (with N grown to keep the log-strike spacing, up to 2^18) when alpha lies close to a singularity of the damped integrand. Defaults to None.

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

    Parameters
    ----------
    n : int, optional
        The exponent for the minimum number of grid points (N = 2^n), by
        default 12. ``N`` is doubled (up to ``2^18``) until the damped
        characteristic function has decayed to 1e-12 at ``u = N eta``, and the
        log-strike grid is centred on the strike, so no interpolation is
        needed.
    eta : float, optional
        The spacing of the grid in the frequency domain, by default 0.125
        (0.25 left a 3e-5 discretisation error on ATM prices).
    alpha : float | None, optional
        The dampening parameter. If None, it is chosen per option by the
        Lord & Kahl (2007) rule inside the strip where ``E[S_T^(alpha+1)]``
        is finite (negative values below -1 price the put), and ``eta`` is
        reduced (with ``N`` grown to keep the log-strike spacing, up to
        ``2^18``) when ``alpha`` lies close to a singularity of the damped
        integrand. 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 = 1000.0, limit: int = 200, epsabs: float = 1e-09, epsrel: float = 1e-09, method: str = 'gauss', n_nodes: int = 24, adaptive: bool = True)

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 1000.0. Short maturities need the long tail: 200 left errors of 1e-5 (Heston, 10 days) to 5e-4 (NIG, T = 0.1).

1000.0
limit int

Maximum number of sub-intervals for the adaptive method, by default 200.

200
epsabs float

Absolute error tolerance of the adaptive method, by default 1e-9.

1e-09
epsrel float

Relative error tolerance of the adaptive method, by default 1e-9.

1e-09
method ('gauss', 'quad')

"gauss" (default) evaluates the characteristic function once on a composite Gauss-Legendre rule (geometric panels [0,1],[1,2],[2,4],... up to upper_bound, n_nodes per panel). Where the characteristic function has decayed by upper_bound this matches quad to about 1e-12 and is much cheaper; slowly decaying characteristic functions need adaptive. "quad" uses scipy.integrate.quad with a Python callback per node.

"gauss"
n_nodes int

Gauss-Legendre nodes per panel, by default 24.

24
adaptive bool

For "gauss": extend upper_bound until the characteristic function has decayed and refine n_nodes while it is still significant in the outer panels, by default True. Slowly decaying characteristic functions (Variance Gamma and CGMY at short maturities, strong Feller violation in Heston) otherwise leave the fixed rule under-resolved; COSTechnique handles them at lower cost.

True
Source code in src/optpricing/techniques/integration.py
def __init__(
    self,
    *,
    upper_bound: float = 1000.0,
    limit: int = 200,
    epsabs: float = 1e-9,
    epsrel: float = 1e-9,
    method: str = "gauss",
    n_nodes: int = 24,
    adaptive: bool = True,
):
    """
    Initializes the numerical integration solver.

    Parameters
    ----------
    upper_bound : float, optional
        The upper limit of the integration, by default 1000.0. Short
        maturities need the long tail: 200 left errors of 1e-5 (Heston,
        10 days) to 5e-4 (NIG, T = 0.1).
    limit : int, optional
        Maximum number of sub-intervals for the adaptive method, by default 200.
    epsabs : float, optional
        Absolute error tolerance of the adaptive method, by default 1e-9.
    epsrel : float, optional
        Relative error tolerance of the adaptive method, by default 1e-9.
    method : {"gauss", "quad"}, optional
        ``"gauss"`` (default) evaluates the characteristic function once on a
        composite Gauss-Legendre rule (geometric panels
        ``[0,1],[1,2],[2,4],...`` up to ``upper_bound``, ``n_nodes`` per
        panel). Where the characteristic function has decayed by
        ``upper_bound`` this matches ``quad`` to about 1e-12 and is much
        cheaper; slowly decaying characteristic functions need ``adaptive``.
        ``"quad"`` uses ``scipy.integrate.quad`` with a Python callback per node.
    n_nodes : int, optional
        Gauss-Legendre nodes per panel, by default 24.
    adaptive : bool, optional
        For ``"gauss"``: extend ``upper_bound`` until the characteristic
        function has decayed and refine ``n_nodes`` while it is still
        significant in the outer panels, by default True. Slowly decaying
        characteristic functions (Variance Gamma and CGMY at short
        maturities, strong Feller violation in Heston) otherwise leave the
        fixed rule under-resolved; `COSTechnique` handles them at lower cost.
    """
    if method not in ("gauss", "quad"):
        raise ValueError("method must be 'gauss' or 'quad'.")
    self.upper_bound = upper_bound
    self.limit = limit
    self.epsabs = epsabs
    self.epsrel = epsrel
    self.method = method
    self.n_nodes = int(n_nodes)
    self.adaptive = adaptive
    self._cached_results: dict[str, Any] = {}
    self._cache_key: tuple | None = None

delta

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

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

The delta of the last price call is reused if it was computed for the same inputs; otherwise the option is priced first. If the analytic delta is unavailable, it falls back to the 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.

    The delta of the last `price` call is reused if it was computed for the
    same inputs; otherwise the option is priced first. If the analytic delta
    is unavailable, it falls back to the 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._is_cached(option, stock, model, rate, kwargs):
        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)

gauss_nodes classmethod

gauss_nodes(upper_bound: float, n_nodes: int) -> tuple[np.ndarray, np.ndarray]

Composite Gauss-Legendre nodes and weights on geometric panels of [0, upper_bound] (cached).

Source code in src/optpricing/techniques/integration.py
@classmethod
def gauss_nodes(
    cls, upper_bound: float, n_nodes: int
) -> tuple[np.ndarray, np.ndarray]:
    """Composite Gauss-Legendre nodes and weights on geometric panels of
    ``[0, upper_bound]`` (cached)."""
    key = (float(upper_bound), int(n_nodes))
    cached = cls._node_cache.get(key)
    if cached is None:
        edges = [0.0, 1.0]
        while edges[-1] * 2.0 < upper_bound:
            edges.append(edges[-1] * 2.0)
        edges.append(float(upper_bound))
        x, w = np.polynomial.legendre.leggauss(n_nodes)
        nodes, weights = [], []
        for a, b in zip(edges[:-1], edges[1:]):
            nodes.append(0.5 * (b - a) * x + 0.5 * (b + a))
            weights.append(0.5 * (b - a) * w)
        cached = cls._node_cache[key] = (
            np.concatenate(nodes),
            np.concatenate(weights),
        )
    return cached

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
    )
    self._cache_key = (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, n_jobs: int | None = None, chunk_size: int = DEFAULT_CHUNK_SIZE, variance_scheme: str = 'qe')

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
n_jobs int | None

Maximum number of threads simulating path chunks concurrently. None uses OPTPRICING_MC_THREADS or the CPU count. Results do not depend on this value.

None
chunk_size int

Paths per chunk (and per random stream), by default 8192. Results depend on the seed and on this value only.

DEFAULT_CHUNK_SIZE
variance_scheme ('qe', 'euler')

Discretisation of the Heston/Bates variance process: Andersen's Quadratic-Exponential scheme with martingale correction (default, accurate with ~50 steps even when the Feller condition fails) or full-truncation Euler.

"qe"
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,
    n_jobs: int | None = None,
    chunk_size: int = DEFAULT_CHUNK_SIZE,
    variance_scheme: str = "qe",
):
    """
    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.
    n_jobs : int | None, optional
        Maximum number of threads simulating path chunks concurrently. None
        uses ``OPTPRICING_MC_THREADS`` or the CPU count. Results do not
        depend on this value.
    chunk_size : int, optional
        Paths per chunk (and per random stream), by default 8192. Results
        depend on the seed and on this value only.
    variance_scheme : {"qe", "euler"}, optional
        Discretisation of the Heston/Bates variance process: Andersen's
        Quadratic-Exponential scheme with martingale correction (default,
        accurate with ~50 steps even when the Feller condition fails) or
        full-truncation Euler.
    """
    if antithetic and n_paths % 2 != 0:
        n_paths += 1
    if variance_scheme not in ("qe", "euler"):
        raise ValueError("variance_scheme must be 'qe' or 'euler'.")
    self.n_paths = n_paths
    self.n_steps = n_steps
    self.antithetic = antithetic
    self.rng = np.random.default_rng(seed)
    self.n_jobs = n_jobs
    self.chunk_size = int(chunk_size)
    self.variance_scheme = variance_scheme

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, q=q, rng=self.rng)
    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, rannacher_steps: int = 2)

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. The grid spacing is chosen so that the spot lies exactly on a node.

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
rannacher_steps int

Number of initial Crank-Nicolson steps replaced by pairs of fully implicit half-steps (Rannacher 1984 start-up). This damps the oscillations the non-smooth payoff otherwise induces in gamma. Default 2; set 0 for plain Crank-Nicolson.

2
Source code in src/optpricing/techniques/pde.py
def __init__(
    self,
    S_max_mult: float = 3.0,
    M: int = 200,
    N: int = 200,
    rannacher_steps: int = 2,
):
    """
    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. The grid spacing is chosen so that the spot lies
        exactly on a node.
    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.
    rannacher_steps : int, optional
        Number of initial Crank-Nicolson steps replaced by pairs of fully
        implicit half-steps (Rannacher 1984 start-up). This damps the
        oscillations the non-smooth payoff otherwise induces in gamma.
        Default 2; set 0 for plain Crank-Nicolson.
    """
    self.S_max_mult = float(S_max_mult)
    self.M = int(M)
    self.N = int(N)
    self.rannacher_steps = int(rannacher_steps)
    self._cached_results: dict[str, Any] = {}
    self._cache_key: tuple | None = None

delta

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

Returns the delta from the PDE grid.

The grid of the last price call is reused if it was built for the same inputs; otherwise the option is priced first.

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

    The grid of the last `price` call is reused if it was built for the
    same inputs; otherwise the option is priced first.
    """
    self._ensure_cached(option, stock, model, rate)
    return self._cached_results["delta"]

gamma

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

Returns the gamma from the PDE grid.

The grid of the last price call is reused if it was built for the same inputs; otherwise the option is priced first.

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

    The grid of the last `price` call is reused if it was built for the
    same inputs; otherwise the option is priced first.
    """
    self._ensure_cached(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)
    self._cache_key = (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