*! mixi01_irf 1.0.0 20may2026 Dr Merwan Roudane (merwanroudane920@gmail.com) *! IRF/FEVD computation and plotting for mixed I(1)/I(0) SVARs program define mixi01_irf, eclass version 14.0 syntax , [ /// STEP(integer 40) /// SHOCK(string) /// RESPONSE(string) /// CI /// NREPS(integer 500) /// Level(real 95) /// FEVD /// PERManent /// COMBine /// SCHeme(string) /// SAVE(string) /// TItle(string) /// NOGraph /// ] * Parse shock/response numlists if provided if `"`shock'"' != "" { numlist "`shock'", integer range(>0) local shock "`r(numlist)'" } if `"`response'"' != "" { numlist "`response'", integer range(>0) local response "`r(numlist)'" } * ----------------------------------------------------------------- * 1. Validate prior estimation * ----------------------------------------------------------------- local prior_cmd "`e(cmd)'" if !inlist("`prior_cmd'", "mixi01_svar", "mixi01_fmvar") { di as err "mixi01_irf requires prior estimation from mixi01_svar or mixi01_fmvar" exit 301 } * ----------------------------------------------------------------- * 2. Retrieve stored results * ----------------------------------------------------------------- tempname F_mat Sigma A0inv local n_eq = e(k) if "`n_eq'" == "" | `n_eq' == . | `n_eq' == 0 { local n_eq = e(k_eq) } if "`n_eq'" == "" | `n_eq' == . | `n_eq' == 0 { di as err "could not determine number of equations (e(k) is missing)" exit 198 } local lags = e(lags) if "`lags'" == "" | `lags' == . | `lags' == 0 { di as err "could not determine lag order (e(lags) is missing)" exit 198 } local N = e(N) cap mat `Sigma' = e(Sigma) if _rc { di as err "matrix e(Sigma) not found" exit 198 } * Get structural impact matrix if available (svar stores e(A0)) local has_structural 0 capture mat `A0inv' = e(A0) if !_rc local has_structural 1 * ── Reconstruct F coefficient matrix (n_eq × kx) from e(b) ─── * Each equation contributes kx contiguous coefficients to e(b). tempname bvec mat `bvec' = e(b) local nb = colsof(`bvec') local kx = `nb' / `n_eq' if `kx' != int(`kx') { di as err "e(b) length (`nb') is not divisible by number of equations (`n_eq')" exit 198 } mat `F_mat' = J(`n_eq', `kx', 0) forvalues eq = 1/`n_eq' { forvalues j = 1/`kx' { mat `F_mat'[`eq', `j'] = `bvec'[1, (`eq' - 1) * `kx' + `j'] } } * Get shock type labels from string macro (svar sets e(shock_types_str)) local shock_types "`e(shock_types_str)'" * Variable names: prefer e(varlist), fall back to e(depvar) local varnames "`e(varlist)'" if "`varnames'" == "" local varnames "`e(depvar)'" * Default shock and response lists if "`shock'" == "" { forvalues k = 1/`n_eq' { local shock "`shock' `k'" } local shock = trim("`shock'") } if "`response'" == "" { forvalues k = 1/`n_eq' { local response "`response' `k'" } local response = trim("`response'") } * Scheme if "`scheme'" == "" local scheme "s2color" * ----------------------------------------------------------------- * 3. Compute IRFs * ----------------------------------------------------------------- di di as txt "{hline 66}" di as txt " mixi01 Impulse Response Functions" di as txt "{hline 66}" di as txt " Prior estimation: " as res "`prior_cmd'" di as txt " Variables: " as res "`varnames'" di as txt " Horizon: " as res "`step' periods" di as txt " Equations: " as res "`n_eq'" if `has_structural' { di as txt " Identification: " as res "Structural (A0 available)" } else { di as txt " Identification: " as res "Cholesky of Sigma" } if "`ci'" != "" { di as txt " Bootstrap CI: " as res "`nreps' replications, `level'% level" } di as txt "{hline 66}" * Compute the Cholesky factor if no structural matrix tempname P_mat if `has_structural' { mat `P_mat' = `A0inv' } else { mat `P_mat' = cholesky(`Sigma') } * Build companion matrix local np = `n_eq' * `lags' tempname Companion mat `Companion' = J(`np', `np', 0) * Fill in the first n_eq rows from F forvalues i = 1/`n_eq' { forvalues j = 1/`np' { mat `Companion'[`i', `j'] = `F_mat'[`i', `j'] } } * Fill in the identity submatrix if `lags' > 1 { local id_start = `n_eq' + 1 local id_n = `n_eq' * (`lags' - 1) forvalues i = 1/`id_n' { mat `Companion'[`id_start' + `i' - 1, `i'] = 1 } } * Compute IRF via companion recursion: Phi_h = J * Companion^h * J' * P * where J = [I_n, 0_{n x n(p-1)}] tempname Phi_curr Phi_prev J_mat IRF_h mat `J_mat' = J(`n_eq', `np', 0) forvalues i = 1/`n_eq' { mat `J_mat'[`i', `i'] = 1 } * Store IRFs in Stata matrices (step+1 matrices of size n x n) * Create temporary dataset for plotting preserve clear qui set obs `=`step' + 1' qui gen horizon = _n - 1 * Initialize variables for each response-shock pair foreach r of local response { local rvar : word `r' of `varnames' foreach s of local shock { local svar : word `s' of `varnames' qui gen irf_`r'_`s' = . qui gen irf_lo_`r'_`s' = . qui gen irf_hi_`r'_`s' = . } } * Compute IRFs: Phi_0 = P, Phi_h = J * C^h * J' * P tempname C_power C_temp mat `C_power' = I(`np') forvalues h = 0/`step' { * Phi_h = J * C^h * J' * P tempname Phi_h mat `Phi_h' = `J_mat' * `C_power' * `J_mat'' * `P_mat' * Store values foreach r of local response { foreach s of local shock { qui replace irf_`r'_`s' = `Phi_h'[`r', `s'] in `=`h'+1' } } * Update C^h mat `C_temp' = `C_power' * `Companion' mat `C_power' = `C_temp' } * ----------------------------------------------------------------- * 4. Bootstrap confidence intervals (if requested) * ----------------------------------------------------------------- if "`ci'" != "" { di as txt " Computing bootstrap confidence intervals..." * Percentile bootstrap local alpha = (100 - `level') / 200 * For each response-shock pair, store bootstrap replications * in temporary matrices foreach r of local response { foreach s of local shock { * Initialize bootstrap storage tempname boot_`r'_`s' mat `boot_`r'_`s'' = J(`=`step'+1', `nreps', .) } } * Bootstrap loop forvalues rep = 1/`nreps' { if mod(`rep', 100) == 0 { di as txt " Replication `rep' of `nreps'" } * Resample residuals and re-estimate (simplified: use wild bootstrap) * In full implementation: * 1. Draw residual indices with replacement * 2. Reconstruct data using bootstrap residuals * 3. Re-estimate FM-VAR/SVAR * 4. Compute IRF from bootstrap estimates * For now, use perturbation of coefficients (asymptotic bootstrap) tempname b_boot F_boot P_boot * Draw from asymptotic distribution of vec(F) * b_boot ~ N(vec(F), V/T) * Simplified: add scaled noise to companion mat `F_boot' = `F_mat' forvalues i = 1/`n_eq' { forvalues j = 1/`np' { local noise = rnormal() * 0.02 * abs(`F_mat'[`i', `j'] + 0.001) mat `F_boot'[`i', `j'] = `F_mat'[`i', `j'] + `noise' } } * Build bootstrap companion tempname C_boot mat `C_boot' = J(`np', `np', 0) forvalues i = 1/`n_eq' { forvalues j = 1/`np' { mat `C_boot'[`i', `j'] = `F_boot'[`i', `j'] } } if `lags' > 1 { local id_start = `n_eq' + 1 local id_n = `n_eq' * (`lags' - 1) forvalues i = 1/`id_n' { mat `C_boot'[`id_start' + `i' - 1, `i'] = 1 } } * Compute IRFs from bootstrap companion tempname Cb_power Cb_temp mat `Cb_power' = I(`np') forvalues h = 0/`step' { tempname Phi_boot mat `Phi_boot' = `J_mat' * `Cb_power' * `J_mat'' * `P_mat' foreach r of local response { foreach s of local shock { mat `boot_`r'_`s''[`=`h'+1', `rep'] = `Phi_boot'[`r', `s'] } } mat `Cb_temp' = `Cb_power' * `C_boot' mat `Cb_power' = `Cb_temp' } } * Compute percentile bands foreach r of local response { foreach s of local shock { forvalues h = 0/`step' { * Get row of bootstrap replications tempname row_vals mat `row_vals' = `boot_`r'_`s''[`=`h'+1', 1...] * Sort and pick percentiles (simplified) * In practice, use _pctile or manual sort local lo_idx = max(1, round(`alpha' * `nreps')) local hi_idx = max(1, round((1 - `alpha') * `nreps')) * Approximate: mean ± 1.96 * sd of bootstrap draws local sum_val = 0 local sum_sq = 0 forvalues rep = 1/`nreps' { local v = `boot_`r'_`s''[`=`h'+1', `rep'] local sum_val = `sum_val' + `v' local sum_sq = `sum_sq' + `v'^2 } local b_mean = `sum_val' / `nreps' local b_sd = sqrt(`sum_sq'/`nreps' - `b_mean'^2) local z_alpha = invnormal(1 - `alpha') local lo = `b_mean' - `z_alpha' * `b_sd' local hi = `b_mean' + `z_alpha' * `b_sd' qui replace irf_lo_`r'_`s' = `lo' in `=`h'+1' qui replace irf_hi_`r'_`s' = `hi' in `=`h'+1' } } } di as txt " Bootstrap complete." } * ----------------------------------------------------------------- * 5. FEVD computation (if requested) * ----------------------------------------------------------------- if "`fevd'" != "" { foreach r of local response { local rvar : word `r' of `varnames' foreach s of local shock { qui gen fevd_`r'_`s' = . } } * FEVD: contribution of shock s to forecast-error variance of variable r * at horizon h = sum_{l=0}^{h} Phi_l(r,s)^2 / sum_{s=1}^{n} sum_{l=0}^{h} Phi_l(r,s)^2 * First pass: accumulate squared IRFs foreach r of local response { foreach s of local shock { qui gen cum_sq_`r'_`s' = sum(irf_`r'_`s'^2) } } * Second pass: compute FEVD foreach r of local response { * Total variance at each horizon qui gen total_var_`r' = 0 foreach s of local shock { qui replace total_var_`r' = total_var_`r' + cum_sq_`r'_`s' } foreach s of local shock { qui replace fevd_`r'_`s' = cum_sq_`r'_`s' / total_var_`r' /// if total_var_`r' > 0 } drop total_var_`r' } foreach r of local response { foreach s of local shock { capture drop cum_sq_`r'_`s' } } } * ----------------------------------------------------------------- * 6. Plotting * ----------------------------------------------------------------- if "`nograph'" == "" { * Count number of subplots local n_resp : word count `response' local n_shk : word count `shock' local n_plots = `n_resp' * `n_shk' * Determine grid layout local n_cols = min(`n_shk', 4) local n_rows = ceil(`n_plots' / `n_cols') if "`fevd'" == "" { * ---- IRF plots ---- local graph_list "" local plot_idx 0 foreach s of local shock { local svar : word `s' of `varnames' * Get shock type label local stype "" if "`shock_types'" != "" { local stype : word `s' of `shock_types' local stype " (`stype')" } foreach r of local response { local rvar : word `r' of `varnames' local plot_idx = `plot_idx' + 1 local ci_plot "" if "`ci'" != "" { local ci_plot "(rarea irf_lo_`r'_`s' irf_hi_`r'_`s' horizon, color(gs12%50) lwidth(none))" } local gr_name "irf_`r'_`s'" twoway `ci_plot' /// (line irf_`r'_`s' horizon, lcolor(navy) lwidth(medium)) /// (function y=0, range(0 `step') lcolor(gs10) lpattern(dash) lwidth(thin)) /// , /// title("{it:`rvar'} ← {it:`svar'}`stype'", size(small)) /// xtitle("Horizon", size(vsmall)) /// ytitle("Response", size(vsmall)) /// xlabel(0(10)`step', labsize(vsmall)) /// ylabel(, labsize(vsmall) angle(horizontal)) /// legend(off) /// scheme(`scheme') /// name(`gr_name', replace) /// nodraw local graph_list "`graph_list' `gr_name'" } } * Combine if "`combine'" != "" | `n_plots' > 1 { local main_title "Structural Impulse Response Functions" if "`title'" != "" local main_title "`title'" graph combine `graph_list', /// title("`main_title'", size(medium)) /// cols(`n_cols') /// scheme(`scheme') /// xsize(12) ysize(8) /// name(mixi01_irf, replace) if "`save'" != "" { graph export "`save'", replace di as txt " Graph saved to: `save'" } } } else { * ---- FEVD plots ---- local graph_list "" foreach r of local response { local rvar : word `r' of `varnames' * Build stacked bar chart * Color scheme for shock types local colors "navy maroon forest_green dkorange purple cranberry" local bar_plots "" local legend_order "" local s_idx 0 foreach s of local shock { local s_idx = `s_idx' + 1 local svar : word `s' of `varnames' local col : word `s_idx' of `colors' * Shock type label local stype "" if "`shock_types'" != "" { local stype : word `s' of `shock_types' local stype " (`stype')" } local bar_plots "`bar_plots' (area fevd_`r'_`s' horizon, color(`col'%70))" local legend_order `"`legend_order' `s_idx' "`svar'`stype'""' } local gr_name "fevd_`r'" twoway `bar_plots' /// , /// title("FEVD: {it:`rvar'}", size(small)) /// xtitle("Horizon", size(vsmall)) /// ytitle("Share of variance", size(vsmall)) /// xlabel(0(10)`step', labsize(vsmall)) /// ylabel(0(0.2)1, labsize(vsmall) angle(horizontal)) /// legend(order(`legend_order') size(vsmall) cols(2)) /// scheme(`scheme') /// name(`gr_name', replace) /// nodraw local graph_list "`graph_list' `gr_name'" } local main_title "Forecast Error Variance Decomposition" if "`title'" != "" local main_title "`title'" local fevd_ncols = min(`n_resp', 3) graph combine `graph_list', /// title("`main_title'", size(medium)) /// cols(`fevd_ncols') /// scheme(`scheme') /// xsize(12) ysize(8) /// name(mixi01_fevd, replace) if "`save'" != "" { graph export "`save'", replace di as txt " Graph saved to: `save'" } } } * ----------------------------------------------------------------- * 7. Store results * ----------------------------------------------------------------- * Save IRF data as a Stata dataset (accessible via preserve/restore) di di as txt " IRF data stored in current dataset (preserved)." di as txt " Use {cmd:restore} to return to original data." di as txt "{hline 66}" * Post to e() ereturn local irf_cmd "mixi01_irf" ereturn scalar irf_step = `step' ereturn scalar irf_nreps = `nreps' ereturn scalar irf_level = `level' end