SpilloverResult#

class cultivars.multivariate.large_dim.spillover.SpilloverResult(*, names, horizon, table, total, to_others, from_others, net, net_pairwise)[source]

Bases: _SummaryMixin

A connectedness table and the directional readings it implies.

All quantities are percentages of forecast-error variance from the row-normalized generalized decomposition,

\[\theta_{ij}(H) = \frac{\sigma_{jj}^{-1} \sum_{h=0}^{H} (e_i' \Psi_h \Sigma_u e_j)^2} {\sum_{h=0}^{H} e_i' \Psi_h \Sigma_u \Psi_h' e_i}, \qquad \tilde\theta_{ij}(H) = 100\,\frac{\theta_{ij}(H)}{\sum_j \theta_{ij}(H)},\]

with \(\Psi_h\) the moving-average matrices of the fitted system and \(\Sigma_u\) its innovation covariance. The table is \(\tilde\theta\); its off-diagonal mass is connectedness, column sums are what each variable’s shocks contribute to the others, row sums what each variable receives, and the difference ranks transmitters against receivers.

Note

horizon is the largest moving-average lead in the sum, so the shares cumulate \(\Psi_0, \ldots, \Psi_H\) – \(H + 1\) terms – and equal 100 * result.generalized_fevd(horizon)[-1] on any result that offers that method. Rows of table sum to 100 by construction; columns do not. net sums to zero across variables, and total equals the mean of from_others and of to_others. Because generalized shocks are correlated the raw shares \(\theta_{ij}\) do not sum to one, which is why the rows are normalized and why the table is a relative attribution rather than an additive decomposition.

Variables:
  • names – Variable labels, indexing every axis below.

  • horizon – The forecast horizon the decomposition was read at.

  • table – (k, k) connectedness table; entry [i, j] is the share of i’s forecast-error variance associated with shocks to j. Rows sum to 100.

  • total – The total spillover index – average off-diagonal mass, in [0, 100].

  • to_others – (k,) directional spillover to others: what shocks to each variable contribute to everyone else’s variance.

  • from_others – (k,) directional spillover from others: how much of each variable’s variance arrives from elsewhere.

  • net – to_others - from_others; positive ranks a variable as a net transmitter.

  • net_pairwise – (k, k) antisymmetric net pairwise spillovers, table[j, i] - table[i, j] read as i’s net transmission to j.

See also

  • Spillover – the view that produces this record.

  • VARResult – its generalized_fevd gives the same shares, unnormalized rows, at every horizon.

References

Diebold, F. X., & Yilmaz, K. (2012). Better to give than to receive: Predictive directional measurement of volatility spillovers. International Journal of Forecasting, 28(1), 57-66.

Pesaran, M. H., & Shin, Y. (1998). Generalized impulse response analysis in linear multivariate models. Economics Letters, 58(1), 17-29.

Example

A three-variable VAR(1) in which only y2 drives y1; the table names y2 the transmitter and y1 the receiver, and the accounting identities hold:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((300, 3))
>>> a = np.array([[0.5, 0.3, 0.0], [0.0, 0.5, 0.0], [0.0, 0.0, 0.5]])
>>> for t in range(1, 300):
...     y[t] = a @ y[t - 1] + rng.standard_normal(3)
>>> spill = Spillover(VAR(y, order=1).fit(), horizon=10).compute()
>>> spill.names, spill.horizon, spill.k_endog, spill.table.shape
(('y1', 'y2', 'y3'), 10, 3, (3, 3))
>>> bool(np.allclose(spill.table.sum(axis=1), 100.0)), bool(abs(spill.net.sum()) < 1e-9)
(True, True)
>>> bool(spill.table[0, 1] > 5.0), bool(spill.table[1, 0] < 2.0)
(True, True)
>>> int(np.argmax(spill.net)), int(np.argmin(spill.net))
(1, 0)
>>> bool(abs(spill.total - spill.from_others.mean()) < 1e-9)
True

Private methods#

_summary_table()

Build the structured summary.