Skip to content

Stochastic Processes

This page gives an overview of all stochastic processes available in the library.

Available processes

Diffusion

Process Description
WienerProcess Standard Brownian motion

Mean-reverting (intensity)

Process Description
CIR Cox-Ingersoll-Ross square-root diffusion
Vasicek Gaussian Ornstein-Uhlenbeck process
NGOU Generic non-Gaussian OU process driven by a pure-jump Lévy process
GammaOU Non-Gaussian OU process with Gamma stationary marginal

Jump processes

Process Description
PoissonBase Abstract base for Poisson-style jump processes
PoissonProcess Homogeneous Poisson process
CompoundPoissonProcess Poisson process with random jump sizes
DSP Doubly stochastic (Cox) Poisson process

Stochastic volatility

Process Description
Heston Classical square-root stochastic volatility model
HestonJ Heston model with compound Poisson jumps
DoubleHeston Two independent Heston variance processes
DoubleHestonJ Double Heston with compound Poisson jumps on the first component
BNS Barndorff-Nielsen and Shephard model with Gamma-OU variance
BNS2 Two-factor BNS with convex-combination variance

Jump diffusion

Process Description
JumpDiffusion Diffusion with compound Poisson jumps

Copulas

Process Description
Copula Abstract base class for bivariate copulas
IndependentCopula Independence copula \(C(u, v) = u v\)
FrankCopula Archimedean Frank copula

Base classes

quantflow.sp.base.StochasticProcess pydantic-model

Bases: BaseModel, ABC

Base class for stochastic processes in continuous time

sample_from_draws abstractmethod

sample_from_draws(draws, *args)

Sample Paths from the process given a set of draws

PARAMETER DESCRIPTION
draws

Pre-drawn random increments (typically standard normal)

TYPE: Paths

*args

Additional pre-drawn paths for multi-factor models

TYPE: Paths DEFAULT: ()

Source code in quantflow/sp/base.py
@abstractmethod
def sample_from_draws(
    self,
    draws: Annotated[
        Paths,
        Doc("Pre-drawn random increments (typically standard normal)"),
    ],
    *args: Annotated[
        Paths,
        Doc("Additional pre-drawn paths for multi-factor models"),
    ],
) -> Paths:
    """Sample [Paths][quantflow.ta.paths.Paths]
    from the process given a set of draws"""

sample abstractmethod

sample(n, time_horizon=1, time_steps=100)

Generate random Paths from the process.

PARAMETER DESCRIPTION
n

number of paths

TYPE: int

time_horizon

time horizon

TYPE: float DEFAULT: 1

time_steps

number of time steps to arrive at horizon

TYPE: int DEFAULT: 100

Source code in quantflow/sp/base.py
@abstractmethod
def sample(
    self,
    n: Annotated[int, Doc("number of paths")],
    time_horizon: Annotated[float, Doc("time horizon")] = 1,
    time_steps: Annotated[
        int, Doc("number of time steps to arrive at horizon")
    ] = 100,
) -> Paths:
    """Generate random [Paths][quantflow.ta.paths.Paths] from the process."""

characteristic_exponent abstractmethod

characteristic_exponent(t, u)

Characteristic exponent at time t for a given input parameter

Source code in quantflow/sp/base.py
@abstractmethod
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    """Characteristic exponent at time `t` for a given input parameter"""

characteristic

characteristic(t, u)

Characteristic function at time t for a given input parameter u

The characteristic function represents the Fourier transform of the probability density function

\[\begin{equation} \Phi = {\mathbb E} \left[e^{i u x_t}\right] = e^{-\phi(t, u)} \end{equation}\]

where \(\phi\) is the characteristic exponent, which can be more easily computed for many processes.

PARAMETER DESCRIPTION
t

Time horizon

TYPE: FloatArrayLike

u

Characteristic function input parameter

TYPE: Vector

Source code in quantflow/sp/base.py
def characteristic(
    self,
    t: Annotated[FloatArrayLike, Doc("Time horizon")],
    u: Annotated[Vector, Doc("Characteristic function input parameter")],
) -> Vector:
    r"""Characteristic function at time `t` for a given input parameter `u`

    The characteristic function represents the Fourier transform of the
    probability density function

    \begin{equation}
        \Phi = {\mathbb E} \left[e^{i u x_t}\right] = e^{-\phi(t, u)}
    \end{equation}

    where $\phi$ is the characteristic exponent, which can be more easily
    computed for many processes.
    """
    return np.exp(-self.characteristic_exponent(t, u))

convexity_correction

convexity_correction(t)

Convexity correction for the process

Source code in quantflow/sp/base.py
def convexity_correction(self, t: FloatArrayLike) -> Vector:
    """Convexity correction for the process"""
    return -self.characteristic_exponent(t, complex(0, -1)).real

analytical_std

analytical_std(t)

Analytical standard deviation of the process at time t

This has a closed form solution if the process has an analytical variance

Source code in quantflow/sp/base.py
def analytical_std(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical standard deviation of the process at time `t`

    This has a closed form solution if the process has an analytical variance
    """
    return np.sqrt(self.analytical_variance(t))

analytical_mean

analytical_mean(t)

Analytical mean of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_mean(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical mean of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_variance

analytical_variance(t)

Analytical variance of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_variance(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical variance of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_pdf

analytical_pdf(t, x)

Analytical pdf of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_pdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Analytical pdf of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_cdf

analytical_cdf(t, x)

Analytical cdf of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_cdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Analytical cdf of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

quantflow.sp.base.StochasticProcess1D pydantic-model

Bases: StochasticProcess

Base class for 1D stochastic process in continuous time

marginal

marginal(t)

Marginal distribution of the process at time t

Source code in quantflow/sp/base.py
def marginal(self, t: FloatArrayLike) -> StochasticProcess1DMarginal:
    """Marginal distribution of the process at time `t`"""
    return StochasticProcess1DMarginal(process=self, t=t)

domain_range

domain_range()
Source code in quantflow/sp/base.py
def domain_range(self) -> Bounds:
    return default_bounds()

frequency_range

frequency_range(std, max_frequency=None)

Maximum frequency when calculating characteristic functions

Source code in quantflow/sp/base.py
def frequency_range(self, std: float, max_frequency: float | None = None) -> Bounds:
    """Maximum frequency when calculating characteristic functions"""
    if max_frequency is None:
        max_frequency = np.sqrt(40 / std / std)
    return Bounds(0, max_frequency)

support

support(mean, std, points)

Support of the process at time t

Source code in quantflow/sp/base.py
def support(self, mean: float, std: float, points: int) -> FloatArray:
    """Support of the process at time `t`"""
    bounds = self.domain_range()
    start = float(sigfig(bound_from_any(bounds.lb, mean - std)))
    end = float(sigfig(bound_from_any(bounds.ub, mean + std)))
    return np.linspace(start, end, points + 1)

sample_from_draws abstractmethod

sample_from_draws(draws, *args)

Sample Paths from the process given a set of draws

PARAMETER DESCRIPTION
draws

Pre-drawn random increments (typically standard normal)

TYPE: Paths

*args

Additional pre-drawn paths for multi-factor models

TYPE: Paths DEFAULT: ()

Source code in quantflow/sp/base.py
@abstractmethod
def sample_from_draws(
    self,
    draws: Annotated[
        Paths,
        Doc("Pre-drawn random increments (typically standard normal)"),
    ],
    *args: Annotated[
        Paths,
        Doc("Additional pre-drawn paths for multi-factor models"),
    ],
) -> Paths:
    """Sample [Paths][quantflow.ta.paths.Paths]
    from the process given a set of draws"""

sample abstractmethod

sample(n, time_horizon=1, time_steps=100)

Generate random Paths from the process.

PARAMETER DESCRIPTION
n

number of paths

TYPE: int

time_horizon

time horizon

TYPE: float DEFAULT: 1

time_steps

number of time steps to arrive at horizon

TYPE: int DEFAULT: 100

Source code in quantflow/sp/base.py
@abstractmethod
def sample(
    self,
    n: Annotated[int, Doc("number of paths")],
    time_horizon: Annotated[float, Doc("time horizon")] = 1,
    time_steps: Annotated[
        int, Doc("number of time steps to arrive at horizon")
    ] = 100,
) -> Paths:
    """Generate random [Paths][quantflow.ta.paths.Paths] from the process."""

characteristic_exponent abstractmethod

characteristic_exponent(t, u)

Characteristic exponent at time t for a given input parameter

Source code in quantflow/sp/base.py
@abstractmethod
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    """Characteristic exponent at time `t` for a given input parameter"""

characteristic

characteristic(t, u)

Characteristic function at time t for a given input parameter u

The characteristic function represents the Fourier transform of the probability density function

\[\begin{equation} \Phi = {\mathbb E} \left[e^{i u x_t}\right] = e^{-\phi(t, u)} \end{equation}\]

where \(\phi\) is the characteristic exponent, which can be more easily computed for many processes.

PARAMETER DESCRIPTION
t

Time horizon

TYPE: FloatArrayLike

u

Characteristic function input parameter

TYPE: Vector

Source code in quantflow/sp/base.py
def characteristic(
    self,
    t: Annotated[FloatArrayLike, Doc("Time horizon")],
    u: Annotated[Vector, Doc("Characteristic function input parameter")],
) -> Vector:
    r"""Characteristic function at time `t` for a given input parameter `u`

    The characteristic function represents the Fourier transform of the
    probability density function

    \begin{equation}
        \Phi = {\mathbb E} \left[e^{i u x_t}\right] = e^{-\phi(t, u)}
    \end{equation}

    where $\phi$ is the characteristic exponent, which can be more easily
    computed for many processes.
    """
    return np.exp(-self.characteristic_exponent(t, u))

convexity_correction

convexity_correction(t)

Convexity correction for the process

Source code in quantflow/sp/base.py
def convexity_correction(self, t: FloatArrayLike) -> Vector:
    """Convexity correction for the process"""
    return -self.characteristic_exponent(t, complex(0, -1)).real

analytical_std

analytical_std(t)

Analytical standard deviation of the process at time t

This has a closed form solution if the process has an analytical variance

Source code in quantflow/sp/base.py
def analytical_std(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical standard deviation of the process at time `t`

    This has a closed form solution if the process has an analytical variance
    """
    return np.sqrt(self.analytical_variance(t))

analytical_mean

analytical_mean(t)

Analytical mean of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_mean(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical mean of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_variance

analytical_variance(t)

Analytical variance of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_variance(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical variance of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_pdf

analytical_pdf(t, x)

Analytical pdf of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_pdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Analytical pdf of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_cdf

analytical_cdf(t, x)

Analytical cdf of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_cdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Analytical cdf of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

quantflow.sp.base.IntensityProcess pydantic-model

Bases: StochasticProcess1D

Base class for mean reverting 1D processes which can be used as stochastic intensity

Fields:

rate pydantic-field

rate = 1.0

Instantaneous initial rate \(x_0\)

kappa pydantic-field

kappa = 1.0

Mean reversion speed \(\kappa\)

integrated_log_laplace abstractmethod

integrated_log_laplace(t, u)

The log-Laplace transform of the cumulative process:

\[\begin{equation} \phi_{t, u} = \log {\mathbb E} \left[e^{-u \int_0^t x_s ds}\right] \end{equation}\]
PARAMETER DESCRIPTION
t

time horizon

TYPE: FloatArrayLike

u

frequency

TYPE: Vector

Source code in quantflow/sp/base.py
@abstractmethod
def integrated_log_laplace(
    self,
    t: Annotated[FloatArrayLike, Doc("time horizon")],
    u: Annotated[Vector, Doc("frequency")],
) -> Vector:
    r"""The log-Laplace transform of the cumulative process:

    \begin{equation}
        \phi_{t, u} = \log {\mathbb E} \left[e^{-u \int_0^t x_s ds}\right]
    \end{equation}
    """

domain_range

domain_range()
Source code in quantflow/sp/base.py
def domain_range(self) -> Bounds:
    return Bounds(0, np.inf)

ekt

ekt(t)
Source code in quantflow/sp/base.py
def ekt(self, t: FloatArrayLike) -> FloatArrayLike:
    return np.exp(-self.kappa * t)

sample_from_draws abstractmethod

sample_from_draws(draws, *args)

Sample Paths from the process given a set of draws

PARAMETER DESCRIPTION
draws

Pre-drawn random increments (typically standard normal)

TYPE: Paths

*args

Additional pre-drawn paths for multi-factor models

TYPE: Paths DEFAULT: ()

Source code in quantflow/sp/base.py
@abstractmethod
def sample_from_draws(
    self,
    draws: Annotated[
        Paths,
        Doc("Pre-drawn random increments (typically standard normal)"),
    ],
    *args: Annotated[
        Paths,
        Doc("Additional pre-drawn paths for multi-factor models"),
    ],
) -> Paths:
    """Sample [Paths][quantflow.ta.paths.Paths]
    from the process given a set of draws"""

sample abstractmethod

sample(n, time_horizon=1, time_steps=100)

Generate random Paths from the process.

PARAMETER DESCRIPTION
n

number of paths

TYPE: int

time_horizon

time horizon

TYPE: float DEFAULT: 1

time_steps

number of time steps to arrive at horizon

TYPE: int DEFAULT: 100

Source code in quantflow/sp/base.py
@abstractmethod
def sample(
    self,
    n: Annotated[int, Doc("number of paths")],
    time_horizon: Annotated[float, Doc("time horizon")] = 1,
    time_steps: Annotated[
        int, Doc("number of time steps to arrive at horizon")
    ] = 100,
) -> Paths:
    """Generate random [Paths][quantflow.ta.paths.Paths] from the process."""

characteristic_exponent abstractmethod

characteristic_exponent(t, u)

Characteristic exponent at time t for a given input parameter

Source code in quantflow/sp/base.py
@abstractmethod
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    """Characteristic exponent at time `t` for a given input parameter"""

characteristic

characteristic(t, u)

Characteristic function at time t for a given input parameter u

The characteristic function represents the Fourier transform of the probability density function

\[\begin{equation} \Phi = {\mathbb E} \left[e^{i u x_t}\right] = e^{-\phi(t, u)} \end{equation}\]

where \(\phi\) is the characteristic exponent, which can be more easily computed for many processes.

PARAMETER DESCRIPTION
t

Time horizon

TYPE: FloatArrayLike

u

Characteristic function input parameter

TYPE: Vector

Source code in quantflow/sp/base.py
def characteristic(
    self,
    t: Annotated[FloatArrayLike, Doc("Time horizon")],
    u: Annotated[Vector, Doc("Characteristic function input parameter")],
) -> Vector:
    r"""Characteristic function at time `t` for a given input parameter `u`

    The characteristic function represents the Fourier transform of the
    probability density function

    \begin{equation}
        \Phi = {\mathbb E} \left[e^{i u x_t}\right] = e^{-\phi(t, u)}
    \end{equation}

    where $\phi$ is the characteristic exponent, which can be more easily
    computed for many processes.
    """
    return np.exp(-self.characteristic_exponent(t, u))

convexity_correction

convexity_correction(t)

Convexity correction for the process

Source code in quantflow/sp/base.py
def convexity_correction(self, t: FloatArrayLike) -> Vector:
    """Convexity correction for the process"""
    return -self.characteristic_exponent(t, complex(0, -1)).real

analytical_std

analytical_std(t)

Analytical standard deviation of the process at time t

This has a closed form solution if the process has an analytical variance

Source code in quantflow/sp/base.py
def analytical_std(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical standard deviation of the process at time `t`

    This has a closed form solution if the process has an analytical variance
    """
    return np.sqrt(self.analytical_variance(t))

analytical_mean

analytical_mean(t)

Analytical mean of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_mean(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical mean of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_variance

analytical_variance(t)

Analytical variance of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_variance(self, t: FloatArrayLike) -> FloatArrayLike:
    """Analytical variance of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_pdf

analytical_pdf(t, x)

Analytical pdf of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_pdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Analytical pdf of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

analytical_cdf

analytical_cdf(t, x)

Analytical cdf of the process at time t

Implement if available

Source code in quantflow/sp/base.py
def analytical_cdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Analytical cdf of the process at time `t`

    Implement if available
    """
    raise NotImplementedError

marginal

marginal(t)

Marginal distribution of the process at time t

Source code in quantflow/sp/base.py
def marginal(self, t: FloatArrayLike) -> StochasticProcess1DMarginal:
    """Marginal distribution of the process at time `t`"""
    return StochasticProcess1DMarginal(process=self, t=t)

frequency_range

frequency_range(std, max_frequency=None)

Maximum frequency when calculating characteristic functions

Source code in quantflow/sp/base.py
def frequency_range(self, std: float, max_frequency: float | None = None) -> Bounds:
    """Maximum frequency when calculating characteristic functions"""
    if max_frequency is None:
        max_frequency = np.sqrt(40 / std / std)
    return Bounds(0, max_frequency)

support

support(mean, std, points)

Support of the process at time t

Source code in quantflow/sp/base.py
def support(self, mean: float, std: float, points: int) -> FloatArray:
    """Support of the process at time `t`"""
    bounds = self.domain_range()
    start = float(sigfig(bound_from_any(bounds.lb, mean - std)))
    end = float(sigfig(bound_from_any(bounds.ub, mean + std)))
    return np.linspace(start, end, points + 1)

quantflow.sp.base.StochasticProcess1DMarginal pydantic-model

Bases: Marginal1D, Generic[P]

Fields:

process pydantic-field

process

t pydantic-field

t

std_norm

std_norm()

Standard deviation at a time horizon normalized by the time

Source code in quantflow/sp/base.py
def std_norm(self) -> Vector:
    """Standard deviation at a time horizon normalized by the time"""
    return np.sqrt(self.variance() / self.t)

characteristic

characteristic(u)
Source code in quantflow/sp/base.py
def characteristic(self, u: Vector) -> Vector:
    return self.process.characteristic(self.t, u)

domain_range

domain_range()
Source code in quantflow/sp/base.py
def domain_range(self) -> Bounds:
    return self.process.domain_range()

frequency_range

frequency_range(max_frequency=None)
Source code in quantflow/sp/base.py
def frequency_range(self, max_frequency: float | None = None) -> Bounds:
    std = float(np.min(self.std()))
    return self.process.frequency_range(std, max_frequency=max_frequency)

pdf_analytical

pdf_analytical(x)
Source code in quantflow/sp/base.py
def pdf_analytical(self, x: FloatArrayLike) -> FloatArrayLike:
    return self.process.analytical_pdf(self.t, x)

cdf_analytical

cdf_analytical(x)
Source code in quantflow/sp/base.py
def cdf_analytical(self, x: FloatArrayLike) -> FloatArrayLike:
    return self.process.analytical_cdf(self.t, x)

mean

mean()
Source code in quantflow/sp/base.py
def mean(self) -> FloatArrayLike:
    try:
        return self.process.analytical_mean(self.t)
    except NotImplementedError:
        return self.mean_from_characteristic()

variance

variance()
Source code in quantflow/sp/base.py
def variance(self) -> FloatArrayLike:
    try:
        return self.process.analytical_variance(self.t)
    except NotImplementedError:
        return self.variance_from_characteristic()

support

support(points=100, *, std_mult=4)
Source code in quantflow/sp/base.py
def support(self, points: int = 100, *, std_mult: float = 4) -> FloatArray:
    return self.process.support(
        float(self.mean()), std_mult * float(self.std()), points
    )

call_option_carr_madan_alpha

call_option_carr_madan_alpha()

Option alpha parameter for integrability of call option transform in the Carr-Madan formula.

The choice of alpha is crucial for the numerical stability of the Carr-Madan formula. A common choice is to set alpha to a value that ensures the integrand decays sufficiently fast at high frequencies. The maturity-dependent heuristic below (high alpha for short maturities, decaying to a 0.5 floor for long ones) has been found to work well in practice across the processes in this library; override on a per-process basis if needed.

Source code in quantflow/sp/base.py
def call_option_carr_madan_alpha(self) -> float:
    """Option alpha parameter for integrability of call option transform
    in the Carr-Madan formula.

    The choice of alpha is crucial for the numerical stability of the Carr-Madan
    formula. A common choice is to set alpha to a value that ensures the integrand
    decays sufficiently fast at high frequencies. The maturity-dependent heuristic
    below (high alpha for short maturities, decaying to a 0.5 floor for long ones)
    has been found to work well in practice across the processes in this library;
    override on a per-process basis if needed.
    """
    return max(8 * np.max(np.exp(-2 * self.t)), 0.5)

sample

sample(size=1)

Draw samples by inverse-transform sampling of the CDF.

The cdf is evaluated on the support grid and inverted by interpolation against uniform draws.

Subclasses with a closed-form sampler should override this.

PARAMETER DESCRIPTION
size

Number of samples to draw.

TYPE: int DEFAULT: 1

Source code in quantflow/dists/marginal1d.py
def sample(
    self,
    size: Annotated[int, Doc("Number of samples to draw.")] = 1,
) -> FloatArray:
    """Draw samples by inverse-transform sampling of the CDF.

    The [cdf][..cdf] is evaluated on the [support][..support] grid and
    inverted by interpolation against uniform draws.

    Subclasses with a closed-form sampler should override this.
    """
    x = self.support()
    cdf = np.asarray(self.cdf(x), dtype=float)
    return cast(FloatArray, np.interp(np.random.uniform(size=size), cdf, x))

call_option

call_option(
    n=None,
    *,
    pricing_method=CARR_MADAN,
    cos_moneyness_std_precision=12,
    max_moneyness=1.5,
    max_frequency=None,
    alpha=None,
    simpson_rule=False,
    use_fft=False
)

Price call options via one of the available Fourier-based methods, as a function of log-strike

PARAMETER DESCRIPTION
n

Number of discretization points for the transform. Defaults to 128.

TYPE: int | None DEFAULT: None

pricing_method

Method to use for option pricing via Fourier transform of the call option price. Defaults to Lewis.

TYPE: OptionPricingMethod DEFAULT: CARR_MADAN

cos_moneyness_std_precision

Truncation parameter for COS: the integration interval is set to [-cos_moneyness_std_precisionstd, cos_moneyness_std_precisionstd].

TYPE: float DEFAULT: 12

max_moneyness

Maximum moneyness to calculate prices. The log-strike grid is set to [-max_moneynessstd, max_moneynessstd]. Used by Lewis and Carr & Madan methods only.

TYPE: float DEFAULT: 1.5

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

alpha

Damping parameter for integrability of the Carr-Madan integrand, it is ignored if not applicable.

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform rather than FRFT. Default is False.

TYPE: bool DEFAULT: False

Source code in quantflow/dists/marginal1d.py
def call_option(
    self,
    n: Annotated[
        int | None,
        Doc("Number of discretization points for the transform. Defaults to 128."),
    ] = None,
    *,
    pricing_method: Annotated[
        OptionPricingMethod,
        Doc(
            "Method to use for option pricing via Fourier transform of the "
            "call option price. Defaults to Lewis."
        ),
    ] = OptionPricingMethod.CARR_MADAN,
    cos_moneyness_std_precision: Annotated[
        float,
        Doc(
            "Truncation parameter for COS: the integration interval is set to "
            "[-cos_moneyness_std_precision*std, cos_moneyness_std_precision*std]."
        ),
    ] = 12,
    max_moneyness: Annotated[
        float,
        Doc(
            "Maximum moneyness to calculate prices. The log-strike grid is set to "
            "[-max_moneyness*std, max_moneyness*std]. "
            "Used by Lewis and Carr & Madan methods only."
        ),
    ] = 1.5,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    alpha: Annotated[
        float | None,
        Doc(
            "Damping parameter for integrability of the Carr-Madan integrand, "
            "it is ignored if not applicable."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform rather than FRFT. Default is False.")
    ] = False,
) -> OptionPricingResult:
    """Price call options via one of the available Fourier-based methods,
    as a function of log-strike
    """
    match pricing_method:
        case OptionPricingMethod.COS:
            return self.call_option_cos(
                n,
                moneyness_std_precision=cos_moneyness_std_precision,
            )
        case OptionPricingMethod.CARR_MADAN:
            return self.call_option_carr_madan(
                n,
                max_frequency=max_frequency,
                max_moneyness=max_moneyness,
                alpha=alpha,
                simpson_rule=simpson_rule,
                use_fft=use_fft,
            )
        case _:
            return self.call_option_lewis(
                n,
                max_frequency=max_frequency,
                max_moneyness=max_moneyness,
                simpson_rule=simpson_rule,
                use_fft=use_fft,
            )

call_option_carr_madan

call_option_carr_madan(
    n=None,
    *,
    max_moneyness=1.5,
    max_frequency=None,
    alpha=None,
    simpson_rule=False,
    use_fft=False
)

Call option price via Carr & Madan method

PARAMETER DESCRIPTION
n

Number of discretization points for the transform. Defaults to 128.

TYPE: int | None DEFAULT: None

max_moneyness

Maximum moneyness to calculate prices. The log-strike grid is set to [-max_moneynessstd, max_moneynessstd].

TYPE: float DEFAULT: 1.5

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

alpha

Damping parameter for integrability of the Carr-Madan integrand. Defaults to call_option_carr_madan_alpha.

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform. Default is False.

TYPE: bool DEFAULT: False

Source code in quantflow/dists/marginal1d.py
def call_option_carr_madan(
    self,
    n: Annotated[
        int | None,
        Doc("Number of discretization points for the transform. Defaults to 128."),
    ] = None,
    *,
    max_moneyness: Annotated[
        float,
        Doc(
            "Maximum moneyness to calculate prices. The log-strike grid is set to "
            "[-max_moneyness*std, max_moneyness*std]. "
        ),
    ] = 1.5,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    alpha: Annotated[
        float | None,
        Doc(
            "Damping parameter for integrability of the Carr-Madan integrand. "
            "Defaults to "
            "[call_option_carr_madan_alpha][..call_option_carr_madan_alpha]."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform. Default is False.")
    ] = False,
) -> OptionPricingTransformResult:
    """Call option price via Carr & Madan method"""
    max_log_strike = max_moneyness * self.std_validated()
    transform = self.get_transform(
        n,
        lambda m: self.option_support(m + 1, max_log_strike=max_log_strike),
        max_frequency=max_frequency,
        simpson_rule=simpson_rule,
        use_fft=use_fft,
    )
    alpha = alpha or self.call_option_carr_madan_alpha()
    phi = cast(
        np.ndarray,
        self.call_option_transform(transform.frequency_domain - 1j * alpha),
    )
    result = transform(phi, use_fft=use_fft)
    return OptionPricingTransformResult(
        log_strikes=result.x,
        call_prices=result.y * np.exp(-alpha * result.x),
    )

call_option_cos

call_option_cos(n=None, *, moneyness_std_precision=12)

Call option price via the COS method (Fang & Oosterlee 2008).

The call price at log-strike \(k\) is approximated by the cosine series

\[\begin{equation} C(k) \approx e^k \sum_{j=0}^{N-1}{}^{\prime} \mathrm{Re}\!\left[ \phi\!\left(\frac{j\pi}{b-a}\right) e^{-i j\pi (k+a)/(b-a)} \right] V_j \end{equation}\]

where the prime denotes a half weight on the \(j=0\) term, \([a,b]\) is the truncation interval, and \(V_j\) are the cosine payoff coefficients for the normalised call payoff \((e^y - 1)^+\) integrated over \([0, b]\). The \(e^k\) factor converts from strike-normalised to forward-space pricing.

Returns an OptionPricingCosResult with the precomputed coefficient vector. Use call_price to evaluate at arbitrary log-strikes in \(O(N)\) per strike.

PARAMETER DESCRIPTION
n

Number of cosine series terms. Defaults to 128.

TYPE: int | None DEFAULT: None

moneyness_std_precision

Truncation parameter: the integration interval is set to [-moneyness_std_precisionstd, moneyness_std_precisionstd].

TYPE: float DEFAULT: 12

Source code in quantflow/dists/marginal1d.py
def call_option_cos(
    self,
    n: Annotated[
        int | None,
        Doc("Number of cosine series terms. Defaults to 128."),
    ] = None,
    *,
    moneyness_std_precision: Annotated[
        float,
        Doc(
            "Truncation parameter: the integration interval is set to "
            "[-moneyness_std_precision*std, moneyness_std_precision*std]."
        ),
    ] = 12,
) -> OptionPricingCosResult:
    r"""Call option price via the COS method (Fang & Oosterlee 2008).

    The call price at log-strike $k$ is approximated by the cosine series

    \begin{equation}
        C(k) \approx e^k \sum_{j=0}^{N-1}{}^{\prime}
            \mathrm{Re}\!\left[
                \phi\!\left(\frac{j\pi}{b-a}\right)
                e^{-i j\pi (k+a)/(b-a)}
            \right] V_j
    \end{equation}

    where the prime denotes a half weight on the $j=0$ term, $[a,b]$ is the
    truncation interval, and $V_j$ are the cosine payoff coefficients for the
    normalised call payoff $(e^y - 1)^+$ integrated over $[0, b]$.
    The $e^k$ factor converts from strike-normalised to forward-space pricing.

    Returns an
    [OptionPricingCosResult][quantflow.dists.OptionPricingCosResult]
    with the precomputed coefficient vector. Use
    [call_price][quantflow.dists.OptionPricingCosResult.call_price]
    to evaluate at arbitrary log-strikes in $O(N)$ per strike.
    """
    n = n or 128
    std = self.std_validated()
    a = -moneyness_std_precision * std
    b = moneyness_std_precision * std
    bma = b - a

    j = np.arange(n)
    nu = j * np.pi / bma

    phi = np.asarray(self.characteristic_corrected(nu))

    sin_nu_a = np.sin(nu * a)
    cos_nu_a = np.cos(nu * a)
    sign_j = (-1.0) ** j
    safe_nu = np.where(j == 0, 1.0, nu)
    chi = (np.exp(b) * sign_j - cos_nu_a + nu * sin_nu_a) / np.where(
        j == 0, 1.0, 1.0 + nu**2
    )
    psi = np.where(j == 0, b, sin_nu_a / safe_nu)
    V = 2.0 / bma * (chi - psi)
    weights = np.ones(n)
    weights[0] = 0.5
    return OptionPricingCosResult(
        a=a,
        nu=nu,
        coeff=weights * phi * V,
    )

call_option_lewis

call_option_lewis(
    n=None,
    *,
    max_moneyness=1.5,
    max_frequency=None,
    simpson_rule=False,
    use_fft=False
)

Call option price via the Lewis (2001) formula

PARAMETER DESCRIPTION
n

Number of discretization points for the transform. Defaults to 128.

TYPE: int | None DEFAULT: None

max_moneyness

Maximum moneyness to calculate prices. The log-strike grid is set to [-max_moneynessstd, max_moneynessstd].

TYPE: float DEFAULT: 1.5

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform. Default is False.

TYPE: bool DEFAULT: False

Source code in quantflow/dists/marginal1d.py
def call_option_lewis(
    self,
    n: Annotated[
        int | None,
        Doc("Number of discretization points for the transform. Defaults to 128."),
    ] = None,
    *,
    max_moneyness: Annotated[
        float,
        Doc(
            "Maximum moneyness to calculate prices. The log-strike grid is set to "
            "[-max_moneyness*std, max_moneyness*std]. "
        ),
    ] = 1.5,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform. Default is False.")
    ] = False,
) -> OptionPricingTransformResult:
    """Call option price via the Lewis (2001) formula"""
    max_log_strike = max_moneyness * self.std_validated()
    transform = self.get_transform(
        n,
        lambda m: self.option_support(m + 1, max_log_strike=max_log_strike),
        max_frequency=max_frequency,
        simpson_rule=simpson_rule,
        use_fft=use_fft,
    )
    phi = cast(np.ndarray, self.lewis_transform(transform.frequency_domain))
    result = transform(phi, use_fft=use_fft)
    k = result.x
    return OptionPricingTransformResult(
        log_strikes=k,
        call_prices=1.0 - np.exp(0.5 * k) * result.y,
    )

call_option_transform

call_option_transform(u)

Call option transform

PARAMETER DESCRIPTION
u

Frequency domain points (possibly complex-shifted).

TYPE: Vector

Source code in quantflow/dists/marginal1d.py
def call_option_transform(
    self,
    u: Annotated[
        Vector, Doc("Frequency domain points (possibly complex-shifted).")
    ],
) -> Vector:
    """Call option transform"""
    uj = 1j * u
    return self.characteristic_corrected(u - 1j) / (uj * uj + uj)

cdf

cdf(x)

Compute the cumulative distribution function.

It returns the cdf_analytical when available, otherwise it interpolates the cdf obtained from the characteristic function via cdf_from_characteristic.

PARAMETER DESCRIPTION
x

Location in the stochastic process domain space. If a numpy array, the output should have the same shape as the input.

TYPE: FloatArrayLike

Source code in quantflow/dists/marginal1d.py
def cdf(
    self,
    x: Annotated[
        FloatArrayLike,
        Doc(
            "Location in the stochastic process domain space. If a numpy array,"
            " the output should have the same shape as the input."
        ),
    ],
) -> FloatArrayLike:
    """Compute the cumulative distribution function.

    It returns the [cdf_analytical][..cdf_analytical]
    when available, otherwise it interpolates the cdf obtained from the
    characteristic function via
    [cdf_from_characteristic][..cdf_from_characteristic].
    """
    try:
        return self.cdf_analytical(x)
    except NotImplementedError:
        result = self.cdf_from_characteristic()
        return np.interp(x, result.x, result.y)

cdf_from_characteristic

cdf_from_characteristic(
    n=None,
    *,
    max_frequency=None,
    simpson_rule=False,
    use_fft=False,
    frequency_n=None
)

Compute the cumulative distribution function from the characteristic function.

The density from pdf_from_characteristic is cumulatively integrated over the space grid and normalised to one.

PARAMETER DESCRIPTION
n

Number of discretization points for the transform. Defaults to 128.

TYPE: int | None DEFAULT: None

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform. Default is False.

TYPE: bool DEFAULT: False

frequency_n

Number of points for the frequency grid. Overrides n if provided.

TYPE: int | None DEFAULT: None

Source code in quantflow/dists/marginal1d.py
def cdf_from_characteristic(
    self,
    n: Annotated[
        int | None,
        Doc("Number of discretization points for the transform. Defaults to 128."),
    ] = None,
    *,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform. Default is False.")
    ] = False,
    frequency_n: Annotated[
        int | None,
        Doc("Number of points for the frequency grid. Overrides n if provided."),
    ] = None,
) -> TransformResult:
    """Compute the cumulative distribution function from the characteristic
    function.

    The density from [pdf_from_characteristic][..pdf_from_characteristic] is
    cumulatively integrated over the space grid and normalised to one.
    """
    density = self.pdf_from_characteristic(
        n,
        max_frequency=max_frequency,
        simpson_rule=simpson_rule,
        use_fft=use_fft,
        frequency_n=frequency_n,
    )
    x = density.x
    pdf = np.clip(np.real(density.y), 0.0, None)
    cdf = np.concatenate(
        ([0.0], np.cumsum(0.5 * (pdf[1:] + pdf[:-1]) * np.diff(x)))
    )
    return TransformResult(x=x, y=cdf / cdf[-1])

cdf_jacobian

cdf_jacobian(x)

Jacobian of the cdf with respect to the parameters of the process. It is useful for optimization purposes if necessary.

Optional to implement, otherwise raises NotImplementedError if called.

PARAMETER DESCRIPTION
x

Location in the state space of the process.

TYPE: FloatArrayLike

Source code in quantflow/dists/marginal1d.py
def cdf_jacobian(
    self,
    x: Annotated[
        FloatArrayLike, Doc("Location in the state space of the process.")
    ],
) -> np.ndarray:
    """
    Jacobian of the cdf with respect to the parameters of the process.
    It is useful for optimization purposes if necessary.

    Optional to implement, otherwise raises ``NotImplementedError`` if called.
    """
    raise NotImplementedError("Analytical CDF Jacobian not available")

characteristic_corrected

characteristic_corrected(u)

Characteristic function corrected for the convexity of the log-price distribution

PARAMETER DESCRIPTION
u

Frequency domain points.

TYPE: Vector

Source code in quantflow/dists/marginal1d.py
def characteristic_corrected(
    self,
    u: Annotated[Vector, Doc("Frequency domain points.")],
) -> Vector:
    """Characteristic function corrected for the convexity of the log-price
    distribution"""
    convexity = np.log(self.characteristic(-1j))
    return self.characteristic(u) * np.exp(-1j * u * convexity)

characteristic_df

characteristic_df(
    n=None, *, max_frequency=None, simpson_rule=False
)

Compute the characteristic function with n discretization points and a max frequency

PARAMETER DESCRIPTION
n

Number of discretization points for the transform. Defaults to 128.

TYPE: int | None DEFAULT: None

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

Source code in quantflow/dists/marginal1d.py
def characteristic_df(
    self,
    n: Annotated[
        int | None,
        Doc("Number of discretization points for the transform. Defaults to 128."),
    ] = None,
    *,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
) -> pd.DataFrame:
    """
    Compute the characteristic function with n discretization points
    and a max frequency
    """
    transform = self.get_transform(
        n,
        self.support,
        max_frequency=max_frequency,
        simpson_rule=simpson_rule,
    )
    psi = self.characteristic(transform.frequency_domain)
    return transform.characteristic_df(cast(np.ndarray, psi))

get_transform

get_transform(
    n,
    support,
    *,
    max_frequency=None,
    simpson_rule=False,
    use_fft=False
)
PARAMETER DESCRIPTION
n

Number of discretization points. Defaults to 128.

TYPE: int | None

support

Function returning the space domain grid given the number of points.

TYPE: Callable[[int], FloatArray]

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform. Default is False.

TYPE: bool DEFAULT: False

Source code in quantflow/dists/marginal1d.py
def get_transform(
    self,
    n: Annotated[
        int | None,
        Doc("Number of discretization points. Defaults to 128."),
    ],
    support: Annotated[
        Callable[[int], FloatArray],
        Doc("Function returning the space domain grid given the number of points."),
    ],
    *,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform. Default is False.")
    ] = False,
) -> Transform:
    n = n or 128
    if use_fft:
        bounds = self.domain_range()
    else:
        x = support(n)
        bounds = Bounds(float(np.min(x)), float(np.max(x)))
    return Transform.create(
        n,
        frequency_range=self.frequency_range(max_frequency),
        domain_range=bounds,
        simpson_rule=simpson_rule,
    )

lewis_transform

lewis_transform(u)

Lewis (2001) call option transform - no damping parameter required

PARAMETER DESCRIPTION
u

Frequency domain points.

TYPE: Vector

Source code in quantflow/dists/marginal1d.py
def lewis_transform(
    self,
    u: Annotated[Vector, Doc("Frequency domain points.")],
) -> Vector:
    """Lewis (2001) call option transform - no damping parameter required"""
    return self.characteristic_corrected(u - 0.5j) / (u * u + 0.25)

mean_from_characteristic

mean_from_characteristic(*, d=0.001)

Calculate mean as first derivative of characteristic function at 0

PARAMETER DESCRIPTION
d

Step size for finite-difference approximation of the derivative.

TYPE: float DEFAULT: 0.001

Source code in quantflow/dists/marginal1d.py
def mean_from_characteristic(
    self,
    *,
    d: Annotated[
        float,
        Doc("Step size for finite-difference approximation of the derivative."),
    ] = 0.001,
) -> FloatArrayLike:
    """Calculate mean as first derivative of characteristic function at 0"""
    m = -0.5 * 1j * (self.characteristic(d) - self.characteristic(-d)) / d
    return m.real

option_support

option_support(points=101, max_log_strike=1.0)

Compute the x axis.

PARAMETER DESCRIPTION
points

Number of support points.

TYPE: int DEFAULT: 101

max_log_strike

Maximum absolute log-strike.

TYPE: float DEFAULT: 1.0

Source code in quantflow/dists/marginal1d.py
def option_support(
    self,
    points: Annotated[int, Doc("Number of support points.")] = 101,
    max_log_strike: Annotated[float, Doc("Maximum absolute log-strike.")] = 1.0,
) -> FloatArray:
    """
    Compute the x axis.
    """
    return np.linspace(-max_log_strike, max_log_strike, points)

option_time_value

option_time_value(
    n=128,
    *,
    max_frequency=None,
    max_log_strike=1,
    alpha=1.1,
    simpson_rule=False,
    use_fft=False
)

Option time value

PARAMETER DESCRIPTION
n

Number of discretization points for the transform.

TYPE: int DEFAULT: 128

max_frequency

Maximum frequency for the transform grid. Defaults to frequency_range().

TYPE: float | None DEFAULT: None

max_log_strike

Maximum absolute log-strike for the output grid.

TYPE: float DEFAULT: 1

alpha

Contour shift parameter controlling the integration strip.

TYPE: float DEFAULT: 1.1

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform. Default is False.

TYPE: bool DEFAULT: False

Source code in quantflow/dists/marginal1d.py
def option_time_value(
    self,
    n: Annotated[
        int,
        Doc("Number of discretization points for the transform."),
    ] = 128,
    *,
    max_frequency: Annotated[
        float | None,
        Doc(
            "Maximum frequency for the transform grid. "
            "Defaults to frequency_range()."
        ),
    ] = None,
    max_log_strike: Annotated[
        float, Doc("Maximum absolute log-strike for the output grid.")
    ] = 1,
    alpha: Annotated[
        float,
        Doc("Contour shift parameter controlling the integration strip."),
    ] = 1.1,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform. Default is False.")
    ] = False,
) -> TransformResult:
    """Option time value"""
    transform = self.get_transform(
        n,
        lambda m: self.option_support(m + 1, max_log_strike=max_log_strike),
        max_frequency=max_frequency,
        simpson_rule=simpson_rule,
        use_fft=use_fft,
    )
    phi = cast(
        np.ndarray,
        self.option_time_value_transform(transform.frequency_domain, alpha),
    )
    result = transform(phi, use_fft=use_fft)
    time_value = result.y / np.sinh(alpha * result.x)
    return TransformResult(x=result.x, y=time_value)

option_time_value_transform

option_time_value_transform(u, alpha=1.1)

Option time value transform

This transform does not require any additional correction since the integrand is already bounded for positive and negative moneyness

PARAMETER DESCRIPTION
u

Frequency domain points.

TYPE: Vector

alpha

Contour shift parameter controlling the integration strip.

TYPE: float DEFAULT: 1.1

Source code in quantflow/dists/marginal1d.py
def option_time_value_transform(
    self,
    u: Annotated[Vector, Doc("Frequency domain points.")],
    alpha: Annotated[
        float, Doc("Contour shift parameter controlling the integration strip.")
    ] = 1.1,
) -> Vector:
    """Option time value transform

    This transform does not require any additional correction since
    the integrand is already bounded for positive and negative moneyness"""
    ia = 1j * alpha
    return 0.5 * (
        self._option_time_value_transform(u - ia)
        - self._option_time_value_transform(u + ia)
    )

pdf

pdf(x)

Compute the probability density (or mass) function.

It returns the analytical pdf from pdf_analytical when available, otherwise it interpolates the pdf obtained from the characteristic function via pdf_from_characteristic.

PARAMETER DESCRIPTION
x

Location in the stochastic process domain space. If a numpy array, the output should have the same shape as the input.

TYPE: FloatArrayLike

Source code in quantflow/dists/marginal1d.py
def pdf(
    self,
    x: Annotated[
        FloatArrayLike,
        Doc(
            "Location in the stochastic process domain space. If a numpy array,"
            " the output should have the same shape as the input."
        ),
    ],
) -> FloatArrayLike:
    """Compute the probability density (or mass) function.

    It returns the analytical pdf from [pdf_analytical][..pdf_analytical]
    when available, otherwise it interpolates the pdf obtained from the
    characteristic function via
    [pdf_from_characteristic][..pdf_from_characteristic].
    """
    try:
        return self.pdf_analytical(x)
    except NotImplementedError:
        density = self.pdf_from_characteristic()
        return np.interp(x, density.x, np.real(density.y))

pdf_from_characteristic

pdf_from_characteristic(
    n=None,
    *,
    max_frequency=None,
    simpson_rule=False,
    use_fft=False,
    frequency_n=None
)

Compute the probability density function from the characteristic function.

PARAMETER DESCRIPTION
n

Number of discretization points to use in the transform. If None, use 128.

TYPE: int | None DEFAULT: None

max_frequency

The maximum frequency to use in the transform. If not provided, the value from the frequency_range method is used. Only needed for special cases/testing.

TYPE: float | None DEFAULT: None

simpson_rule

Use Simpson's rule for integration. Default is False.

TYPE: bool DEFAULT: False

use_fft

Use FFT for the transform. Default is False.

TYPE: bool DEFAULT: False

frequency_n

Number of points for the frequency grid. Overrides n if provided.

TYPE: int | None DEFAULT: None

Source code in quantflow/dists/marginal1d.py
def pdf_from_characteristic(
    self,
    n: Annotated[
        int | None,
        Doc(
            "Number of discretization points to use in the transform."
            " If None, use 128."
        ),
    ] = None,
    *,
    max_frequency: Annotated[
        float | None,
        Doc(
            "The maximum frequency to use in the transform. If not provided,"
            " the value from the [frequency_range]"
            "[..frequency_range] method is used."
            " Only needed for special cases/testing."
        ),
    ] = None,
    simpson_rule: Annotated[
        bool, Doc("Use Simpson's rule for integration. Default is False.")
    ] = False,
    use_fft: Annotated[
        bool, Doc("Use FFT for the transform. Default is False.")
    ] = False,
    frequency_n: Annotated[
        int | None,
        Doc("Number of points for the frequency grid. Overrides n if provided."),
    ] = None,
) -> TransformResult:
    """
    Compute the probability density function from the characteristic function.
    """
    transform = self.get_transform(
        frequency_n or n,
        self.support,
        max_frequency=max_frequency,
        simpson_rule=simpson_rule,
        use_fft=use_fft,
    )
    psi = cast(np.ndarray, self.characteristic(transform.frequency_domain))
    return transform(psi, use_fft=use_fft)

pdf_jacobian

pdf_jacobian(x)

Jacobian of the pdf with respect to the parameters of the process. It has a base implementation that computes it from the cdf_jacobian method, but a subclass should overload this method if a more optimized way of computing it is available.

PARAMETER DESCRIPTION
x

Location in the state space of the process.

TYPE: FloatArrayLike

Source code in quantflow/dists/marginal1d.py
def pdf_jacobian(
    self,
    x: Annotated[
        FloatArrayLike, Doc("Location in the state space of the process.")
    ],
) -> FloatArrayLike:
    """
    Jacobian of the pdf with respect to the parameters of the process.
    It has a base implementation that computes it from the
    [cdf_jacobian][..cdf_jacobian] method,
    but a subclass should overload this method if a
    more optimized way of computing it is available.
    """
    return self.cdf_jacobian(x) - self.cdf_jacobian(x - 1)

std

std()

Standard deviation at a time horizon

Source code in quantflow/dists/marginal1d.py
def std(self) -> FloatArrayLike:
    """Standard deviation at a time horizon"""
    return np.sqrt(self.variance())

std_validated

std_validated()

Float standard deviation, raising if it is not finite or non-positive.

Used by the Fourier-based pricing methods to set the log-strike grid range; degenerate parameter samples (e.g. during calibration) can produce a non-finite std and would otherwise crash deep in the transform with a confusing error.

Source code in quantflow/dists/marginal1d.py
def std_validated(self) -> float:
    """Float standard deviation, raising if it is not finite or non-positive.

    Used by the Fourier-based pricing methods to set the log-strike grid
    range; degenerate parameter samples (e.g. during calibration) can
    produce a non-finite std and would otherwise crash deep in the
    transform with a confusing error.
    """
    std = float(self.std())
    if not np.isfinite(std) or std <= 0:
        raise ValueError(
            f"Marginal std is not finite or non-positive: {std!r}; "
            "model parameters are likely invalid"
        )
    return std

std_from_characteristic

std_from_characteristic()

Calculate standard deviation as square root of variance

Source code in quantflow/dists/marginal1d.py
def std_from_characteristic(self) -> FloatArrayLike:
    """Calculate standard deviation as square root of variance"""
    return np.sqrt(self.variance_from_characteristic())

variance_from_characteristic

variance_from_characteristic(*, d=0.001)

Calculate variance as second derivative of characteristic function at 0

PARAMETER DESCRIPTION
d

Step size for finite-difference approximation of the derivative.

TYPE: float DEFAULT: 0.001

Source code in quantflow/dists/marginal1d.py
def variance_from_characteristic(
    self,
    *,
    d: Annotated[
        float,
        Doc("Step size for finite-difference approximation of the derivative."),
    ] = 0.001,
) -> FloatArrayLike:
    """Calculate variance as second derivative of characteristic function at 0"""
    c1 = self.characteristic(d)
    c0 = self.characteristic(0)
    c2 = self.characteristic(-d)
    m = -0.5 * 1j * (c1 - c2) / d
    s = -(c1 - 2 * c0 + c2) / (d * d) - m * m
    return s.real