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

Source code in quantflow/sp/base.py
@abstractmethod
def sample_from_draws(self, draws: Paths, *args: Paths) -> 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

Source code in quantflow/sp/base.py
@abstractmethod
def sample_from_draws(self, draws: Paths, *args: Paths) -> 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

Source code in quantflow/sp/base.py
@abstractmethod
def sample_from_draws(self, draws: Paths, *args: Paths) -> 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:

model_config class-attribute instance-attribute

model_config = ConfigDict(arbitrary_types_allowed=True)

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

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

cdf

cdf(x)
Source code in quantflow/sp/base.py
def cdf(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)

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/utils/marginal.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/utils/marginal.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/utils/marginal.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.utils.marginal.OptionPricingCosResult]
    with the precomputed coefficient vector. Use
    [call_price][quantflow.utils.marginal.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/utils/marginal.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/utils/marginal.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_from_characteristic

cdf_from_characteristic(n=None, *, max_frequency=None, simpson_rule=False, use_fft=False, frequency_n=None)
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/utils/marginal.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:
    raise NotImplementedError("CDF not available")

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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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_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/utils/marginal.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]"
            "[quantflow.utils.marginal.Marginal1D.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/utils/marginal.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][quantflow.utils.marginal.Marginal1D.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/utils/marginal.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/utils/marginal.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/utils/marginal.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/utils/marginal.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