""" Modelo Estocástico de Deuda Bruta del Gobierno Central de Chile (MED) Consejo Fiscal Autónomo de Chile (CFA) @autores: Código desarrollado por Pedro Dosque Código revisado por Gabriel A. Valenzuela Vicencio de la Gerencia de Estudios del CFA Actualizado el 15 de octubre de 2025 """ ############################################################################## ### Modelo Estocástico de deuda bruta del Gobierno Central de Chile ### ############################################################################## #%% Preparaciones previas ## Descargar librerías no existentes en el computador import subprocess import importlib import sys # chequear e instalar paquetes librerias_requeridas = ["os", "numpy", "pandas", "scipy", "matplotlib", "statsmodels", "warnings", "pickle"] def instalar_e_importar(libreria): try: importlib.import_module(libreria) except ImportError: print(f"Instalando {libreria}...") try: subprocess.check_call([sys.executable, "-m", "pip", "install", libreria]) except subprocess.CalledProcessError as e: print(f" Error al instalar {libreria}: {e}") else: print(f"{libreria} instalado correctamente.") for libreria in librerias_requeridas: instalar_e_importar(libreria) ## Importación de librerias y funciones específicas import os import warnings import pickle import numpy as np import pandas as pd import matplotlib.pyplot as plt import statsmodels.api as sm import statsmodels as sm2 from statsmodels.tsa.api import VAR from statsmodels.formula.api import ols from numpy import transpose from numpy.linalg import inv, slogdet from scipy.stats import skew, kurtosis from scipy.optimize import minimize from matplotlib.colors import to_rgba from openpyxl.styles import PatternFill, Font from openpyxl.utils import get_column_letter ## Cambiar directorio de trabajo os.chdir('C:/Users/Lenovo/OneDrive - Ministerio de Hacienda/Modelo de deuda estocástico con VAR') # Ubicación de fichero Excel con las bases de datos file = 'Datos/base_datos.xlsx' ## Seed para números aleatorios np.random.seed(111) #%% Parámetros del modelo trim_final = '2025-06' # Último mes de último trimestre usado en cálculos trimestrales (formato yyyy-mm). y_final = '2024-12' # Último mes de último año usado en cálculos anuales (formato yyyy-12). n_h = 20 # Trimestres a proyectar (4*años a estimar). n_b = 20000 # Número de simulaciones (replicaciones). prop = 0.362 # Proporción de deuda en moneda extranjera (a fin de último año disponible). prop_uf = 0.296 # Proporción de deuda en UF (a fin de último año disponible). dur_p = 9.9 # Duración promedio de los bonos en pesos chilenos últimos 10 años. dur_uf = 12.0 # Duración promedio de los bonos en UF últimos 10 años. dur_f = 12.7 # Duración promedio de los bonos en moneda extranjera últimos 10 años. j_d_0 = 0.0281 # Tasa de interés efectiva de deuda en moneda local al inicio de la proyección. j_e_0 = 0.0261 # Tasa de interés efectiva de deuda en moneda extranjera al inicio de la proyección. p_ref_t = 4.09 # Precio de referencia del cobre del año en curso. infl_usa0 = 0.031 # Proyección de inflación de EE.UU para el año en curso. infl_usa1 = 0.026 # Proyección de inflación de EE.UU para el año siguiente. infl_usa2 = 0.021 # Proyección de inflación de EE.UU para dos años más. infl_usa3 = 0.020 # Proyección de inflación de EE.UU para tres años más. infl_usaLR = 0.020 # Proyección de inflación de EE.UU para cuatro años más. bpmf = 0.04 # Balance Primario Máximo Factible. riesgo = 0.1 # Proporción máxima de veces que se puede superar el bpmf. # Valores de largo plazo (LP) para las variables en el VAR g_ss = ((0.02+1)**(1/4)-1)*100 # Crecimiento tendencial del PIB def_ss = ((0.03+1)**(1/4)-1)*100 # Deflactor del PIB en el LP e_ss = ((0.01+1)**(1/4)-1)*100 # Depreciación del TCN trimestral en el LP c_ss = 4.09/(1+infl_usa0) if float(trim_final[5:7]) in range(9, 12) else 4.09 # Precio del cobre en el LP i_ss = 5.6-3.0 # Tasa de interés local real en el LP iu_ss = 3.35-2.0+1.25 # Tasa de interés extranjera real en el LP (tasa USA a LP - meta inf. + spread) inf_ss = ((0.03+1)**(1/4)-1)*100 # Inflación trimestral según IPC en el LP # Opción de añadir directamente estimación de balance primario para el año en curso. usar_bp_0 = 0 # 1 para usar pb_0, 0 para no usarlo bp_0 = -0.005 # Opción de añadir Precio de referencia del cobre del próximo año (de haber). usar_pr_1 = 0 # 1 para usar p_ref_1, 0 para no usarlo p_ref_1 = 4.09 # Precio de referencia a usar si usar_pr_1 = 1 # Parámetro condicional (1: estimar VAR, 2: ocupar resultados guardados): optimizar = 1 archivo_resultado = 'result_var.pkl' # Percentiles para fancharts p1 = 5 p2 = 20 p3 = 30 p4 = 40 # Parámetros de colores para los gráficos (código Hex): color_azul_principal = '#1F3F6D' color_azul_secundario = '#2A5593' color_rojo_principal = '#920000' color_rojo_secundario = '#92172F' color_gris_principal = '#D9D9D9' color_gris_secundario = '#8AABB8' #%% Importación y preparación de bases de datos # Ignorar advertencias de openpyxl relacionadas con formato condicional warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl") ## Importación bases de datos trimestrales, anuales y de deuda del Modelo determinístico data_trim = pd.read_excel(file, sheet_name='trimestral', header=0, index_col='fecha') data_anual = pd.read_excel(file, sheet_name='anual', header=0, index_col='fecha') deuda_det = pd.read_excel(file, sheet_name='Deuda modelo deterministico', header=0, index_col='fecha')*100 ### Preparación de base de datos trimestral # Asegurarnos que el índice es frecuencia trimestral: data_trim.index = pd.to_datetime(data_trim.index) data_trim = data_trim.asfreq('QE') # Año calendario # Añadir variables en variación trimestral porcentual data2 = data_trim.pct_change(fill_method=None)*100 data2 = data2.drop(columns=['btu10', 'treasury_r10'], errors="ignore") data2 = data2.add_suffix('_chg') data2.rename(columns={'gdpr_cl_chg': 'g_real', 'gdpr_usa_chg': 'g_real_usa', 'ipc_cl_chg': 'inf_cl', 'tc_clp_usd_chg': 'dep_tcn', 'cpi_usa_chg': 'inf_usa'}, inplace=True) # Añadir estimación de tasa de interés real de deuda en moneda extranjera de Chile data_trim['i_usa10_mas_EMBI'] = data_trim['treasury_r10'] + data_trim['embi_cl']/100 # Añadir precio real del cobre ultimo_dic = data_trim[data_trim.index.month == 12].index.max() ipc_usa_base = data_trim.loc[ultimo_dic, 'cpi_usa'] data_trim['copper_r'] = data_trim['copper_nom']*ipc_usa_base/data_trim['cpi_usa'] # Unir bases y seleccionar periodo para preparación de base de datos data_trim=pd.concat([data_trim,data2], axis=1)['2003-03':trim_final] # Identificar si hay variables duplicadas duplicadas = data_trim.columns[data_trim.columns.duplicated()] if not duplicadas.empty: print("Variables duplicadas:", list(duplicadas)) ### Preparación de la base de datos anual # Asegurarnos que el índice es frecuencia anual: data_anual.index = pd.to_datetime(data_anual.index) data_anual = data_anual.asfreq('YE') # Año calendario ## Transformar y crear variables anuales # Variables básicas data_anual['g'] = data_anual['gdpr'].pct_change(fill_method=None) # Crecimiento real data_anual['inf_ipc'] = data_anual['ipc'].pct_change(fill_method=None)*100 # Inflación IPC data_anual['chg_c_n'] = data_anual['copper_n'].diff() # Var. precio cobre nominal data_anual['chg_p_ref'] = data_anual['copper_ref'].diff() # Var. precio de referencia # Cálculo de razones fiscales sobre PIB gdpn_m = data_anual['gdpn']*1000 # PIB nominal en millones for var in ['bp', 'bal', 'gint', 'orc', 'deuda']: data_anual[f'{var}_pib'] = data_anual[var]/gdpn_m data_anual.rename(columns = {'bp_pib': 'bp_pib', # Balance primario (% del PIB) 'bal_pib': 'b_pib', # Balance total (% del PIB) 'gint_pib': 'gi_pib', # Gastos por intereses (% del PIB) 'orc_pib': 'orc_pib', # ORC (% del PIB) 'deuda_pib': 'd_p'}, # Deuda (% del PIB) inplace = True) # Rezagos y potencias de la deuda bruta data_anual['d_p_lag'] = data_anual['d_p'].shift() # Rezago de la deuda (% del PIB) data_anual['d_p_lag_squared'] = data_anual['d_p_lag']**2 # Rezago de la deuda al cuadrado data_anual['d_p_lag_cubed'] = data_anual['d_p_lag']**3 # Rezago de la deuda al cubo # Fondos del Tesoro Público como porcentaje del PIB for fondo in ['fees', # FEES 'frp', # FRP 'oatp', # OATP 'oatp2', # OATP sin Fondo por la Educación 'ret_fees']: # Retiros del FEES data_anual[f'{fondo}_p'] = data_anual[fondo]*data_anual['tcn_a']/gdpn_m data_anual['deuda_neta'] = data_anual['d_p'] - (data_anual['fees_p'] + data_anual['frp_p'] + data_anual['oatp_p']) # Deuda neta (% PIB) # Variables dummy data_anual['crisis'] = (data_anual['g']<0).astype(int) # Dummy de crisis data_anual['regla_fiscal'] = (data_anual.index>='2001-12-01').astype(int) # Dummy de regla fiscal # Diferencia precio BML y referencial del cobre con Dummy de regla fiscal data_anual['brecha_cobre'] = (data_anual["copper_n"] - data_anual["copper_ref"]).fillna(0) # Calcular brecha de producto con filtro HP gap_aux = data_anual.loc['1980-12':y_final, ['gdpr']].copy() gap_aux['gdpr_cycle'], gap_aux['gdpr_trend'] = sm.tsa.filters.hpfilter(gap_aux, lamb=6.25) data_anual['gdpr_gap'] = gap_aux['gdpr']/gap_aux['gdpr_trend'] - 1 data_anual['brecha_pos'] = (data_anual['gdpr_gap']>0).astype(int) data_anual['brecha_pos_int'] = data_anual['gdpr_gap']*data_anual['brecha_pos'] # Brecha si es positiva data_anual['brecha_neg_int'] = data_anual['gdpr_gap']*(1 - data_anual['brecha_pos']) # Brecha si es negativa # Seleccionar periodo de análisis data_anual=data_anual['1990-12':y_final] # Detectar y reportar variables duplicadas duplicadas = data_anual.columns[data_anual.columns.duplicated()] if not duplicadas.empty: print("Variables duplicadas:", list(duplicadas)) ### Asegurarnos que el índice de deuda_det es frecuencia anual: deuda_det.index = pd.to_datetime(deuda_det.index) deuda_det = deuda_det.asfreq('YE') # Año calendario #%% Estimación VAR ### Gráfico de las variables usadas en VAR # variables a graficar data_plot = data_trim[['g_real', 'def_chile_chg', 'dep_tcn', 'copper_r', 'btu10', 'i_usa10_mas_EMBI', 'inf_cl']].copy() n_vars_plot = len(data_plot.columns) data_plot.rename(columns={'g_real': 'Crecimiento real', 'def_chile_chg': 'Variación deflactor del PIB', 'dep_tcn': 'Depreciación TCN', 'copper_r': 'Precio real del cobre', 'btu10': 'BTU a 10 años', 'i_usa10_mas_EMBI': 'US treasury a 10 años indexado + EMBI', 'inf_cl': 'Variación del IPC'}, inplace=True) # Gráficos año_base = pd.Timestamp(ultimo_dic).year fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(8,10), dpi=180) axes = axes.flatten() for i, ax in enumerate(axes[:len(data_plot.columns)]): data_aux=data_plot[data_plot.columns[i]] # selección de variable # Estilo del gráfico ax.plot(data_aux, color=color_azul_principal, linewidth=1.5, label=data_plot.columns[i]) ax.set_title(data_plot.columns[i], fontsize=12, fontweight='bold', fontfamily='Tw Cen MT') ax.spines[["top", "right"]].set_alpha(0) ax.tick_params(axis='both', direction='out', labelsize=8) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.axhline(y=0, color='black', alpha=0.3, linestyle='-', linewidth=0.5) ax.set_ylabel(f"US$ de {año_base}" if data_plot.columns[i]=='Precio real del cobre' else 'Porcentaje', fontsize=8, fontfamily='Tw Cen MT') # si hay menos variables que subplots, apagar los ejes sobrantes for j in range(n_vars_plot, len(axes)): axes[j].axis('off') plt.tight_layout(pad=2.0) plt.subplots_adjust(top=0.92) plt.suptitle("Variables económicas usadas en VAR para MED", fontsize=14, fontweight='bold', fontfamily='Tw Cen MT') plt.show() ### Estimación de VAR sin restricciones como base para la estimación restringida # Seleccionar variables a utilizar en VAR data_var = data_trim[['g_real', 'def_chile_chg', 'dep_tcn', 'copper_r', 'btu10', 'i_usa10_mas_EMBI', 'inf_cl']].copy() # Crear modelo VAR var_model = VAR(data_var) # Ajustar el modelo var_model_fitted = var_model.fit(maxlags=1, verbose=True, trend='c') ### Estimación de VAR con restricciones de largo plazo error reescalado ## Creación de restricciones de largo plazo: beta = R*gamma + r R = np.array([ [-g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss, 0, 0, 0, 0, 0, 0], # parámetro 1 [g ecuación : intercepto para g ] [0, -g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss, 0, 0, 0, 0, 0], # parámetro 2 [def ecuación : intercepto para def] [0, 0, -g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss, 0, 0, 0, 0], # parámetro 3 [e ecuación : intercepto para e ] [0, 0, 0, -g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss, 0, 0, 0], # parámetro 4 [c ecuación : intercepto para c ] [0, 0, 0, 0, -g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss, 0, 0], # parámetro 5 [i ecuación : intercepto para i ] [0, 0, 0, 0, 0, -g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss, 0], # parámetro 6 [iu ecuación : intercepto para iu ] [0, 0, 0, 0, 0, 0, -g_ss, 0, 0, 0, 0, 0, 0, -def_ss, 0, 0, 0, 0, 0, 0, -e_ss, 0, 0, 0, 0, 0, 0, -c_ss, 0, 0, 0, 0, 0, 0, -i_ss, 0, 0, 0, 0, 0, 0, -iu_ss, 0, 0, 0, 0, 0, 0, -inf_ss], # parámetro 7 [inf ecuación : intercepto para inf] [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 8 [g ecuación : lag para g ] [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 9 [def ecuación : lag para g ] [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 10 [e ecuación : lag para g ] [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 11 [c ecuación : lag para g ] [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 12 [i ecuación : lag para g ] [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 13 [iu ecuación : lag para g ] [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 14 [inf ecuación : lag para g ] [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 15 [g ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 16 [def ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 17 [e ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 18 [c ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 19 [i ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 20 [iu ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 21 [inf ecuación : lag para def] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 22 [g ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 23 [def ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 24 [e ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 25 [c ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 26 [i ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 27 [iu ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 28 [inf ecuación : lag para e ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 29 [g ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 30 [def ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 31 [e ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 32 [c ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 33 [i ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 34 [iu ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 35 [inf ecuación : lag para c ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 36 [g ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 37 [def ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 38 [e ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 39 [c ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 40 [i ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 41 [iu ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 42 [inf ecuación : lag para i ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 43 [g ecuación : lag para iu ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 44 [def ecuación : lag para iu ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 45 [e ecuación : lag para iu ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 46 [c ecuación : lag para iu ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 47 [i ecuación : lag para iu ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], # parámetro 48 [iu ecuación : lag para iu ] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], # parámetro 49 [inf ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], # parámetro 50 [g ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], # parámetro 51 [def ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], # parámetro 52 [e ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], # parámetro 53 [c ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], # parámetro 54 [i ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], # parámetro 55 [iu ecuación : lag para inf] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], # parámetro 56 [inf ecuación : lag para inf] ]) valores_ss = np.array([g_ss, def_ss, e_ss, c_ss, i_ss, iu_ss, inf_ss]) r = np.concatenate([valores_ss, np.zeros(49)]).reshape([56,1]) ## Configuración inicial y preparación datos n_v, TT = data_var.shape[1], len(data_var) # Número de variables en el VAR gamma_0 = var_model_fitted.params.to_numpy()[1:,:].flatten() # Initial Guess de coeficientes de variables endógenas vectorizados X = np.array(data_var[1:]) # Variables endógenas x = X.flatten().reshape(-1,1) # vec(X) Z = np.concatenate((np.ones([TT,1]), data_var), axis=1)[:-1].T # rezago de variables endógenas más constante (k+1 x TT-1) I_L = np.identity(n_v) I_T = np.identity(TT-1) z = x - (np.kron(Z.T, I_L))@r # Creamos parámetros iniciales de volatilidad por pandemia theta = np.array([4.0, 3.0, 2.0, 0.5]) # [s0, s1, s2, rho] initial_guess = np.concatenate([gamma_0, theta]) # Agregamos restricciones (bounds) a los parámetros de gamma y theta para optimización eps=1e-8 bounds=[(None, None)]*len(gamma_0) + [(eps, None),(eps, None),(eps, None),(0, 0.7)] ## Función para crear vector que escala variables según efecto de la pandemia def crear_escalar(data, s, rho): escalar = np.ones([len(data), 1]) # Creamos variable escalar (s) inicio_covid = data.index.get_loc('2020-06').start # Buscamos cuándo empieza la pandemia escalar[inicio_covid:inicio_covid+3] = s # Modificamos escalar tras inicio de covid distancia = np.arange(len(escalar)) - (inicio_covid + 2) # Efecto decae con el tiempo escalar[inicio_covid+3:] = np.array(1+(s[2,0]-1)*rho**np.maximum(distancia[inicio_covid+3:], 0)).reshape(-1,1) return escalar ## Función para estimar VAR con restricciones de LP y control por volatilidad de la pandemia def negative_ll_restricted_VAR(params): # Parámetros gamma = params[:len(gamma_0)].reshape(-1,1) theta = params[len(gamma_0):] s = theta[:-1].reshape(-1,1) rho = theta[-1] escalar = crear_escalar(data_var, s, rho) # Creamos vector para reescalar shocks de pandemia data_var_s = data_var/escalar # Calculamos variables reescaladas ## Estimamos errores # Preparación datos X_s = np.array(data_var_s[1:]) # Variables endógenas x_s = X_s.flatten().reshape(-1,1) # Vectorización: vec(X_s) Z_s = np.concatenate((np.ones([len(data_var_s),1]),data_var_s), axis=1)[:-1].T # Variables endógenas rezagadas Z_s_kron = np.kron(Z_s.T, I_L) z_s = x_s - Z_s_kron@r betas = inv(Z_s@Z_s.T)@(Z_s@X_s) # Estimador MCO # Estimación errores u = z_s - Z_s_kron@(R@gamma) U = X_s - Z_s.T@betas sigma2 = (U.T@U)/(len(data_var)-1) # Varianza-covarianza de los residuos # Función LL nll = -n_v*np.sum(np.log(escalar[1:])) - 0.5*len(data_var)*slogdet(sigma2)[1] - (u.T@np.kron(I_T, inv(sigma2))@u)[0,0] return -nll # Retorna el negativo del log-likelihood (para maximizar en vez de minimizar) ## Realizamos maximización if optimizar == 1: print("Estimando modelo VAR con restricciones de Largo Plazo...") result=minimize(negative_ll_restricted_VAR, initial_guess, method='L-BFGS-B', bounds=bounds) # Guardar resultados with open(archivo_resultado, "wb") as f: pickle.dump(result, f) elif optimizar == 2: if os.path.exists(archivo_resultado): with open(archivo_resultado, 'rb') as f: result = pickle.load(f) print(f"Resultado cargado desde archivo: {archivo_resultado}") else: raise FileNotFoundError(f"No se encontró el archivo {archivo_resultado}. Correr con optimizar = 1 primero.") else: raise ValueError("La variable 'optimizar' debe ser 1 (para estimar) o 2 (para cargar resultado).") # Extraer los parámetros estimados gamma_tilda = result.x[:len(gamma_0)] theta = result.x[len(gamma_0):] s_est, rho_est = theta[:-1].reshape(-1,1), theta[-1] ## Extraer coeficientes de VAR necesarios para hacer simulaciones beta_tilda = R@(gamma_tilda.reshape(-1,1)) + r B_tilda = beta_tilda.reshape([n_v+1,n_v]) escalar = crear_escalar(data_var, s_est, rho_est) # Escalamiento por efectos de pandemia data_var_s = data_var/escalar # variables ajustadas por escalar X_s = np.array(data_var_s[1:]) # Variables endógenas Z_s = np.concatenate((np.ones([len(data_var_s),1]), data_var_s), axis=1)[:-1] # lag de variables endógenas U = (X_s - Z_s@B_tilda) # residuos sigma_tilda = (U.T@U)/(len(data_var) - 1) # Matriz de varianzas y covarianzas cons = B_tilda[0, :] # Constante de cada ecuación del VAR alphas = B_tilda[1:, :].T # coeficientes por ecuación del VAR (forma más natural para simulaciones) sigma2 = sigma_tilda # Matriz de covarianzas de errores (alias para matriz de varianzas) ## Gráfico de residuos. # Diccionario de nombres descriptivos nombres = { 'g_real': 'Crecimiento real', 'def_chile_chg': 'Variación deflactor del PIB', 'dep_tcn': 'Depreciación TCN', 'copper_r': 'Precio real del cobre', 'btu10': 'BTU a 10 años', 'i_usa10_mas_EMBI': 'US treasury a 10 años indexado + EMBI', 'inf_cl': 'Variación del IPC'} # Obtener residuos de VAR original residuos = var_model_fitted.resid # Crear gráfico fig, ax = plt.subplots(dpi=180) color_base = np.array(to_rgba(color_azul_secundario)) n_vars = U.shape[1] # Graficar residuos con gradiente de color for i in range(n_vars): color = color_base.copy() factor = 1 - i/(1.5*n_vars) color[:2] = np.minimum(color[:2]+0.2*i/n_vars, 1.0) color[3] = factor # opacidad ax.plot(residuos.index, U[:,i], linewidth=1.5, label=nombres.get(residuos.columns[i], residuos.columns[i]), color=color) # Formato gráfico ax.set_title("Residuos del modelo VAR", fontsize=14, fontweight='bold', fontfamily='Tw Cen MT') ax.set_yticks(ax.get_yticks()) ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction='out') ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) ax.legend(loc='upper right', fontsize=8, frameon=False) plt.tight_layout() plt.show() #%% Simulación de shocks y variables económicas del Modelo VAR ## Simulación de shocks con distribución normal shocks_var = np.random.multivariate_normal(mean=np.zeros(n_v), cov=sigma2, size=(n_b,n_h)) shocks_var = np.transpose(shocks_var, (1,2,0)) # Reordenar a (n_h, n_v, n_b) ## Proyectar variables del VAR con shocks n_b veces tray=np.empty([n_h+1, n_v, n_b]) # Creamos matriz para guardar trayectorias tray[0, :, :] = data_var.iloc[-1, :].to_numpy()[:, None] # Ocupamos como punto de partida la última observación de la muestra histórica # Agregamos de manera iterativa la proyección para cada trimestre siguiente for j in range(1, n_h+1): tray[j, :, :] = cons[:, None] + alphas@tray[j-1, :, :] + shocks_var[j-1, :, :] tray =tray[1:, :, :] # Removemos el primer periodo (observado) # Gráficos de fancharts de simulaciones de variables económicas # títulos para cada fanchart titles = {'g_real': 'Crecimiento real', 'def_chile_chg': 'Variación deflactor del PIB', 'dep_tcn': 'Depreciación TCN', 'copper_r': 'Precio real del cobre', 'btu10': 'BTU a 10 años', 'i_usa10_mas_EMBI': 'US Treasury a 10 años indexado + EMBI', 'inf_cl': 'Variación del IPC'} timestamps = data_var.index[-40:] trim_sim = pd.date_range(start=timestamps[-1], periods=n_h+1, freq='Q') for i, var in enumerate(data_var.columns): perc = np.percentile(tray[:,i,:], np.arange(10, 100, 10), axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ultimo_efectivo = data_var.iloc[-1,i] mediana_simulacion = np.concatenate([[ultimo_efectivo], perc[4]]) ax.plot(timestamps, data_var.iloc[-40:,i], color='black', linewidth=1.5, label="Datos históricos") ax.plot(trim_sim, mediana_simulacion, color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Áreas de fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): lower = np.concatenate([[ultimo_efectivo], perc[j]]) upper = np.concatenate([[ultimo_efectivo], perc[8-j]]) ax.fill_between(trim_sim, lower, upper, color=color_rojo_secundario, alpha=alpha, label=f"Rango P{10*(j + 1)} - P{90 - 10*j}") # Línea punteada vertical y texto ax.axvline(x=trim_sim[0], color='gray', linestyle='--', linewidth=1) ax.text(trim_sim[0], ax.get_ylim()[1]*0.95, "Proyecciones", rotation=90, fontsize=12, verticalalignment='top', color='gray', fontfamily='Tw Cen MT') ax.set_title(titles[var], fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Trimestres", fontsize=12, fontfamily='Tw Cen MT') ax.set_ylabel('US$ de 2023' if data_plot.columns[i]=='Precio real del cobre' else 'Porcentaje', fontsize=12, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction='out') ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.legend(loc="upper left", fontsize=10, frameon=False) plt.tight_layout() plt.show() ## Estimar valores anuales # Las tasas de interés ya están anualizadas, pero se ocupa el promedio anual de # los valores trimestrales. Se tiene que anualizar las tasas de variación # trimestral del PIB, deflactor, TCN y precio del cobre. # Añadimos a las trayectorias las observaciones de la base de datos que son del año en curso. trimestres_actual = int(data_trim.index[-1].month / 3) obs_actuales = data_var.tail(trimestres_actual).to_numpy() if trimestres_actual <= 3 else None tray_aux = np.empty([n_h+trimestres_actual, n_v, n_b]) # matriz extendida for i in range(0, n_b): if trimestres_actual <= 3 and obs_actuales is not None: tray_aux[:, :, i] = np.vstack([obs_actuales, tray[:,:,i]]) else: tray_aux[:n_h, :, i] = tray[:, :, i] # Para cuando no hay datos del año en curso. # Cálculo de trayectorias anuales (tasa de crecimiento o promedio) n_años = int(n_h/4) tray_anual = np.empty([int(n_h/4), n_v, n_b]) # Índices de diciembre de cada año proyectado diciembre_idx = np.arange(3, 3+4*n_años, 4) for i in range(n_v): if i<3 or i==6: # crecimiento compuesto for k in range(n_b): for t, j in enumerate(diciembre_idx): vals = tray_aux[j-3:j+1, i, k]/100 + 1 tray_anual[t, i, k] = 100*(np.prod(vals) - 1) else: # promedio simple for k in range(n_b): for t, j in enumerate(diciembre_idx): tray_anual[t, i, k] = tray_aux[j-3:j+1, i, k].mean() # Gráficos de fancharts de simulaciones de variables macroeconómicas en años año_inicio = data_var.index.year[-1:] + 1 if data_var.index.month[-1:]==12 else data_var.index.year[-1:] años_sim = np.arange(año_inicio[0], año_inicio[0]+int(n_h/4)) for i, var in enumerate(data_var.columns): perc = np.percentile(tray_anual[:,i,:], np.arange(10, 100, 10), axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años_sim, transpose(perc)[:,4], color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Áreas de fancharts for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): ax.fill_between(años_sim, perc[j], perc[8-j], color=color_rojo_secundario, alpha=alpha, label=f"Rango P{10*(j+1)} - P{90-10*j}") # Formato estilo ax.set_title(titles[var], fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_ylabel('US$ de 2023' if var=='copper_r' else 'Porcentaje', fontsize=12, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction='out') ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.legend(loc="lower left", fontsize=10, frameon=False) if (perc<0).any(): ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) plt.tight_layout() plt.show() #%% Función de reacción fiscal (FRF), simulación de otros rquerimientos de capital y activos ## Gráfico de variables usadas en FRF data_plot = data_anual[['bp_pib', 'd_p', 'brecha_cobre', 'gdpr_gap', 'crisis', 'regla_fiscal', 'brecha_pos', 'ret_fees_p']].copy() data_plot[['bp_pib', 'd_p']]*= 100 # Escalar a porcentaje del PIB data_plot.rename(columns={'bp_pib': 'Balance primnario', 'd_p': 'deuda bruta del GC', 'brecha_cobre': 'Diferencia precio del cobre y su referencial', 'gdpr_gap': 'Brecha del producto', 'crisis': 'Dummy de crisis económica', 'regla_fiscal': 'Dummy Regla fiscal en uso', 'brecha_pos': 'Brecha del producto positiva', 'ret_fees_p':'Retiros del FEES (% del PIB)' }, inplace=True) y_labels=['% del PIB','% del PIB','USc/lb','% del PIB','% desviación', '1 = crisis ecónomica','1 = regla fiscal en uso','1 = brecha positiva','% del PIB'] fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(10,12), dpi=180) axes = axes.flatten() for i, ax in enumerate(axes[:len(data_plot.columns)]): data_aux = data_plot[data_plot.columns[i]] ax.plot(data_aux, color=color_azul_principal, linewidth=1.5, label=data_plot.columns[i]) ax.set_title(data_plot.columns[i], fontsize=12, fontweight='bold', fontfamily='Tw Cen MT') ax.set_ylabel(y_labels[i], fontsize=9, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_alpha(0) ax.tick_params(axis='both', direction='out', labelsize=8) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') if (data_plot.iloc[:,i]<0).any(): ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) plt.tight_layout(pad=2.0) plt.subplots_adjust(top=0.92) plt.suptitle("Variables usadas en Función de Reacción Fiscal (FRF)",fontsize=14,fontweight='bold',fontfamily='Times New Roman') plt.show() ## Estimación AR(1) con términos exógenos y constante data_anual['ret_fees_p'] = data_anual['ret_fees_p'].fillna(0) frf = sm2.tsa.arima.model.ARIMA(data_anual['bp_pib'], exog=data_anual[['d_p_lag','d_p_lag_squared','d_p_lag_cubed', 'brecha_cobre','regla_fiscal', 'ret_fees_p', 'brecha_pos_int','brecha_neg_int','crisis']], order=(1,0,0), trend='c').fit(cov_type="robust", method_kwargs = {"maxiter": 500}) frf_params = frf.params # Guardamos resultados (parámetros obtenidos) # Análisis resultados print(frf.summary()) fig = frf.plot_diagnostics(figsize=(10,8), lags=10) plt.tight_layout() plt.show() ## Gráfico balance primario efectivo vs estimado dentro de la muestra # Calcular diferencias bp_estimado = frf.fittedvalues bp_efectivo = data_anual['bp_pib'].iloc[0:(1+len(bp_estimado))] diferencia = bp_efectivo.values - bp_estimado.values # Calcular ticks simétricos para ambos ejes tick_interval = 0.03 # Ajustar esto según necesidad tick_range = np.arange(-0.09, 0.10, tick_interval) # Por ejemplo, -9% a 9% del PIB. # Gráfico con eje secundario fig, ax1 = plt.subplots(dpi=180) # Eje primario: líneas efectivas y estimadas ax1.plot(bp_estimado.index, bp_estimado, color=color_rojo_principal, label='Estimado (ajustado)') ax1.plot(bp_efectivo.index, bp_efectivo, color=color_azul_principal, linestyle='--', label='Efectivo (observado)') ax1.set_title("Balance primario: efectivo vs estimado", fontsize=14, fontweight='bold', fontfamily='Tw Cen MT') ax1.set_xlabel("Año", fontsize=12, fontfamily='Tw Cen MT') # Estetica eje primario: ax1.set_ylabel("Balance primario (% del PIB)", fontsize=12, fontfamily='Tw Cen MT') ax1.set_ylim(-0.09, 0.09) ax1.set_yticks(tick_range) ax1.spines[['top', 'right']].set_visible(False) ax1.tick_params(axis='both', direction='out') ax1.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax1.tick_params(labelsize = 8) ax1.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) # Eje secundario: diferencia como barras ax2 = ax1.twinx() ax2.bar(bp_estimado.index, diferencia, width=250, color=color_gris_secundario, alpha=0.3, edgecolor='black', linewidth=0.5, label='Diferencia (efectivo - estimado)') ax2.set_ylabel("Diferencia (pp del PIB)", fontsize=12, fontfamily='Tw Cen MT') ax2.set_ylim(-0.09, 0.09) ax2.set_yticks(tick_range) ax2.tick_params(axis='y', labelsize=8) ax2.spines[['top', 'left']].set_visible(False) # Combinar leyendas handles1, labels1 = ax1.get_legend_handles_labels() handles2, labels2 = ax2.get_legend_handles_labels() ax1.legend(handles1 + handles2, labels1 + labels2, loc='lower left', fontsize=9, frameon=False) plt.tight_layout() plt.show() ## Proyección de shocks fiscales con distribución normal shocks_FRF = np.empty([int(n_h/4), 1, n_b]) # Creamos espacio para almacenar los shocks for j in range(0,n_b): # Para cada muestra shocks_FRF[:, 0, j] = np.random.normal(0, np.sqrt((1-frf_params['ar.L1']**2)*frf_params['sigma2']), int(n_h/4)) tray_anual = np.concatenate((tray_anual,shocks_FRF), axis=1) ### Otros requerimientos de capital # Modelamos como un AR(1) en los errores orc = sm2.tsa.arima.model.ARIMA(data_anual['orc_pib']['2008':y_final], order=(1,0,0), trend='c').fit() orc_params = orc.params # Guardamos parámetros necesarios para simulación orc_shock_sd = np.sqrt((1 - orc_params['ar.L1']**2)*orc_params['sigma2']) # desv. est. de shock a ORC # Simulamos trayectorias de orc para cada simulación sim_orc = np.empty([int(n_h/4), 1, n_b]) for i in range(0, n_b): sim_orc[0, 0, i] = orc_params['const'] + orc_params['ar.L1']*(data_anual['orc_pib'].iloc[-1] - orc_params['const']) + np.random.normal(0,orc_shock_sd) for j in range(1, int(n_h/4)): sim_orc[j, 0, i] = orc_params['const'] + orc_params['ar.L1']*(sim_orc[j-1,0,i] - orc_params['const']) + np.random.normal(0,orc_shock_sd) tray_anual = np.concatenate((tray_anual,sim_orc), axis=1) ## Otros Activos del Tesoro Público (OATP) # Modelamos los OATP como un modelo de corrección de errores data_oatp = data_anual.loc['2007-12':y_final, ['oatp_p']] # base de datos para la estimación data_oatp = data_oatp.assign(oatp_mod=np.mean(data_anual['oatp2_p'][-10:]) - data_oatp[['oatp_p']].shift(), # desvío repecto a media chg_oatp=data_oatp[['oatp_p']].diff()) # variación anual oatp = sm2.regression.linear_model.OLS(data_oatp.loc['2008-12':y_final,'chg_oatp'],data_oatp.loc['2008-12':y_final,'oatp_mod'], hasconst=False) oatp_result = oatp.fit() oatp_params = oatp_result.params # Guardamos parámetros necesarios para simulación print(oatp_result.summary()) # Simulación de shocks sim_shock_oatp = np.empty([int(n_h/4), 1, n_b]) for i in range(0, n_b): for j in range(0, int(n_h/4)): sim_shock_oatp[j, 0, i] = np.random.normal(0, np.sqrt(oatp_result.mse_resid)) #%% Proyección de deuda bruta y neta ##### Variables necesarias para estimar FRF y deuda ##### ### Brecha del precio del cobre ## Estimación del precio de referencia con OLS en rezagos de la variación anual de sí mismo data_copper = data_anual[['chg_c_n','chg_p_ref']].copy() data_copper.index = pd.to_datetime(data_copper.index) # aseguramos que index sea tipo datetime if usar_pr_1 == 0: # Agregamos variación del precio de referencia del año en curso data_copper.loc[pd.Timestamp(f"{data_copper.index[-1].year+1}-12-31")] = [np.nan, p_ref_t-data_anual['copper_ref'].iloc[-1]] else: # Si ya existe un precio de referencia para el próximo año también agregamos variación del próximo año data_copper.loc[pd.Timestamp(f"{data_copper.index[-1].year+1}-12-31")] = [np.nan, p_ref_t-data_anual['copper_ref'].iloc[-1]] data_copper.loc[pd.Timestamp(f"{data_copper.index[-1].year+2}-12-31")] = [np.nan, p_ref_1-p_ref_t] for lag in range(1, 5): # Creamos 4 rezagos data_copper[f'chg_c_n_l{lag}'] = data_copper['chg_c_n'].shift(lag) p_ref_ols = ols('chg_p_ref ~ chg_c_n_l1 + chg_c_n_l2 + chg_c_n_l3 + chg_c_n_l4', data=data_copper) p_ref_ols_result = p_ref_ols.fit() print(p_ref_ols_result.summary()) pref_params = p_ref_ols_result.params ## Gráfico precio de referencia efectivo vs estimado dentro de la muestra # Calcular diferencia entre observado y ajustado valores_ajustados = p_ref_ols_result.fittedvalues valores_efectivos = data_copper['chg_p_ref'].reindex(valores_ajustados.index) mask = valores_efectivos.notna() & valores_ajustados.notna() valores_ajustados = valores_ajustados[mask] valores_efectivos = valores_efectivos[mask] diferencia = valores_efectivos - valores_ajustados # Establecer límites y ticks comunes ymin = min(valores_efectivos.min(), valores_ajustados.min(), -abs(diferencia).max()) ymax = max(valores_efectivos.max(), valores_ajustados.max(), abs(diferencia).max()) ylim = max(abs(ymin), abs(ymax)) yticks = np.round(np.linspace(-ylim, ylim, num=9), 2) # Mismos valores, redondeados # Crear gráfico fig, ax1 = plt.subplots(dpi=180) # Curvas principales ax1.plot(valores_ajustados, color=color_rojo_principal, label='Estimado (ajustado)') ax1.plot(valores_efectivos, color=color_azul_principal, linestyle='--', label='Efectivo (observado)') ax1.set_title("Variación Precio de ref. del cobre: efectivo vs estimado", fontsize=14, fontweight='bold', fontfamily='Tw Cen MT', pad=15) ax1.set_xlabel("Año", fontsize=12, fontfamily='Tw Cen MT') ax1.set_ylabel("Variación en precio de referencia", fontsize=12, fontfamily='Tw Cen MT') ax1.set_ylim(-ylim, ylim) ax1.set_yticks(yticks) ax1.spines[['top', 'right']].set_visible(False) ax1.tick_params(axis='both', direction='out') ax1.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax1.tick_params(labelsize=8) ax1.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) # Eje secundario: diferencia como barras ax2 = ax1.twinx() ax2.bar(valores_ajustados.index, diferencia, width=250, color=color_gris_secundario, alpha=0.3, edgecolor='black', linewidth=0.5, label='Diferencia (efectivo - estimado)') ax2.set_ylabel("Diferencia", fontsize=12, fontfamily='Tw Cen MT') ax2.set_ylim(-ylim, ylim) ax2.set_yticks(yticks) ax2.tick_params(axis='y', labelsize=8) ax2.spines[['top', 'left']].set_visible(False) # Combinar leyendas handles1, labels1=ax1.get_legend_handles_labels() handles2, labels2=ax2.get_legend_handles_labels() ax1.legend(handles1+handles2, labels1+labels2, loc='lower left', fontsize=9, frameon=False) plt.tight_layout() plt.show() ## Simulación de brecha del precio del cobre sim_cobre = np.empty([int(n_h/4), 3, n_b]) for i in range(0, n_b): for j in range(0,int(n_h/4)): # Calcula precio nominal (0) y de referencia (1) if j == 0: sim_cobre[j, 0, i] = tray_anual[j,3,i]*(1+infl_usa0) sim_cobre[j, 1, i] = p_ref_t elif j == 1: sim_cobre[j, 0, i] = tray_anual[j,3,i]*(1+infl_usa0)*(1+infl_usa1) if usar_pr_1 == 1: sim_cobre[j, 1, i] = p_ref_1 elif usar_pr_1 == 0: sim_cobre[j, 1, i] = sim_cobre[j-1,1,i] + \ (pref_params['Intercept'] + \ pref_params['chg_c_n_l1']*(sim_cobre[j-1,0,i] - data_anual['copper_n'].iloc[-1]) + \ pref_params['chg_c_n_l2']*data_anual['chg_c_n'].iloc[-1] + \ pref_params['chg_c_n_l3']*data_anual['chg_c_n'].iloc[-2] + \ pref_params['chg_c_n_l4']*data_anual['chg_c_n'].iloc[-3]) elif j == 2: sim_cobre[j, 0, i] = tray_anual[j,3,i]*(1+infl_usa0)*(1+infl_usa1)*(1+infl_usa2) sim_cobre[j, 1, i] = sim_cobre[j-1,1,i] + \ (pref_params['Intercept'] + \ pref_params['chg_c_n_l1']*(sim_cobre[j-1,0,i] - sim_cobre[j-2,0,i]) + \ pref_params['chg_c_n_l2']*(sim_cobre[j-2,0,i] -data_anual['copper_n'].iloc[-1]) + \ pref_params['chg_c_n_l3']*data_anual['chg_c_n'].iloc[-1] + \ pref_params['chg_c_n_l4']*data_anual['chg_c_n'].iloc[-2]) elif j == 3: sim_cobre[j, 0, i] = tray_anual[j,3,i]*(1+infl_usa0)*(1+infl_usa1)*(1+infl_usa2)*(1+infl_usa3) sim_cobre[j, 1, i] = sim_cobre[j-1,1,i] + \ (pref_params['Intercept'] + \ pref_params['chg_c_n_l1']*(sim_cobre[j-1,0,i] - sim_cobre[j-2,0,i]) + \ pref_params['chg_c_n_l2']*(sim_cobre[j-2,0,i] - sim_cobre[j-3,0,i]) + \ pref_params['chg_c_n_l3']*(sim_cobre[j-3,0,i] - data_anual['copper_n'].iloc[-1]) + \ pref_params['chg_c_n_l4']*data_anual['chg_c_n'].iloc[-1]) elif j == 4: sim_cobre[j, 0, i] = tray_anual[j,3,i]*(1+infl_usa0)*(1+infl_usa1)*(1+infl_usa2)*(1+infl_usa3)*(1+infl_usaLR) sim_cobre[j, 1, i] = sim_cobre[j-1,1,i] + \ (pref_params['Intercept'] + \ pref_params['chg_c_n_l1']*(sim_cobre[j-1,0,i] - sim_cobre[j-2,0,i]) + \ pref_params['chg_c_n_l2']*(sim_cobre[j-2,0,i] - sim_cobre[j-3,0,i]) + \ pref_params['chg_c_n_l3']*(sim_cobre[j-3,0,i] - sim_cobre[j-4,0,i]) + \ pref_params['chg_c_n_l4']*(sim_cobre[j-4,0,i] - data_anual['copper_n'].iloc[-1])) else: sim_cobre[j, 0, i] = tray_anual[j,3,i]*(1+infl_usa0)*(1+infl_usa1)*(1+infl_usa2)*(1+infl_usa3)*(1+infl_usaLR)**(j-3) sim_cobre[j, 1, i] = sim_cobre[j-1,1,i] + \ (pref_params['Intercept'] + \ pref_params['chg_c_n_l1']*(sim_cobre[j-1,0,i] - sim_cobre[j-2,0,i]) + \ pref_params['chg_c_n_l2']*(sim_cobre[j-2,0,i] - sim_cobre[j-3,0,i]) + \ pref_params['chg_c_n_l3']*(sim_cobre[j-3,0,i] - sim_cobre[j-4,0,i]) + \ pref_params['chg_c_n_l4']*(sim_cobre[j-4,0,i] - sim_cobre[j-5,0,i])) # Calcula brecha del precio del cobre sim_cobre[j, 2, i] = sim_cobre[j,0,i] - sim_cobre[j,1,i] tray_anual = np.concatenate((tray_anual, sim_cobre[:,2:3,:]), axis=1) ## Gráfico de proyecciones de brecha del precio del cobre perc = np.percentile(tray_anual[:,9,:]*100, np.arange(10,100,10), axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años_sim, perc[4], color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Rellenar áreas de fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): ax.fill_between(años_sim, perc[j], perc[8-j], color=color_rojo_secundario, alpha=alpha, label=f"Rango P{10*(j+1)} - P{90-10*j}") ax.set_title("Brecha del precio del cobre ($USc/lb)", fontsize=16, fontweight='bold', fontfamily='Tw Cen MT',pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_xticks(años_sim) ax.set_ylabel("Centavos de dólar", fontsize=12, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction='out', labelsize=10) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) ax.legend(loc="lower left", fontsize=10, frameon=False) plt.tight_layout() plt.show() ### PIB real, brecha del PIB y crisis sim_pibr = np.empty([int(n_h/4), 6, n_b]) for i in range(0, n_b): # Calcular el PIB real proyectado sim_pibr[0, 0, i] = data_anual['gdpr'].iloc[-1]*(1+tray_anual[0,0,i]/100) for j in range(1, int(n_h/4)): sim_pibr[j, 0, i] = sim_pibr[j-1,0,i]*(1+tray_anual[j,0,i]/100) # Estimamos brecha del PIB con PIB tendencial según filtro HP serie_total = np.concatenate((data_anual['gdpr'].to_numpy(), sim_pibr[:,0,i]), axis=0) # Unimos muestra con simulación _, tendencia = sm.tsa.filters.hpfilter(serie_total, lamb=6.25) # Guardamos resultados sim_pibr[:, 1, i] = tendencia[-int(n_h/4):] # La tendencia de los años simulados sim_pibr[:, 2, i] = sim_pibr[:,0,i]/sim_pibr[:,1,i] - 1 # Brecha del PIB sim_pibr[:, 3, i] = np.where(sim_pibr[:,2,i] > 0, sim_pibr[:,2,i], 0) # Brechas si es positiva sim_pibr[:, 4, i] = np.where(sim_pibr[:,2,i] <= 0, sim_pibr[:,2,i], 0) # Brechas si es negativa for j in range(0, int(n_h/4)): sim_pibr[j, 5, i] = np.where(tray_anual[j,0,i] < 0, 1, 0) # Año con crisis o no tray_anual = np.concatenate((tray_anual, sim_pibr[:,3:6,:]), axis=1) # Gráfico de proyecciones de brecha del PIB perc = np.percentile(sim_pibr[:,2,:], np.arange(10,100,10), axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años_sim, perc[4], color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Rellenar áreas de fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): ax.fill_between(años_sim, perc[j], perc[8-j], color=color_rojo_secundario, alpha=alpha, label=f"Rango P{10*(j+1)} - P{90-10*j}") ax.set_title("Brecha del PIB (% de desviación)", fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_xticks(años_sim) ax.set_ylabel("Porcentaje", fontsize=12, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction='out') ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.tick_params(labelsize=8) ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) ax.legend(loc="lower left", fontsize=10, frameon=False) plt.tight_layout() plt.show() ### Tasas de interés inf_prom = np.mean(data_anual["inf_ipc"][-10:]) # Inflación promedio últimos 10 años j_uf_0 = j_d_0 - ((1-prop-prop_uf)/(1-prop))*inf_prom/100 # Tasa de interés UF efectiva del último año j_p_0 = j_d_0 + (prop_uf/(1-prop))*inf_prom/100 # Tasa de interés pesos efectiva del último año sim_tasas = np.empty([int(n_h/4),6,n_b]) for i in range(0, n_b): for j in range(0, int(n_h/4)): sim_tasas[j, 0, i] = np.where(tray_anual[j,4,i]+tray_anual[j,6,i]>0, tray_anual[j,4,i]+tray_anual[j,6,i], 0) # Tasa int. nom. en pesos chilenos sim_tasas[j, 1, i] = tray_anual[j, 4, i] # Tasa int. en UF sim_tasas[j ,2, i] = np.where(tray_anual[j,5,i]+tray_anual[j,6,i]>0, tray_anual[j,5,i]+tray_anual[j,6,i], 0) # Tasa int. nom. mon. extranjera if j == 0: sim_tasas[j, 3, i] = (1-1/dur_p)*j_p_0*100 + (1/dur_p)*sim_tasas[j,0,i] # Tasa de interés de la deuda en mon. local sim_tasas[j, 4, i] = (1-1/dur_uf)*j_uf_0*100 + (1/dur_uf)*sim_tasas[j,1,i] # Tasa de interés de la deuda en UF sim_tasas[j, 5, i] = (1-1/dur_f)*j_e_0*100 + (1/dur_f)*sim_tasas[j,2,i] # Tasa de interés de la deuda en mon. extranjera else: sim_tasas[j, 3, i] = (1-1/dur_p)*sim_tasas[j-1,3,i] + (1/dur_p)*sim_tasas[j,0,i] sim_tasas[j, 4, i] = (1-1/dur_uf)*sim_tasas[j-1,4,i] + (1/dur_uf)*sim_tasas[j,1,i] sim_tasas[j, 5, i] = (1-1/dur_f)*sim_tasas[j-1,5,i] + (1/dur_f)*sim_tasas[j,2,i] tray_anual = np.concatenate((tray_anual, sim_tasas[:,3:6,:]), axis=1) ##### Simulación de trayectorias de balance primario, deuda, activos ##### ##### y gastos por intereses ##### sim_bp_d = np.empty([int(n_h/4), 4, n_b]) # Para guardar resultados deuda, balance primario e intereses sim_act = np.empty([int(n_h/4), 10, n_b]) # Para guardar resultados activos for i in range(0, n_b): for j in range(0, int(n_h/4)): if j == 0: ### Simular activos ## FRP sim_act[j, 0, i] = max(0, min(data_anual['b_pib'].iloc[-1], 0.005)) # Aporte sim_act[j, 1, i] = 0.001 if data_anual['frp_p'].iloc[-1]>0.001 else data_anual['frp_p'].iloc[-1] # Retiro sim_act[j, 2, i] = data_anual['frp_p'].iloc[-1] + sim_act[j,0,i] - sim_act[j,1,i] # Saldo final ## FEES # Aporte if data_anual['b_pib'].iloc[-1] > max(0, min(data_anual['b_pib'].iloc[-1], 0.005)): sim_act[j, 3, i] = data_anual['b_pib'].iloc[-1] - max(0, min(data_anual['b_pib'].iloc[-1], 0.005)) \ if data_anual['fees_p'].iloc[-1] + data_anual['b_pib'].iloc[-1] - max(0, min(data_anual['b_pib'].iloc[-1], 0.005)) < 0.07 \ else 0.07 - data_anual['fees_p'].iloc[-1] else: sim_act[j, 3, i] = 0 sim_act[j, 4, i] = data_anual['fees_p'].iloc[-1] + sim_act[j,3,i] # Saldo final antes de retiros # Retiro if tray_anual[j, 12, i] == 1 and sim_pibr[j, 2, i] <= -0.03: sim_act[j, 5, i] = 2*tray_anual[j,0,i] if sim_act[j,4,i] >= -2*tray_anual[j,0,i] else -sim_act[j,4,i] else: sim_act[j,5,i] = 0 sim_act[j,6,i] = sim_act[j,4,i] + sim_act[j,5,i] # Saldo final ## OATP sim_act[j, 7, i] = data_anual['oatp_p'].iloc[-1] + oatp_params['oatp_mod']*(np.mean(data_anual['oatp2_p'][-10:]) - data_anual['oatp_p'].iloc[-1]) +\ sim_shock_oatp[j,0,i] sim_act[j, 7, i] = sim_act[j,7,i] if sim_act[j,7,i] > 0 else 0 ## Total activos sim_act[j, 8, i] = sim_act[j,2,i] + sim_act[j,6,i] + sim_act[j,7,i] ### Simular balance primario if usar_bp_0 == 0: sim_bp_d[j, 0, i] = frf_params['const'] + frf_params['ret_fees_p']*sim_act[j,5,i] +\ frf_params[['d_p_lag', 'd_p_lag_squared', 'd_p_lag_cubed']]@[data_anual['d_p'].iloc[-1], data_anual['d_p'].iloc[-1]**2, data_anual['d_p'].iloc[-1]**3] + \ frf_params['brecha_cobre']*tray_anual[j,9,i] +\ frf_params['brecha_pos_int']*tray_anual[j,10,i] +\ frf_params['brecha_neg_int']*tray_anual[j,11,i] +\ frf_params['regla_fiscal'] +\ frf_params['crisis']*tray_anual[j,12,i] +\ frf_params['ar.L1']*frf.resid.iloc[-1] + tray_anual[j,7,i] elif usar_bp_0 == 1: sim_bp_d[j, 0, i] = bp_0 + tray_anual[j,7,i]*(1-data_trim.tail().index[0].month/12) mu = frf_params['ar.L1']*frf.resid.iloc[-1] + tray_anual[j,7,i] ### Simular deuda sim_bp_d[j, 1, i] = ((prop*(1+tray_anual[j,15,i]/100)*(1+tray_anual[j,2,i]/100) +\ prop_uf*(1+tray_anual[j,14,i]/100)*(1+tray_anual[j,6,i]/100 )+\ (1-prop-prop_uf)*(1+tray_anual[j,13,i]/100))/ \ ((1+tray_anual[j,0,i]/100)*(1+tray_anual[j,1,i]/100)))* \ data_anual['d_p'].iloc[-1] -\ sim_bp_d[j,0,i] + tray_anual[j,8,i] +\ (sim_act[j,8,i] - (data_anual['fees_p'].iloc[-1]+data_anual['frp_p'].iloc[-1] + data_anual['oatp_p'].iloc[-1])/((1+tray_anual[j,0,i]/100)*(1+tray_anual[j,1,i]/100))) ### Simular gastos por intereses sim_bp_d[j, 2, i] = ((prop*tray_anual[j,15,i]/100*(1+tray_anual[j,2,i]/100) + prop_uf*tray_anual[j,14,i]/100*(1+tray_anual[j,6,i]/100) + (1-prop-prop_uf)*tray_anual[j,13,i]/100)/ \ ((1+tray_anual[j,0,i]/100)*(1+tray_anual[j,1,i]/100)))* \ data_anual['d_p'].iloc[-1] else: ### Simular activos # FRP sim_act[j, 0, i] = max(0, min(sim_bp_d[j-1,3,i], 0.005)) # Aporte sim_act[j, 1, i] = 0.001 if sim_act[j-1,1,i] > 0.001 else sim_act[j-1,1,i] # Retiro sim_act[j, 2, i] = sim_act[j-1,2,i] + sim_act[j,0,i] - sim_act[j,1,i] # Saldo final ## FEES # Aporte if sim_bp_d[j-1, 3, i] > max(0, min(sim_bp_d[j-1,3,i], 0.005)): sim_act[j, 3, i] = sim_bp_d[j-1,3,i] - max(0, min(sim_bp_d[j-1,3,i],0.005)) \ if sim_act[j-1,4,i] + sim_bp_d[j-1,3,i] - max(0, min(sim_bp_d[j-1,3,i], 0.005)) < 0.07 else 0.07 - sim_act[j-1,4,i] else: sim_act[j, 3, i] = 0 sim_act[j, 4, i] = sim_act[j-1,6,i] + sim_act[j,3,i] # Saldo final antes de retiros # Retiro if tray_anual[j, 12, i] == 1 and sim_pibr[j,2,i] <= -0.03: sim_act[j, 5, i] = 2*tray_anual[j,0,i] if sim_act[j,4,i] >= -2*tray_anual[j,0,i] else -sim_act[j,4,i] else: sim_act[j, 5, i] = 0 sim_act[j, 6, i] = sim_act[j,4,i] + sim_act[j,5,i] # Saldo final ## OATP sim_act[j, 7, i] = sim_act[j-1,7,i] + oatp_params['oatp_mod']*(np.mean(data_anual['oatp2_p'][-10:]) - sim_act[j-1,7,i]) +\ sim_shock_oatp[j,0,i] sim_act[j, 7, i] = sim_act[j,7,i] if sim_act[j,7,i] > 0 else 0 ## Total activos sim_act[j, 8, i] = sim_act[j,2,i] + sim_act[j,6,i] + sim_act[j,7,i] ### Simular balance primario sim_bp_d[j, 0, i] = frf_params['const'] + frf_params['ret_fees_p']*sim_act[j,5,i] +\ frf_params[['d_p_lag', 'd_p_lag_squared', 'd_p_lag_cubed']]@[sim_bp_d[j-1,1,i], sim_bp_d[j-1,1,i]**2, sim_bp_d[j-1,1,i]**3] + \ frf_params['brecha_cobre']*tray_anual[j,9,i] +\ frf_params['brecha_pos_int']*tray_anual[j,10,i] +\ frf_params['brecha_neg_int']*tray_anual[j,11,i] +\ frf_params['regla_fiscal'] +\ frf_params['crisis']*tray_anual[j,12,i] +\ frf_params['ar.L1']*(mu)+tray_anual[j,7,i] mu = frf_params['ar.L1']*(mu) + tray_anual[j,7,i] ### Simular deuda sim_bp_d[j, 1, i] = ((prop*(1+tray_anual[j,15,i]/100)*(1+tray_anual[j,2,i]/100) +\ prop_uf*(1+tray_anual[j,14,i]/100)*(1+tray_anual[j,6,i]/100) +\ (1-prop-prop_uf)*(1+tray_anual[j,13,i]/100))/ \ ((1+tray_anual[j,0,i]/100)*(1+tray_anual[j,1,i]/100)))* \ sim_bp_d[j-1,1,i] -\ sim_bp_d[j,0,i] + tray_anual[j,8,i] +\ (sim_act[j,8,i] - sim_act[j-1,8,i]/((1+tray_anual[j,0,i]/100)*(1+tray_anual[j,1,i]/100))) ### Simular gastos por intereses sim_bp_d[j, 2, i] = ((prop*tray_anual[j,15,i]/100*(1+tray_anual[j,2,i]/100) + prop_uf*tray_anual[j,14,i]/100*(1+tray_anual[j,6,i]/100) + (1-prop-prop_uf)*tray_anual[j,13,i]/100)/ \ ((1+tray_anual[j,0,i]/100)*(1+tray_anual[j,1,i]/100)))* \ sim_bp_d[j-1,1,i] ### Balance total sim_bp_d[j, 3, i] = sim_bp_d[j,0,i] - sim_bp_d[j,2,i] sim_bp_d = sim_bp_d*100 ##### Gráficos y exportación de resultados ###### ### Gráficos de fancharts de simulaciones de balance fiscal, deuda bruta y gastos por intereses titles = ['Balance primario (% del PIB)', 'Deuda bruta del Gobierno Central (% del PIB)', 'Gastos por intereses (% del PIB)', 'Balance total (% del PIB)'] data_plot = data_anual[['bp_pib', 'd_p', 'gi_pib', 'b_pib']]*100 # Percentiles percentiles = [p1, p2, p3, p4] alphas = [0.1, 0.2, 0.3, 0.4] # Años históricos y de simulación años = data_plot.index.year[-20:] años_sim = np.arange(años[-1]+1, años[-1]+1+int(n_h/4)) años_con = np.concatenate([años[[-1]],años_sim]) for i in range(0, 4): perc = np.percentile(sim_bp_d[:,i,:], [p1,p2,p3,p4,50,100-p4,100-p3,100-p2,100-p1], axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años, data_plot.iloc[-20:,i], color='black', linewidth=1.5, label='Datos efectivos') ultimo_efectivo = data_plot.iloc[-1,i] mediana_simulacion = np.concatenate([[ultimo_efectivo], perc[4]]) ax.plot(años_con, mediana_simulacion, color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") for j, alpha in enumerate(alphas[::-1]): lower = np.concatenate([[ultimo_efectivo], perc[j]]) upper = np.concatenate([[ultimo_efectivo], perc[8-j]]) ax.fill_between(años_con, lower, upper, color=color_rojo_secundario, alpha=alpha, label=f"Rango {percentiles[::-1][j]} - {100-percentiles[::-1][j]}") # Línea punteada vertical y texto ax.axvline(x=años_con[0], color='gray', linestyle='--', linewidth=1) ax.text(años_con[0], ax.get_ylim()[1]*0.95, "Proyecciones", rotation=90, fontsize=9, verticalalignment='top', color='gray', fontfamily='Tw Cen MT') ax.set_title(titles[i], fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_xticks(np.arange(años[0], años_sim[-1:]+1, 2)) ax.set_ylabel("Porcentaje del PIB", fontsize=12, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction = 'out', labelsize=10) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.legend(loc="upper left", fontsize=10, frameon=False) if (perc < 0).any(): ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.3) plt.tight_layout() plt.savefig(f'fanchart_{titles[i]}.jpg',transparent=True) plt.show() ### Gráfico con fanchart sobre deuda bruta estimada con media de modelo determinístico # Preparar años simulados con frecuencia correcta años_sim2 = pd.date_range(start=data_plot.index[-1]+pd.DateOffset(years=1), periods=int(n_h/4), freq="YE-DEC") años_sim_labels = años_sim2.year # Calcular percentiles de deuda simulada y diferencia con la mediana percentiles = [p1, p2, p3, p4, 50, 100-p4, 100-p3, 100-p2, 100-p1] perc_db = np.percentile(sim_bp_d[:,1,:], percentiles, axis=1) # Deuda por percentil dif_mediana = perc_db - perc_db[4] # Diferencia percentil respecto a mediana db_det = deuda_det.loc[deuda_det.index.isin(años_sim2), deuda_det.columns[0]] # Seleccionamos sólo proyecciones de modelo determinístico de años simulados. if dif_mediana.shape[1] != len(db_det): # Aseguramos que dimensiones son compatibles antes de calcular percentiles. dif_mediana = dif_mediana.T per_db_det = dif_mediana + db_det.values.ravel() # Preparar datos para gráfico data_db = data_anual[['d_p']]*100 años = data_db.index.year[-20:] ultimo_efectivo = data_db.iloc[-1,0] fig, ax = plt.subplots(dpi=180) # Línea de datos efectivos ax.plot(años, data_db.iloc[-20:,0], color='black', linewidth=1.5, label='Datos efectivos') # Mediana simulada mediana = np.concatenate([[ultimo_efectivo], per_db_det[4]]) ax.plot(años_con, mediana, color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Áreas de fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): lower = np.concatenate([[ultimo_efectivo],per_db_det[j]]) upper = np.concatenate([[ultimo_efectivo],per_db_det[8-j]]) ax.fill_between(años_con, lower, upper, color=color_rojo_secundario, alpha=alpha, label=f"Rango {percentiles[j]} - {percentiles[8-j]}") # Línea horizontal de referencia nivel_prudente = 45 ax.axhline(nivel_prudente, color=color_azul_secundario, linestyle='--', linewidth=1) ax.text(años[0], nivel_prudente+0.8, "Nivel prudente de deuda (45% del PIB)", color=color_azul_secundario, fontsize=10, va='bottom') # Línea punteada vertical y texto ax.axvline(x=años_con[0], color='gray', linestyle='--', linewidth=1) ax.text(años_con[0], ax.get_ylim()[1]*0.5, "Proyecciones", rotation=90, fontsize=9, verticalalignment='top', color='gray', fontfamily='Tw Cen MT') # Estética del gráfico ax.set_title('Deuda bruta del Gobierno Central ("baseline centered") (% del PIB)', fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_ylabel("% del PIB", fontsize=12, fontfamily='Tw Cen MT') ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction='out', labelsize=8) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.legend(loc="center left", fontsize=8, frameon=False) plt.tight_layout() plt.savefig('fanchart_deuda_bruta_baseline_centered.jpg', transparent = True) plt.show() ### Gráfico de activos perc = np.percentile(sim_act[:,8,:], percentiles, axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años_sim, perc[4], color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Áreas de fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): ax.fill_between(años_sim, perc.T[:,j], perc.T[:,8-j], color=color_rojo_secundario, alpha=alpha, label=f"Rango {percentiles[j]} - {percentiles[8-j]}") ax.set_title("Total activos del Tesoro Público (% del PIB)", fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_ylabel("% del PIB", fontsize=12, fontfamily='Tw Cen MT') ax.set_xticks(años_sim) ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction = 'out', labelsize=10) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.legend(loc="upper left", fontsize=8, frameon=False) plt.tight_layout() plt.show() ### Gráfico de Deuda neta # Calcular deuda neta simulada sim_act[:, 9, :] = sim_bp_d[:,1,:] - sim_act[:,8,:]*100 perc = np.percentile(sim_act[:,9,:], percentiles, axis=1) fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años, data_anual['deuda_neta'].iloc[-20:]*100, color='black', linewidth=1.5, label='Datos efectivos') ultimo_efectivo = data_anual['deuda_neta'].iloc[-1]*100 mediana_simulacion = np.concatenate([[ultimo_efectivo], perc.T[:, 4]]) ax.plot(años_con, mediana_simulacion, color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Áreas de fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): lower = np.concatenate([[ultimo_efectivo],perc.T[:,j]]) upper = np.concatenate([[ultimo_efectivo],perc.T[:, 8-j]]) ax.fill_between(años_con, lower, upper, color=color_rojo_secundario, alpha=alpha, label=f"Rango {percentiles[j]} - {percentiles[8-j]}") # Línea punteada vertical y texto ax.axvline(x=años_con[0], color='gray', linestyle='--', linewidth=1) ax.text(años_con[0], ax.get_ylim()[1]*0.5, "Proyecciones", rotation=90, fontsize=9, verticalalignment='top', color='gray', fontfamily='Tw Cen MT') # Estilo del gráfico ax.set_title('Deuda neta del Gobierno Central (% del PIB)', fontsize=16, fontweight='bold', fontfamily='Tw Cen MT',pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_ylabel("Porcentaje del PIB",fontsize=12,fontfamily='Tw Cen MT') ax.set_xticks(np.arange(años[0],años_sim[-1:]+1,2)) ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction = 'out', labelsize=10) ax.grid(axis='y', color=color_gris_principal, alpha=0.3, linestyle='--') ax.legend(loc="upper left", fontsize=10, frameon=False) plt.tight_layout() plt.savefig('fanchart_deuda_neta.jpg',transparent=True) plt.show() ### Gráfico con fanchart sobre deuda neta estimada con media modelo determinístico # Preparar años simulados con frecuencia correcta años_sim2 = pd.date_range(start=data_plot.index[-1]+pd.DateOffset(years=1), periods=int(n_h/4),freq="YE-DEC") años_sim_labels = años_sim2.year perc_dn = np.percentile(sim_act[:,9,:], percentiles, axis=1) # Calcular percentiles de deuda simulada y diferencia con la mediana dif_mediana = perc_dn - perc_dn[4] # Diferencia percentil respecto a mediana dn_det=deuda_det.loc[deuda_det.index.isin(años_sim2), deuda_det.columns[1]] # Seleccionamos sólo proyecciones de modelo determinístico de años simulados. if dif_mediana.shape[1] != len(dn_det): # Aseguramos que dimensiones son compatibles antes de calcular percentiles. dif_mediana = dif_mediana.T per_dn_det = dif_mediana + dn_det.values.ravel() data_dn = data_anual[['deuda_neta']]*100 ultimo_efectivo = data_dn.iloc[-1,0] # Gráfico fig, ax = plt.subplots(figsize=(10,6), dpi=180) ax.plot(años, data_dn.iloc[-20:,0], color='black', linewidth=1.5, label='Datos efectivos') mediana = np.concatenate([[ultimo_efectivo], per_dn_det[4]]) ax.plot(años_con, mediana, color=color_rojo_principal, linestyle='--', linewidth=2, label="Mediana simulación") # Áreas fanchart for j, alpha in zip(range(3, -1, -1), [0.4, 0.3, 0.2, 0.1]): lower = np.concatenate([[ultimo_efectivo], per_dn_det[j]]) upper = np.concatenate([[ultimo_efectivo], per_dn_det[8-j]]) ax.fill_between(años_con, lower, upper, color=color_rojo_secundario, alpha=alpha, label=f"Rango {percentiles[j]} - {percentiles[8-j]}") # Línea punteada vertical y texto ax.axvline(x=años_con[0], color='gray', linestyle='--', linewidth=1) ax.text(años_con[0], ax.get_ylim()[1]*0.5, "Proyecciones", rotation=90, fontsize=9, verticalalignment='top', color='gray', fontfamily='Tw Cen MT') # Estética del gráfico ax.set_title('Deuda neta del Gobierno Central ("baseline centered") (% del PIB)', fontsize=16, fontweight='bold', fontfamily='Tw Cen MT', pad=20) ax.set_xlabel("Años", fontsize=12, fontfamily='Tw Cen MT') ax.set_ylabel("Porcentaje del PIB", fontsize=12, fontfamily='Tw Cen MT') ax.set_xticks(np.arange(años[0],años_sim[-1:]+1,2)) ax.spines[['top', 'right']].set_visible(False) ax.tick_params(axis='both', direction = 'out', labelsize=10) ax.grid(axis='y', color=color_gris_principal, alpha=0.3,linestyle='--') ax.legend(loc="upper left", fontsize=10, frameon=False) plt.tight_layout() plt.savefig('fanchart_deuda_neta_MED_baseline_centered.jpg',transparent=True) plt.show() ### Resultados numéricos # Media y mediana anual de la proyección de deuda, balance primario, gastos por intereses # y balance total para horizonte de proyección. resultados = pd.DataFrame() resultados['mediana deuda'] = np.percentile(sim_bp_d[:,1,:], 50, axis=1).reshape(-1) resultados['media deuda'] = np.mean(sim_bp_d[:,1,:], axis=1) resultados['mediana bp'] = np.percentile(sim_bp_d[:,0,:], 50, axis=1).reshape(-1) resultados['media bp'] = np.mean(sim_bp_d[:,0,:], axis=1) resultados['mediana gint'] = np.percentile(sim_bp_d[:,2,:], 50, axis=1).reshape(-1) resultados['media gint'] = np.mean(sim_bp_d[:,2,:], axis=1) resultados['mediana bal'] = np.percentile(sim_bp_d[:,3,:], 50, axis=1).reshape(-1) resultados['media bal'] = np.mean(sim_bp_d[:,3,:], axis=1) resultados.index = años_sim2 # Probabilidad de superar el nivel prudente de deuda al final del horizonte de proyección. sup_prud = (sim_bp_d[:,1,:] > 45).astype(int) resultados['prob>n.prud'] = np.round(np.sum(sup_prud,axis=1)/n_b*100, 1) # Estadísticos de la deuda resultados['desv.est. deuda'] = np.std(sim_bp_d[:,1,:], axis=1) resultados['skewness deuda'] = skew(sim_bp_d[:,1,:], axis=1) resultados['kurtosis deuda'] = kurtosis(sim_bp_d[:,1,:], axis=1) # Percentiles deuda modelo MED perc_db_med = pd.DataFrame(perc_db.T) perc_db_med.index = años_sim2 perc_db_med.columns = p1, p2, p3, p4, 50, 100-p4, 100-p3, 100-p2, 100-p1 # Percentiles deuda modelo MED con mediana de modelo deterministico perc_db_det = pd.DataFrame(per_db_det.T) perc_db_det.index = años_sim2 perc_db_det.columns = p1, p2, p3, p4, 50, 100-p4, 100-p3, 100-p2, 100-p1 # Exportamos resultados a Excel with pd.ExcelWriter('resultados modelo MED.xlsx') as writer: resultados.to_excel(excel_writer=writer, sheet_name='Resumen resultados') perc_db_med.to_excel(excel_writer=writer, sheet_name='Percentiles deuda MED') perc_db_det.to_excel(excel_writer=writer, sheet_name='Percentiles deuda mod det') workbook = writer.book for sheet_name in writer.sheets: sheet = writer.sheets[sheet_name] # Customizar los headers header_fill = PatternFill(start_color="07434B", end_color="07434B", fill_type="solid") header_font = Font(bold=True, color="FFFFFF") # Texto en negrita for col_num, cell in enumerate(sheet[1], start=1): # Primera fila (títulos) cell.fill = header_fill cell.font = header_font # Customizar el resto de las celdas for row_idx, row in enumerate(sheet.iter_rows(min_row=2), start=2): # Excluye fila con títulos for col_idx, cell in enumerate(row, start=1): if col_idx == 1: # Primrera columna (Index) cell.fill = PatternFill(start_color="CFDDE2", end_color="CFDDE2", fill_type="solid") else: # Resto de las columnas cell.fill = PatternFill(start_color="E8EEF0", end_color="E8EEF0", fill_type="solid") for col_idx, cell in enumerate(sheet[1], start=1): # Itera sólo por los títulos (Headers) if col_idx > 1: # Saltarse la primera columna header_length = len(str(cell.value)) # Obtener el largo del header adjusted_width = header_length+2 # Ajustar el ancho sheet.column_dimensions[get_column_letter(col_idx)].width=adjusted_width