Skip to content

Barndorff-Nielson & Shephard process

quantflow.sp.bns.BNS pydantic-model

Bases: StochasticProcess1D

Barndorff-Nielson & Shephard (BNS) stochastic volatility model.

This is a stochastic volatility model where the variance process is given by a non-Gaussian Ornstein-Uhlenbeck process driven by a pure-jump Lévy process. The BNS model is defined by the following system of SDEs:

\[\begin{equation} \begin{aligned} dx_t &= \sqrt{v_t} dw_t + \rho dz_{\kappa t} \\ dv_t &= -\kappa v_t dt + dz_{\kappa t} \end{aligned} \end{equation}\]

The model is flexible and can capture various stylized facts of financial markets, such as volatility clustering and leverage effects.

This implementation uses a GammaOU process for the variance, which is a common choice in the BNS model.

Fields:

variance_process pydantic-field

variance_process

Variance process

rho pydantic-field

rho = 0

Correlation between variance and price processes, in (-1, 1)

create classmethod

create(vol, kappa, decay, rho)

Convenience constructor for BNS process with parameters of the variance process

Source code in quantflow/sp/bns.py
@classmethod
def create(cls, vol: float, kappa: float, decay: float, rho: float) -> Self:
    """Convenience constructor for BNS process with parameters
    of the variance process
    """
    return cls(
        variance_process=GammaOU.create(rate=vol * vol, kappa=kappa, decay=decay),
        rho=rho,
    )

characteristic_exponent

characteristic_exponent(t, u)
Source code in quantflow/sp/bns.py
def characteristic_exponent(self, t: FloatArrayLike, u: Vector) -> Vector:
    return -self._zeta(t, 0.5 * Im * u * u, self.rho * u)

sample

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

sample_from_draws

sample_from_draws(path_dw, *args)
Source code in quantflow/sp/bns.py
def sample_from_draws(self, path_dw: Paths, *args: Paths) -> Paths:
    if args:
        path_dz = args[0]
    else:
        # generate the background driving process samples if not provided
        path_dz = self.variance_process.bdlp.sample(
            path_dw.samples, path_dw.t, path_dw.time_steps
        )
    dt = path_dw.dt
    # sample the activity rate process
    v = self.variance_process.sample_from_draws(path_dz)
    # create the time-changed Brownian motion
    dw = path_dw.data * np.sqrt(v.data * dt)
    paths = np.zeros(dw.shape)
    paths[1:] = np.cumsum(dw[:-1], axis=0) + path_dz.data
    return Paths(t=path_dw.t, data=paths)

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^{-\psi(t, u)} \end{equation}\]

where \(\psi\) 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^{-\psi(t, u)}
    \end{equation}

    where $\psi$ 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)
Source code in quantflow/sp/base.py
def analytical_std(self, t: FloatArrayLike) -> FloatArrayLike:
    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 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)