macroshock 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {macroshock-0.2.0/src/macroshock.egg-info → macroshock-0.2.2}/PKG-INFO +15 -15
- {macroshock-0.2.0 → macroshock-0.2.2}/README.md +14 -14
- {macroshock-0.2.0 → macroshock-0.2.2}/pyproject.toml +1 -1
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/bootstrap.py +12 -12
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/data.py +5 -4
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/fc.py +1 -1
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/fevd.py +5 -6
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/identification.py +81 -35
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/irf.py +5 -5
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/macroshock.py +75 -200
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/plotting.py +47 -53
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/var.py +5 -4
- {macroshock-0.2.0 → macroshock-0.2.2/src/macroshock.egg-info}/PKG-INFO +15 -15
- {macroshock-0.2.0 → macroshock-0.2.2}/LICENSE +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/setup.cfg +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/__init__.py +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock/stats.py +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock.egg-info/SOURCES.txt +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock.egg-info/dependency_links.txt +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock.egg-info/requires.txt +0 -0
- {macroshock-0.2.0 → macroshock-0.2.2}/src/macroshock.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: macroshock
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Powerful time series econometrics for Structural Vector Autoregressions (SVAR)
|
|
5
5
|
Author-email: Patricio Arias <patoariaslopez2@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -41,13 +41,13 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
## Main Features
|
|
43
43
|
|
|
44
|
-
* **Restricted VAR estimation** (Lütkepohl-style GLS), with support for:
|
|
44
|
+
* - **Restricted VAR estimation** (Lütkepohl-style GLS), with support for:
|
|
45
45
|
|
|
46
46
|
- Constant, trend and quadratic trend.
|
|
47
47
|
- Exogenous variables.
|
|
48
48
|
- Seasonal dummies (quarterly or monthly).
|
|
49
49
|
- Linear restrictions on the coefficients (`restrictions='zeros'` or `'custom zeros'`).
|
|
50
|
-
* **Four structural identification methods**:
|
|
50
|
+
* - **Four structural identification methods**:
|
|
51
51
|
|
|
52
52
|
|Method|Description|
|
|
53
53
|
|-|-|
|
|
@@ -56,12 +56,12 @@ Dynamic: license-file
|
|
|
56
56
|
|`signs`|Sign restrictions (orthogonal rotation sampling with acceptance/rejection)|
|
|
57
57
|
|`IV`|Instrumental variable identification (proxy-SVAR)|
|
|
58
58
|
|
|
59
|
-
* **Impulse-response functions (IRF)** with confidence bands.
|
|
60
|
-
* **Forecast error variance decomposition (FEVD)**.
|
|
61
|
-
* **Conditional forecasts** with confidence bands.
|
|
62
|
-
* **Bootstrap** (normal or *wild* resampling) for inference.
|
|
63
|
-
* **Built-in plots** for IRF, forecasts and FEVD, publication-ready.
|
|
64
|
-
* **Stability test** of the system (characteristic polynomial roots).
|
|
59
|
+
* - **Impulse-response functions (IRF)** with confidence bands.
|
|
60
|
+
* - **Forecast error variance decomposition (FEVD)**.
|
|
61
|
+
* - **Conditional forecasts** with confidence bands.
|
|
62
|
+
* - **Bootstrap** (normal or *wild* resampling) for inference.
|
|
63
|
+
* - **Built-in plots** for IRF, forecasts and FEVD, publication-ready.
|
|
64
|
+
* - **Stability test** of the system (characteristic polynomial roots).
|
|
65
65
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
@@ -96,7 +96,7 @@ model = SVAR(
|
|
|
96
96
|
horizon=8, # forecast horizon
|
|
97
97
|
past=12, # past periods to show in the forecast
|
|
98
98
|
alpha=32, # confidence interval level (32 -> 68% central band)
|
|
99
|
-
reps
|
|
99
|
+
reps=1000, # bootstrap replications
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
# Runs the complete pipeline: Y/X construction, VAR estimation,
|
|
@@ -116,13 +116,13 @@ model.S() # identifies the structural system (matrix B)
|
|
|
116
116
|
model.summary() # prints the estimation summary
|
|
117
117
|
|
|
118
118
|
# Impulse response to shock 1, with "pair" bands (low/high percentile)
|
|
119
|
-
ir = model.ImpulseResponse(
|
|
119
|
+
ir = model.ImpulseResponse(1, show=True, bands="pair")
|
|
120
120
|
|
|
121
121
|
# Forecast with "many" bands (multiple stacked confidence levels)
|
|
122
122
|
fc = model.Forecast(show=True, bands="many")
|
|
123
123
|
|
|
124
|
-
# Forecast error variance decomposition for
|
|
125
|
-
vd = model.VarianceDecomp(
|
|
124
|
+
# Forecast error variance decomposition for variable 1
|
|
125
|
+
vd = model.VarianceDecomp(1, show=True)
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Each of these methods returns a dictionary with the point estimate and confidence bands at different levels (`'point'`, `'low'`, `'high'`, `2.5`, `5`, `95`, `97.5`, `0.5`, `99.5`), indexed by variable name.
|
|
@@ -143,7 +143,7 @@ By default it uses the Cholesky decomposition. If `matrix\_short` is passed (a b
|
|
|
143
143
|
model = SVAR(data=df, variables=variables, lags=4, method="long")
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
By default it uses the Cholesky decomposition. If `matrix\
|
|
146
|
+
By default it uses the Cholesky decomposition. If `matrix\_long` is passed (a binary matrix of 1s and 0s), the `B` matrix satisfying the indicated zero restrictions is solved numerically.
|
|
147
147
|
|
|
148
148
|
### 3\. Sign restrictions
|
|
149
149
|
|
|
@@ -157,7 +157,7 @@ model = SVAR(
|
|
|
157
157
|
method="signs",
|
|
158
158
|
matrix\_signs=matrix\_signs,
|
|
159
159
|
steps\_signs=4, # horizons over which signs are checked
|
|
160
|
-
reps
|
|
160
|
+
reps=1000, # number of accepted B matrices
|
|
161
161
|
)
|
|
162
162
|
```
|
|
163
163
|
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
## Main Features
|
|
6
6
|
|
|
7
|
-
* **Restricted VAR estimation** (Lütkepohl-style GLS), with support for:
|
|
7
|
+
* - **Restricted VAR estimation** (Lütkepohl-style GLS), with support for:
|
|
8
8
|
|
|
9
9
|
- Constant, trend and quadratic trend.
|
|
10
10
|
- Exogenous variables.
|
|
11
11
|
- Seasonal dummies (quarterly or monthly).
|
|
12
12
|
- Linear restrictions on the coefficients (`restrictions='zeros'` or `'custom zeros'`).
|
|
13
|
-
* **Four structural identification methods**:
|
|
13
|
+
* - **Four structural identification methods**:
|
|
14
14
|
|
|
15
15
|
|Method|Description|
|
|
16
16
|
|-|-|
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
|`signs`|Sign restrictions (orthogonal rotation sampling with acceptance/rejection)|
|
|
20
20
|
|`IV`|Instrumental variable identification (proxy-SVAR)|
|
|
21
21
|
|
|
22
|
-
* **Impulse-response functions (IRF)** with confidence bands.
|
|
23
|
-
* **Forecast error variance decomposition (FEVD)**.
|
|
24
|
-
* **Conditional forecasts** with confidence bands.
|
|
25
|
-
* **Bootstrap** (normal or *wild* resampling) for inference.
|
|
26
|
-
* **Built-in plots** for IRF, forecasts and FEVD, publication-ready.
|
|
27
|
-
* **Stability test** of the system (characteristic polynomial roots).
|
|
22
|
+
* - **Impulse-response functions (IRF)** with confidence bands.
|
|
23
|
+
* - **Forecast error variance decomposition (FEVD)**.
|
|
24
|
+
* - **Conditional forecasts** with confidence bands.
|
|
25
|
+
* - **Bootstrap** (normal or *wild* resampling) for inference.
|
|
26
|
+
* - **Built-in plots** for IRF, forecasts and FEVD, publication-ready.
|
|
27
|
+
* - **Stability test** of the system (characteristic polynomial roots).
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
30
30
|
|
|
@@ -59,7 +59,7 @@ model = SVAR(
|
|
|
59
59
|
horizon=8, # forecast horizon
|
|
60
60
|
past=12, # past periods to show in the forecast
|
|
61
61
|
alpha=32, # confidence interval level (32 -> 68% central band)
|
|
62
|
-
reps
|
|
62
|
+
reps=1000, # bootstrap replications
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
# Runs the complete pipeline: Y/X construction, VAR estimation,
|
|
@@ -79,13 +79,13 @@ model.S() # identifies the structural system (matrix B)
|
|
|
79
79
|
model.summary() # prints the estimation summary
|
|
80
80
|
|
|
81
81
|
# Impulse response to shock 1, with "pair" bands (low/high percentile)
|
|
82
|
-
ir = model.ImpulseResponse(
|
|
82
|
+
ir = model.ImpulseResponse(1, show=True, bands="pair")
|
|
83
83
|
|
|
84
84
|
# Forecast with "many" bands (multiple stacked confidence levels)
|
|
85
85
|
fc = model.Forecast(show=True, bands="many")
|
|
86
86
|
|
|
87
|
-
# Forecast error variance decomposition for
|
|
88
|
-
vd = model.VarianceDecomp(
|
|
87
|
+
# Forecast error variance decomposition for variable 1
|
|
88
|
+
vd = model.VarianceDecomp(1, show=True)
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
Each of these methods returns a dictionary with the point estimate and confidence bands at different levels (`'point'`, `'low'`, `'high'`, `2.5`, `5`, `95`, `97.5`, `0.5`, `99.5`), indexed by variable name.
|
|
@@ -106,7 +106,7 @@ By default it uses the Cholesky decomposition. If `matrix\_short` is passed (a b
|
|
|
106
106
|
model = SVAR(data=df, variables=variables, lags=4, method="long")
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
By default it uses the Cholesky decomposition. If `matrix\
|
|
109
|
+
By default it uses the Cholesky decomposition. If `matrix\_long` is passed (a binary matrix of 1s and 0s), the `B` matrix satisfying the indicated zero restrictions is solved numerically.
|
|
110
110
|
|
|
111
111
|
### 3\. Sign restrictions
|
|
112
112
|
|
|
@@ -120,7 +120,7 @@ model = SVAR(
|
|
|
120
120
|
method="signs",
|
|
121
121
|
matrix\_signs=matrix\_signs,
|
|
122
122
|
steps\_signs=4, # horizons over which signs are checked
|
|
123
|
-
reps
|
|
123
|
+
reps=1000, # number of accepted B matrices
|
|
124
124
|
)
|
|
125
125
|
```
|
|
126
126
|
|
|
@@ -10,7 +10,7 @@ from .fc import compute_forecast
|
|
|
10
10
|
from .fevd import forecast_error_variance_decomposition
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def bootstrap_svar(model, typ: str, shock: int = 1, reps: int = 0):
|
|
13
|
+
def bootstrap_svar(model, typ: str, shock: int = 1, reps: int = 100, impact: float = 1.0, steps: int = 30, horizon: int = 5, past: int = 24):
|
|
14
14
|
"""
|
|
15
15
|
Replica la lógica original de SVAR.Bootstrap, pero fuera de la clase.
|
|
16
16
|
|
|
@@ -30,19 +30,19 @@ def bootstrap_svar(model, typ: str, shock: int = 1, reps: int = 0):
|
|
|
30
30
|
- IR: (reps, steps)
|
|
31
31
|
- FC: (reps, past + horizon)
|
|
32
32
|
"""
|
|
33
|
+
if model.method == 'signs':
|
|
34
|
+
raise ValueError("No bootstrap in sign restriction SVAR")
|
|
33
35
|
|
|
34
|
-
if reps == 0:
|
|
35
|
-
reps = model.reps_default
|
|
36
36
|
|
|
37
37
|
# BEGIN BOOTSTRAP
|
|
38
38
|
b = 0
|
|
39
39
|
|
|
40
40
|
if typ == "IR":
|
|
41
|
-
draws = {var: np.zeros((reps,
|
|
41
|
+
draws = {var: np.zeros((reps, steps)) for var in model.variables}
|
|
42
42
|
elif typ == "FC":
|
|
43
|
-
draws = {var: np.zeros((reps,
|
|
43
|
+
draws = {var: np.zeros((reps, past + horizon)) for var in model.variables}
|
|
44
44
|
elif typ == "VD":
|
|
45
|
-
draws = {var: np.zeros((reps,
|
|
45
|
+
draws = {var: np.zeros((reps, steps)) for var in model.variables}
|
|
46
46
|
else:
|
|
47
47
|
raise ValueError("typ must be 'IR', 'FC' or 'VD'.")
|
|
48
48
|
|
|
@@ -157,11 +157,11 @@ def bootstrap_svar(model, typ: str, shock: int = 1, reps: int = 0):
|
|
|
157
157
|
[np.ones((model.n_obs, 1)), trend, trend**2, Xsim]
|
|
158
158
|
)
|
|
159
159
|
|
|
160
|
-
if model.
|
|
161
|
-
if len(model.
|
|
162
|
-
Xsim_exog = model.
|
|
160
|
+
if model.data_exogenous is not None:
|
|
161
|
+
if len(model.variables_exogenous) == model.n_obs:
|
|
162
|
+
Xsim_exog = model.data_exogenous
|
|
163
163
|
else:
|
|
164
|
-
Xsim_exog = model.
|
|
164
|
+
Xsim_exog = model.data_exogenous[-model.n_obs :]
|
|
165
165
|
if len(Xsim_exog) != model.n_obs:
|
|
166
166
|
print("Exogenous variables in different domain")
|
|
167
167
|
Xsim = np.hstack([Xsim, Xsim_exog])
|
|
@@ -242,7 +242,7 @@ def bootstrap_svar(model, typ: str, shock: int = 1, reps: int = 0):
|
|
|
242
242
|
|
|
243
243
|
# ---------- almacenar draws ----------
|
|
244
244
|
if typ == "IR":
|
|
245
|
-
ir_b = impulse_response(Bsim, Fsim, shock, steps=
|
|
245
|
+
ir_b = impulse_response(Bsim, Fsim, shock, steps=steps, impact=impact)
|
|
246
246
|
|
|
247
247
|
if np.any(Bsim != 0):
|
|
248
248
|
if model.method in ("short", "long", "IV"):
|
|
@@ -260,7 +260,7 @@ def bootstrap_svar(model, typ: str, shock: int = 1, reps: int = 0):
|
|
|
260
260
|
|
|
261
261
|
if typ == "VD":
|
|
262
262
|
vd_dict = forecast_error_variance_decomposition(
|
|
263
|
-
Bsim, Fsim, steps=
|
|
263
|
+
Bsim, Fsim, steps=steps
|
|
264
264
|
)
|
|
265
265
|
if np.any(Bsim != 0):
|
|
266
266
|
if model.method in ("short", "long", "IV"):
|
|
@@ -32,11 +32,11 @@ def YX(model) -> YXR:
|
|
|
32
32
|
|
|
33
33
|
model.n_obs, _ = X.shape
|
|
34
34
|
|
|
35
|
-
if model.
|
|
35
|
+
if model.data_exogenous is not None:
|
|
36
36
|
if model.n_obsex==model.n_obs:
|
|
37
|
-
X_exog=model.
|
|
37
|
+
X_exog=model.data_exogenous
|
|
38
38
|
else:
|
|
39
|
-
X_exog=model.
|
|
39
|
+
X_exog=model.data_exogenous[-model.n_obs:]
|
|
40
40
|
if len(X_exog)!=model.n_obs:
|
|
41
41
|
print('Exogenous variables in different domain')
|
|
42
42
|
X=np.hstack([X,X_exog])
|
|
@@ -107,4 +107,5 @@ def YX(model) -> YXR:
|
|
|
107
107
|
|
|
108
108
|
model.R=np.delete(RRR, drop, axis=1)
|
|
109
109
|
|
|
110
|
-
return YXR(Y=model.Y, X=model.X, R=model.R)
|
|
110
|
+
return YXR(Y=model.Y, X=model.X, R=model.R)
|
|
111
|
+
|
|
@@ -38,7 +38,7 @@ def compute_forecast(modelo, F):
|
|
|
38
38
|
if modelo.const == 1:
|
|
39
39
|
FYs += beta_const
|
|
40
40
|
elif modelo.const == 2:
|
|
41
|
-
FYs += beta_const + (
|
|
41
|
+
FYs += beta_const + (i * beta_trend)
|
|
42
42
|
elif modelo.const == 3:
|
|
43
43
|
FYs += beta_const + (i * beta_trend) + ((i**2) * beta_sqrd)
|
|
44
44
|
|
|
@@ -34,16 +34,15 @@ def forecast_error_variance_decomposition(
|
|
|
34
34
|
|
|
35
35
|
n_vars = B.shape[0]
|
|
36
36
|
|
|
37
|
-
# 1. Pre-calculate Orthogonalized Impulse Responses for all horizons
|
|
37
|
+
# 1. Pre-calculate Orthogonalized Impulse Responses for all horizons.
|
|
38
|
+
# Accumulate F^h incrementally (F^h = F^(h-1) @ F) instead of calling
|
|
39
|
+
# np.linalg.matrix_power(F, h) from scratch at every horizon.
|
|
38
40
|
theta = np.zeros((steps, n_vars, n_vars))
|
|
41
|
+
F_power = np.eye(F.shape[0]) # F^0
|
|
39
42
|
for h in range(steps):
|
|
40
|
-
if h == 0:
|
|
41
|
-
F_power = np.eye(F.shape[0])
|
|
42
|
-
else:
|
|
43
|
-
F_power = np.linalg.matrix_power(F, h)
|
|
44
|
-
|
|
45
43
|
Psi_h = F_power[:n_vars, :n_vars]
|
|
46
44
|
theta[h] = Psi_h @ B
|
|
45
|
+
F_power = F_power @ F # advance to F^(h+1) for the next iteration
|
|
47
46
|
|
|
48
47
|
# 2. Calculate Variance Decomposition per variable
|
|
49
48
|
VD = {}
|
|
@@ -250,7 +250,7 @@ def identify_signs(
|
|
|
250
250
|
FEVDL[k1 + 1].append(vd_check)
|
|
251
251
|
|
|
252
252
|
m_accepted += 1
|
|
253
|
-
if m_accepted in list(range(
|
|
253
|
+
if m_accepted in list(range(0+reps//10, reps + 1, reps//10)):
|
|
254
254
|
print(f"{m_accepted}/{reps}")
|
|
255
255
|
else:
|
|
256
256
|
n_rejected += 1
|
|
@@ -265,37 +265,75 @@ def identify_signs(
|
|
|
265
265
|
"=============================="
|
|
266
266
|
)
|
|
267
267
|
B_zero = np.zeros((n_vars, n_vars))
|
|
268
|
+
|
|
269
|
+
# Same key set as the successful path below (ir_* and vd_*,
|
|
270
|
+
# zero-filled with the right shape) so SVAR.S() can always
|
|
271
|
+
# unpack 'extra' the same way, whether or not sampling aborted.
|
|
272
|
+
ir_stats = _zero_stat_dicts(steps, n_vars, extra_percentiles=True)
|
|
273
|
+
vd_stats = _zero_stat_dicts(steps, n_vars, extra_percentiles=False)
|
|
274
|
+
|
|
275
|
+
extra = {
|
|
276
|
+
"IRL": IRL,
|
|
277
|
+
"MB": MB,
|
|
278
|
+
"ir_median": ir_stats["median"],
|
|
279
|
+
"ir_mean": ir_stats["mean"],
|
|
280
|
+
"ir_high": ir_stats["high"],
|
|
281
|
+
"ir_low": ir_stats["low"],
|
|
282
|
+
"ir_00": ir_stats["00"],
|
|
283
|
+
"ir_99": ir_stats["99"],
|
|
284
|
+
"ir_02": ir_stats["02"],
|
|
285
|
+
"ir_97": ir_stats["97"],
|
|
286
|
+
"ir_05": ir_stats["05"],
|
|
287
|
+
"ir_95": ir_stats["95"],
|
|
288
|
+
"ir_10": ir_stats["10"],
|
|
289
|
+
"ir_90": ir_stats["90"],
|
|
290
|
+
"ir_16": ir_stats["16"],
|
|
291
|
+
"ir_84": ir_stats["84"],
|
|
292
|
+
"ir_25": ir_stats["25"],
|
|
293
|
+
"ir_75": ir_stats["75"],
|
|
294
|
+
"FEVDL": FEVDL,
|
|
295
|
+
"vd_median": vd_stats["median"],
|
|
296
|
+
"vd_mean": vd_stats["mean"],
|
|
297
|
+
"vd_high": vd_stats["high"],
|
|
298
|
+
"vd_low": vd_stats["low"],
|
|
299
|
+
"vd_00": vd_stats["00"],
|
|
300
|
+
"vd_99": vd_stats["99"],
|
|
301
|
+
"vd_02": vd_stats["02"],
|
|
302
|
+
"vd_97": vd_stats["97"],
|
|
303
|
+
"vd_05": vd_stats["05"],
|
|
304
|
+
"vd_95": vd_stats["95"],
|
|
305
|
+
"alpha": alpha,
|
|
306
|
+
"steps_signs": steps_signs,
|
|
307
|
+
}
|
|
308
|
+
|
|
268
309
|
return StructuralResults(
|
|
269
310
|
B=B_zero,
|
|
270
311
|
method="signs",
|
|
271
312
|
n_rejected=n_rejected,
|
|
272
313
|
m_accepted=m_accepted,
|
|
273
314
|
corr=0,
|
|
274
|
-
extra=
|
|
315
|
+
extra=extra,
|
|
275
316
|
)
|
|
276
317
|
|
|
277
|
-
# Build IR bands across accepted draws
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
ir_99 =
|
|
284
|
-
ir_02 =
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
ir_90 = {}
|
|
290
|
-
ir_16 = {}
|
|
291
|
-
ir_84 = {}
|
|
292
|
-
ir_25 = {}
|
|
293
|
-
ir_75 = {}
|
|
318
|
+
# Build IR bands across accepted draws, starting from a zero baseline so
|
|
319
|
+
# that a shock with zero accepted draws (edge case) degrades to zeros
|
|
320
|
+
# instead of crashing np.stack([]) with a ValueError.
|
|
321
|
+
ir_stats = _zero_stat_dicts(steps, n_vars, extra_percentiles=True)
|
|
322
|
+
ir_median, ir_mean = ir_stats["median"], ir_stats["mean"]
|
|
323
|
+
ir_high, ir_low = ir_stats["high"], ir_stats["low"]
|
|
324
|
+
ir_00, ir_99 = ir_stats["00"], ir_stats["99"]
|
|
325
|
+
ir_02, ir_97 = ir_stats["02"], ir_stats["97"]
|
|
326
|
+
ir_05, ir_95 = ir_stats["05"], ir_stats["95"]
|
|
327
|
+
ir_10, ir_90 = ir_stats["10"], ir_stats["90"]
|
|
328
|
+
ir_16, ir_84 = ir_stats["16"], ir_stats["84"]
|
|
329
|
+
ir_25, ir_75 = ir_stats["25"], ir_stats["75"]
|
|
294
330
|
|
|
295
331
|
alpha_low = alpha / 2.0
|
|
296
332
|
alpha_high = 100.0 - alpha_low
|
|
297
333
|
|
|
298
334
|
for key, lst in IRL.items():
|
|
335
|
+
if len(lst) == 0:
|
|
336
|
+
continue # leave the zero baseline for this shock
|
|
299
337
|
stack = np.stack(lst, axis=0) # (n_accept, steps, n_vars)
|
|
300
338
|
ir_median[key] = np.median(stack, axis=0)
|
|
301
339
|
ir_mean[key] = np.mean(stack, axis=0)
|
|
@@ -318,20 +356,16 @@ def identify_signs(
|
|
|
318
356
|
# above: 'high' uses the lower percentile and 'low' the upper one,
|
|
319
357
|
# since that is the swap SVAR.ImpulseResponse() already compensates
|
|
320
358
|
# for on read -- SVAR.VarianceDecomp() will do the same for FEVD).
|
|
321
|
-
|
|
322
|
-
vd_mean =
|
|
323
|
-
vd_high =
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
vd_02 = {}
|
|
328
|
-
vd_97 = {}
|
|
329
|
-
vd_05 = {}
|
|
330
|
-
vd_95 = {}
|
|
359
|
+
vd_stats = _zero_stat_dicts(steps, n_vars, extra_percentiles=False)
|
|
360
|
+
vd_median, vd_mean = vd_stats["median"], vd_stats["mean"]
|
|
361
|
+
vd_high, vd_low = vd_stats["high"], vd_stats["low"]
|
|
362
|
+
vd_00, vd_99 = vd_stats["00"], vd_stats["99"]
|
|
363
|
+
vd_02, vd_97 = vd_stats["02"], vd_stats["97"]
|
|
364
|
+
vd_05, vd_95 = vd_stats["05"], vd_stats["95"]
|
|
331
365
|
|
|
332
366
|
for key, lst in FEVDL.items():
|
|
333
367
|
if len(lst) == 0:
|
|
334
|
-
continue
|
|
368
|
+
continue # leave the zero baseline for this shock
|
|
335
369
|
stack = np.stack(lst, axis=0) # (n_accept, steps, n_vars)
|
|
336
370
|
vd_median[key] = np.median(stack, axis=0)
|
|
337
371
|
vd_mean[key] = np.mean(stack, axis=0)
|
|
@@ -345,8 +379,8 @@ def identify_signs(
|
|
|
345
379
|
vd_95[key] = np.percentile(stack, 95.0, axis=0)
|
|
346
380
|
|
|
347
381
|
B_median = np.median(np.stack(MB, axis=0), axis=0)
|
|
348
|
-
B_mean = np.mean(np.stack(MB, axis=0), axis=0)
|
|
349
|
-
|
|
382
|
+
#B_mean = np.mean(np.stack(MB, axis=0), axis=0)
|
|
383
|
+
|
|
350
384
|
extra = {
|
|
351
385
|
"IRL": IRL,
|
|
352
386
|
"MB": MB,
|
|
@@ -382,7 +416,7 @@ def identify_signs(
|
|
|
382
416
|
}
|
|
383
417
|
|
|
384
418
|
return StructuralResults(
|
|
385
|
-
B=
|
|
419
|
+
B=B_median,
|
|
386
420
|
method="signs",
|
|
387
421
|
n_rejected=n_rejected,
|
|
388
422
|
m_accepted=m_accepted,
|
|
@@ -467,7 +501,7 @@ def identify_iv(
|
|
|
467
501
|
C = np.linalg.cholesky(sigma_u)
|
|
468
502
|
q = solve_triangular(C, B[:, j], lower=True)
|
|
469
503
|
v = np.linalg.norm(q)
|
|
470
|
-
B[:, j] = B[:, j]
|
|
504
|
+
B[:, j] = B[:, j] / v
|
|
471
505
|
|
|
472
506
|
extra = {
|
|
473
507
|
"z": z_list,
|
|
@@ -482,4 +516,16 @@ def identify_iv(
|
|
|
482
516
|
m_accepted=None,
|
|
483
517
|
corr=corr_list,
|
|
484
518
|
extra=extra,
|
|
485
|
-
)
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def _zero_stat_dicts(steps: int, n_vars: int, extra_percentiles: bool = False) -> dict:
|
|
522
|
+
|
|
523
|
+
def _zero_per_shock():
|
|
524
|
+
return {k: np.zeros((steps, n_vars)) for k in range(1, n_vars + 1)}
|
|
525
|
+
|
|
526
|
+
names = ["median", "mean", "high", "low", "00", "99", "02", "97", "05", "95"]
|
|
527
|
+
if extra_percentiles:
|
|
528
|
+
names += ["10", "90", "16", "84", "25", "75"]
|
|
529
|
+
|
|
530
|
+
return {name: _zero_per_shock() for name in names}
|
|
531
|
+
|
|
@@ -27,14 +27,14 @@ def impulse_response(
|
|
|
27
27
|
impulse = np.zeros((n_vars, 1))
|
|
28
28
|
impulse[shock - 1, 0] = impact
|
|
29
29
|
|
|
30
|
+
# Accumulate F^h incrementally (F^h = F^(h-1) @ F) instead of calling
|
|
31
|
+
# np.linalg.matrix_power(F, h) from scratch at every horizon.
|
|
32
|
+
F_power = np.eye(n_vars * lags) # F^0
|
|
30
33
|
for h in range(steps):
|
|
31
|
-
if h == 0:
|
|
32
|
-
F_power = np.eye(n_vars * lags)
|
|
33
|
-
else:
|
|
34
|
-
F_power = np.linalg.matrix_power(F, h)
|
|
35
34
|
Psi_h = F_power[:n_vars, :n_vars]
|
|
36
35
|
response = Psi_h @ B @ impulse
|
|
37
36
|
ir[h, :] = response[:, 0]
|
|
37
|
+
F_power = F_power @ F # advance to F^(h+1) for the next iteration
|
|
38
38
|
|
|
39
39
|
return ir
|
|
40
40
|
|
|
@@ -101,4 +101,4 @@ def compute_bands_from_draws(
|
|
|
101
101
|
stats["q99_5"][var] = np.percentile(arr, 99.5, axis=0)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
return stats
|
|
104
|
+
return stats
|
|
@@ -13,7 +13,6 @@ from .identification import (
|
|
|
13
13
|
identify_iv,
|
|
14
14
|
)
|
|
15
15
|
from .bootstrap import bootstrap_svar
|
|
16
|
-
|
|
17
16
|
from .fc import compute_forecast
|
|
18
17
|
from .irf import compute_bands_from_draws
|
|
19
18
|
from .fevd import forecast_error_variance_decomposition
|
|
@@ -28,30 +27,30 @@ class SVAR:
|
|
|
28
27
|
def __init__(self,
|
|
29
28
|
data,
|
|
30
29
|
variables,
|
|
31
|
-
lags,
|
|
32
|
-
transformation=None,
|
|
33
|
-
const=1,
|
|
34
|
-
rescaling=None,
|
|
35
|
-
restrictions=None,
|
|
36
|
-
R=None,
|
|
37
|
-
method='short',
|
|
38
|
-
matrix_long=None,
|
|
39
|
-
matrix_short=None,
|
|
40
|
-
matrix_signs=None,
|
|
41
|
-
iv=None,
|
|
42
|
-
shocks=None,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
DUM=None,
|
|
46
|
-
impact=1,
|
|
47
|
-
steps=60,
|
|
48
|
-
steps_signs=1,
|
|
49
|
-
resampling=1,
|
|
50
|
-
alpha=32,
|
|
51
|
-
|
|
52
|
-
titles=None,
|
|
53
|
-
horizon=6,
|
|
54
|
-
past=12
|
|
30
|
+
lags: int,
|
|
31
|
+
transformation = None,
|
|
32
|
+
const: int = 1,
|
|
33
|
+
rescaling = None,
|
|
34
|
+
restrictions = None,
|
|
35
|
+
R = None,
|
|
36
|
+
method: str = 'short',
|
|
37
|
+
matrix_long = None,
|
|
38
|
+
matrix_short = None,
|
|
39
|
+
matrix_signs = None,
|
|
40
|
+
iv = None,
|
|
41
|
+
shocks = None,
|
|
42
|
+
data_exogenous = None,
|
|
43
|
+
variables_exogenous = None,
|
|
44
|
+
DUM = None,
|
|
45
|
+
impact: float = 1.0,
|
|
46
|
+
steps: int = 60,
|
|
47
|
+
steps_signs: int = 1,
|
|
48
|
+
resampling: int = 1,
|
|
49
|
+
alpha: float = 32,
|
|
50
|
+
reps:int = 1000,
|
|
51
|
+
titles = None,
|
|
52
|
+
horizon: int = 6,
|
|
53
|
+
past: int = 12
|
|
55
54
|
):
|
|
56
55
|
|
|
57
56
|
|
|
@@ -72,15 +71,15 @@ class SVAR:
|
|
|
72
71
|
self.matrix_signs=matrix_signs
|
|
73
72
|
self.iv=iv
|
|
74
73
|
self.shocks=shocks
|
|
75
|
-
self.
|
|
76
|
-
self.
|
|
74
|
+
self.data_exogenous=data_exogenous
|
|
75
|
+
self.variables_exogenous=variables_exogenous
|
|
77
76
|
self.DUM=DUM
|
|
78
77
|
self.impact=impact
|
|
79
78
|
self.steps=steps
|
|
80
79
|
self.steps_signs=steps_signs
|
|
81
80
|
self.resampling=resampling
|
|
82
81
|
self.alpha=alpha
|
|
83
|
-
self.
|
|
82
|
+
self.reps=reps
|
|
84
83
|
self.titles=titles
|
|
85
84
|
self.horizon=horizon
|
|
86
85
|
self.past=past
|
|
@@ -96,17 +95,17 @@ class SVAR:
|
|
|
96
95
|
if self.method=='IV':
|
|
97
96
|
self.iv=self.data[self.iv].values
|
|
98
97
|
self.data=self.data[self.variables].values
|
|
99
|
-
if
|
|
100
|
-
if isinstance(
|
|
101
|
-
self.
|
|
102
|
-
self.n_obsex,self.n_ex=self.
|
|
98
|
+
if data_exogenous is not None:
|
|
99
|
+
if isinstance(data_exogenous, pd.DataFrame):
|
|
100
|
+
self.data_exogenous=self.data_exogenous[self.variables_exogenous].values
|
|
101
|
+
self.n_obsex,self.n_ex=self.data_exogenous.shape
|
|
103
102
|
else:
|
|
104
103
|
self.n_ex=0
|
|
105
|
-
if self.
|
|
106
|
-
self.
|
|
104
|
+
if self.variables_exogenous is None:
|
|
105
|
+
self.variables_exogenous=['Exog'+f"{i+1}" for i in range(self.n_ex)]
|
|
107
106
|
for i in range(len(self.transformation)):
|
|
108
107
|
if self.transformation[i]=="diff":
|
|
109
|
-
self.data[1:,i]=np.diff(self.data[:, i])
|
|
108
|
+
self.data[1:,i]=np.diff(self.data[:, i])*self.rescaling[i]
|
|
110
109
|
elif self.transformation[i]=="logdiff":
|
|
111
110
|
self.data[1:,i]=np.diff(np.log(self.data[:,i]))*self.rescaling[i]
|
|
112
111
|
elif self.transformation[i]=="log":
|
|
@@ -151,6 +150,7 @@ class SVAR:
|
|
|
151
150
|
self.n_obs = res.n_obs
|
|
152
151
|
self.n_vars = res.n_vars
|
|
153
152
|
self.n_cols = res.n_cols
|
|
153
|
+
self.n_params = res.n_params
|
|
154
154
|
|
|
155
155
|
# Build companion matrix F exactly as before
|
|
156
156
|
top = self.beta[:, self.const : self.const + (self.lags * self.n_vars)]
|
|
@@ -187,7 +187,7 @@ class SVAR:
|
|
|
187
187
|
matrix_signs=self.matrix_signs,
|
|
188
188
|
steps_signs=self.steps_signs,
|
|
189
189
|
steps=self.steps,
|
|
190
|
-
reps=self.
|
|
190
|
+
reps=self.reps,
|
|
191
191
|
alpha=self.alpha,
|
|
192
192
|
)
|
|
193
193
|
self.B = struct.B
|
|
@@ -256,7 +256,7 @@ class SVAR:
|
|
|
256
256
|
for j in range(self.n_vars):
|
|
257
257
|
lagged.append("Lag"+f"{i+1}"+"."+self.variables[j])
|
|
258
258
|
|
|
259
|
-
summary0="
|
|
259
|
+
summary0="\nSummary\n=====================================\nModel: SVARX\nMethod: OLS\nDate/Time: "+datetime.now().strftime("%Y-%m-%d %H:%M:%S %Z")+"\n-------------------------------------\nNo. of Equations: "+f"{self.n_vars:>18.2f}"+"\nNo. of Observations: "+f"{self.n_obs:>15.2f}"+"\nFree Parameters: "+f"{self.n_params:>19.2f}"+"\nLog Likelihood: "+f"{self.ll:>20.6f}"+"\nAIC: "+f"{self.aic:>31.6f}"+"\nBIC: "+f"{self.bic:>31.6f}"+"\nHQIC: "+f"{self.hqic:>30.6f}"+"\nFPE: "+f"{self.fpe:>31.6f}"+"\nDet: "+f"{self.det:>31.6f}"+"\n-------------------------------------\n"
|
|
260
260
|
for i in range(self.n_vars):
|
|
261
261
|
summary1="Results for equation "+self.variables[i]+"\n=====================================================================\n coefficient std. error t-stat p-value\n---------------------------------------------------------------------\n"
|
|
262
262
|
if self.const==1:
|
|
@@ -274,7 +274,7 @@ class SVAR:
|
|
|
274
274
|
summary2 = (lagged[j-self.const]+f"{self.beta[i,j]:>{26-len(lagged[j-self.const])}.6f}"f"{self.beta_std[i,j]:>16.6f}"f"{self.tstat[i,j]:>12.3f}"f"{self.pvalue[i,j]:>12.3f}\n")
|
|
275
275
|
summary1=summary1+summary2
|
|
276
276
|
elif j in range(self.const+(self.lags*self.n_vars),self.const+(self.lags*self.n_vars)+self.n_ex):
|
|
277
|
-
summary2 = (self.
|
|
277
|
+
summary2 = (self.variables_exogenous[j-self.const-(self.lags*self.n_vars)]+f"{self.beta[i,j]:>{26-len(self.variables_exogenous[j-self.const-(self.lags*self.n_vars)])}.6f}"f"{self.beta_std[i,j]:>16.6f}"f"{self.tstat[i,j]:>12.3f}"f"{self.pvalue[i,j]:>12.3f}\n")
|
|
278
278
|
summary1=summary1+summary2
|
|
279
279
|
elif j in range(self.const+(self.lags*self.n_vars)+self.n_ex,self.const+(self.lags*self.n_vars)+self.n_ex+self.n_dum):
|
|
280
280
|
summary2 = (self.dummies[j-self.const-(self.lags*self.n_vars)-self.n_ex]+f"{self.beta[i,j]:>{26-len(self.dummies[j-self.const-(self.lags*self.n_vars)-self.n_ex])}.6f}"f"{self.beta_std[i,j]:>16.6f}"f"{self.tstat[i,j]:>12.3f}"f"{self.pvalue[i,j]:>12.3f}\n")
|
|
@@ -315,29 +315,27 @@ class SVAR:
|
|
|
315
315
|
#--------------------------------------------------------------------------
|
|
316
316
|
|
|
317
317
|
# impulse response calculation and plotting function
|
|
318
|
-
def ImpulseResponse(self, shock, show=True, bands='pair', steps=None, impact=None):
|
|
318
|
+
def ImpulseResponse(self, shock: int, show: bool = True, bands: str = 'pair', reps: int = None, steps: int = None, impact: float = None):
|
|
319
|
+
if reps is None:
|
|
320
|
+
reps = self.reps
|
|
319
321
|
if steps is None:
|
|
320
322
|
steps = self.steps
|
|
321
|
-
else:
|
|
322
|
-
self.steps = steps
|
|
323
323
|
if impact is None:
|
|
324
324
|
impact = self.impact
|
|
325
|
-
else:
|
|
326
|
-
self.impact = impact
|
|
327
325
|
"""
|
|
328
326
|
Calcula IRFs (punto y bandas) y, opcionalmente, grafica.
|
|
329
327
|
Mantiene la API y el output de la versión original.
|
|
330
328
|
"""
|
|
331
|
-
print("\nshock: " + str(shock) + "\n")
|
|
332
|
-
|
|
333
329
|
# tipo de bandas
|
|
334
330
|
self.bands = bands
|
|
335
331
|
|
|
336
332
|
# Métodos distintos a 'signs': bootstrap estándar
|
|
337
333
|
if self.method != 'signs':
|
|
334
|
+
|
|
335
|
+
print("\nCalculating responses to shock " + str(shock) + ":\n")
|
|
338
336
|
|
|
339
337
|
# Bootstrap de IRFs para este shock
|
|
340
|
-
self.drawsIR=bootstrap_svar(self, typ='IR', shock=shock)
|
|
338
|
+
self.drawsIR=bootstrap_svar(self, typ='IR', shock=shock, reps=reps, steps=steps)
|
|
341
339
|
|
|
342
340
|
# Calcular medias y cuantiles a partir de self.drawsIR
|
|
343
341
|
stats = compute_bands_from_draws(self.drawsIR, self.alpha)
|
|
@@ -356,6 +354,9 @@ class SVAR:
|
|
|
356
354
|
|
|
357
355
|
# Método 'signs': ya tenemos el conjunto de identificaciones
|
|
358
356
|
else:
|
|
357
|
+
|
|
358
|
+
print("\nResponses to shock " + str(shock) + "\n")
|
|
359
|
+
|
|
359
360
|
# self.ir_median, self.ir_mean, etc. vienen de identify_signs (identification.py),
|
|
360
361
|
# indexados por número de shock.
|
|
361
362
|
|
|
@@ -426,84 +427,15 @@ class SVAR:
|
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
return output
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
print("\nshock: "+str(shock)+"\n")
|
|
432
|
-
# type of bands
|
|
433
|
-
self.bands=bands
|
|
434
|
-
|
|
435
|
-
# for signs IMF are already estimated from the set identification process, thus this bootstrap is only for the other methods
|
|
436
|
-
if self.method!='signs':
|
|
437
|
-
|
|
438
|
-
self.Bootstrap('IR',shock=shock)
|
|
439
|
-
|
|
440
|
-
# alpha levels
|
|
441
|
-
lower_q = self.alpha / 2
|
|
442
|
-
upper_q = 100 - self.alpha / 2
|
|
443
|
-
#--------------------------------------------------------------
|
|
444
|
-
|
|
445
|
-
# storage for IRF
|
|
446
|
-
self.ir_point = {}
|
|
447
|
-
self.ir_mean = {}
|
|
448
|
-
self.ir_low = {}
|
|
449
|
-
self.ir_high = {}
|
|
450
|
-
self.ir_95 = {}
|
|
451
|
-
self.ir_05 = {}
|
|
452
|
-
self.ir_00 = {}
|
|
453
|
-
self.ir_99 = {}
|
|
454
|
-
self.ir_02 = {}
|
|
455
|
-
self.ir_97 = {}
|
|
456
|
-
|
|
457
|
-
for var in self.variables:
|
|
458
|
-
x=np.zeros((self.reps_default,self.steps))
|
|
459
|
-
for j in range(self.reps_default):
|
|
460
|
-
x[j,:]=self.drawsIR[var][j]
|
|
461
|
-
self.drawsIR[var]=x
|
|
462
|
-
|
|
463
|
-
for var in self.variables:
|
|
464
|
-
self.ir_point[var] = np.mean(self.drawsIR[var], axis=0)
|
|
465
|
-
self.ir_mean[var] = np.mean(self.drawsIR[var], axis=0)
|
|
466
|
-
self.ir_low[var] = np.percentile(self.drawsIR[var], lower_q, axis=0)
|
|
467
|
-
self.ir_high[var] = np.percentile(self.drawsIR[var], upper_q, axis=0)
|
|
468
|
-
self.ir_95[var] = np.percentile(self.drawsIR[var], 95, axis=0)
|
|
469
|
-
self.ir_05[var] = np.percentile(self.drawsIR[var], 5, axis=0)
|
|
470
|
-
self.ir_00[var] = np.percentile(self.drawsIR[var], 0.5, axis=0)
|
|
471
|
-
self.ir_99[var] = np.percentile(self.drawsIR[var], 99.5, axis=0)
|
|
472
|
-
self.ir_02[var] = np.percentile(self.drawsIR[var], 2.5, axis=0)
|
|
473
|
-
self.ir_97[var] = np.percentile(self.drawsIR[var], 97.5, axis=0)
|
|
474
|
-
#--------------------------------------------------------------
|
|
475
|
-
|
|
476
|
-
# if method is signs we already have the identification set for B matrices thus no bootstrap
|
|
477
|
-
elif self.method=='signs':
|
|
478
|
-
|
|
479
|
-
self.ir_point = self.ir_median[shock]
|
|
480
|
-
self.ir_low = self.ir_low[shock]
|
|
481
|
-
self.ir_high = self.ir_high[shock]
|
|
482
|
-
self.ir_02 = self.ir_02[shock]
|
|
483
|
-
self.ir_97 = self.ir_97[shock]
|
|
484
|
-
self.ir_05 = self.ir_05[shock]
|
|
485
|
-
self.ir_95 = self.ir_95[shock]
|
|
486
|
-
self.ir_00 = self.ir_00[shock]
|
|
487
|
-
self.ir_99 = self.ir_99[shock]
|
|
488
|
-
self.ir_mean = self.ir_mean[shock]
|
|
489
|
-
#--------------------------------------------------------------
|
|
490
|
-
|
|
491
|
-
# show the plots if indicated
|
|
492
|
-
if show==True:
|
|
493
|
-
plot_irf_svar(shock)
|
|
494
|
-
|
|
495
|
-
#--------------------------------------------------------------
|
|
496
|
-
|
|
497
|
-
# output set up
|
|
498
|
-
if self.method!='signs':
|
|
499
|
-
output={'point':self.ir_mean, 'low':self.ir_low, 'high':self.ir_high, 2.5:self.ir_02, 5:self.ir_05, 0.5:self.ir_00, 99.5:self.ir_99,97.5:self.ir_97,95:self.ir_95}
|
|
500
|
-
else:
|
|
501
|
-
output={'point':self.ir_point, 'low':self.ir_low, 'high':self.ir_high, 2.5:self.ir_02, 5:self.ir_05, 0.5:self.ir_00, 99.5:self.ir_99,97.5:self.ir_97,95:self.ir_95}
|
|
502
|
-
|
|
503
|
-
return output
|
|
504
430
|
|
|
505
431
|
# forecast calculation and plotting (THIS FUNCTION MIMICS THE IMPULSE RESPONSE ONE FOR BOOTSTAP AND BANDS LOGIC)
|
|
506
|
-
def Forecast(self, show=True, bands='many'):
|
|
432
|
+
def Forecast(self, show: bool = True, bands: str = 'many', horizon: int = None, past: int = None, reps: int = None):
|
|
433
|
+
if reps is None:
|
|
434
|
+
reps = self.reps
|
|
435
|
+
if horizon is None:
|
|
436
|
+
horizon = self.horizon
|
|
437
|
+
if past is None:
|
|
438
|
+
past = self.past
|
|
507
439
|
"""
|
|
508
440
|
Calcula pronósticos (punto y bandas) y, opcionalmente, grafica.
|
|
509
441
|
Mantiene la API y estructura de salida de la función original.
|
|
@@ -511,13 +443,13 @@ class SVAR:
|
|
|
511
443
|
# Para métodos distintos de 'signs', hacemos bootstrap
|
|
512
444
|
if self.method != 'signs':
|
|
513
445
|
|
|
514
|
-
print("
|
|
446
|
+
print("\nCalculating forecast:\n")
|
|
515
447
|
|
|
516
448
|
# Pronóstico determinista (no lo usabas mucho, pero lo mantenemos)
|
|
517
449
|
self.fc_point = compute_forecast(self, self.F)
|
|
518
450
|
|
|
519
451
|
# Bootstrap de pronósticos
|
|
520
|
-
self.drawsFC=bootstrap_svar(self, typ='FC')
|
|
452
|
+
self.drawsFC=bootstrap_svar(self, typ='FC', horizon=horizon, past=past, reps=reps)
|
|
521
453
|
|
|
522
454
|
# Calcular bandas a partir de self.drawsFC
|
|
523
455
|
stats = compute_bands_from_draws(self.drawsFC, self.alpha)
|
|
@@ -535,7 +467,7 @@ class SVAR:
|
|
|
535
467
|
# Para 'signs', no había bootstrap en tu código: solo trayectoria determinista
|
|
536
468
|
elif self.method == 'signs':
|
|
537
469
|
|
|
538
|
-
print('
|
|
470
|
+
print('\nNo bands Forecast\n')
|
|
539
471
|
|
|
540
472
|
fc_det = compute_forecast(self, self.F) # (past + horizon, n_vars)
|
|
541
473
|
|
|
@@ -582,92 +514,33 @@ class SVAR:
|
|
|
582
514
|
}
|
|
583
515
|
|
|
584
516
|
return output
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
if self.method!='signs':
|
|
588
|
-
|
|
589
|
-
print("\nreps_default:\n")
|
|
590
|
-
|
|
591
|
-
self.fc_point = self.FC(self.F)
|
|
592
|
-
|
|
593
|
-
self.Bootstrap('FC')
|
|
594
|
-
|
|
595
|
-
lower_q = self.alpha / 2
|
|
596
|
-
upper_q = 100 - self.alpha / 2
|
|
597
|
-
|
|
598
|
-
self.fc_mean = {}
|
|
599
|
-
self.fc_low = {}
|
|
600
|
-
self.fc_high = {}
|
|
601
|
-
self.fc_95 = {}
|
|
602
|
-
self.fc_50 = {}
|
|
603
|
-
self.fc_05 = {}
|
|
604
|
-
self.fc_02 = {}
|
|
605
|
-
self.fc_97 = {}
|
|
606
|
-
self.fc_00 = {}
|
|
607
|
-
self.fc_99 = {}
|
|
608
|
-
|
|
609
|
-
for var in self.variables:
|
|
610
|
-
x=np.zeros((self.reps_default,self.past+self.horizon))
|
|
611
|
-
for j in range(self.reps_default):
|
|
612
|
-
x[j,:]=self.drawsFC[var][j]
|
|
613
|
-
self.drawsFC[var]=x
|
|
614
|
-
|
|
615
|
-
for var in self.variables:
|
|
616
|
-
self.fc_mean[var] = np.mean(self.drawsFC[var], axis=0)
|
|
617
|
-
self.fc_low[var] = np.percentile(self.drawsFC[var], lower_q, axis=0)
|
|
618
|
-
self.fc_high[var] = np.percentile(self.drawsFC[var], upper_q, axis=0)
|
|
619
|
-
self.fc_95[var] = np.percentile(self.drawsFC[var], 95, axis=0)
|
|
620
|
-
self.fc_99[var] = np.percentile(self.drawsFC[var], 99.5, axis=0)
|
|
621
|
-
self.fc_00[var] = np.percentile(self.drawsFC[var], 0.5, axis=0)
|
|
622
|
-
self.fc_05[var] = np.percentile(self.drawsFC[var], 5, axis=0)
|
|
623
|
-
self.fc_02[var] = np.percentile(self.drawsFC[var], 2.5, axis=0)
|
|
624
|
-
self.fc_97[var] = np.percentile(self.drawsFC[var], 97.5, axis=0)
|
|
625
|
-
|
|
626
|
-
elif self.method=='signs':
|
|
627
|
-
|
|
628
|
-
print('no bands Forecast:')
|
|
629
|
-
self.fc_mean = {self.variables[i]:self.FC(self.F)[:,i] for i in range(self.n_vars)}
|
|
630
|
-
self.fc_low = self.fc_mean
|
|
631
|
-
self.fc_high = self.fc_mean
|
|
632
|
-
self.fc_95 = self.fc_mean
|
|
633
|
-
self.fc_99 = self.fc_mean
|
|
634
|
-
self.fc_00 = self.fc_mean
|
|
635
|
-
self.fc_05 = self.fc_mean
|
|
636
|
-
self.fc_02 = self.fc_mean
|
|
637
|
-
self.fc_97 = self.fc_mean
|
|
638
|
-
|
|
639
|
-
if show==True:
|
|
640
|
-
|
|
641
|
-
self.bands=bands
|
|
642
|
-
|
|
643
|
-
plot_forecast_svar(self)
|
|
644
|
-
|
|
645
|
-
output={'point':self.fc_mean, 'low':self.fc_low, 'high':self.fc_high, 2.5:self.fc_02, 5:self.fc_05,97.5:self.fc_97,95:self.fc_95,0.5:self.fc_00,99.5:self.fc_99}
|
|
646
|
-
|
|
647
|
-
return output
|
|
648
517
|
|
|
649
518
|
|
|
650
|
-
def VarianceDecomp(self, shock: int
|
|
519
|
+
def VarianceDecomp(self, shock: int, show: bool = True, bands: str = 'pair', steps: int = None, reps: int = None):
|
|
651
520
|
"""
|
|
652
521
|
Calcula la Descomposición de Varianza (punto y bandas) y, opcionalmente, grafica.
|
|
653
522
|
Mantiene la API y estructura de salida de ImpulseResponse y Forecast.
|
|
654
523
|
"""
|
|
655
524
|
if steps is None:
|
|
656
525
|
steps = self.steps
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
print("Calculating Variance Decomposition:\n")
|
|
526
|
+
if reps is None:
|
|
527
|
+
reps = self.reps
|
|
661
528
|
|
|
662
529
|
self.bands = bands
|
|
663
530
|
|
|
664
531
|
if self.method != 'signs':
|
|
532
|
+
|
|
533
|
+
print(f"\nCalculating FEVD for {self.variables[shock-1]}:\n")
|
|
534
|
+
|
|
665
535
|
self.vd_det = forecast_error_variance_decomposition(self.B, self.F, steps=self.steps)
|
|
666
536
|
|
|
667
|
-
self.drawsVD = bootstrap_svar(self, typ='VD', shock=shock)
|
|
668
|
-
|
|
537
|
+
self.drawsVD = bootstrap_svar(self, typ='VD', shock=shock, reps=reps, steps=steps)
|
|
538
|
+
|
|
539
|
+
#self.vd_point
|
|
540
|
+
|
|
669
541
|
stats = compute_bands_from_draws(self.drawsVD, self.alpha)
|
|
670
|
-
|
|
542
|
+
|
|
543
|
+
self.vd_point = stats["point"]
|
|
671
544
|
self.vd_mean = stats["mean"]
|
|
672
545
|
self.vd_low = stats["low"]
|
|
673
546
|
self.vd_high = stats["high"]
|
|
@@ -679,6 +552,8 @@ class SVAR:
|
|
|
679
552
|
self.vd_99 = stats["q99_5"]
|
|
680
553
|
|
|
681
554
|
elif self.method == 'signs':
|
|
555
|
+
|
|
556
|
+
print(f"\nFEVD for {self.variables[shock-1]}\n")
|
|
682
557
|
|
|
683
558
|
# Deterministic FEVD for the median B matrix (self.B is already
|
|
684
559
|
# the median of the accepted set for 'signs'). This is what
|
|
@@ -727,11 +602,11 @@ class SVAR:
|
|
|
727
602
|
if show is True:
|
|
728
603
|
# Uses self.vd_det in both branches -- plot_variance_decomp_svar()
|
|
729
604
|
# doesn't need to know about self.method at all.
|
|
730
|
-
plot_variance_decomp_svar(self)
|
|
605
|
+
plot_variance_decomp_svar(self, shock)
|
|
731
606
|
|
|
732
607
|
if self.method != 'signs':
|
|
733
608
|
output = {
|
|
734
|
-
'point': self.
|
|
609
|
+
'point': self.vd_point,
|
|
735
610
|
'low': self.vd_low,
|
|
736
611
|
'high': self.vd_high,
|
|
737
612
|
2.5: self.vd_02,
|
|
@@ -801,8 +676,8 @@ class SVAR:
|
|
|
801
676
|
self.Forecast(bands=FCbands)
|
|
802
677
|
|
|
803
678
|
if showVD==True:
|
|
804
|
-
self.
|
|
805
|
-
|
|
679
|
+
for k in range(self.n_vars):
|
|
680
|
+
self.VarianceDecomp(k+1,show=show,bands=IRbands)
|
|
806
681
|
|
|
807
682
|
class SVEC:
|
|
808
683
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# plotting.py
|
|
2
1
|
from __future__ import annotations
|
|
3
2
|
|
|
4
3
|
import numpy as np
|
|
@@ -231,73 +230,68 @@ def plot_forecast_svar(model):
|
|
|
231
230
|
|
|
232
231
|
plt.tight_layout(rect=[0, 0, 1, 0.95])
|
|
233
232
|
|
|
234
|
-
def plot_variance_decomp_svar(model):
|
|
233
|
+
def plot_variance_decomp_svar(model, shock: int):
|
|
235
234
|
"""
|
|
236
|
-
Grafica la Descomposición de Varianza (FEVD) para
|
|
237
|
-
|
|
238
|
-
|
|
235
|
+
Grafica la Descomposición de Varianza (FEVD) para una sola variable de
|
|
236
|
+
un modelo SVAR: un stackplot con la contribución de cada shock
|
|
237
|
+
estructural a la varianza del error de pronóstico de esa variable, en
|
|
238
|
+
cada horizonte.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
model : objeto SVAR
|
|
243
|
+
Debe tener .variables, .shocks, .steps y .vd_det poblados (p.ej.
|
|
244
|
+
tras llamar model.VarianceDecomp()).
|
|
245
|
+
shock : int
|
|
246
|
+
Índice 1-based de la variable a graficar. Coincide directamente con
|
|
247
|
+
las llaves de model.vd_det (indexado 1..n_vars por variable).
|
|
239
248
|
"""
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
# Adjust layout dynamically based on the number of variables
|
|
243
|
-
cols = 1
|
|
244
|
-
rows = n_vars
|
|
245
|
-
|
|
246
|
-
fig, axes = plt.subplots(rows, cols, figsize=(8, 2+ 2 * rows), squeeze=False)
|
|
247
|
-
axes = axes.flatten()
|
|
248
|
-
|
|
249
|
+
var_name = model.variables[shock - 1]
|
|
250
|
+
|
|
249
251
|
horizon = np.arange(model.steps)
|
|
250
252
|
|
|
251
253
|
# Check for assigned shocks
|
|
252
254
|
if model.shocks is None:
|
|
253
255
|
model.shocks = model.variables
|
|
254
|
-
|
|
256
|
+
|
|
257
|
+
fig, ax = plt.subplots(figsize=(7, 4))
|
|
258
|
+
|
|
255
259
|
fig.suptitle(
|
|
256
|
-
"Forecast Error Variance Decomposition",
|
|
257
|
-
fontsize=
|
|
260
|
+
f"Forecast Error Variance Decomposition: {var_name}",
|
|
261
|
+
fontsize=13,
|
|
258
262
|
fontname="Times New Roman",
|
|
259
263
|
color="black"
|
|
260
264
|
)
|
|
261
|
-
|
|
265
|
+
|
|
262
266
|
# Generate a distinct color palette for the structural shocks
|
|
263
267
|
colors = plt.cm.tab10(np.linspace(0, 1, len(model.shocks)))
|
|
264
|
-
|
|
265
|
-
for
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
# Add a single, centralized legend for the entire figure
|
|
290
|
-
fig.legend(
|
|
291
|
-
loc='lower center',
|
|
292
|
-
ncol=len(model.shocks),
|
|
293
|
-
bbox_to_anchor=(0.5, -0.05),
|
|
268
|
+
|
|
269
|
+
# Extract the deterministic FEVD matrix for the target variable.
|
|
270
|
+
# model.vd_det[shock] is shape (steps, n_shocks); multiply by 100 and
|
|
271
|
+
# transpose (.T) so stackplot can read shape (n_shocks, steps).
|
|
272
|
+
y_data = (model.vd_det[shock] * 100).T
|
|
273
|
+
|
|
274
|
+
ax.stackplot(
|
|
275
|
+
horizon,
|
|
276
|
+
y_data,
|
|
277
|
+
labels=model.shocks,
|
|
278
|
+
colors=colors,
|
|
279
|
+
alpha=0.9
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
ax.set_ylim([0, 100])
|
|
283
|
+
ax.set_xlim([0, model.steps - 1])
|
|
284
|
+
ax.set_xlabel("Horizonte")
|
|
285
|
+
ax.set_ylabel("Varianza Explicada (%)")
|
|
286
|
+
ax.grid(True, axis='y', alpha=0.3)
|
|
287
|
+
|
|
288
|
+
ax.legend(
|
|
289
|
+
loc='lower center',
|
|
290
|
+
ncol=len(model.shocks),
|
|
291
|
+
bbox_to_anchor=(0.5, -0.45),
|
|
294
292
|
frameon=False,
|
|
295
293
|
fontsize=11
|
|
296
294
|
)
|
|
297
295
|
|
|
298
|
-
|
|
299
|
-
for k in range(i + 1, len(axes)):
|
|
300
|
-
axes[k].set_visible(False)
|
|
301
|
-
|
|
302
|
-
fig.tight_layout(rect=[0, 0.05, 1, 0.95])
|
|
296
|
+
fig.tight_layout()
|
|
303
297
|
plt.show()
|
|
@@ -26,10 +26,10 @@ class VARResults:
|
|
|
26
26
|
beta_std: np.ndarray
|
|
27
27
|
tstat: np.ndarray
|
|
28
28
|
pvalue: np.ndarray
|
|
29
|
-
F: np.ndarray
|
|
30
29
|
n_obs: int
|
|
31
30
|
n_vars: int
|
|
32
31
|
n_cols: int
|
|
32
|
+
n_params: int
|
|
33
33
|
|
|
34
34
|
def estimate_var(Y: np.ndarray, X: np.ndarray, R: np.ndarray) -> VARResults:
|
|
35
35
|
# 1. Basic Setup
|
|
@@ -98,10 +98,11 @@ def estimate_var(Y: np.ndarray, X: np.ndarray, R: np.ndarray) -> VARResults:
|
|
|
98
98
|
aic = 2.0 * n_params - 2.0 * ll
|
|
99
99
|
bic = n_params * np.log(n_obs) - 2.0 * ll
|
|
100
100
|
hqic = 2.0 * n_params * np.log(np.log(n_obs)) - 2.0 * ll
|
|
101
|
-
|
|
101
|
+
fpe = np.linalg.det(sigma_u) * ((n_obs + n_cols) / (n_obs - n_cols)) ** n_vars
|
|
102
|
+
|
|
102
103
|
return VARResults(
|
|
103
104
|
beta=beta, sigma_u=sigma_u, resid=resid, Y_hat=Y_hat,
|
|
104
105
|
ll=ll, aic=aic, bic=bic, hqic=hqic, det=np.linalg.det(sigma_u),
|
|
105
|
-
fpe=
|
|
106
|
-
|
|
106
|
+
fpe=fpe, beta_std=beta_std, tstat=tstat, pvalue=pvalue,
|
|
107
|
+
n_obs=n_obs, n_vars=n_vars, n_cols=n_cols, n_params=n_params
|
|
107
108
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: macroshock
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Powerful time series econometrics for Structural Vector Autoregressions (SVAR)
|
|
5
5
|
Author-email: Patricio Arias <patoariaslopez2@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -41,13 +41,13 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
## Main Features
|
|
43
43
|
|
|
44
|
-
* **Restricted VAR estimation** (Lütkepohl-style GLS), with support for:
|
|
44
|
+
* - **Restricted VAR estimation** (Lütkepohl-style GLS), with support for:
|
|
45
45
|
|
|
46
46
|
- Constant, trend and quadratic trend.
|
|
47
47
|
- Exogenous variables.
|
|
48
48
|
- Seasonal dummies (quarterly or monthly).
|
|
49
49
|
- Linear restrictions on the coefficients (`restrictions='zeros'` or `'custom zeros'`).
|
|
50
|
-
* **Four structural identification methods**:
|
|
50
|
+
* - **Four structural identification methods**:
|
|
51
51
|
|
|
52
52
|
|Method|Description|
|
|
53
53
|
|-|-|
|
|
@@ -56,12 +56,12 @@ Dynamic: license-file
|
|
|
56
56
|
|`signs`|Sign restrictions (orthogonal rotation sampling with acceptance/rejection)|
|
|
57
57
|
|`IV`|Instrumental variable identification (proxy-SVAR)|
|
|
58
58
|
|
|
59
|
-
* **Impulse-response functions (IRF)** with confidence bands.
|
|
60
|
-
* **Forecast error variance decomposition (FEVD)**.
|
|
61
|
-
* **Conditional forecasts** with confidence bands.
|
|
62
|
-
* **Bootstrap** (normal or *wild* resampling) for inference.
|
|
63
|
-
* **Built-in plots** for IRF, forecasts and FEVD, publication-ready.
|
|
64
|
-
* **Stability test** of the system (characteristic polynomial roots).
|
|
59
|
+
* - **Impulse-response functions (IRF)** with confidence bands.
|
|
60
|
+
* - **Forecast error variance decomposition (FEVD)**.
|
|
61
|
+
* - **Conditional forecasts** with confidence bands.
|
|
62
|
+
* - **Bootstrap** (normal or *wild* resampling) for inference.
|
|
63
|
+
* - **Built-in plots** for IRF, forecasts and FEVD, publication-ready.
|
|
64
|
+
* - **Stability test** of the system (characteristic polynomial roots).
|
|
65
65
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
@@ -96,7 +96,7 @@ model = SVAR(
|
|
|
96
96
|
horizon=8, # forecast horizon
|
|
97
97
|
past=12, # past periods to show in the forecast
|
|
98
98
|
alpha=32, # confidence interval level (32 -> 68% central band)
|
|
99
|
-
reps
|
|
99
|
+
reps=1000, # bootstrap replications
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
# Runs the complete pipeline: Y/X construction, VAR estimation,
|
|
@@ -116,13 +116,13 @@ model.S() # identifies the structural system (matrix B)
|
|
|
116
116
|
model.summary() # prints the estimation summary
|
|
117
117
|
|
|
118
118
|
# Impulse response to shock 1, with "pair" bands (low/high percentile)
|
|
119
|
-
ir = model.ImpulseResponse(
|
|
119
|
+
ir = model.ImpulseResponse(1, show=True, bands="pair")
|
|
120
120
|
|
|
121
121
|
# Forecast with "many" bands (multiple stacked confidence levels)
|
|
122
122
|
fc = model.Forecast(show=True, bands="many")
|
|
123
123
|
|
|
124
|
-
# Forecast error variance decomposition for
|
|
125
|
-
vd = model.VarianceDecomp(
|
|
124
|
+
# Forecast error variance decomposition for variable 1
|
|
125
|
+
vd = model.VarianceDecomp(1, show=True)
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Each of these methods returns a dictionary with the point estimate and confidence bands at different levels (`'point'`, `'low'`, `'high'`, `2.5`, `5`, `95`, `97.5`, `0.5`, `99.5`), indexed by variable name.
|
|
@@ -143,7 +143,7 @@ By default it uses the Cholesky decomposition. If `matrix\_short` is passed (a b
|
|
|
143
143
|
model = SVAR(data=df, variables=variables, lags=4, method="long")
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
By default it uses the Cholesky decomposition. If `matrix\
|
|
146
|
+
By default it uses the Cholesky decomposition. If `matrix\_long` is passed (a binary matrix of 1s and 0s), the `B` matrix satisfying the indicated zero restrictions is solved numerically.
|
|
147
147
|
|
|
148
148
|
### 3\. Sign restrictions
|
|
149
149
|
|
|
@@ -157,7 +157,7 @@ model = SVAR(
|
|
|
157
157
|
method="signs",
|
|
158
158
|
matrix\_signs=matrix\_signs,
|
|
159
159
|
steps\_signs=4, # horizons over which signs are checked
|
|
160
|
-
reps
|
|
160
|
+
reps=1000, # number of accepted B matrices
|
|
161
161
|
)
|
|
162
162
|
```
|
|
163
163
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|