pyconnectedness 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ """
2
+ Connectedness measures
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from importlib.metadata import version
8
+
9
+ __version__ = version("pyconnectedness")
10
+
11
+ from .connectedness import (
12
+ ConnectednessResult,
13
+ DynamicConnectednessResult,
14
+ FrequencyConnectednessResult,
15
+ VARFit,
16
+ dynamic_connectedness,
17
+ fit_var,
18
+ frequency_connectedness,
19
+ generalized_fevd,
20
+ normalize_fevd,
21
+ orthogonalized_fevd,
22
+ static_connectedness,
23
+ )
24
+
25
+ __all__ = ["fit_var", "VARFit",
26
+ "generalized_fevd", "normalize_fevd", "orthogonalized_fevd",
27
+ "ConnectednessResult", "static_connectedness",
28
+ "DynamicConnectednessResult", "dynamic_connectedness",
29
+ "FrequencyConnectednessResult", "frequency_connectedness",
30
+ "__version__",
31
+ ]
32
+
33
+
34
+
35
+
File without changes
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from .decomposition import generalized_fevd, normalize_fevd, orthogonalized_fevd
4
+ from .dynamic import DynamicConnectednessResult, dynamic_connectedness
5
+ from .frequency import FrequencyConnectednessResult, frequency_connectedness
6
+ from .static import ConnectednessResult, static_connectedness
7
+ from .var import VARFit, fit_var
8
+
9
+ __all__ = [
10
+ "fit_var",
11
+ "VARFit",
12
+ "static_connectedness",
13
+ "ConnectednessResult",
14
+ "generalized_fevd",
15
+ "orthogonalized_fevd",
16
+ "normalize_fevd",
17
+ "dynamic_connectedness",
18
+ "DynamicConnectednessResult",
19
+ "frequency_connectedness",
20
+ "FrequencyConnectednessResult",
21
+ ]
22
+
23
+
@@ -0,0 +1,135 @@
1
+ r"""
2
+ Forecast error variance decompositions for connectedness analysis.
3
+
4
+ Two schemes are provided:
5
+
6
+ * :func:`generalized_fevd` — the generalized decomposition of Pesaran and
7
+ Shin (1998), invariant to variable ordering (Diebold-Yilmaz 2012/2014).
8
+ * :func:`orthogonalized_fevd` — the Cholesky decomposition of Diebold and
9
+ Yilmaz (2009), which depends on variable ordering.
10
+
11
+ References
12
+ ----------
13
+ Pesaran and Shin (1998) Generalized impulse response analysis in linear
14
+ multivariate models. Economics Letters, 58, 17-29.
15
+
16
+ Diebold and Yilmaz (2009) Measuring financial asset return and volatility
17
+ spillovers, with application to global equity markets. The Economic Journal,
18
+ 119, 158-171.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import numpy as np
24
+
25
+
26
+ def generalized_fevd(ma_coefficients: np.ndarray, sigma: np.ndarray) -> np.ndarray:
27
+ r"""
28
+ Generalized forecast error variance decomposition (Pesaran-Shin 1998).
29
+
30
+ Parameters
31
+ ----------
32
+ ma_coefficients : ndarray (H x k x k)
33
+ Moving-average matrices :math:`\Phi_0, \ldots, \Phi_{H-1}`.
34
+ sigma : ndarray (k x k)
35
+ Residual covariance matrix :math:`\Sigma_u`.
36
+
37
+ Notes
38
+ -----
39
+ The share of the H-step forecast error variance of variable i due to
40
+ shocks in variable j is
41
+
42
+ .. math::
43
+
44
+ \theta_{ij}(H) = \frac{\sigma_{jj}^{-1}
45
+ \sum_{h=0}^{H-1} (e_i' \Phi_h \Sigma e_j)^2}
46
+ {\sum_{h=0}^{H-1} e_i' \Phi_h \Sigma \Phi_h' e_i}
47
+
48
+ where :math:`e_i` is a selection vector and :math:`\sigma_{jj}` the j-th
49
+ diagonal element of :math:`\Sigma`. Rows do not sum to one under this
50
+ scheme; apply :func:`normalize_fevd`.
51
+
52
+ Returns
53
+ -------
54
+ ndarray (k x k)
55
+ Matrix theta with theta[i, j] the contribution of j to i.
56
+ """
57
+ ma = np.asarray(ma_coefficients)
58
+ sigma = np.asarray(sigma)
59
+ H, k, _ = ma.shape
60
+
61
+ sigma_jj = np.diag(sigma)
62
+ numerator = np.zeros((k, k))
63
+ denominator = np.zeros(k)
64
+ for h in range(H):
65
+ phi = ma[h]
66
+ phi_sigma = phi @ sigma # entry [i, j] = e_i' Phi_h Sigma e_j
67
+ numerator += phi_sigma ** 2
68
+ denominator += np.diag(phi_sigma @ phi.T)
69
+
70
+ return (numerator / sigma_jj[np.newaxis, :]) / denominator[:, np.newaxis]
71
+
72
+
73
+ def orthogonalized_fevd(ma_coefficients: np.ndarray, sigma: np.ndarray) -> np.ndarray:
74
+ r"""
75
+ Orthogonalized (Cholesky) forecast error variance decomposition (DY 2009).
76
+
77
+ Parameters
78
+ ----------
79
+ ma_coefficients : ndarray (H x k x k)
80
+ Moving-average matrices :math:`\Phi_0, \ldots, \Phi_{H-1}`.
81
+ sigma : ndarray (k x k)
82
+ Residual covariance matrix :math:`\Sigma_u`.
83
+
84
+ Notes
85
+ -----
86
+ Uses the Cholesky factor :math:`P` with :math:`\Sigma = P P'`:
87
+
88
+ .. math::
89
+
90
+ \theta_{ij}(H) = \frac{\sum_{h=0}^{H-1} (e_i' \Phi_h P e_j)^2}
91
+ {\sum_{h=0}^{H-1} e_i' \Phi_h \Sigma \Phi_h' e_i}
92
+
93
+ Rows sum to one by construction. The result depends on the column order
94
+ of the data — the known ordering sensitivity of the DY-2009 method.
95
+
96
+ Returns
97
+ -------
98
+ ndarray (k x k)
99
+ Matrix theta with theta[i, j] the contribution of j to i.
100
+ """
101
+ ma = np.asarray(ma_coefficients)
102
+ sigma = np.asarray(sigma)
103
+ H, k, _ = ma.shape
104
+
105
+ P = np.linalg.cholesky(sigma) # Sigma = P P', lower triangular
106
+ numerator = np.zeros((k, k))
107
+ denominator = np.zeros(k)
108
+ for h in range(H):
109
+ phi = ma[h]
110
+ phi_P = phi @ P
111
+ numerator += phi_P ** 2
112
+ denominator += np.diag(phi @ sigma @ phi.T)
113
+
114
+ return numerator / denominator[:, np.newaxis]
115
+
116
+
117
+ def normalize_fevd(theta: np.ndarray) -> np.ndarray:
118
+ """
119
+ Normalize a decomposition row-wise so each row sums to one.
120
+
121
+ Parameters
122
+ ----------
123
+ theta : ndarray (k x k)
124
+
125
+ Notes
126
+ -----
127
+ Required after :func:`generalized_fevd`. A no-op after
128
+ :func:`orthogonalized_fevd`, whose rows already sum to one.
129
+
130
+ Returns
131
+ -------
132
+ ndarray (k x k)
133
+ """
134
+ theta = np.asarray(theta)
135
+ return theta / theta.sum(axis=1, keepdims=True)
@@ -0,0 +1,153 @@
1
+ """
2
+ Rolling-window (dynamic) connectedness measures
3
+
4
+ References
5
+ ----------
6
+ Diebold and Yilmaz (2009) Measuring financial asset return and volatility
7
+ spillovers, with application to global equity markets. The Economic Journal,
8
+ 119, 158-171.
9
+ Diebold and Yilmaz (2012) Better to give than to receive: predictive
10
+ directional measurement of volatility spillovers. International Journal of
11
+ Forecasting, 28, 57-66.
12
+ """
13
+
14
+ from dataclasses import dataclass
15
+
16
+ import numpy as np
17
+ import pandas as pd
18
+
19
+ from .static import static_connectedness
20
+ from .var import fit_var
21
+
22
+
23
+ @dataclass
24
+ class DynamicConnectednessResult:
25
+ """
26
+ Rolling-window connectedness measures.
27
+
28
+ Parameters
29
+ ----------
30
+ total : Series (T,)
31
+ Total connectedness (spillover) index per window, in percent.
32
+ directional_to : DataFrame (T x k)
33
+ Contribution of each variable to the forecast error variance of all
34
+ others, off-diagonal column sums.
35
+ directional_from : DataFrame (T x k)
36
+ Contribution received by each variable from all others, off-diagonal
37
+ row sums
38
+ net : DataFrame (T x k)
39
+ TO minus FROM. Sums to zero across variables in every window.
40
+ fevd : ndarray (T x k x k)
41
+ pairwise_net: ndarray (T x k x k)
42
+ names : list of str
43
+ window, horizon, lag_order : int
44
+ method : str
45
+
46
+
47
+ """
48
+
49
+ total: pd.Series
50
+ directional_to: pd.DataFrame
51
+ directional_from: pd.DataFrame
52
+ net: pd.DataFrame
53
+ fevd: np.ndarray
54
+ pairwise_net: np.ndarray
55
+ names: list
56
+ window: int
57
+ horizon: int
58
+ lag_order: int
59
+ method: str = "generalized"
60
+
61
+ @property
62
+ def k(self):
63
+ return len(self.names)
64
+
65
+ @property
66
+ def n_windows(self):
67
+ return len(self.total)
68
+
69
+
70
+ def dynamic_connectedness(
71
+ data, window, horizon=10, *, method="generalized", lags=None, **fit_kwargs
72
+ ):
73
+ r"""
74
+ Connectedness measures over a rolling estimation window
75
+
76
+ Parameters
77
+ ----------
78
+ data : DataFrame (n x k)
79
+ Observations in rows, variables in columns
80
+ window : int
81
+ Number of observations per estimation window
82
+ horizon : int
83
+ Forecast horizon H of the variance decomposition.
84
+ method : {"generalized", "orthogonalized"}
85
+ Decomposition applied in every window, passed on to
86
+ static_connectedness. DY-2009 is Cholesky, DY-2012 onward generalized.
87
+ lags : int or None
88
+ VAR order, held fixed across windows. If None it is selected once on
89
+ the full sample and then frozen; see Notes.
90
+ **fit_kwargs
91
+ Further arguments for fit_var, e.g. ``ic="aic"``, ``trend="c"``.
92
+
93
+
94
+
95
+ Returns
96
+ -------
97
+ DynamicConnectednessResult
98
+
99
+ Raises
100
+ ------
101
+ TypeError
102
+ If data is not a DataFrame.
103
+ ValueError
104
+ If the window is longer than the sample.
105
+ """
106
+ if not isinstance(data, pd.DataFrame):
107
+ raise TypeError("data must be a DataFrame")
108
+
109
+ nobs, k = data.shape
110
+ if window > nobs:
111
+ raise ValueError(f"window ({window}) exceeds the sample length ({nobs})")
112
+
113
+ if lags is None:
114
+ lags = fit_var(data, **fit_kwargs).lag_order
115
+
116
+ index = data.index[window - 1:] # right-aligned, as in the papers
117
+ n_windows = len(index)
118
+
119
+ theta = np.empty((n_windows, k, k))
120
+ to = np.empty((n_windows, k))
121
+ frm = np.empty((n_windows, k))
122
+ total = np.empty(n_windows)
123
+
124
+ for t in range(n_windows):
125
+ res = static_connectedness(
126
+ data.iloc[t:t + window],
127
+ horizon,
128
+ method=method,
129
+ lags=lags,
130
+ **fit_kwargs,
131
+ )
132
+ theta[t] = res.fevd
133
+ to[t] = np.asarray(res.directional_to)
134
+ frm[t] = np.asarray(res.directional_from)
135
+ total[t] = res.total
136
+
137
+ names = list(data.columns)
138
+
139
+ pairwise = theta.transpose(0,2,1) - theta
140
+
141
+ return DynamicConnectednessResult(
142
+ total=pd.Series(total, index=index, name="total"),
143
+ directional_to=pd.DataFrame(to, index=index, columns=names),
144
+ directional_from=pd.DataFrame(frm, index=index, columns=names),
145
+ net=pd.DataFrame(to - frm, index=index, columns=names), # = res.net
146
+ fevd=theta,
147
+ pairwise_net= pairwise,
148
+ names=names,
149
+ window=window,
150
+ horizon=horizon,
151
+ lag_order=lags,
152
+ method=method,
153
+ )
@@ -0,0 +1,185 @@
1
+ r"""
2
+ Frequency connectedness (Baruník-Křehlík 2018).
3
+
4
+ References
5
+ ----------
6
+ Baruník and Křehlík (2018) Measuring the frequency dynamics of financial
7
+ connectedness and systemic risk. Journal of Financial Econometrics, 16, 271-296.
8
+
9
+ Diebold and Yilmaz (2012) Better to give than to receive: predictive directional
10
+ measurement of volatility spillovers. International Journal of
11
+ Forecasting, 28, 57-66.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+
18
+ import numpy as np
19
+ import pandas as pd
20
+
21
+ from .static import _build_result
22
+ from .var import VARFit, fit_var
23
+
24
+
25
+ @dataclass
26
+ class FrequencyConnectednessResult:
27
+ """
28
+ Connectedness by frequency band.
29
+
30
+ Parameters
31
+ ----------
32
+ bands : dict
33
+ Band label -> ConnectednessResult, the frequency connectedness
34
+ within : dict
35
+ Band label -> ConnectednessResult, the band rescaled as if it were the
36
+ whole system.
37
+ share : Series
38
+ Share of each band in the forecast error variance, in percent. Sums
39
+ to 100.
40
+ total : Series
41
+ Total connectedness per band, in percent
42
+ horizon : int
43
+ """
44
+
45
+ bands: dict
46
+ within: dict
47
+ share: pd.Series
48
+ total: pd.Series
49
+ horizon: int
50
+
51
+ def __repr__(self) -> str:
52
+ return (f"FrequencyConnectednessResult(TCI={self.total.sum():.2f}%, "
53
+ f"bands={self.total.round(2).to_dict()})")
54
+
55
+
56
+ def frequency_connectedness(
57
+ data: pd.DataFrame | None = None,
58
+ horizon: int = 100,
59
+ *,
60
+ periods: tuple = (5, 20),
61
+ method: str = "generalized",
62
+ var_fit: VARFit | None = None,
63
+ **fit_kwargs,
64
+ ) -> FrequencyConnectednessResult:
65
+ r"""
66
+ Compute Baruník-Křehlík frequency connectedness.
67
+
68
+ Provide either `data`` (a VAR is estimated internally) or a pre-fitted
69
+ `var_fit`
70
+
71
+ Parameters
72
+ ----------
73
+ data : DataFrame, optional
74
+ Multivariate time series, one column per variable.
75
+ horizon : int
76
+ Forecast horizon H, also the number of points of the frequency grid.
77
+ periods : tuple of int
78
+ Band cutoffs as cycle lengths in observations, increasing and larger
79
+ than 2
80
+ method : {"generalized", "orthogonalized"}
81
+ Decomposition scheme, as in static_connectedness.
82
+ var_fit : VARFit, optional
83
+ Pre-fitted VAR model; skips the internal estimation.
84
+ **fit_kwargs
85
+ Passed through to :func:`pyconnectedness.connectedness.var.fit_var`
86
+ (e.g. ``lags``, ``ic``, ``max_lags``).
87
+
88
+ Notes
89
+ -----
90
+ The share of the forecast error variance of variable i due to shocks in
91
+ j on the frequency band d is
92
+
93
+ .. math::
94
+
95
+ \tilde\theta_{ij}(d) = \frac{\sigma_{jj}^{-1} \int_d
96
+ |(\Psi(e^{-i\omega}) \Sigma)_{ij}|^2 d\omega}
97
+ {\sum_l \sigma_{ll}^{-1} \int_{-\pi}^{\pi}
98
+ |(\Psi(e^{-i\omega}) \Sigma)_{il}|^2 d\omega}
99
+
100
+ with :math:`\Psi(e^{-i\omega}) = \sum_h \Phi_h e^{-i\omega h}`.
101
+
102
+ A cutoff of p observations is the frequency :math:`2\pi / p`. As the R
103
+ package frequencyConnectedness takes the cutoffs in radians, so
104
+ `periods=(5, 20)` corresponds to
105
+ `c(pi + 0.00001, 2*pi/5, 2*pi/20, 0)`.
106
+
107
+ Returns
108
+ -------
109
+ FrequencyConnectednessResult
110
+
111
+ Raises
112
+ ------
113
+ ValueError
114
+ If neither `data` nor `var_fit` is given, `method` is unknown,
115
+ `periods` is not increasing or not larger than 2, or a band holds no
116
+ frequency at the given horizon.
117
+ """
118
+ if var_fit is None:
119
+ if data is None:
120
+ raise ValueError("provide either 'data' or 'var_fit'")
121
+ var_fit = fit_var(data, **fit_kwargs)
122
+
123
+ if min(periods) <= 2:
124
+ raise ValueError("periods must be larger than 2")
125
+ if (np.diff(periods) <= 0).any():
126
+ raise ValueError("periods must be increasing")
127
+
128
+ names = var_fit.names
129
+ sigma = var_fit.sigma
130
+ k = var_fit.k
131
+
132
+ # 1) MA matrices Phi_0, ..., Phi_{H-1}, H x k x k
133
+ ma = var_fit.ma_coefficients(horizon)
134
+
135
+ # 2) psi[s] = Psi(exp(-i omega_s)) = sum_h Phi_h exp(-i omega_s h)
136
+ psi = np.fft.fft(ma, axis=0)
137
+
138
+ # 3) omega_s = 2 pi s / H; rows above H/2 are the negative frequencies and
139
+ # fold onto their positive twins
140
+ omega = 2 * np.pi * np.abs(np.fft.fftfreq(horizon))
141
+
142
+ # 4) contribution of shocks in j to variable i at each frequency
143
+ if method == "generalized":
144
+ numerator = np.abs(psi @ sigma) ** 2 / np.diag(sigma)
145
+ elif method == "orthogonalized":
146
+ numerator = np.abs(psi @ np.linalg.cholesky(sigma)) ** 2
147
+ else:
148
+ raise ValueError(
149
+ f"unknown method '{method}'; use 'generalized' or 'orthogonalized'"
150
+ )
151
+
152
+ # 5) normalize row i over all frequencies and all shocks
153
+ theta = numerator / numerator.sum(axis=(0, 2))[np.newaxis, :, np.newaxis]
154
+
155
+ # 6) band edges as frequencies, high to low
156
+ tol = 1e-10
157
+ edges = [np.inf] + [2 * np.pi / p for p in periods] + [0.0]
158
+
159
+ labels = [f"<={periods[0]}"]
160
+ for i in range(1, len(periods)):
161
+ labels.append(f"{periods[i - 1]}-{periods[i]}")
162
+ labels.append(f">{periods[-1]}")
163
+
164
+ # 7)sum theta over the frequencies of each band
165
+ bands = {}
166
+ within = {}
167
+ share = {}
168
+ total = {}
169
+ for b, label in enumerate(labels):
170
+ in_band = (omega >= edges[b + 1] - tol) & (omega < edges[b] - tol)
171
+ if not in_band.any():
172
+ raise ValueError(f"band '{label}' is empty at horizon {horizon}")
173
+ theta_band = theta[in_band].sum(axis=0)
174
+ bands[label] = _build_result(theta_band, names)
175
+ within[label] = _build_result(theta_band * k / theta_band.sum(), names)
176
+ share[label] = theta_band.sum() / k * 100.0
177
+ total[label] = bands[label].total
178
+
179
+ return FrequencyConnectednessResult(
180
+ bands=bands,
181
+ within=within,
182
+ share=pd.Series(share, name="share"),
183
+ total=pd.Series(total, name="total"),
184
+ horizon=horizon,
185
+ )
@@ -0,0 +1,163 @@
1
+ r"""
2
+ Static connectedness measures (Diebold-Yilmaz 2009, 2012).
3
+
4
+ From a normalized variance decomposition :math:`D = [\tilde\theta_{ij}]` the
5
+ spillover measures are derived: the total connectedness index (TCI),
6
+ directional "from" (shocks received), directional "to" (shocks transmitted), and
7
+ net (to minus from).
8
+
9
+ References
10
+ ----------
11
+ Diebold and Yilmaz (2009) Measuring financial asset return and volatility
12
+ spillovers, with application to global equity markets. The Economic Journal,
13
+ 119, 158-171.
14
+
15
+ Diebold and Yilmaz (2012) Better to give than to receive: predictive
16
+ directional measurement of volatility spillovers. International Journal of
17
+ Forecasting, 28, 57-66.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass
23
+
24
+ import numpy as np
25
+ import pandas as pd
26
+
27
+ from .decomposition import generalized_fevd, normalize_fevd, orthogonalized_fevd
28
+ from .var import VARFit, fit_var
29
+
30
+
31
+ @dataclass
32
+ class ConnectednessResult:
33
+ """
34
+ Result of a static connectedness computation.
35
+
36
+ Parameters
37
+ ----------
38
+ table : DataFrame
39
+ Spillover table in Diebold-Yilmaz layout: the normalized decomposition
40
+ (in percent) with "FROM" column and "TO"/"NET" rows.
41
+ fevd : DataFrame (k x k)
42
+ Normalized variance decomposition, in percent.
43
+ total : float
44
+ Total connectedness index, in percent.
45
+ directional_to : Series
46
+ "To others" spillover per variable.
47
+ directional_from : Series
48
+ "From others" spillover per variable.
49
+ net : Series
50
+ Net spillover per variable (to minus from).
51
+ """
52
+
53
+ table: pd.DataFrame
54
+ fevd: pd.DataFrame
55
+ total: float
56
+ directional_to: pd.Series
57
+ directional_from: pd.Series
58
+ net: pd.Series
59
+ pairwise_net: pd.DataFrame
60
+
61
+ def __repr__(self) -> str:
62
+ return f"ConnectednessResult(TCI={self.total:.2f}%, n={self.fevd.shape[0]})"
63
+
64
+
65
+ def _measures(theta_norm: np.ndarray) -> dict:
66
+ """Compute directional and total measures from a normalized decomposition."""
67
+ k = theta_norm.shape[0]
68
+ own = np.diag(theta_norm)
69
+ to_others = (theta_norm.sum(axis=0) - own) * 100.0 # sums, off-diagonal
70
+ from_others = (theta_norm.sum(axis=1) - own) * 100.0 # sums, off-diagonal
71
+ net = to_others - from_others
72
+ incl_own = theta_norm.sum(axis=0) * 100.0
73
+ total = (theta_norm.sum() - np.trace(theta_norm)) / k * 100.0
74
+ pairwise = (theta_norm.T - theta_norm) * 100.0 # C_ij = theta_ji - theta_ij
75
+ return {"to": to_others, "from": from_others, "net": net, "incl_own":incl_own,
76
+ "total": total, "pairwise": pairwise}
77
+
78
+
79
+
80
+ def _build_result(theta_norm: np.ndarray, names: list) -> ConnectednessResult:
81
+ """Assemble a result object from a normalized decomposition."""
82
+ m = _measures(theta_norm)
83
+ fevd_df = pd.DataFrame(theta_norm * 100.0, index=names, columns=names)
84
+
85
+ # Spillover table in DY layout: FEVD + FROM column, then TO and NET rows
86
+ table = fevd_df.copy()
87
+ table["FROM"] = m["from"]
88
+ to_row = pd.Series(dict(zip(names, m["to"], strict=True)), name="TO")
89
+ to_row["FROM"] = m["to"].sum() # corner - sum of TO
90
+ incl_row = pd.Series(dict(zip(names,m["incl_own"], strict=True)), name="TO_incl_own")
91
+ incl_row ["FROM"] = m["total"]
92
+ net_row = pd.Series(dict(zip(names, m["net"], strict=True)), name="NET")
93
+ net_row["FROM"] = 0.0 # m["total"] # corner entry = total connectedness index
94
+ table = pd.concat([table, to_row.to_frame().T, incl_row.to_frame().T, net_row.to_frame().T])
95
+
96
+ return ConnectednessResult(
97
+ table=table,
98
+ fevd=fevd_df,
99
+ total=float(m["total"]),
100
+ directional_to=pd.Series(m["to"], index=names, name="TO"),
101
+ directional_from=pd.Series(m["from"], index=names, name="FROM"),
102
+ net=pd.Series(m["net"], index=names, name="NET"),
103
+ pairwise_net=pd.DataFrame(m["pairwise"], index=names, columns=names)#.T,
104
+ )
105
+
106
+ def static_connectedness(
107
+ data: pd.DataFrame | None = None,
108
+ horizon: int = 10,
109
+ *,
110
+ method: str = "generalized",
111
+ var_fit: VARFit | None = None,
112
+ **fit_kwargs,
113
+ ) -> ConnectednessResult:
114
+ """
115
+ Compute static Diebold-Yilmaz connectedness.
116
+
117
+ Provide either ``data`` (a VAR is estimated internally) or a pre-fitted
118
+ ``var_fit``.
119
+
120
+ Parameters
121
+ ----------
122
+ data : DataFrame, optional
123
+ Multivariate time series, one column per variable.
124
+ horizon : int
125
+ Forecast horizon H for the variance decomposition.
126
+ method : {"generalized", "orthogonalized"}
127
+ Decomposition scheme - "generalized" (Pesaran-Shin, order-invariant,
128
+ reproduces DY-2012/2014) or "orthogonalized" (Cholesky, order-
129
+ dependent, reproduces DY-2009). Under "orthogonalized" the column
130
+ order of the data determines the result.
131
+ var_fit : VARFit, optional
132
+ Pre-fitted VAR model; skips the internal estimation.
133
+ **fit_kwargs
134
+ Passed through to :func:`pyconnectedness.connectedness.var.fit_var`
135
+ (e.g. ``lags``, ``ic``, ``max_lags``).
136
+
137
+ Returns
138
+ -------
139
+ ConnectednessResult
140
+
141
+ Raises
142
+ ------
143
+ ValueError
144
+ If neither ``data`` nor ``var_fit`` is given, or ``method`` is unknown.
145
+ """
146
+ if var_fit is None:
147
+ if data is None:
148
+ raise ValueError("provide either 'data' or 'var_fit'")
149
+ var_fit = fit_var(data, **fit_kwargs)
150
+
151
+ ma = var_fit.ma_coefficients(horizon)
152
+ if method == "generalized":
153
+ theta = generalized_fevd(ma, var_fit.sigma)
154
+ elif method == "orthogonalized":
155
+ theta = orthogonalized_fevd(ma, var_fit.sigma)
156
+ else:
157
+ raise ValueError(
158
+ f"unknown method '{method}'; use 'generalized' or 'orthogonalized'"
159
+ )
160
+ theta_norm = normalize_fevd(theta)
161
+
162
+
163
+ return _build_result(theta_norm, var_fit.names)