macroshock 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.
macroshock/__init__.py ADDED
@@ -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
macroshock/data.py ADDED
@@ -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
+
macroshock/fc.py ADDED
@@ -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