macroshock 0.1.0__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.1.0/LICENSE +21 -0
- macroshock-0.1.0/PKG-INFO +37 -0
- macroshock-0.1.0/README.md +2 -0
- macroshock-0.1.0/pyproject.toml +19 -0
- macroshock-0.1.0/setup.cfg +4 -0
- macroshock-0.1.0/src/macroshock/__init__.py +1 -0
- macroshock-0.1.0/src/macroshock/bootstrap.py +259 -0
- macroshock-0.1.0/src/macroshock/data.py +111 -0
- macroshock-0.1.0/src/macroshock/fc.py +68 -0
- macroshock-0.1.0/src/macroshock/identification.py +315 -0
- macroshock-0.1.0/src/macroshock/irf.py +104 -0
- macroshock-0.1.0/src/macroshock/macroshock.py +788 -0
- macroshock-0.1.0/src/macroshock/plotting.py +275 -0
- macroshock-0.1.0/src/macroshock/stats.py +78 -0
- macroshock-0.1.0/src/macroshock/var.py +109 -0
- macroshock-0.1.0/src/macroshock.egg-info/PKG-INFO +37 -0
- macroshock-0.1.0/src/macroshock.egg-info/SOURCES.txt +18 -0
- macroshock-0.1.0/src/macroshock.egg-info/dependency_links.txt +1 -0
- macroshock-0.1.0/src/macroshock.egg-info/requires.txt +4 -0
- macroshock-0.1.0/src/macroshock.egg-info/top_level.txt +1 -0
macroshock-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patricio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: macroshock
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Author-email: Patricio Arias <patoariaslopez2@gmail.com>
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 Patricio
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: numpy>=1.24.0
|
|
31
|
+
Requires-Dist: pandas>=2.0.0
|
|
32
|
+
Requires-Dist: scipy>=1.10.0
|
|
33
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# Macro-Shock
|
|
37
|
+
Macro Shock is a library designed to solve Structural Vector Auto Regressive (SVAR) models.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "macroshock"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"numpy>=1.24.0",
|
|
11
|
+
"pandas>=2.0.0",
|
|
12
|
+
"scipy>=1.10.0",
|
|
13
|
+
"matplotlib>=3.7.0",
|
|
14
|
+
]
|
|
15
|
+
authors = [
|
|
16
|
+
{name = "Patricio Arias", email = "patoariaslopez2@gmail.com"},
|
|
17
|
+
]
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
license = { file = "LICENSE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .macroshock import SVAR
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# bootstrap.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from scipy.linalg import solve_triangular
|
|
7
|
+
from .var import estimate_var
|
|
8
|
+
from .fc import compute_forecast
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def bootstrap_svar(model, typ: str, shock: int = 1):
|
|
12
|
+
"""
|
|
13
|
+
Replica la lógica original de SVAR.Bootstrap, pero fuera de la clase.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
model : objeto tipo SVAR
|
|
18
|
+
Debe tener los mismos atributos que usaba el método Bootstrap original.
|
|
19
|
+
typ : {'IR', 'FC'}
|
|
20
|
+
Tipo de bootstrap: IRFs o Forecasts.
|
|
21
|
+
shock : int
|
|
22
|
+
Índice (1-based) del shock para IRFs.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
draws : dict[str, np.ndarray]
|
|
27
|
+
Diccionario {variable: draws} con shape:
|
|
28
|
+
- IR: (reps, steps)
|
|
29
|
+
- FC: (reps, past + horizon)
|
|
30
|
+
"""
|
|
31
|
+
# BEGIN BOOTSTRAP
|
|
32
|
+
b = 0
|
|
33
|
+
|
|
34
|
+
if typ == "IR":
|
|
35
|
+
draws = {var: np.zeros((model.reps, model.steps)) for var in model.variables}
|
|
36
|
+
elif typ == "FC":
|
|
37
|
+
draws = {
|
|
38
|
+
var: np.zeros((model.reps, model.past + model.horizon))
|
|
39
|
+
for var in model.variables
|
|
40
|
+
}
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError("typ must be 'IR' or 'FC'.")
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
# progreso
|
|
46
|
+
if b in list(
|
|
47
|
+
range(int(model.reps / 10) - 1, model.reps, int(model.reps / 10))
|
|
48
|
+
):
|
|
49
|
+
print(str(b + 1) + "/" + str(model.reps))
|
|
50
|
+
|
|
51
|
+
# ---------- tipo de remuestreo ----------
|
|
52
|
+
if model.resampling == 1: # normal
|
|
53
|
+
resampled_resid = model.resid.copy()
|
|
54
|
+
for i in range(model.n_obs):
|
|
55
|
+
rnd = np.random.randint(0, model.n_obs)
|
|
56
|
+
resampled_resid[i] = model.resid[rnd]
|
|
57
|
+
elif model.resampling == 2: # wild
|
|
58
|
+
rsigns = np.random.choice([-1, 1], size=model.resid.shape[0])
|
|
59
|
+
# Nota: igual que el código original, esto modifica model.resid en sitio
|
|
60
|
+
resampled_resid = model.resid
|
|
61
|
+
for i in range(model.resid.shape[0]):
|
|
62
|
+
resampled_resid[i, :] = model.resid[i, :] * rsigns[i]
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("resampling must be 1 (normal) or 2 (wild).")
|
|
65
|
+
|
|
66
|
+
# ---------- condición inicial para recursión hacia adelante ----------
|
|
67
|
+
Y_sim = model.Y.copy()
|
|
68
|
+
Y_0 = np.zeros((model.n_vars * model.lags,))
|
|
69
|
+
|
|
70
|
+
for i in range(model.lags):
|
|
71
|
+
Y_0[i * model.n_vars : (i + 1) * model.n_vars] = model.data[
|
|
72
|
+
model.lags - i - 1, :
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
# ---------- generar datos simulados ----------
|
|
76
|
+
for i in range(model.n_obs):
|
|
77
|
+
u = np.zeros((model.n_vars * model.lags,))
|
|
78
|
+
u[0 : model.n_vars] = resampled_resid[i]
|
|
79
|
+
|
|
80
|
+
if model.const == 0:
|
|
81
|
+
FY_0 = model.F @ Y_0
|
|
82
|
+
Y_0 = FY_0 + u
|
|
83
|
+
elif model.const == 1:
|
|
84
|
+
beta_const = np.zeros((model.n_vars * model.lags,))
|
|
85
|
+
beta_const[0 : model.n_vars] = model.beta[:, 0]
|
|
86
|
+
FY_0 = model.F @ Y_0
|
|
87
|
+
Y_0 = beta_const + FY_0 + u
|
|
88
|
+
elif model.const == 2:
|
|
89
|
+
beta_const = np.zeros((model.n_vars * model.lags,))
|
|
90
|
+
beta_const[0 : model.n_vars] = model.beta[:, 0]
|
|
91
|
+
beta_trend = np.zeros((model.n_vars * model.lags,))
|
|
92
|
+
beta_trend[0 : model.n_vars] = model.beta[:, 1]
|
|
93
|
+
FY_0 = model.F @ Y_0
|
|
94
|
+
Y_0 = beta_const + ((i + model.lags) * beta_trend) + FY_0 + u
|
|
95
|
+
elif model.const == 3:
|
|
96
|
+
beta_const = np.zeros((model.n_vars * model.lags,))
|
|
97
|
+
beta_const[0 : model.n_vars] = model.beta[:, 0]
|
|
98
|
+
beta_trend = np.zeros((model.n_vars * model.lags,))
|
|
99
|
+
beta_trend[0 : model.n_vars] = model.beta[:, 1]
|
|
100
|
+
beta_sqrd = np.zeros((model.n_vars * model.lags,))
|
|
101
|
+
beta_sqrd[0 : model.n_vars] = model.beta[:, 2]
|
|
102
|
+
FY_0 = model.F @ Y_0
|
|
103
|
+
Y_0 = (
|
|
104
|
+
beta_const
|
|
105
|
+
+ (i * beta_trend)
|
|
106
|
+
+ ((i**2) * beta_sqrd)
|
|
107
|
+
+ FY_0
|
|
108
|
+
+ u
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if model.DUM is not None:
|
|
112
|
+
Y_0[0 : model.n_vars] = Y_0[0 : model.n_vars] + model.beta[
|
|
113
|
+
:,
|
|
114
|
+
model.const
|
|
115
|
+
+ (model.n_vars * model.lags)
|
|
116
|
+
+ model.n_ex : model.const
|
|
117
|
+
+ (model.n_vars * model.lags)
|
|
118
|
+
+ model.n_ex
|
|
119
|
+
+ model.n_dum,
|
|
120
|
+
] @ model.D[i, :]
|
|
121
|
+
|
|
122
|
+
Y_sim[i] = Y_0[0 : model.n_vars]
|
|
123
|
+
|
|
124
|
+
Y_sim = np.vstack([model.data[0 : model.lags, :], Y_sim])
|
|
125
|
+
|
|
126
|
+
# ---------- remuestreo del instrumento (método IV) ----------
|
|
127
|
+
if model.method == "IV":
|
|
128
|
+
iv = model.z
|
|
129
|
+
if model.resampling == 2 and model.iv is not None:
|
|
130
|
+
for i in range(model.n_iv):
|
|
131
|
+
for j in range(len(iv[i])):
|
|
132
|
+
iv[i][j] = iv[i][j] * rsigns[-len(iv[i]) :][j]
|
|
133
|
+
|
|
134
|
+
# ---------- construir Ysim y Xsim ----------
|
|
135
|
+
Ysim = Y_sim[model.lags :]
|
|
136
|
+
Xsim_list = []
|
|
137
|
+
|
|
138
|
+
for i in range(model.lags):
|
|
139
|
+
Xsim_list.append(
|
|
140
|
+
Y_sim[(model.lags - i - 1) : model.n_obs + model.lags - i - 1]
|
|
141
|
+
)
|
|
142
|
+
Xsim = np.hstack(Xsim_list)
|
|
143
|
+
|
|
144
|
+
if model.const == 1:
|
|
145
|
+
Xsim = np.hstack([np.ones((model.n_obs, 1)), Xsim])
|
|
146
|
+
elif model.const == 2:
|
|
147
|
+
trend = np.arange(1, model.n_obs + 1).reshape(-1, 1)
|
|
148
|
+
Xsim = np.hstack([np.ones((model.n_obs - model.lags, 1)), trend, Xsim])
|
|
149
|
+
elif model.const == 3:
|
|
150
|
+
trend = np.arange(1, model.n_obs + 1).reshape(-1, 1)
|
|
151
|
+
Xsim = np.hstack(
|
|
152
|
+
[np.ones((model.n_obs, 1)), trend, trend**2, Xsim]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if model.EXOGdata is not None:
|
|
156
|
+
if len(model.EXOG) == model.n_obs:
|
|
157
|
+
Xsim_exog = model.EXOGdata
|
|
158
|
+
else:
|
|
159
|
+
Xsim_exog = model.EXOGdata[-model.n_obs :]
|
|
160
|
+
if len(Xsim_exog) != model.n_obs:
|
|
161
|
+
print("Exogenous variables in different domain")
|
|
162
|
+
Xsim = np.hstack([Xsim, Xsim_exog])
|
|
163
|
+
|
|
164
|
+
if model.DUM is not None:
|
|
165
|
+
Xsim = np.hstack([Xsim, model.D[: model.n_obs, :]])
|
|
166
|
+
|
|
167
|
+
# ---------- asegurar shapes ----------
|
|
168
|
+
if isinstance(Ysim, pd.Series):
|
|
169
|
+
Ysim = Ysim.values.reshape(-1, 1)
|
|
170
|
+
if isinstance(Xsim, pd.Series):
|
|
171
|
+
Xsim = Xsim.values.reshape(-1, 1)
|
|
172
|
+
|
|
173
|
+
Ysim = Ysim.reshape(-1, 1) if Ysim.ndim == 1 else Ysim
|
|
174
|
+
Xsim = Xsim.reshape(-1, 1) if Xsim.ndim == 1 else Xsim
|
|
175
|
+
|
|
176
|
+
Xsim = np.array(Xsim, dtype=float)
|
|
177
|
+
Ysim = np.array(Ysim, dtype=float)
|
|
178
|
+
|
|
179
|
+
# ---------- estimar VAR simulado ----------
|
|
180
|
+
simres=estimate_var(Ysim, Xsim, model.R)
|
|
181
|
+
|
|
182
|
+
XXsim = Xsim.T @ Xsim
|
|
183
|
+
XXsim_inv = np.linalg.inv(XXsim)
|
|
184
|
+
betasim = simres.beta
|
|
185
|
+
sigma_usim = simres.sigma_u
|
|
186
|
+
residsim = simres.resid
|
|
187
|
+
|
|
188
|
+
beta_stdsim = np.zeros((model.n_vars, model.n_cols))
|
|
189
|
+
for i in range(model.n_vars):
|
|
190
|
+
beta_stdsim[i, :] = np.sqrt(
|
|
191
|
+
np.diagonal(sigma_usim[i, i] * XXsim_inv)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# ---------- construir Fsim ----------
|
|
195
|
+
topsim = betasim[:, -(model.n_vars * model.lags) :]
|
|
196
|
+
bottomsim = np.zeros(
|
|
197
|
+
((model.lags - 1) * model.n_vars, model.lags * model.n_vars)
|
|
198
|
+
)
|
|
199
|
+
for i in range((model.lags - 1) * model.n_vars):
|
|
200
|
+
bottomsim[i, i] = 1
|
|
201
|
+
Fsim = np.vstack([topsim, bottomsim])
|
|
202
|
+
|
|
203
|
+
# ---------- identificar Bsim ----------
|
|
204
|
+
if model.method == "short":
|
|
205
|
+
Bsim = np.linalg.cholesky(sigma_usim)
|
|
206
|
+
|
|
207
|
+
elif model.method == "long":
|
|
208
|
+
M1 = np.eye(model.n_vars)
|
|
209
|
+
for k in range(model.lags):
|
|
210
|
+
M1 = M1 - Fsim[
|
|
211
|
+
0 : model.n_vars, k * model.n_vars : (k + 1) * model.n_vars
|
|
212
|
+
]
|
|
213
|
+
M1 = np.linalg.inv(M1)
|
|
214
|
+
D = np.linalg.cholesky(M1 @ sigma_usim @ M1.T)
|
|
215
|
+
Bsim = np.linalg.solve(M1, D)
|
|
216
|
+
|
|
217
|
+
elif model.method == "IV":
|
|
218
|
+
usim = residsim
|
|
219
|
+
for j in range(model.n_iv):
|
|
220
|
+
XX_invfssim = (np.var(iv[j])) ** (-1)
|
|
221
|
+
betafssim = XX_invfssim * (iv[j].T @ usim[-len(iv[j]) :, j])
|
|
222
|
+
ujhatsim = iv[j] * betafssim
|
|
223
|
+
Bsim = np.zeros((model.n_vars, model.n_vars))
|
|
224
|
+
Bsim[j, j] = 1
|
|
225
|
+
for i in range(model.n_vars):
|
|
226
|
+
XX_invsssim = (np.var(ujhatsim)) ** (-1)
|
|
227
|
+
if i != j:
|
|
228
|
+
betasssim = XX_invsssim * (
|
|
229
|
+
ujhatsim.T.T @ usim[-len(iv[j]) :, i]
|
|
230
|
+
)
|
|
231
|
+
Bsim[i, j] = betasssim
|
|
232
|
+
Csim = np.linalg.cholesky(sigma_usim)
|
|
233
|
+
q = solve_triangular(Csim, Bsim[:, j], lower=True)
|
|
234
|
+
vsim = np.linalg.norm(q)
|
|
235
|
+
# vsim = np.linalg.norm(Csim[:,j])
|
|
236
|
+
Bsim[:, j] = Bsim[:, j] / vsim
|
|
237
|
+
|
|
238
|
+
# ---------- almacenar draws ----------
|
|
239
|
+
if typ == "IR":
|
|
240
|
+
ir_b = model.IR(Bsim, Fsim, shock)
|
|
241
|
+
|
|
242
|
+
if np.any(Bsim != 0):
|
|
243
|
+
if model.method in ("short", "long", "IV"):
|
|
244
|
+
for k in range(model.n_vars):
|
|
245
|
+
draws[model.variables[k]][b, :] = ir_b[:, k]
|
|
246
|
+
b += 1
|
|
247
|
+
|
|
248
|
+
if typ == "FC":
|
|
249
|
+
fc_b = compute_forecast(model, Fsim)
|
|
250
|
+
if np.any(Bsim != 0):
|
|
251
|
+
if model.method in ("short", "long", "IV"):
|
|
252
|
+
for k in range(model.n_vars):
|
|
253
|
+
draws[model.variables[k]][b, :] = fc_b[:, k]
|
|
254
|
+
b += 1
|
|
255
|
+
|
|
256
|
+
if b >= model.reps:
|
|
257
|
+
break
|
|
258
|
+
|
|
259
|
+
return draws
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#data.py
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class YXR:
|
|
9
|
+
Y: np.ndarray
|
|
10
|
+
X: np.ndarray
|
|
11
|
+
R: np.ndarray
|
|
12
|
+
|
|
13
|
+
def YX(model) -> YXR:
|
|
14
|
+
|
|
15
|
+
model.n_obs, _ = model.data.shape
|
|
16
|
+
|
|
17
|
+
Y = model.data[model.lags:,:]
|
|
18
|
+
X = []
|
|
19
|
+
|
|
20
|
+
for i in range(model.lags):
|
|
21
|
+
X.append(model.data[(model.lags-i-1) : model.n_obs - model.lags + (model.lags-i-1),:])
|
|
22
|
+
X = np.hstack(X)
|
|
23
|
+
|
|
24
|
+
if model.const == 1:
|
|
25
|
+
X = np.hstack([np.ones((model.n_obs - model.lags, 1)), X])
|
|
26
|
+
elif model.const == 2:
|
|
27
|
+
trend = np.arange(1, model.n_obs - model.lags + 1).reshape(-1, 1)
|
|
28
|
+
X = np.hstack([np.ones((model.n_obs - model.lags, 1)), trend, X])
|
|
29
|
+
elif model.const == 3:
|
|
30
|
+
trend = np.arange(1, model.n_obs - model.lags + 1).reshape(-1, 1)
|
|
31
|
+
X = np.hstack([np.ones((model.n_obs - model.lags, 1)), trend, trend**2, X])
|
|
32
|
+
|
|
33
|
+
model.n_obs, _ = X.shape
|
|
34
|
+
|
|
35
|
+
if model.EXOGdata is not None:
|
|
36
|
+
if model.n_obsex==model.n_obs:
|
|
37
|
+
X_exog=model.EXOGdata
|
|
38
|
+
else:
|
|
39
|
+
X_exog=model.EXOGdata[-model.n_obs:]
|
|
40
|
+
if len(X_exog)!=model.n_obs:
|
|
41
|
+
print('Exogenous variables in different domain')
|
|
42
|
+
X=np.hstack([X,X_exog])
|
|
43
|
+
|
|
44
|
+
if model.DUM is not None:
|
|
45
|
+
if model.DUM=='Q':
|
|
46
|
+
model.dummies=['Q'+f"{i+1}" for i in range(4)]
|
|
47
|
+
model.n_dum=3
|
|
48
|
+
model.D=np.zeros((model.n_obs+model.horizon,4))
|
|
49
|
+
for j in range(model.n_obs+model.horizon):
|
|
50
|
+
for x in range(4):
|
|
51
|
+
if j in range(x,model.n_obs+model.horizon,4):
|
|
52
|
+
model.D[j,x]=1
|
|
53
|
+
if model.DUM=='M':
|
|
54
|
+
model.dummies=['M'+f"{i+1}" for i in range(12)]
|
|
55
|
+
model.n_dum=11
|
|
56
|
+
model.D=np.zeros((model.n_obs+model.horizon,12))
|
|
57
|
+
for j in range(model.n_obs+model.horizon):
|
|
58
|
+
for x in range(12):
|
|
59
|
+
if j in range(x,model.n_obs+model.horizon,12):
|
|
60
|
+
model.D[j,x]=1
|
|
61
|
+
model.D=model.D[:,:model.n_dum]
|
|
62
|
+
model.Dhorizon=model.D[model.n_obs:model.n_obs+model.horizon,:]
|
|
63
|
+
X=np.hstack([X,model.D[0:model.n_obs,:]])
|
|
64
|
+
else:
|
|
65
|
+
model.n_dum=0
|
|
66
|
+
|
|
67
|
+
model.Y=Y
|
|
68
|
+
model.X=X
|
|
69
|
+
|
|
70
|
+
if isinstance(model.Y, pd.Series):
|
|
71
|
+
model.Y=model.Y.values.reshape(-1, 1)
|
|
72
|
+
if isinstance(model.X, pd.Series):
|
|
73
|
+
model.X=model.X.values.reshape(-1, 1)
|
|
74
|
+
|
|
75
|
+
model.Y = model.Y.reshape(-1, 1) if model.Y.ndim == 1 else model.Y
|
|
76
|
+
model.X = model.X.reshape(-1, 1) if model.X.ndim == 1 else model.X
|
|
77
|
+
|
|
78
|
+
_ , model.n_vars=model.Y.shape
|
|
79
|
+
model.n_obs, model.n_cols= model.X.shape
|
|
80
|
+
|
|
81
|
+
model.X = np.array(model.X, dtype=float)
|
|
82
|
+
model.Y = np.array(model.Y, dtype=float)
|
|
83
|
+
|
|
84
|
+
if model.restrictions is None:
|
|
85
|
+
model.R=np.eye(model.n_cols*model.n_vars)
|
|
86
|
+
|
|
87
|
+
elif model.restrictions=='zeros':
|
|
88
|
+
RR=np.eye(model.n_cols*model.n_vars)
|
|
89
|
+
drop=[]
|
|
90
|
+
|
|
91
|
+
for i in range(model.n_vars):
|
|
92
|
+
for j in range(model.n_vars):
|
|
93
|
+
if model.R[i][j]==0:
|
|
94
|
+
for k in range(model.lags):
|
|
95
|
+
drop.append(model.const*model.n_vars+i+(model.n_vars*j)+(k*model.n_vars*model.n_vars))
|
|
96
|
+
|
|
97
|
+
model.R=np.delete(RR, drop, axis=1)
|
|
98
|
+
|
|
99
|
+
elif model.restrictions=='custom zeros':
|
|
100
|
+
RR=model.R.ravel(order='F')
|
|
101
|
+
RRR=np.eye(len(RR))
|
|
102
|
+
drop=[]
|
|
103
|
+
|
|
104
|
+
for i in range(len(RR)):
|
|
105
|
+
if RR[i]==0:
|
|
106
|
+
drop.append(i)
|
|
107
|
+
|
|
108
|
+
model.R=np.delete(RRR, drop, axis=1)
|
|
109
|
+
|
|
110
|
+
return YXR(Y=model.Y, X=model.X, R=model.R)
|
|
111
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#fc.py
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def compute_forecast(modelo, F):
|
|
5
|
+
|
|
6
|
+
# 1. Initialize State Vector (start)
|
|
7
|
+
start = np.zeros((modelo.n_vars * modelo.lags, 1))
|
|
8
|
+
for i in range(modelo.lags):
|
|
9
|
+
# Stacking lags: y_t, y_{t-1}, ...
|
|
10
|
+
start[i*modelo.n_vars : (i+1)*modelo.n_vars, :] = \
|
|
11
|
+
modelo.Y[modelo.n_obs - i - 1, :].reshape(-1, 1)
|
|
12
|
+
|
|
13
|
+
# 2. Initialize Forecast Array
|
|
14
|
+
Yfc = np.zeros((modelo.horizon + modelo.past, modelo.n_vars))
|
|
15
|
+
Yfc[0:modelo.past, :] = modelo.Y[-modelo.past:, :]
|
|
16
|
+
|
|
17
|
+
# 3. Pre-compute Deterministic Vectors (Optimization)
|
|
18
|
+
# Moving this outside the loop prevents creating new arrays 120 times
|
|
19
|
+
beta_const = np.zeros((modelo.n_vars * modelo.lags, 1))
|
|
20
|
+
beta_trend = np.zeros((modelo.n_vars * modelo.lags, 1))
|
|
21
|
+
beta_sqrd = np.zeros((modelo.n_vars * modelo.lags, 1))
|
|
22
|
+
|
|
23
|
+
if modelo.const >= 1:
|
|
24
|
+
beta_const[0:modelo.n_vars, 0] = modelo.beta[:, 0]
|
|
25
|
+
if modelo.const >= 2:
|
|
26
|
+
beta_trend[0:modelo.n_vars, 0] = modelo.beta[:, 1]
|
|
27
|
+
if modelo.const == 3:
|
|
28
|
+
beta_sqrd[0:modelo.n_vars, 0] = modelo.beta[:, 2]
|
|
29
|
+
|
|
30
|
+
# 4. Forecast Loop
|
|
31
|
+
for i in range(modelo.horizon):
|
|
32
|
+
|
|
33
|
+
# A. Transition Step
|
|
34
|
+
FYs = F @ start
|
|
35
|
+
|
|
36
|
+
# B. Add Deterministic Terms (Const, Trend, etc)
|
|
37
|
+
# Note: Ensure your trend logic (i + lags) matches your estimation definition
|
|
38
|
+
if modelo.const == 1:
|
|
39
|
+
FYs += beta_const
|
|
40
|
+
elif modelo.const == 2:
|
|
41
|
+
FYs += beta_const + ((i + modelo.lags) * beta_trend)
|
|
42
|
+
elif modelo.const == 3:
|
|
43
|
+
FYs += beta_const + (i * beta_trend) + ((i**2) * beta_sqrd)
|
|
44
|
+
|
|
45
|
+
# C. Add Dummies (if present)
|
|
46
|
+
if modelo.DUM is not None:
|
|
47
|
+
# Calculate dummy index offset for readability
|
|
48
|
+
idx_start = modelo.const + (modelo.lags * modelo.n_vars) + modelo.n_ex
|
|
49
|
+
|
|
50
|
+
if modelo.DUM == 'Q':
|
|
51
|
+
dummy_coefs = modelo.beta[:, idx_start : idx_start+3]
|
|
52
|
+
dummy_vals = modelo.Dhorizon[i, :].reshape(-1, 1)
|
|
53
|
+
FYs[0:modelo.n_vars, :] += dummy_coefs @ dummy_vals
|
|
54
|
+
|
|
55
|
+
elif modelo.DUM == 'M':
|
|
56
|
+
dummy_coefs = modelo.beta[:, idx_start : idx_start+11]
|
|
57
|
+
dummy_vals = modelo.Dhorizon[i, :].reshape(-1, 1)
|
|
58
|
+
FYs[0:modelo.n_vars, :] += dummy_coefs @ dummy_vals
|
|
59
|
+
|
|
60
|
+
# D. Store Forecast (THE FIX)
|
|
61
|
+
# We take the first 'n_vars' rows from the 'FYs' column vector
|
|
62
|
+
# and flatten them to fit into the Yfc row.
|
|
63
|
+
Yfc[modelo.past + i, :] = FYs[0:modelo.n_vars, 0]
|
|
64
|
+
|
|
65
|
+
# E. Update state for next iteration
|
|
66
|
+
start = FYs
|
|
67
|
+
|
|
68
|
+
return Yfc
|