tail_dependence

Defined in fynance.metrics

tail_dependence(X, q=0.05)[source]

Pairwise lower-tail dependence of a (T, N) returns panel.

Unlike var/cvar/cdar, this takes returns directly (see the module docstring): tail dependence is a co-exceedance property of two series, not a summary of one equity curve, so it does not fit the single-curve METRICS contract (mirroring information_coefficient’s (pred, real)-pair convention).

Estimates, for each pair of assets \((i, j)\), the empirical probability [4] that asset \(i\) is in its own lower q-tail given that asset \(j\) is in its own lower q-tail:

\[\lambda_{ij} = P\left(x_i \leq Q_i(q) \mid x_j \leq Q_j(q)\right)\]
Parameters:
Xarray_like of shape (T, N)

Returns panel (not prices), one column per asset.

qfloat, optional

Tail probability, in (0, 1). Default is 0.05.

Returns:
np.ndarray[np.float64, ndim=2] of shape (N, N)

Symmetric lower-tail dependence matrix, diagonal 1.

See also

var, information_coefficient

Notes

\(Q_i(q)\) is the empirical q-quantile of column i (the same k-th-smallest-observation convention as var, _tail_k), so by construction exactly \(k = \max(1, \lfloor qT \rfloor)\) observations of every column are ``<= `` its own threshold. The estimator is then

\[\lambda_{ij} = \frac{\#\{t : x_{ti} \leq Q_i(q) \text{ and } x_{tj} \leq Q_j(q)\}}{k}\]

Because the denominator k is the same for both conditioning directions (it only depends on q and T, not on which column conditions on which), \(\lambda_{ij} = \lambda_{ji}\): the matrix is symmetric by construction, unlike a generic conditional probability. The diagonal is exactly 1 (an event’s exceedance is always joint with itself).

References

[4]

Longin, F., and Solnik, B., 2001, Extreme Correlation of International Equity Markets, Journal of Finance, 56(2), 649-676.

Examples

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> comonotone = rng.standard_normal(2000)
>>> R = np.stack([comonotone, comonotone], axis=1)
>>> lam = tail_dependence(R, q=0.05)
>>> bool(lam[0, 1] > 0.95)
True
>>> np.diag(lam)
array([1., 1.])