NormalInverseGaussian#
The Normal-Inverse Gaussian (NIG) distribution is the normal variance-mean mixture
with an InverseGaussian subordinator — the GIG case \(p = -\tfrac12,\; a = \lambda/\mu_{IG}^2,\; b = \lambda\). It combines semi-heavy tails with tractable moments and is the standard robust default for financial returns.
Density gallery#
The skewness \(\gamma\) tilts the marginal; the InverseGaussian mixing gives it its characteristic semi-heavy tails.
Parametrizations#
Built from the shared location/shape block and the InverseGaussian subordinator:
Symbol |
Attribute |
Meaning |
|---|---|---|
\(\mu\) |
|
location \((d,)\) |
\(\gamma\) |
|
skewness \((d,)\) |
\(\Sigma = L_\Sigma L_\Sigma^\top\) |
|
dispersion Cholesky \((d, d)\) |
\(\mu_{IG}\) |
|
InverseGaussian mean |
\(\lambda\) |
|
InverseGaussian shape |
The marginal is not an exponential family, but the joint \((X, Y)\) is — its natural parametrization and the EM machinery are in The Generalized Hyperbolic Distribution.
Quick usage#
Raw \((\gamma, \Sigma, \mu_{IG})\) are identified only up to the scale gauge \(Y \mapsto cY\) (see EM Algorithm for Generalized Hyperbolic Distributions); compare the invariants \(\mu\), \(\gamma E[Y]\), and \(E[Y]\,\Sigma\) instead.
mu = jnp.array([0.0, 0.0])
gamma = jnp.array([0.3, -0.4])
Sigma = jnp.array([[1.0, 0.3], [0.3, 1.0]])
nig = NormalInverseGaussian.from_classical(mu=mu, gamma=gamma, sigma=Sigma, mu_ig=1.0, lam=1.5)
print("mean:", np.asarray(nig.mean()))
print("cov:\n", np.asarray(nig.cov()))
print("skewness:", np.asarray(nig.skewness()))
print("excess kurtosis:", np.asarray(nig.kurtosis()))
X = nig.rvs(2_000, seed=0)
result = NormalInverseGaussian.default_init(X).fit(X, max_iter=50, tol=1e-3)
fit = result.model
ey = float(fit.joint.subordinator().mean())
print("converged:", bool(result.converged), "n_iter:", int(result.n_iter))
print("mu:", np.asarray(fit.mu))
print("gamma * E[Y]:", np.asarray(fit.gamma) * ey) # true [0.3, -0.4] (E[Y]=1)
print("E[Y] * Sigma:\n", ey * np.asarray(fit.sigma()))
print("mean:", np.asarray(fit.mean()))
print("cov:\n", np.asarray(fit.cov()))
mean: [ 0.3 -0.4]
cov:
[[1.06 0.22 ]
[0.22 1.10667]]
skewness: [ 0.58277 -0.76047]
excess kurtosis: [2.45283 2.77108]
converged: True n_iter: 18
mu: [0.03145 0.00892]
gamma * E[Y]: [ 0.2587 -0.39766]
E[Y] * Sigma:
[[1.01448 0.28978]
[0.28978 1.03098]]
mean: [ 0.29015 -0.38873]
cov:
[[1.07348 0.19909]
[0.19909 1.17039]]
The \(d = 1\) sibling UnivariateNormalInverseGaussian
adds cdf / ppf for VaR-style tail calculations; the
FactorNormalInverseGaussian handles many assets.