Skip to content

OU Processes

These are the classes that implement gaussian and non-gaussian Ornstein-Uhlenbeck process.

Gaussian OU process

quantflow.sp.ou.Vasicek pydantic-model

Bases: StochasticProcess1D

Gaussian OU process, also known as the Vasicek model.

Historically, the Vasicek model was used to model the short rate, but it can be used to model any process that reverts to a mean level at a rate proportional to the difference between the current level and the mean level. Unlike intensity processes, the Vasicek process can take negative values, so it is not constrained to a positive domain.

\[\begin{equation} dx_t = \kappa (\theta - x_t) dt + \sigma dw_t \end{equation}\]

where \(\kappa\) is the mean reversion speed, \(\theta\) is the mean level, and \(\sigma\) is the volatility of the process. The solution to the SDE is given by

\[\begin{equation} x_t = x_0 e^{-\kappa t} + \theta (1 - e^{-\kappa t}) + \sigma \int_0^t e^{-\kappa (t-s)} dw_s \end{equation}\]

Fields:

rate pydantic-field

rate = 1.0

Initial value \(x_0\)

kappa pydantic-field

kappa = 1.0

Mean reversion speed \(\kappa\)

theta pydantic-field

theta = 1.0

Mean level \(\theta\)

bdlp pydantic-field

bdlp

Background driving Wiener process

characteristic_exponent

characteristic_exponent(t, u)

The characteristic exponent of the Vasicek process is given by

\[\begin{equation} \phi_{x_t}(u) = - iu \mathbb{E}[x_t] + \frac{1}{2} u^2 \text{Var}[x_t] \end{equation}\]
Source code in quantflow/sp/ou.py
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    r"""The characteristic exponent of the Vasicek process is given by

    \begin{equation}
        \phi_{x_t}(u) = - iu \mathbb{E}[x_t] + \frac{1}{2} u^2 \text{Var}[x_t]
    \end{equation}
    """
    mu = self.analytical_mean(t)
    var = self.analytical_variance(t)
    return u * (-1j * mu + 0.5 * var * u)

sample

sample(n, time_horizon=1, time_steps=100)
Source code in quantflow/sp/ou.py
def sample(self, n: int, time_horizon: float = 1, time_steps: int = 100) -> Paths:
    paths = Paths.normal_draws(n, time_horizon, time_steps)
    return self.sample_from_draws(paths)

sample_from_draws

sample_from_draws(draws, *args)
Source code in quantflow/sp/ou.py
def sample_from_draws(self, draws: Paths, *args: Paths) -> Paths:
    kappa = self.kappa
    theta = self.theta
    dt = draws.dt
    sdt = self.bdlp.sigma * np.sqrt(dt)
    paths = np.zeros(draws.data.shape)
    paths[0, :] = self.rate
    for t in range(draws.time_steps):
        x = paths[t, :]
        dx = kappa * (theta - x) * dt + sdt * draws.data[t, :]
        paths[t + 1, :] = x + dx
    return Paths(t=draws.t, data=paths)

ekt

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

analytical_mean

analytical_mean(t)

The analytical mean of the Vasicek process is given by

\[\begin{equation} \begin{aligned} \mathbb{E}[x_t] &= x_0 e^{-\kappa t} + \theta (1 - e^{-\kappa t}) \\ &= \theta \qquad \text{as } t \to \infty \end{aligned} \end{equation}\]
Source code in quantflow/sp/ou.py
def analytical_mean(self, t: FloatArrayLike) -> FloatArrayLike:
    r"""The analytical mean of the Vasicek process is given by

    \begin{equation}
        \begin{aligned}
        \mathbb{E}[x_t] &= x_0 e^{-\kappa t} + \theta (1 - e^{-\kappa t}) \\
        &= \theta \qquad \text{as } t \to \infty
        \end{aligned}
    \end{equation}
    """
    ekt = self.ekt(t)
    return self.rate * ekt + self.theta * (1 - ekt)

analytical_variance

analytical_variance(t)

The analytical variance of the Vasicek process is given by

\[\begin{equation} \begin{aligned} \text{Var}[x_t] &= \frac{\sigma^2}{2\kappa} (1 - e^{-2\kappa t}) \\ &= \frac{\sigma^2}{2\kappa} \qquad \text{as } t \to \infty \end{aligned} \end{equation}\]
Source code in quantflow/sp/ou.py
def analytical_variance(self, t: FloatArrayLike) -> FloatArrayLike:
    r"""The analytical variance of the Vasicek process is given by

    \begin{equation}
        \begin{aligned}
        \text{Var}[x_t] &= \frac{\sigma^2}{2\kappa} (1 - e^{-2\kappa t}) \\
        &= \frac{\sigma^2}{2\kappa} \qquad \text{as } t \to \infty
        \end{aligned}
    \end{equation}
    """
    ekt = self.ekt(2 * t)
    return 0.5 * self.bdlp.sigma2 * (1 - ekt) / self.kappa

analytical_pdf

analytical_pdf(t, x)
Source code in quantflow/sp/ou.py
def analytical_pdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    return norm.pdf(x, loc=self.analytical_mean(t), scale=self.analytical_std(t))

analytical_cdf

analytical_cdf(t, x)
Source code in quantflow/sp/ou.py
def analytical_cdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    return norm.cdf(x, loc=self.analytical_mean(t), scale=self.analytical_std(t))

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

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)

Non-Gaussian OU process

quantflow.sp.ou.NGOU pydantic-model

Bases: IntensityProcess, Generic[D]

The general definition of a non-Gaussian OU process is defined as the solution to the SDE

\[\begin{equation} dx_t = -\kappa x_t dt + dZ_{\kappa t} \end{equation}\]

where \(Z_{\kappa t}\) is a pure jump Lévy process, also known as the background driving Lévy process (BDLP). The process is mean-reverting with mean reversion speed \(\kappa>0\).

The unusual timing \(dZ_{\kappa t}\) is deliberately chosen so that it will turn out that whatever the value of \(\kappa\), the marginal distribution of \(x_t\) will be unchanged.

The solution to the SDE is given by

\[\begin{equation} x_t = x_0 e^{-\kappa t} + \int_0^t e^{-\kappa (t-s)} dZ_{\kappa s} \end{equation}\]

Fields:

bdlp pydantic-field

bdlp

Background driving Lévy process is a CompoundPoissonProcess with jump distribution D

intensity property

intensity

The intensity of the NGOU process is the intensity of the background driving Lévy process

rate pydantic-field

rate = 1.0

Instantaneous initial rate \(x_0\)

kappa pydantic-field

kappa = 1.0

Mean reversion speed \(\kappa\)

characteristic_exponent abstractmethod

characteristic_exponent(t, u)

Characteristic exponent of a non-Gaussian OU process driven by a Lévy process \(Z\) with characteristic exponent \(\phi_Z\):

\[\begin{equation} \phi_{x_t, u} = - iu\, x_0\, e^{-\kappa t} + \int_{u e^{-\kappa t}}^{u} \frac{\phi_Z(v)}{v}\,dv \end{equation}\]
Source code in quantflow/sp/ou.py
@abstractmethod
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    r"""Characteristic exponent of a non-Gaussian OU process driven by a
    Lévy process $Z$ with characteristic exponent $\phi_Z$:

    \begin{equation}
        \phi_{x_t, u} = - iu\, x_0\, e^{-\kappa t}
            + \int_{u e^{-\kappa t}}^{u} \frac{\phi_Z(v)}{v}\,dv
    \end{equation}
    """

sample_from_draws

sample_from_draws(draws, *args)
Source code in quantflow/sp/ou.py
def sample_from_draws(self, draws: Paths, *args: Paths) -> Paths:
    raise NotImplementedError

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

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)

domain_range

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

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)

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

ekt

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

quantflow.sp.ou.GammaOU pydantic-model

Bases: NGOU[Exponential]

Gamma OU process is a non-Gaussian OU process where the background driving Lévy process is a compound Poisson process with exponential jumps

Fields:

beta property

beta

rate pydantic-field

rate = 1.0

Instantaneous initial rate \(x_0\)

kappa pydantic-field

kappa = 1.0

Mean reversion speed \(\kappa\)

bdlp pydantic-field

bdlp

Background driving Lévy process is a CompoundPoissonProcess with jump distribution D

intensity property

intensity

The intensity of the NGOU process is the intensity of the background driving Lévy process

create classmethod

create(rate=1, decay=1, kappa=1)

Convenience constructor for GammaOU process

Source code in quantflow/sp/ou.py
@classmethod
def create(cls, rate: float = 1, decay: float = 1, kappa: float = 1) -> Self:
    """Convenience constructor for GammaOU process"""
    return cls(
        rate=rate,
        kappa=kappa,
        bdlp=CompoundPoissonProcess[Exponential](
            intensity=rate * decay, jumps=Exponential(decay=decay)
        ),
    )

characteristic_exponent

characteristic_exponent(t, u)

Closed form of the NGOU characteristic exponent when the BDLP is a compound Poisson process with intensity \(\lambda\) and exponential jumps of decay \(\beta\):

\[\begin{equation} \phi_{x_t, u} = - iu\, x_0\, e^{-\kappa t} + \lambda \log\!\left( \frac{\beta - iu}{\beta - iu\, e^{-\kappa t}} \right) \end{equation}\]

Derivation. The characteristic function of the Exponential jumps is

\[\begin{equation} \Phi_J(v) = \frac{\beta}{\beta - iv}, \end{equation}\]

so the BDLP unit-time characteristic exponent (CompoundPoissonProcess at \(t = 1\)) is

\[\begin{equation} \phi_Z(v) = \lambda\bigl(1 - \Phi_J(v)\bigr) = -\frac{i v\, \lambda}{\beta - iv}, \end{equation}\]

and \(\phi_Z(v)/v = -i\lambda / (\beta - iv)\). Substituting \(w = \beta - iv\) (so \(dv = i\, dw\)) in the NGOU integral gives

\[\begin{equation} \int_{u e^{-\kappa t}}^{u} \frac{\phi_Z(v)}{v}\, dv = \lambda \int_{\beta - iu\, e^{-\kappa t}}^{\beta - iu} \frac{dw}{w} = \lambda \log\!\left( \frac{\beta - iu}{\beta - iu\, e^{-\kappa t}} \right), \end{equation}\]

which combined with the drift term \(-iu\, x_0\, e^{-\kappa t}\) yields the closed form above.

Source code in quantflow/sp/ou.py
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    r"""Closed form of the [NGOU][quantflow.sp.ou.NGOU] characteristic
    exponent when the BDLP is a compound Poisson process with intensity
    $\lambda$ and exponential jumps of decay $\beta$:

    \begin{equation}
        \phi_{x_t, u} =
            - iu\, x_0\, e^{-\kappa t}
            + \lambda \log\!\left(
                \frac{\beta - iu}{\beta - iu\, e^{-\kappa t}}
            \right)
    \end{equation}

    Derivation. The characteristic function of the
    [Exponential][quantflow.utils.distributions.Exponential.characteristic]
    jumps is

    \begin{equation}
        \Phi_J(v) = \frac{\beta}{\beta - iv},
    \end{equation}

    so the BDLP unit-time characteristic exponent
    ([CompoundPoissonProcess][quantflow.sp.poisson.CompoundPoissonProcess]
    at $t = 1$) is

    \begin{equation}
        \phi_Z(v) = \lambda\bigl(1 - \Phi_J(v)\bigr)
            = -\frac{i v\, \lambda}{\beta - iv},
    \end{equation}

    and $\phi_Z(v)/v = -i\lambda / (\beta - iv)$. Substituting
    $w = \beta - iv$ (so $dv = i\, dw$) in the NGOU integral gives

    \begin{equation}
        \int_{u e^{-\kappa t}}^{u} \frac{\phi_Z(v)}{v}\, dv
            = \lambda \int_{\beta - iu\, e^{-\kappa t}}^{\beta - iu}
                \frac{dw}{w}
            = \lambda \log\!\left(
                \frac{\beta - iu}{\beta - iu\, e^{-\kappa t}}
            \right),
    \end{equation}

    which combined with the drift term $-iu\, x_0\, e^{-\kappa t}$ yields
    the closed form above.
    """
    b = self.beta
    iu = 1j * u
    c1 = iu * np.exp(-self.kappa * t)
    c0 = self.intensity * np.log((b - c1) / (b - iu))
    return -c0 - c1 * self.rate

integrated_log_laplace

integrated_log_laplace(t, u)

Log-Laplace transform of the time-integrated Gamma-OU process.

\[\begin{equation} \begin{aligned} \phi(t, u) &= \log E\!\left[e^{-u \int_0^t x_s\, ds}\right] \\ &= -u\, x_0\, \frac{1 - e^{-\kappa t}}{\kappa} + \frac{\lambda}{u + \beta\kappa} \!\left[\beta\kappa\, \log\!\frac{\beta\kappa + u(1 - e^{-\kappa t})}{\beta\kappa} - u\kappa t\right] \end{aligned} \end{equation}\]

where \(x_0\) is the initial rate, \(\lambda\) is the BDLP intensity and \(\beta\) the exponential jump decay.

Source code in quantflow/sp/ou.py
def integrated_log_laplace(self, t: FloatArrayLike, u: Vector) -> Vector:
    r"""Log-Laplace transform of the time-integrated Gamma-OU process.

    \begin{equation}
    \begin{aligned}
        \phi(t, u) &= \log E\!\left[e^{-u \int_0^t x_s\, ds}\right] \\
        &= -u\, x_0\, \frac{1 - e^{-\kappa t}}{\kappa}
          + \frac{\lambda}{u + \beta\kappa}
          \!\left[\beta\kappa\,
            \log\!\frac{\beta\kappa + u(1 - e^{-\kappa t})}{\beta\kappa}
            - u\kappa t\right]
    \end{aligned}
    \end{equation}

    where $x_0$ is the initial rate, $\lambda$ is the BDLP intensity and
    $\beta$ the exponential jump decay.
    """
    kappa = self.kappa
    b = self.beta
    muk = -u / kappa
    ekt = np.exp(-kappa * t)
    c1 = muk * (1 - ekt)
    c0 = self.intensity * (
        b * np.log(b / (muk + (b - muk) / ekt)) / (muk - b) - kappa * t
    )
    return c0 + c1 * self.rate

sample

sample(n, time_horizon=1, time_steps=100)
Source code in quantflow/sp/ou.py
def sample(self, n: int, time_horizon: float = 1, time_steps: int = 100) -> Paths:
    dt = time_horizon / time_steps
    jump_process = self.bdlp
    paths = np.zeros((time_steps + 1, n))
    paths[0, :] = self.rate
    for p in range(n):
        arrivals = jump_process.arrivals(self.kappa * time_horizon)
        jumps = jump_process.sample_jumps(len(arrivals))
        pp = paths[:, p]
        i = 1
        for arrival, jump in zip(arrivals, jumps):
            arrival /= self.kappa
            while i * dt < arrival:
                i = self._advance(i, pp, dt)
            if i <= time_steps:
                i = self._advance(i, pp, dt, arrival, jump)
        while i <= time_steps:
            i = self._advance(i, pp, dt)
    return Paths(t=time_horizon, data=paths)

analytical_mean

analytical_mean(t)

Analytical mean of the Gamma-OU process at time \(t\).

\[\begin{equation} E[x_t] = x_0\, e^{-\kappa t} + \frac{\lambda}{\beta}\bigl(1 - e^{-\kappa t}\bigr) \end{equation}\]

which converges to the stationary mean \(\lambda / \beta\) as \(t \to \infty\).

Source code in quantflow/sp/ou.py
def analytical_mean(self, t: FloatArrayLike) -> FloatArrayLike:
    r"""Analytical mean of the Gamma-OU process at time $t$.

    \begin{equation}
        E[x_t] = x_0\, e^{-\kappa t}
               + \frac{\lambda}{\beta}\bigl(1 - e^{-\kappa t}\bigr)
    \end{equation}

    which converges to the stationary mean $\lambda / \beta$ as
    $t \to \infty$.
    """
    ekt = self.ekt(t)
    return self.rate * ekt + (self.intensity / self.beta) * (1 - ekt)

analytical_variance

analytical_variance(t)

Analytical variance of the Gamma-OU process at time \(t\).

\[\begin{equation} \mathrm{Var}(x_t) = \frac{\lambda}{\beta^2} \bigl(1 - e^{-2\kappa t}\bigr) \end{equation}\]

which converges to the stationary variance \(\lambda / \beta^2\) as \(t \to \infty\).

Source code in quantflow/sp/ou.py
def analytical_variance(self, t: FloatArrayLike) -> FloatArrayLike:
    r"""Analytical variance of the Gamma-OU process at time $t$.

    \begin{equation}
        \mathrm{Var}(x_t) = \frac{\lambda}{\beta^2}
            \bigl(1 - e^{-2\kappa t}\bigr)
    \end{equation}

    which converges to the stationary variance $\lambda / \beta^2$ as
    $t \to \infty$.
    """
    return self.intensity * (1 - self.ekt(2 * t)) / (self.beta * self.beta)

analytical_pdf

analytical_pdf(t, x)

Stationary marginal density of the Gamma-OU process. The transient density is not available in closed form; use pdf_from_characteristic for finite \(t\).

Source code in quantflow/sp/ou.py
def analytical_pdf(self, t: FloatArrayLike, x: FloatArrayLike) -> FloatArrayLike:
    """Stationary marginal density of the Gamma-OU process. The transient
    density is not available in closed form; use `pdf_from_characteristic`
    for finite $t$.
    """
    return gamma.pdf(x, self.intensity, scale=1 / self.beta)

sample_from_draws

sample_from_draws(draws, *args)
Source code in quantflow/sp/ou.py
def sample_from_draws(self, draws: Paths, *args: Paths) -> Paths:
    raise NotImplementedError

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

domain_range

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

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)

ekt

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