Skip to content

Option Pricer

The option pricer module provides classes for pricing options using different stochastic volatility models.

quantflow.options.pricer.OptionPricerBase pydantic-model

Bases: BaseModel

Abstract base class for option pricers.

Provides caching of MaturityPricer results and generic pricing/plotting methods. Subclasses implement _compute_maturity to define how call prices are computed for a given time to maturity.

Fields:

ttm pydantic-field

ttm

Cache for MaturityPricer for different time to maturity

reset

reset()

Clear the ttm cache

Source code in quantflow/options/pricer.py
def reset(self) -> None:
    """Clear the [ttm][quantflow.options.pricer.OptionPricerBase.ttm] cache"""
    self.ttm.clear()

maturity

maturity(ttm)

Get a MaturityPricer from cache or compute a new one and return it

Source code in quantflow/options/pricer.py
def maturity(self, ttm: float) -> MaturityPricer:
    """Get a [MaturityPricer][quantflow.options.pricer.MaturityPricer]
    from cache or compute a new one and return it"""
    ttm_int = int(TTM_FACTOR * ttm)
    if ttm_int not in self.ttm:
        ttmr = ttm_int / TTM_FACTOR
        self.ttm[ttm_int] = self._compute_maturity(ttmr)
    return self.ttm[ttm_int]

price

price(option_type, ttm, strike, forward)

Price a single option

This method will use the cache to get the maturity pricer if possible

PARAMETER DESCRIPTION
option_type

Type of the option (call or put)

TYPE: OptionType

ttm

Time to maturity

TYPE: float

strike

Strike price of the option

TYPE: float

forward

Forward price of the underlying

TYPE: float

Source code in quantflow/options/pricer.py
def price(
    self,
    option_type: Annotated[OptionType, Doc("Type of the option (call or put)")],
    ttm: Annotated[float, Doc("Time to maturity")],
    strike: Annotated[float, Doc("Strike price of the option")],
    forward: Annotated[float, Doc("Forward price of the underlying")],
) -> ModelOptionPrice:
    """Price a single option

    This method will use the cache to get the maturity pricer if possible
    """
    return self.maturity(ttm).price(option_type, strike, forward)

call_prices

call_prices(ttms, log_strikes)

Price a batch of call options.

Options are grouped by their ttm so each unique maturity pricer is retrieved (and cached) once and the corresponding log-strikes are interpolated in a single vectorised np.interp call.

PARAMETER DESCRIPTION
ttms

Vector of time to maturities

TYPE: FloatArray

log_strikes

Vector of log-strikes log(K/F)

TYPE: FloatArray

Source code in quantflow/options/pricer.py
def call_prices(
    self,
    ttms: Annotated[FloatArray, Doc("Vector of time to maturities")],
    log_strikes: Annotated[FloatArray, Doc("Vector of log-strikes log(K/F)")],
) -> FloatArray:
    """Price a batch of call options.

    Options are grouped by their `ttm` so each unique maturity pricer is
    retrieved (and cached) once and the corresponding log-strikes are
    interpolated in a single vectorised `np.interp` call.
    """
    out = np.empty_like(log_strikes, dtype=float)
    for ttm in np.unique(ttms):
        mask = ttms == ttm
        mat = self.maturity(float(ttm))
        out[mask] = mat.pricing.call_price(log_strikes[mask])
    return out

plot3d

plot3d(max_moneyness=1.0, support=51, ttm=None, dragmode='turntable', scene_camera=None, **kwargs)

Plot the implied vols surface

It requires plotly to be installed

Source code in quantflow/options/pricer.py
def plot3d(
    self,
    max_moneyness: float = 1.0,
    support: int = 51,
    ttm: FloatArray | None = None,
    dragmode: str = "turntable",
    scene_camera: dict | None = None,
    **kwargs: Any,
) -> Any:
    """Plot the implied vols surface

    It requires plotly to be installed
    """
    if ttm is None:
        ttm = np.arange(0.05, 1.0, 0.05)
    moneyness = np.linspace(-max_moneyness, max_moneyness, support)
    implied = np.zeros((len(ttm), len(moneyness)))
    for i, t in enumerate(ttm):
        maturity = self.maturity(cast(float, t))
        implied[i, :] = maturity.prices(moneyness * np.sqrt(t))["implied_vol"]
    properties: dict = dict(
        xaxis_title="moneyness",
        yaxis_title="TTM",
        colorscale="viridis",
        dragmode=dragmode,
        scene=dict(
            xaxis=dict(title="moneyness"),
            yaxis=dict(title="TTM"),
            zaxis=dict(title="implied_vol"),
        ),
        scene_camera=scene_camera or dict(eye=dict(x=1.2, y=-1.8, z=0.3)),
        contours=dict(
            x=dict(show=True, color="white"), y=dict(show=True, color="white")
        ),
    )
    properties.update(kwargs)
    return plot.plot3d(
        x=moneyness,
        y=ttm,
        z=implied,
        **properties,
    )

quantflow.options.pricer.OptionPricer pydantic-model

Bases: OptionPricerBase, Generic[M]

Pricer for options based on a stochastic process model.

Computes call prices via the inverse Fourier transform of the call option transform function of the underlying stochastic process.

Fields:

model pydantic-field

model

Stochastic process model for the underlying

n pydantic-field

n = 128

Number of discretization points for the marginal distribution

method pydantic-field

method = CARR_MADAN

Method to use for option pricing

cos_moneyness_std_precision pydantic-field

cos_moneyness_std_precision = 12.0

the accuracy of the COS method in number of std at a given TTM

max_moneyness pydantic-field

max_moneyness = 1.5

Maximum time-scaled moneyness to use for the pricing grid. Only used if method is Lewis or Carr & Madan, otherwise ignored.

ttm pydantic-field

ttm

Cache for MaturityPricer for different time to maturity

reset

reset()

Clear the ttm cache

Source code in quantflow/options/pricer.py
def reset(self) -> None:
    """Clear the [ttm][quantflow.options.pricer.OptionPricerBase.ttm] cache"""
    self.ttm.clear()

maturity

maturity(ttm)

Get a MaturityPricer from cache or compute a new one and return it

Source code in quantflow/options/pricer.py
def maturity(self, ttm: float) -> MaturityPricer:
    """Get a [MaturityPricer][quantflow.options.pricer.MaturityPricer]
    from cache or compute a new one and return it"""
    ttm_int = int(TTM_FACTOR * ttm)
    if ttm_int not in self.ttm:
        ttmr = ttm_int / TTM_FACTOR
        self.ttm[ttm_int] = self._compute_maturity(ttmr)
    return self.ttm[ttm_int]

price

price(option_type, ttm, strike, forward)

Price a single option

This method will use the cache to get the maturity pricer if possible

PARAMETER DESCRIPTION
option_type

Type of the option (call or put)

TYPE: OptionType

ttm

Time to maturity

TYPE: float

strike

Strike price of the option

TYPE: float

forward

Forward price of the underlying

TYPE: float

Source code in quantflow/options/pricer.py
def price(
    self,
    option_type: Annotated[OptionType, Doc("Type of the option (call or put)")],
    ttm: Annotated[float, Doc("Time to maturity")],
    strike: Annotated[float, Doc("Strike price of the option")],
    forward: Annotated[float, Doc("Forward price of the underlying")],
) -> ModelOptionPrice:
    """Price a single option

    This method will use the cache to get the maturity pricer if possible
    """
    return self.maturity(ttm).price(option_type, strike, forward)

call_prices

call_prices(ttms, log_strikes)

Price a batch of call options.

Options are grouped by their ttm so each unique maturity pricer is retrieved (and cached) once and the corresponding log-strikes are interpolated in a single vectorised np.interp call.

PARAMETER DESCRIPTION
ttms

Vector of time to maturities

TYPE: FloatArray

log_strikes

Vector of log-strikes log(K/F)

TYPE: FloatArray

Source code in quantflow/options/pricer.py
def call_prices(
    self,
    ttms: Annotated[FloatArray, Doc("Vector of time to maturities")],
    log_strikes: Annotated[FloatArray, Doc("Vector of log-strikes log(K/F)")],
) -> FloatArray:
    """Price a batch of call options.

    Options are grouped by their `ttm` so each unique maturity pricer is
    retrieved (and cached) once and the corresponding log-strikes are
    interpolated in a single vectorised `np.interp` call.
    """
    out = np.empty_like(log_strikes, dtype=float)
    for ttm in np.unique(ttms):
        mask = ttms == ttm
        mat = self.maturity(float(ttm))
        out[mask] = mat.pricing.call_price(log_strikes[mask])
    return out

plot3d

plot3d(max_moneyness=1.0, support=51, ttm=None, dragmode='turntable', scene_camera=None, **kwargs)

Plot the implied vols surface

It requires plotly to be installed

Source code in quantflow/options/pricer.py
def plot3d(
    self,
    max_moneyness: float = 1.0,
    support: int = 51,
    ttm: FloatArray | None = None,
    dragmode: str = "turntable",
    scene_camera: dict | None = None,
    **kwargs: Any,
) -> Any:
    """Plot the implied vols surface

    It requires plotly to be installed
    """
    if ttm is None:
        ttm = np.arange(0.05, 1.0, 0.05)
    moneyness = np.linspace(-max_moneyness, max_moneyness, support)
    implied = np.zeros((len(ttm), len(moneyness)))
    for i, t in enumerate(ttm):
        maturity = self.maturity(cast(float, t))
        implied[i, :] = maturity.prices(moneyness * np.sqrt(t))["implied_vol"]
    properties: dict = dict(
        xaxis_title="moneyness",
        yaxis_title="TTM",
        colorscale="viridis",
        dragmode=dragmode,
        scene=dict(
            xaxis=dict(title="moneyness"),
            yaxis=dict(title="TTM"),
            zaxis=dict(title="implied_vol"),
        ),
        scene_camera=scene_camera or dict(eye=dict(x=1.2, y=-1.8, z=0.3)),
        contours=dict(
            x=dict(show=True, color="white"), y=dict(show=True, color="white")
        ),
    )
    properties.update(kwargs)
    return plot.plot3d(
        x=moneyness,
        y=ttm,
        z=implied,
        **properties,
    )

quantflow.options.pricer.MaturityPricer pydantic-model

Bases: BaseModel

Result of option pricing for a given Time to Maturity

Fields:

ttm pydantic-field

ttm

Time to maturity

pricing pydantic-field

pricing

Call option pricing result for the maturity

name pydantic-field

name = ''

Name of the model

moneyness

moneyness(log_strikes)

Time-adjusted moneyness for one or many log-strikes

Source code in quantflow/options/pricer.py
def moneyness(self, log_strikes: FloatArrayLike) -> FloatArrayLike:
    """Time-adjusted moneyness for one or many log-strikes"""
    return log_strikes / np.sqrt(self.ttm)

price

price(option_type, strike, forward)

Price a single option

This method will use the cache to get the maturity pricer if possible

PARAMETER DESCRIPTION
option_type

Type of the option (call or put)

TYPE: OptionType

strike

Strike price of the option

TYPE: float | Decimal

forward

Forward price of the underlying

TYPE: float | Decimal

Source code in quantflow/options/pricer.py
def price(
    self,
    option_type: Annotated[OptionType, Doc("Type of the option (call or put)")],
    strike: Annotated[float | Decimal, Doc("Strike price of the option")],
    forward: Annotated[float | Decimal, Doc("Forward price of the underlying")],
) -> ModelOptionPrice:
    """Price a single option

    This method will use the cache to get the maturity pricer if possible
    """
    strike_ = to_decimal(strike)
    forward_ = to_decimal(forward)
    log_strike = float((strike_ / forward_).ln())
    result = self.pricing.call_greeks(log_strike)
    return ModelOptionPrice(
        option_type=OptionType.call,
        strike=strike_,
        forward=forward_,
        log_strike=log_strike,
        moneyness=float(self.moneyness(log_strike)),
        ttm=self.ttm,
        price=result.price,
        delta=result.delta,
        gamma=result.gamma,
    ).as_option_type(option_type)

prices

prices(log_strikes)

Price a batch of call options with the same TTM and different log-strikes

Source code in quantflow/options/pricer.py
def prices(self, log_strikes: FloatArray) -> pd.DataFrame:
    """Price a batch of call options with the same TTM and different log-strikes"""
    call_prices = self.pricing.call_price(log_strikes)
    ivs = implied_black_volatility(
        log_strikes,
        call_prices,
        ttm=self.ttm,
        initial_sigma=0.5 * np.ones_like(log_strikes),
        call_put=1.0,
    ).values
    return pd.DataFrame(
        {
            "log_strike": log_strikes,
            "moneyness": self.moneyness(log_strikes),
            "call": call_prices,
            "implied_vol": ivs,
            "time_value": call_prices - np.maximum(0, 1 - np.exp(log_strikes)),
        }
    )

quantflow.options.pricer.ModelOptionPrice pydantic-model

Bases: BaseModel

Model price and sensitivities of an option for a given strike, forward and time to maturity.

The price field is always in forward space, regardless of whether the underlying market quotes options in the quote currency (e.g. SPX, USD) or in the underlying (inverse options, e.g. BTC).

\[\begin{equation} c = \frac{C}{F} \end{equation}\]

Use price_in_quote to recover the quote-currency premium \(C = c\,F\).

Also exposes Black price and sensitivities for comparison.

Fields:

strike pydantic-field

strike

Strike price of the option

option_type pydantic-field

option_type

Type of the option, call or put

forward pydantic-field

forward

Forward price of the underlying

log_strike pydantic-field

log_strike

Log strike over forward, i.e. log(K/F)

moneyness pydantic-field

moneyness

Moneyness

ttm pydantic-field

ttm = 0

Time to maturity in years

price pydantic-field

price

Option price in forward space. Multiply by [forward][quantflow.options.pricer.ModelOptionPrice.price.forward] (or read [price_in_quote][quantflow.options.pricer.ModelOptionPrice.price.price_in_quote]) to obtain the quote-currency premium.

delta pydantic-field

delta

Model delta of the option

gamma pydantic-field

gamma

Model gamma of the option

price_in_quote property

price_in_quote

Premium in the quote currency: forward-space price times forward.

For inverse markets (BTC) the conventional premium is in the underlying and equals [price][quantflow.options.pricer.ModelOptionPrice.price_in_quote.price] directly; callers should pick the convention that matches their downstream consumer.

parity property

parity

Put call parity value for the option, i.e. the difference between call and put price for the same strike and maturity

black property

black

Calculate the Black price for the option using the price as time value and log-strike

intrinsic_value property

intrinsic_value

Calculate the intrinsic value of the option in forward space for the given moneyness

This is the value of the option if it were to expire immediately, and it depends only on the moneyness and the type of the option.

For a call option, the intrinsic value is non-negative when the moneyness is negative, i.e. when the strike is below the forward price. For a put option, the intrinsic value is non-negative when the moneyness is positive, i.e. when the strike is above the forward price.

as_option_type

as_option_type(option_type)

Convert the option price to the given option type via put-call parity.

PARAMETER DESCRIPTION
option_type

Type of the option, call or put

TYPE: OptionType

Source code in quantflow/options/pricer.py
def as_option_type(
    self,
    option_type: Annotated[
        OptionType,
        Doc("Type of the option, call or put"),
    ],
) -> Self:
    """Convert the option price to the given option type via put-call parity."""
    if self.option_type == option_type:
        return self
    if self.option_type == OptionType.call:
        new_price = self.price - self.parity
        new_delta = self.delta - 1.0
    else:
        new_price = self.price + self.parity
        new_delta = self.delta + 1.0
    return self.model_copy(
        update=dict(
            option_type=option_type,
            price=new_price,
            delta=new_delta,
        )
    )