*! version 1.0.0 20mar2026 Dr. Merwan Roudane *! LRMBOUNDS: Bounds Approach to Inference Using the Long Run Multiplier *! Implements Webb, Linn & Lebo (2019, Political Analysis) and *! Webb, Linn & Lebo (2020, Journal of Politics) capture program drop lrmbounds capture program drop _lrmbounds_stars capture program drop _lrmbounds_decision capture program drop _lrmbounds_center capture program drop _lrmbounds_cv_ftest capture program drop _lrmbounds_cv_ttest_ecr capture program drop _lrmbounds_cv_lrm capture program drop _lrmbounds_cv_lrm_lookup capture program drop _lrmbounds_estimate capture program drop _lrmbounds_ftest capture program drop _lrmbounds_lrm capture program drop _lrmbounds_graph * ============================================================================== * FROM: _lrmbounds_stars.ado * ============================================================================== program define _lrmbounds_stars, sclass args pval if missing(`pval') sreturn local s "" else if `pval' < 0.01 sreturn local s "***" else if `pval' < 0.05 sreturn local s "**" else if `pval' < 0.10 sreturn local s "*" else sreturn local s "" end capture program drop _lrmbounds_decision program define _lrmbounds_decision, sclass * Given a test statistic, lower bound, upper bound, and test type * Returns decision string args tstat lb ub testtype if "`testtype'" == "f" { * F-test: reject if above upper bound if `tstat' > `ub' { sreturn local decision "Reject H0" sreturn local dcode "reject" } else if `tstat' < `lb' { sreturn local decision "Fail to Reject" sreturn local dcode "fail" } else { sreturn local decision "Inconclusive" sreturn local dcode "inconclusive" } } else { * t-test: reject if |t| > upper bound (bounds are positive, t is negative) local abst = abs(`tstat') if `abst' > `ub' { sreturn local decision "Reject H0" sreturn local dcode "reject" } else if `abst' < `lb' { sreturn local decision "Fail to Reject" sreturn local dcode "fail" } else { sreturn local decision "Inconclusive" sreturn local dcode "inconclusive" } } end capture program drop _lrmbounds_center program define _lrmbounds_center syntax , TEXT(string) WIDTH(integer) local len = udstrlen("`text'") if `len' >= `width' { di "`text'" _continue } else { local lpad = int((`width' - `len')/2) di _skip(`lpad') "`text'" _continue } end * ============================================================================== * FROM: _lrmbounds_critvals.ado * ============================================================================== * ============================================================================ * PSS F-BOUNDS CRITICAL VALUES * From Pesaran, Shin & Smith (2001), Table CI * Returns: lower I(0) bound and upper I(1) bound * ============================================================================ capture program drop _lrmbounds_cv_ftest program define _lrmbounds_cv_ftest, rclass syntax , K(integer) CASE(integer) ALPHA(real) [NOBS(integer 1000)] * k = number of regressors (independent variables) * case = 3 (unrestricted intercept, no trend) or 5 (unrestricted intercept + trend) * alpha = significance level (0.01, 0.025, 0.05, 0.10) * nobs = sample size (for Narayan small-sample adjustment) * ----------------------------------------------------------------------- * Case III: Unrestricted intercept, no trend * PSS (2001) Table CI(iii), asymptotic critical values * Format: I(0)_lower I(1)_upper * ----------------------------------------------------------------------- if `case' == 3 { if `alpha' == 0.10 { if `k' == 1 { local lb = 3.02 local ub = 3.51 } else if `k' == 2 { local lb = 2.63 local ub = 3.35 } else if `k' == 3 { local lb = 2.37 local ub = 3.20 } else if `k' == 4 { local lb = 2.20 local ub = 3.09 } else if `k' == 5 { local lb = 2.08 local ub = 3.00 } else if `k' == 6 { local lb = 1.99 local ub = 2.94 } else if `k' == 7 { local lb = 1.92 local ub = 2.89 } else if `k' == 8 { local lb = 1.85 local ub = 2.85 } else if `k' == 9 { local lb = 1.80 local ub = 2.80 } else if `k' == 10 { local lb = 1.76 local ub = 2.77 } else { local lb = . local ub = . } } else if `alpha' == 0.05 { if `k' == 1 { local lb = 3.62 local ub = 4.16 } else if `k' == 2 { local lb = 3.10 local ub = 3.87 } else if `k' == 3 { local lb = 2.79 local ub = 3.67 } else if `k' == 4 { local lb = 2.56 local ub = 3.49 } else if `k' == 5 { local lb = 2.39 local ub = 3.38 } else if `k' == 6 { local lb = 2.27 local ub = 3.28 } else if `k' == 7 { local lb = 2.17 local ub = 3.21 } else if `k' == 8 { local lb = 2.11 local ub = 3.15 } else if `k' == 9 { local lb = 2.04 local ub = 3.11 } else if `k' == 10 { local lb = 1.98 local ub = 3.04 } else { local lb = . local ub = . } } else if `alpha' == 0.025 { if `k' == 1 { local lb = 4.18 local ub = 4.79 } else if `k' == 2 { local lb = 3.55 local ub = 4.38 } else if `k' == 3 { local lb = 3.17 local ub = 4.14 } else if `k' == 4 { local lb = 2.89 local ub = 3.87 } else if `k' == 5 { local lb = 2.70 local ub = 3.73 } else if `k' == 6 { local lb = 2.55 local ub = 3.61 } else if `k' == 7 { local lb = 2.42 local ub = 3.50 } else if `k' == 8 { local lb = 2.33 local ub = 3.42 } else if `k' == 9 { local lb = 2.26 local ub = 3.35 } else if `k' == 10 { local lb = 2.19 local ub = 3.30 } else { local lb = . local ub = . } } else if `alpha' == 0.01 { if `k' == 1 { local lb = 4.94 local ub = 5.58 } else if `k' == 2 { local lb = 4.13 local ub = 5.00 } else if `k' == 3 { local lb = 3.65 local ub = 4.66 } else if `k' == 4 { local lb = 3.29 local ub = 4.37 } else if `k' == 5 { local lb = 3.06 local ub = 4.15 } else if `k' == 6 { local lb = 2.88 local ub = 3.99 } else if `k' == 7 { local lb = 2.73 local ub = 3.90 } else if `k' == 8 { local lb = 2.62 local ub = 3.77 } else if `k' == 9 { local lb = 2.54 local ub = 3.68 } else if `k' == 10 { local lb = 2.45 local ub = 3.61 } else { local lb = . local ub = . } } else { local lb = . local ub = . } } * ----------------------------------------------------------------------- * Case V: Unrestricted intercept + unrestricted trend * ----------------------------------------------------------------------- else if `case' == 5 { if `alpha' == 0.10 { if `k' == 1 { local lb = 3.78 local ub = 4.27 } else if `k' == 2 { local lb = 3.38 local ub = 4.02 } else if `k' == 3 { local lb = 3.05 local ub = 3.87 } else if `k' == 4 { local lb = 2.84 local ub = 3.76 } else if `k' == 5 { local lb = 2.68 local ub = 3.67 } else if `k' == 6 { local lb = 2.57 local ub = 3.61 } else if `k' == 7 { local lb = 2.47 local ub = 3.56 } else if `k' == 8 { local lb = 2.40 local ub = 3.52 } else if `k' == 9 { local lb = 2.34 local ub = 3.48 } else if `k' == 10 { local lb = 2.28 local ub = 3.44 } else { local lb = . local ub = . } } else if `alpha' == 0.05 { if `k' == 1 { local lb = 4.42 local ub = 4.99 } else if `k' == 2 { local lb = 3.88 local ub = 4.61 } else if `k' == 3 { local lb = 3.47 local ub = 4.45 } else if `k' == 4 { local lb = 3.24 local ub = 4.35 } else if `k' == 5 { local lb = 3.04 local ub = 4.23 } else if `k' == 6 { local lb = 2.89 local ub = 4.13 } else if `k' == 7 { local lb = 2.78 local ub = 4.07 } else if `k' == 8 { local lb = 2.69 local ub = 4.00 } else if `k' == 9 { local lb = 2.62 local ub = 3.95 } else if `k' == 10 { local lb = 2.54 local ub = 3.91 } else { local lb = . local ub = . } } else if `alpha' == 0.025 { if `k' == 1 { local lb = 5.03 local ub = 5.65 } else if `k' == 2 { local lb = 4.37 local ub = 5.16 } else if `k' == 3 { local lb = 3.89 local ub = 4.97 } else if `k' == 4 { local lb = 3.59 local ub = 4.82 } else if `k' == 5 { local lb = 3.36 local ub = 4.69 } else if `k' == 6 { local lb = 3.19 local ub = 4.59 } else if `k' == 7 { local lb = 3.06 local ub = 4.51 } else if `k' == 8 { local lb = 2.97 local ub = 4.45 } else if `k' == 9 { local lb = 2.88 local ub = 4.40 } else if `k' == 10 { local lb = 2.79 local ub = 4.34 } else { local lb = . local ub = . } } else if `alpha' == 0.01 { if `k' == 1 { local lb = 6.10 local ub = 6.73 } else if `k' == 2 { local lb = 5.15 local ub = 5.94 } else if `k' == 3 { local lb = 4.56 local ub = 5.65 } else if `k' == 4 { local lb = 4.18 local ub = 5.44 } else if `k' == 5 { local lb = 3.90 local ub = 5.22 } else if `k' == 6 { local lb = 3.65 local ub = 5.10 } else if `k' == 7 { local lb = 3.49 local ub = 4.99 } else if `k' == 8 { local lb = 3.36 local ub = 4.89 } else if `k' == 9 { local lb = 3.24 local ub = 4.80 } else if `k' == 10 { local lb = 3.13 local ub = 4.73 } else { local lb = . local ub = . } } else { local lb = . local ub = . } } else { local lb = . local ub = . } * Small-sample adjustment using Narayan (2005) for T < 80 if `nobs' < 80 & `nobs' >= 30 { * Narayan (2005) small-sample critical values, Case III, 5% * Approximate scaling factor based on sample size local scale = 1 + (80 - `nobs') * 0.005 local lb = `lb' * `scale' local ub = `ub' * `scale' } return scalar lb = `lb' return scalar ub = `ub' end * ============================================================================ * PSS ECR t-BOUNDS CRITICAL VALUES * From PSS (2001), Table CII * t-test on the error correction rate (psi_yy) * ============================================================================ capture program drop _lrmbounds_cv_ttest_ecr program define _lrmbounds_cv_ttest_ecr, rclass syntax , K(integer) CASE(integer) ALPHA(real) * Case III: Unrestricted intercept, no trend if `case' == 3 { if `alpha' == 0.10 { if `k' == 1 { local lb = -1.62 local ub = -2.28 } else if `k' == 2 { local lb = -1.62 local ub = -2.68 } else if `k' == 3 { local lb = -1.62 local ub = -3.00 } else if `k' == 4 { local lb = -1.62 local ub = -3.26 } else if `k' == 5 { local lb = -1.62 local ub = -3.49 } else if `k' == 6 { local lb = -1.62 local ub = -3.70 } else if `k' == 7 { local lb = -1.62 local ub = -3.86 } else if `k' == 8 { local lb = -1.62 local ub = -4.04 } else if `k' == 9 { local lb = -1.62 local ub = -4.18 } else if `k' == 10 { local lb = -1.62 local ub = -4.34 } else { local lb = . local ub = . } } else if `alpha' == 0.05 { if `k' == 1 { local lb = -1.95 local ub = -2.60 } else if `k' == 2 { local lb = -1.95 local ub = -3.02 } else if `k' == 3 { local lb = -1.95 local ub = -3.33 } else if `k' == 4 { local lb = -1.95 local ub = -3.60 } else if `k' == 5 { local lb = -1.95 local ub = -3.83 } else if `k' == 6 { local lb = -1.95 local ub = -4.04 } else if `k' == 7 { local lb = -1.95 local ub = -4.21 } else if `k' == 8 { local lb = -1.95 local ub = -4.37 } else if `k' == 9 { local lb = -1.95 local ub = -4.52 } else if `k' == 10 { local lb = -1.95 local ub = -4.66 } else { local lb = . local ub = . } } else if `alpha' == 0.01 { if `k' == 1 { local lb = -2.58 local ub = -3.22 } else if `k' == 2 { local lb = -2.58 local ub = -3.59 } else if `k' == 3 { local lb = -2.58 local ub = -3.93 } else if `k' == 4 { local lb = -2.58 local ub = -4.23 } else if `k' == 5 { local lb = -2.58 local ub = -4.44 } else if `k' == 6 { local lb = -2.58 local ub = -4.67 } else if `k' == 7 { local lb = -2.58 local ub = -4.84 } else if `k' == 8 { local lb = -2.58 local ub = -5.03 } else if `k' == 9 { local lb = -2.58 local ub = -5.17 } else if `k' == 10 { local lb = -2.58 local ub = -5.31 } else { local lb = . local ub = . } } else { local lb = . local ub = . } } * Case V: Unrestricted intercept + unrestricted trend else if `case' == 5 { if `alpha' == 0.10 { if `k' == 1 { local lb = -2.57 local ub = -3.21 } else if `k' == 2 { local lb = -2.57 local ub = -3.53 } else if `k' == 3 { local lb = -2.57 local ub = -3.80 } else if `k' == 4 { local lb = -2.57 local ub = -4.01 } else if `k' == 5 { local lb = -2.57 local ub = -4.21 } else if `k' == 6 { local lb = -2.57 local ub = -4.38 } else if `k' == 7 { local lb = -2.57 local ub = -4.52 } else if `k' == 8 { local lb = -2.57 local ub = -4.66 } else if `k' == 9 { local lb = -2.57 local ub = -4.79 } else if `k' == 10 { local lb = -2.57 local ub = -4.91 } else { local lb = . local ub = . } } else if `alpha' == 0.05 { if `k' == 1 { local lb = -2.86 local ub = -3.53 } else if `k' == 2 { local lb = -2.86 local ub = -3.83 } else if `k' == 3 { local lb = -2.86 local ub = -4.10 } else if `k' == 4 { local lb = -2.86 local ub = -4.34 } else if `k' == 5 { local lb = -2.86 local ub = -4.52 } else if `k' == 6 { local lb = -2.86 local ub = -4.69 } else if `k' == 7 { local lb = -2.86 local ub = -4.85 } else if `k' == 8 { local lb = -2.86 local ub = -4.99 } else if `k' == 9 { local lb = -2.86 local ub = -5.11 } else if `k' == 10 { local lb = -2.86 local ub = -5.23 } else { local lb = . local ub = . } } else if `alpha' == 0.01 { if `k' == 1 { local lb = -3.43 local ub = -4.10 } else if `k' == 2 { local lb = -3.43 local ub = -4.44 } else if `k' == 3 { local lb = -3.43 local ub = -4.73 } else if `k' == 4 { local lb = -3.43 local ub = -4.96 } else if `k' == 5 { local lb = -3.43 local ub = -5.13 } else if `k' == 6 { local lb = -3.43 local ub = -5.31 } else if `k' == 7 { local lb = -3.43 local ub = -5.46 } else if `k' == 8 { local lb = -3.43 local ub = -5.60 } else if `k' == 9 { local lb = -3.43 local ub = -5.72 } else if `k' == 10 { local lb = -3.43 local ub = -5.84 } else { local lb = . local ub = . } } else { local lb = . local ub = . } } else { local lb = . local ub = . } return scalar lb = abs(`lb') return scalar ub = abs(`ub') end * ============================================================================ * WEBB, LINN & LEBO (2019) LRM t-BOUNDS CRITICAL VALUES * From Tables 3–6 in "A Bounds Approach to Inference Using the * Long Run Multiplier" (Political Analysis, 27(3), 2019) * * Table 6 provides the COMBINED bounds given uncertainty about * deterministic features of the DGP: * Lower bound = from DGPs with deterministics (y has trend/constant) * Upper bound = from DGPs without deterministics (pure random walk) * * Values computed via 100,000 MC replications for the LRM t-statistic * in the Bewley IV regression. * * CRITICAL: These are the KEY INNOVATION of Webb et al. (2019). * The bounds are for the absolute value of the t-statistic on each LRM. * ============================================================================ capture program drop _lrmbounds_cv_lrm program define _lrmbounds_cv_lrm, rclass syntax , K(integer) NOBS(integer) ALPHA(real) * k = number of independent variables (regressors) * nobs = effective sample size T * alpha = significance level (0.01, 0.05, 0.10) * Webb et al. (2019) Table 6: Combined bounds * Format: lower_bound upper_bound * Lower bound: minimum |t| needed when all x are I(0) * Upper bound: minimum |t| needed when all x are I(1) * ----------------------------------------------------------------------- * alpha = 0.05 (5% significance level) * From Webb (2019) Table 6 * ----------------------------------------------------------------------- if `alpha' == 0.05 { if `nobs' <= 40 { * T = 30 if `k' == 1 { local lb = 1.05 local ub = 3.69 } else if `k' == 2 { local lb = 1.05 local ub = 3.68 } else if `k' == 3 { local lb = 1.05 local ub = 3.62 } else if `k' == 4 { local lb = 1.05 local ub = 3.61 } else if `k' == 5 { local lb = 1.05 local ub = 3.60 } else { local lb = 1.05 local ub = 3.60 } } else if `nobs' <= 65 { * T = 50 if `k' == 1 { local lb = 1.01 local ub = 3.69 } else if `k' == 2 { local lb = 1.01 local ub = 3.63 } else if `k' == 3 { local lb = 1.01 local ub = 3.63 } else if `k' == 4 { local lb = 1.01 local ub = 3.59 } else if `k' == 5 { local lb = 1.01 local ub = 3.58 } else { local lb = 1.01 local ub = 3.58 } } else { * T >= 80 (asymptotic) if `k' == 1 { local lb = 0.998 local ub = 3.65 } else if `k' == 2 { local lb = 0.998 local ub = 3.60 } else if `k' == 3 { local lb = 0.998 local ub = 3.65 } else if `k' == 4 { local lb = 0.998 local ub = 3.61 } else if `k' == 5 { local lb = 0.998 local ub = 3.60 } else { local lb = 0.998 local ub = 3.60 } } } * ----------------------------------------------------------------------- * alpha = 0.10 (10% significance level) * Interpolated from Webb (2019) Tables 3-5 * ----------------------------------------------------------------------- else if `alpha' == 0.10 { if `nobs' <= 40 { if `k' == 1 { local lb = 0.68 local ub = 3.06 } else if `k' == 2 { local lb = 0.68 local ub = 3.03 } else if `k' == 3 { local lb = 0.68 local ub = 2.99 } else if `k' == 4 { local lb = 0.68 local ub = 2.97 } else if `k' == 5 { local lb = 0.68 local ub = 2.96 } else { local lb = 0.68 local ub = 2.96 } } else if `nobs' <= 65 { if `k' == 1 { local lb = 0.66 local ub = 3.04 } else if `k' == 2 { local lb = 0.66 local ub = 3.01 } else if `k' == 3 { local lb = 0.66 local ub = 2.99 } else if `k' == 4 { local lb = 0.66 local ub = 2.96 } else if `k' == 5 { local lb = 0.66 local ub = 2.95 } else { local lb = 0.66 local ub = 2.95 } } else { if `k' == 1 { local lb = 0.65 local ub = 3.02 } else if `k' == 2 { local lb = 0.65 local ub = 2.99 } else if `k' == 3 { local lb = 0.65 local ub = 2.98 } else if `k' == 4 { local lb = 0.65 local ub = 2.95 } else if `k' == 5 { local lb = 0.65 local ub = 2.94 } else { local lb = 0.65 local ub = 2.94 } } } * ----------------------------------------------------------------------- * alpha = 0.01 (1% significance level) * Interpolated from Webb (2019) Tables 3-5 * ----------------------------------------------------------------------- else if `alpha' == 0.01 { if `nobs' <= 40 { if `k' == 1 { local lb = 1.72 local ub = 4.96 } else if `k' == 2 { local lb = 1.72 local ub = 4.92 } else if `k' == 3 { local lb = 1.72 local ub = 4.88 } else if `k' == 4 { local lb = 1.72 local ub = 4.85 } else if `k' == 5 { local lb = 1.72 local ub = 4.82 } else { local lb = 1.72 local ub = 4.82 } } else if `nobs' <= 65 { if `k' == 1 { local lb = 1.68 local ub = 4.94 } else if `k' == 2 { local lb = 1.68 local ub = 4.90 } else if `k' == 3 { local lb = 1.68 local ub = 4.86 } else if `k' == 4 { local lb = 1.68 local ub = 4.82 } else if `k' == 5 { local lb = 1.68 local ub = 4.80 } else { local lb = 1.68 local ub = 4.80 } } else { if `k' == 1 { local lb = 1.65 local ub = 4.90 } else if `k' == 2 { local lb = 1.65 local ub = 4.86 } else if `k' == 3 { local lb = 1.65 local ub = 4.84 } else if `k' == 4 { local lb = 1.65 local ub = 4.80 } else if `k' == 5 { local lb = 1.65 local ub = 4.78 } else { local lb = 1.65 local ub = 4.78 } } } else { local lb = . local ub = . } return scalar lb = `lb' return scalar ub = `ub' end * ============================================================================ * WEBB (2020) / WEBB (2019) TABLE 6: COMBINED LRM BOUNDS * Used in the empirical applications of the 2020 paper * Sample-size-specific values from Tables 3-5 aggregated * The 2020 paper uses same critical values from 2019 * ============================================================================ capture program drop _lrmbounds_cv_lrm_lookup program define _lrmbounds_cv_lrm_lookup, rclass syntax , K(integer) NOBS(integer) [ALPHA(real 0.05)] * Get the appropriate bounds _lrmbounds_cv_lrm, k(`k') nobs(`nobs') alpha(`alpha') local lb = r(lb) local ub = r(ub) return scalar lb = `lb' return scalar ub = `ub' return scalar nobs = `nobs' return scalar k = `k' return scalar alpha = `alpha' end * ============================================================================== * FROM: _lrmbounds_estimate.ado * ============================================================================== program define _lrmbounds_estimate, rclass syntax varlist(ts min=2) [if] [in], [ /// MAXLAG(integer 4) /// LAGSEL(string) /// LAGS(numlist integer >=0) /// ARDL(numlist integer >=0) /// TREND /// NOCONStant /// ROBUST /// ] marksample touse * Parse dependent and independent variables gettoken depvar indepvars : varlist local nindep : word count `indepvars' if `nindep' < 1 { di as err "At least one independent variable required" exit 198 } * Lag selection if "`lagsel'" == "" local lagsel "bic" local lagsel = lower("`lagsel'") * ================================================================ * Determine lag orders: ARDL(p, q1, q2, ...) * ================================================================ if "`ardl'" != "" { * User specified ARDL(p, q1, q2, ...) directly local nardl : word count `ardl' local p_lag : word 1 of `ardl' local ardl_spec "`p_lag'" local j = 0 foreach xv of local indepvars { local ++j local jj = `j' + 1 if `jj' <= `nardl' { local q_`j' : word `jj' of `ardl' } else { local q_`j' = `p_lag' } local ardl_spec "`ardl_spec',`q_`j''" } } else if "`lags'" != "" { * Single lag for all: lags(p) local p_lag : word 1 of `lags' local ardl_spec "`p_lag'" forvalues j = 1/`nindep' { local q_`j' = `p_lag' local ardl_spec "`ardl_spec',`p_lag'" } } else { * Automatic lag selection via IC local bestlag = 1 local bestic = . forvalues p = 1/`maxlag' { local reglist "" local reglist "`reglist' L.`depvar'" foreach xv of local indepvars { local reglist "`reglist' L.`xv'" } foreach xv of local indepvars { local reglist "`reglist' D.`xv'" } if `p' > 1 { forvalues i = 1/`=`p'-1' { local reglist "`reglist' L`i'D.`depvar'" foreach xv of local indepvars { local reglist "`reglist' L`i'D.`xv'" } } } if "`trend'" != "" { tempvar tvar qui gen double `tvar' = _n if `touse' local reglist "`reglist' `tvar'" } capture qui regress D.`depvar' `reglist' if `touse', `noconstant' if _rc continue local N = e(N) local k_est = e(rank) local rss = e(rss) if "`lagsel'" == "aic" { local ic = `N' * ln(`rss'/`N') + 2 * `k_est' } else { local ic = `N' * ln(`rss'/`N') + ln(`N') * `k_est' } if missing(`bestic') | (`ic' < `bestic') { local bestic = `ic' local bestlag = `p' } } local p_lag = `bestlag' local ardl_spec "`p_lag'" forvalues j = 1/`nindep' { local q_`j' = `p_lag' local ardl_spec "`ardl_spec',`p_lag'" } } * Compute maxq for display local maxq = `p_lag' forvalues j = 1/`nindep' { if `q_`j'' > `maxq' local maxq = `q_`j'' } * ================================================================ * Construct regressor list with per-variable lags * ================================================================ local ecm_levels "" local ecm_diffs "" local allregs "" * Lagged levels: y_{t-1} and x_{t-1} local ecm_levels "L.`depvar'" foreach xv of local indepvars { local ecm_levels "`ecm_levels' L.`xv'" } * Short-run: contemporaneous Dx_t for each x (if q_j >= 0) local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' >= 0 { local ecm_diffs "`ecm_diffs' D.`xv'" } } * Lagged differences of y: LD.y_{t-1}, ..., L(p-1)D.y for ARDL p if `p_lag' > 1 { forvalues i = 1/`=`p_lag'-1' { local ecm_diffs "`ecm_diffs' L`i'D.`depvar'" } } * Lagged differences of each x: L1D.x, ..., L(q_j-1)D.x local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' > 1 { forvalues i = 1/`=`q_`j''-1' { local ecm_diffs "`ecm_diffs' L`i'D.`xv'" } } } * Trend variable local trendvar "" if "`trend'" != "" { tempvar tvar qui gen double `tvar' = _n if `touse' local trendvar "`tvar'" } * Full regressor list local allregs "`ecm_levels' `ecm_diffs' `trendvar'" * ================================================================ * Estimate the ECM by OLS * ================================================================ if "`robust'" != "" { qui regress D.`depvar' `allregs' if `touse', `noconstant' vce(robust) } else { qui regress D.`depvar' `allregs' if `touse', `noconstant' } * Save estimation results local N = e(N) local k_total = e(rank) local r2 = e(r2) local r2_a = e(r2_a) local rmse = e(rmse) local rss = e(rss) local F_model = e(F) local ll = e(ll) * ECR local ecr = _b[L.`depvar'] local ecr_se = _se[L.`depvar'] local ecr_t = `ecr' / `ecr_se' local ecr_p = 2 * ttail(`N' - `k_total', abs(`ecr_t')) * Psi_yx local j = 0 foreach xv of local indepvars { local ++j local psi_yx_`j' = _b[L.`xv'] local psi_yx_se_`j' = _se[L.`xv'] local psi_yx_t_`j' = `psi_yx_`j'' / `psi_yx_se_`j'' local psi_yx_p_`j' = 2 * ttail(`N' - `k_total', abs(`psi_yx_t_`j'')) } * Delta method LRMs tempname b V matrix `b' = e(b) matrix `V' = e(V) local j = 0 foreach xv of local indepvars { local ++j local lrm_`j' = -`psi_yx_`j'' / `ecr' local pos_yy = colnumb(`b', "L.`depvar'") local pos_xj = colnumb(`b', "L.`xv'") local var_yy = `V'[`pos_yy', `pos_yy'] local var_xj = `V'[`pos_xj', `pos_xj'] local cov_xy = `V'[`pos_xj', `pos_yy'] local g1 = -1 / `ecr' local g2 = `psi_yx_`j'' / (`ecr' * `ecr') local var_lrm = `g1'^2 * `var_xj' + `g2'^2 * `var_yy' + 2 * `g1' * `g2' * `cov_xy' local lrm_se_`j' = sqrt(`var_lrm') local lrm_t_`j' = `lrm_`j'' / `lrm_se_`j'' local lrm_p_`j' = 2 * ttail(`N' - `k_total', abs(`lrm_t_`j'')) } * Return results return scalar N = `N' return scalar k = `nindep' return scalar optlag = `p_lag' return scalar r2 = `r2' return scalar r2_a = `r2_a' return scalar rmse = `rmse' return scalar rss = `rss' return scalar ll = `ll' return scalar F_model = `F_model' return scalar k_total = `k_total' return scalar ecr = `ecr' return scalar ecr_se = `ecr_se' return scalar ecr_t = `ecr_t' return scalar ecr_p = `ecr_p' return scalar nindep = `nindep' local j = 0 foreach xv of local indepvars { local ++j return scalar lrm_`j' = `lrm_`j'' return scalar lrm_se_`j' = `lrm_se_`j'' return scalar lrm_t_`j' = `lrm_t_`j'' return scalar lrm_p_`j' = `lrm_p_`j'' return scalar psi_yx_`j' = `psi_yx_`j'' return scalar psi_yx_se_`j' = `psi_yx_se_`j'' return scalar q_`j' = `q_`j'' return local xvar_`j' "`xv'" } return local depvar "`depvar'" return local indepvars "`indepvars'" return local ecm_levels "`ecm_levels'" return local ecm_diffs "`ecm_diffs'" return local allregs "`allregs'" return local trendvar "`trendvar'" return local lagsel "`lagsel'" return local hasrobust "`robust'" return local hastrend "`trend'" return local ardl_spec "`ardl_spec'" end * ============================================================================== * FROM: _lrmbounds_ftest.ado * ============================================================================== program define _lrmbounds_ftest, rclass syntax varlist(ts min=2) [if] [in], [ /// OPTLAG(integer 1) /// ARDLSPEC(string) /// TREND /// NOCONStant /// ROBUST /// ] marksample touse gettoken depvar indepvars : varlist local nindep : word count `indepvars' * Parse ARDL spec if "`ardlspec'" != "" { local nspec : word count `ardlspec' local p_lag : word 1 of `ardlspec' local j = 0 foreach xv of local indepvars { local ++j local jj = `j' + 1 if `jj' <= `nspec' { local q_`j' : word `jj' of `ardlspec' } else { local q_`j' = `p_lag' } } } else { local p_lag = `optlag' forvalues j = 1/`nindep' { local q_`j' = `optlag' } } * Build regressor lists using per-variable lags local ecm_levels "L.`depvar'" foreach xv of local indepvars { local ecm_levels "`ecm_levels' L.`xv'" } local ecm_diffs "" local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' >= 0 { local ecm_diffs "`ecm_diffs' D.`xv'" } } if `p_lag' > 1 { forvalues i = 1/`=`p_lag'-1' { local ecm_diffs "`ecm_diffs' L`i'D.`depvar'" } } local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' > 1 { forvalues i = 1/`=`q_`j''-1' { local ecm_diffs "`ecm_diffs' L`i'D.`xv'" } } } local trendvar "" if "`trend'" != "" { tempvar tvar qui gen double `tvar' = _n if `touse' local trendvar "`tvar'" } local allregs "`ecm_levels' `ecm_diffs' `trendvar'" * Unrestricted model if "`robust'" != "" { qui regress D.`depvar' `allregs' if `touse', `noconstant' vce(robust) } else { qui regress D.`depvar' `allregs' if `touse', `noconstant' } local N_u = e(N) local k_u = e(rank) local rss_u = e(rss) local df_u = e(df_r) local ecr = _b[L.`depvar'] local ecr_se = _se[L.`depvar'] local ecr_t = `ecr' / `ecr_se' * Restricted model (drop all lagged levels) if "`robust'" != "" { qui regress D.`depvar' `ecm_diffs' `trendvar' if `touse', `noconstant' vce(robust) } else { qui regress D.`depvar' `ecm_diffs' `trendvar' if `touse', `noconstant' } local rss_r = e(rss) local k_r = e(rank) * F-statistic local q = `nindep' + 1 local F_pss = ((`rss_r' - `rss_u') / `q') / (`rss_u' / `df_u') * Critical values local case = cond("`trend'" != "", 5, 3) foreach alpha in 0.01 0.05 0.10 { _lrmbounds_cv_ftest, k(`nindep') case(`case') alpha(`alpha') nobs(`N_u') local f_lb_`=100*`alpha'' = r(lb) local f_ub_`=100*`alpha'' = r(ub) } if `F_pss' > `f_ub_5' { local f_decision "Reject H0: Evidence of a level relationship" local f_dcode "reject" } else if `F_pss' < `f_lb_5' { local f_decision "Fail to Reject H0: No evidence of a level relationship" local f_dcode "fail" } else { local f_decision "Inconclusive: F-statistic falls between bounds" local f_dcode "inconclusive" } * ECR t-test foreach alpha in 0.01 0.05 0.10 { _lrmbounds_cv_ttest_ecr, k(`nindep') case(`case') alpha(`alpha') local t_lb_`=100*`alpha'' = r(lb) local t_ub_`=100*`alpha'' = r(ub) } if abs(`ecr_t') > `t_ub_5' { local t_decision "Reject H0: Significant error correction" local t_dcode "reject" } else if abs(`ecr_t') < `t_lb_5' { local t_decision "Fail to Reject H0: No error correction" local t_dcode "fail" } else { local t_decision "Inconclusive" local t_dcode "inconclusive" } * Degenerate equilibrium check local ecr_sig = (abs(`ecr_t') > `t_ub_5') local any_psi_sig = 0 local j = 0 foreach xv of local indepvars { local ++j qui regress D.`depvar' `allregs' if `touse', `noconstant' local psi_t = _b[L.`xv'] / _se[L.`xv'] local psi_p = 2 * ttail(`df_u', abs(`psi_t')) if `psi_p' < 0.05 local any_psi_sig = 1 } if "`f_dcode'" == "reject" { if `ecr_sig' & `any_psi_sig' { local equil_type "Nondegenerate: Valid long-run equilibrium (H_A3)" local equil_code "valid" } else if !`ecr_sig' & `any_psi_sig' { local equil_type "Degenerate: Nonsense equilibrium (H_A1)" local equil_code "nonsense" } else if `ecr_sig' & !`any_psi_sig' { local equil_type "Degenerate: y is independent of x (H_A2)" local equil_code "degenerate" } else { local equil_type "Undefined: Neither psi_yy nor psi_yx significant" local equil_code "undefined" } } else { local equil_type "N/A (F-test null not rejected)" local equil_code "na" } * Returns return scalar F_pss = `F_pss' return scalar N = `N_u' return scalar q = `q' return scalar case = `case' return scalar df_u = `df_u' return scalar rss_u = `rss_u' return scalar rss_r = `rss_r' return scalar f_lb_1 = `f_lb_1' return scalar f_ub_1 = `f_ub_1' return scalar f_lb_5 = `f_lb_5' return scalar f_ub_5 = `f_ub_5' return scalar f_lb_10 = `f_lb_10' return scalar f_ub_10 = `f_ub_10' return scalar ecr = `ecr' return scalar ecr_se = `ecr_se' return scalar ecr_t = `ecr_t' return scalar t_lb_1 = `t_lb_1' return scalar t_ub_1 = `t_ub_1' return scalar t_lb_5 = `t_lb_5' return scalar t_ub_5 = `t_ub_5' return scalar t_lb_10 = `t_lb_10' return scalar t_ub_10 = `t_ub_10' return local f_decision "`f_decision'" return local f_dcode "`f_dcode'" return local t_decision "`t_decision'" return local t_dcode "`t_dcode'" return local equil_type "`equil_type'" return local equil_code "`equil_code'" end * ============================================================================== * FROM: _lrmbounds_lrm.ado * ============================================================================== program define _lrmbounds_lrm, rclass syntax varlist(ts min=2) [if] [in], [ /// OPTLAG(integer 1) /// ARDLSPEC(string) /// TREND /// NOCONStant /// ROBUST /// BEWLEY /// ALPHA(real 0.05) /// ] marksample touse gettoken depvar indepvars : varlist local nindep : word count `indepvars' * Parse ARDL spec if "`ardlspec'" != "" { local nspec : word count `ardlspec' local p_lag : word 1 of `ardlspec' local j = 0 foreach xv of local indepvars { local ++j local jj = `j' + 1 if `jj' <= `nspec' { local q_`j' : word `jj' of `ardlspec' } else { local q_`j' = `p_lag' } } } else { local p_lag = `optlag' forvalues j = 1/`nindep' { local q_`j' = `optlag' } } * Build regressor lists local ecm_levels "L.`depvar'" foreach xv of local indepvars { local ecm_levels "`ecm_levels' L.`xv'" } local ecm_diffs "" local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' >= 0 { local ecm_diffs "`ecm_diffs' D.`xv'" } } if `p_lag' > 1 { forvalues i = 1/`=`p_lag'-1' { local ecm_diffs "`ecm_diffs' L`i'D.`depvar'" } } local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' > 1 { forvalues i = 1/`=`q_`j''-1' { local ecm_diffs "`ecm_diffs' L`i'D.`xv'" } } } local trendvar "" if "`trend'" != "" { tempvar tvar qui gen double `tvar' = _n if `touse' local trendvar "`tvar'" } local allregs "`ecm_levels' `ecm_diffs' `trendvar'" * Estimate ECM if "`robust'" != "" { qui regress D.`depvar' `allregs' if `touse', `noconstant' vce(robust) } else { qui regress D.`depvar' `allregs' if `touse', `noconstant' } local N = e(N) local k_total = e(rank) local df_r = e(df_r) tempname b V matrix `b' = e(b) matrix `V' = e(V) local ecr = _b[L.`depvar'] local ecr_se = _se[L.`depvar'] * Delta method LRMs local j = 0 foreach xv of local indepvars { local ++j local psi_yx = _b[L.`xv'] local psi_yx_se = _se[L.`xv'] local lrm = -`psi_yx' / `ecr' local pos_yy = colnumb(`b', "L.`depvar'") local pos_xj = colnumb(`b', "L.`xv'") local var_yy = `V'[`pos_yy', `pos_yy'] local var_xj = `V'[`pos_xj', `pos_xj'] local cov_xy = `V'[`pos_xj', `pos_yy'] local g1 = -1 / `ecr' local g2 = `psi_yx' / (`ecr' * `ecr') local var_lrm = `g1'^2 * `var_xj' + `g2'^2 * `var_yy' + 2 * `g1' * `g2' * `cov_xy' local lrm_se = sqrt(max(`var_lrm', 0)) local lrm_t = `lrm' / `lrm_se' local dm_lrm_`j' = `lrm' local dm_se_`j' = `lrm_se' local dm_t_`j' = `lrm_t' } * Bewley IV (if requested) * Bewley (1979) regression: y_t = theta*x_t + ... + u_t * where x_t in levels is instrumented by D.x_t and D.y_t * Webb (2019) eq. 13-14 local bewley_ok = 0 if "`bewley'" != "" { * Exogenous regressors (included instruments) local bw_exog "" if `p_lag' > 1 { forvalues i = 1/`=`p_lag'-1' { local bw_exog "`bw_exog' L`i'D.`depvar'" } } local j = 0 foreach xv of local indepvars { local ++j if `q_`j'' > 1 { forvalues i = 1/`=`q_`j''-1' { local bw_exog "`bw_exog' L`i'D.`xv'" } } } if "`trend'" != "" { local bw_exog "`bw_exog' `trendvar'" } * Excluded instruments for x_t levels: D.x_t and D.y_t local bw_excluded "D.`depvar'" foreach xv of local indepvars { local bw_excluded "`bw_excluded' D.`xv'" } capture qui ivregress 2sls `depvar' `bw_exog' /// (`indepvars' = `bw_excluded') if `touse' if !_rc { local j = 0 foreach xv of local indepvars { local ++j local bw_lrm_`j' = _b[`xv'] local bw_se_`j' = _se[`xv'] local bw_t_`j' = `bw_lrm_`j'' / `bw_se_`j'' } local bewley_ok = 1 } } * Webb bounds _lrmbounds_cv_lrm_lookup, k(`nindep') nobs(`N') alpha(`alpha') local cv_lb = r(lb) local cv_ub = r(ub) foreach a in 0.01 0.05 0.10 { _lrmbounds_cv_lrm_lookup, k(`nindep') nobs(`N') alpha(`a') local cv_lb_`=100*`a'' = r(lb) local cv_ub_`=100*`a'' = r(ub) } * Decisions local j = 0 foreach xv of local indepvars { local ++j if `bewley_ok' { local use_t = abs(`bw_t_`j'') } else { local use_t = abs(`dm_t_`j'') } if `use_t' > `cv_ub' { local lrm_decision_`j' "Reject H0: Significant LRR" local lrm_dcode_`j' "reject" } else if `use_t' < `cv_lb' { local lrm_decision_`j' "Fail to Reject H0: No LRR" local lrm_dcode_`j' "fail" } else { local lrm_decision_`j' "Inconclusive" local lrm_dcode_`j' "inconclusive" } } * Returns return scalar N = `N' return scalar k = `nindep' return scalar alpha = `alpha' return scalar ecr = `ecr' return scalar ecr_se = `ecr_se' return scalar cv_lb = `cv_lb' return scalar cv_ub = `cv_ub' return scalar cv_lb_1 = `cv_lb_1' return scalar cv_ub_1 = `cv_ub_1' return scalar cv_lb_5 = `cv_lb_5' return scalar cv_ub_5 = `cv_ub_5' return scalar cv_lb_10 = `cv_lb_10' return scalar cv_ub_10 = `cv_ub_10' local j = 0 foreach xv of local indepvars { local ++j return scalar dm_lrm_`j' = `dm_lrm_`j'' return scalar dm_se_`j' = `dm_se_`j'' return scalar dm_t_`j' = `dm_t_`j'' if `bewley_ok' { return scalar bw_lrm_`j' = `bw_lrm_`j'' return scalar bw_se_`j' = `bw_se_`j'' return scalar bw_t_`j' = `bw_t_`j'' } return local lrm_decision_`j' "`lrm_decision_`j''" return local lrm_dcode_`j' "`lrm_dcode_`j''" return local xvar_`j' "`xv'" } return scalar bewley_ok = `bewley_ok' return local depvar "`depvar'" return local indepvars "`indepvars'" end * ============================================================================== * FROM: _lrmbounds_graph.ado * ============================================================================== program define _lrmbounds_graph syntax , [ /// DEPVAR(string) /// INDEPVARS(string) /// NINDEP(integer 1) /// OPTLAG(integer 1) /// TREND /// NOCONStant /// GRAPHDIR(string) /// FPSS(real 0) /// FLB(real 0) /// FUB(real 0) /// ECRATE(real 0) /// NOBS(integer 100) /// ] if "`graphdir'" == "" local graphdir "lrmbounds_graphs" capture mkdir "`graphdir'" * ================================================================ * Graph 1: LRM Forest Plot with Bounds Decision * Shows each LRM with CI, color-coded by bounds decision * ================================================================ capture { * Re-estimate to get results in memory local ecm_levels "L.`depvar'" foreach xv of local indepvars { local ecm_levels "`ecm_levels' L.`xv'" } local ecm_diffs "" foreach xv of local indepvars { local ecm_diffs "`ecm_diffs' D.`xv'" } if `optlag' > 1 { forvalues i = 1/`=`optlag'-1' { local ecm_diffs "`ecm_diffs' L`i'D.`depvar'" foreach xv of local indepvars { local ecm_diffs "`ecm_diffs' L`i'D.`xv'" } } } local trendvar "" if "`trend'" != "" { tempvar tvar qui gen double `tvar' = _n local trendvar "`tvar'" } qui regress D.`depvar' `ecm_levels' `ecm_diffs' `trendvar', `noconstant' local ecrate_val = _b[L.`depvar'] * Build dataset for forest plot tempname bmat Vmat matrix `bmat' = e(b) matrix `Vmat' = e(V) preserve clear qui set obs `nindep' qui gen str32 varname = "" qui gen double lrm = . qui gen double lrm_se = . qui gen double lrm_lo = . qui gen double lrm_hi = . qui gen double lrm_t = . qui gen int order = _n local j = 0 foreach xv of local indepvars { local ++j local psi_yx = `bmat'[1, colnumb(`bmat', "L.`xv'")] local pos_yy = colnumb(`bmat', "L.`depvar'") local pos_xj = colnumb(`bmat', "L.`xv'") local lrm_val = -`psi_yx' / `ecrate_val' local var_yy = `Vmat'[`pos_yy', `pos_yy'] local var_xj = `Vmat'[`pos_xj', `pos_xj'] local cov_xy = `Vmat'[`pos_xj', `pos_yy'] local g1 = -1 / `ecrate_val' local g2 = `psi_yx' / (`ecrate_val' * `ecrate_val') local var_lrm = `g1'^2 * `var_xj' + `g2'^2 * `var_yy' + 2 * `g1' * `g2' * `cov_xy' local se_val = sqrt(max(`var_lrm', 0)) qui replace varname = "`xv'" in `j' qui replace lrm = `lrm_val' in `j' qui replace lrm_se = `se_val' in `j' qui replace lrm_lo = `lrm_val' - 1.96 * `se_val' in `j' qui replace lrm_hi = `lrm_val' + 1.96 * `se_val' in `j' qui replace lrm_t = abs(`lrm_val' / `se_val') in `j' } * Create forest plot twoway (rcap lrm_lo lrm_hi order, horizontal lcolor("55 71 133") lwidth(medthick)) /// (scatter order lrm, msymbol(D) msize(large) mcolor("220 95 60") mlcolor("55 71 133") mlwidth(thin)), /// yline(0, lcolor(gs10) lpattern(dash)) /// ylabel(1(1)`nindep', valuelabel angle(0) labsize(small)) /// xlabel(, labsize(small) grid glcolor(gs14)) /// ytitle("") xtitle("Long Run Multiplier (LRM)", size(medsmall)) /// title("{bf:Long-Run Multiplier Estimates}", size(medlarge), size(medium) color("55 71 133")) /// subtitle("Webb, Linn & Lebo (2019) Bounds Approach", size(small) color(gs6)) /// legend(off) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_forest, replace) qui graph export "`graphdir'/lrm_forest_plot.png", as(png) width(2400) height(1600) replace restore } * ================================================================ * Graph 2: PSS F-Bounds Comparison Chart * Bar showing F-stat against I(0)/I(1) bounds with colored zones * ================================================================ capture { preserve clear qui set obs 100 qui gen double x = _n / 10 qui gen double zone_below = 0 qui gen double zone_between = 0 qui gen double zone_above = 0 qui replace zone_below = 1 if x <= `flb' qui replace zone_between = 1 if x > `flb' & x <= `fub' qui replace zone_above = 1 if x > `fub' * Create the bar chart twoway (area zone_below x if zone_below == 1, color("144 190 109%40") base(0)) /// (area zone_between x if zone_between == 1, color("255 193 37%40") base(0)) /// (area zone_above x if zone_above == 1, color("220 95 60%40") base(0)) /// (pci 0 `fpss' 1 `fpss', lcolor("55 71 133") lwidth(thick) lpattern(solid)), /// xline(`flb', lcolor("144 190 109") lwidth(medthick) lpattern(dash)) /// xline(`fub', lcolor("220 95 60") lwidth(medthick) lpattern(dash)) /// xtitle("F-statistic", size(medsmall)) ytitle("") /// title("{bf:PSS Bounds Test}", size(medium) color("55 71 133")) /// subtitle("F-statistic vs. Critical Value Bounds (5%)", size(small) color(gs6)) /// ylabel(none) /// xlabel(, labsize(small) grid glcolor(gs14)) /// text(0.9 `flb' "I(0) = `: display %5.3f `flb''", place(w) size(vsmall) color("144 190 109")) /// text(0.9 `fub' "I(1) = `: display %5.3f `fub''", place(e) size(vsmall) color("220 95 60")) /// text(0.5 `fpss' "F = `: display %6.3f `fpss''", place(e) size(small) color("55 71 133")) /// legend(off) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_fbounds, replace) qui graph export "`graphdir'/pss_fbounds.png", as(png) width(2400) height(1200) replace restore } * ================================================================ * Graph 3: Actual vs Fitted with Residuals * ================================================================ capture { qui regress D.`depvar' `ecm_levels' `ecm_diffs' `trendvar', `noconstant' tempvar fitted resid qui predict double `fitted', xb qui predict double `resid', residuals qui tsset local timevar "`r(timevar)'" twoway (tsline D.`depvar', lcolor("55 71 133") lwidth(medthick)) /// (tsline `fitted', lcolor("220 95 60") lwidth(medium) lpattern(dash)), /// ytitle("D.`depvar'", size(medsmall)) /// xtitle("", size(medsmall)) /// title("{bf:Actual vs. ECM-Fitted Values}", size(medlarge), size(medium) color("55 71 133")) /// subtitle("Conditional ECM: D.`depvar'", size(small) color(gs6)) /// legend(order(1 "Actual" 2 "Fitted") rows(1) position(6) size(small) /// region(lcolor(gs14) fcolor(white))) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_actual_fit, replace) qui graph export "`graphdir'/actual_vs_fitted.png", as(png) width(2400) height(1400) replace } * ================================================================ * Graph 4: Residual Diagnostics Panel * Residual time series + histogram + QQ plot * ================================================================ capture { * Residual time series twoway (tsline `resid', lcolor("55 71 133") lwidth(medium)), /// yline(0, lcolor("220 95 60") lwidth(thin) lpattern(dash)) /// ytitle("Residuals", size(medsmall)) /// xtitle("", size(medsmall)) /// title("{bf:ECM Residuals}", size(medium) color("55 71 133")) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_resid_ts, replace) * Histogram histogram `resid', /// fcolor("55 71 133%60") lcolor("55 71 133") /// normal normopts(lcolor("220 95 60") lwidth(medthick)) /// xtitle("Residuals", size(medsmall)) /// ytitle("Density", size(medsmall)) /// title("{bf:Residual Distribution}", size(medium) color("55 71 133")) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_resid_hist, replace) * QQ plot qnorm `resid', /// msymbol(O) msize(small) mcolor("55 71 133") /// rlopts(lcolor("220 95 60") lwidth(medthick)) /// xtitle("Theoretical Quantiles", size(medsmall)) /// ytitle("Sample Quantiles", size(medsmall)) /// title("{bf:Normal Q-Q Plot}", size(medium) color("55 71 133")) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_resid_qq, replace) * Combine into panel graph combine _lrm_resid_ts _lrm_resid_hist _lrm_resid_qq, /// cols(3) /// title("{bf:Residual Diagnostics Panel}", size(medium) color("55 71 133")) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// name(_lrm_resid_panel, replace) qui graph export "`graphdir'/residual_diagnostics.png", as(png) width(3600) height(1200) replace } * ================================================================ * Graph 5: CUSUM Stability Plot * ================================================================ capture { qui regress D.`depvar' `ecm_levels' `ecm_diffs' `trendvar', `noconstant' tempvar resid2 qui predict double `resid2', residuals local N = e(N) local k_p = e(rank) * Compute CUSUM tempvar cusum cusum_upper cusum_lower obs_id qui gen double `cusum' = . qui gen double `obs_id' = _n * Standardize residuals qui sum `resid2' local sigma = r(sd) * Recursive CUSUM local cumsum = 0 forvalues i = 1/`N' { local rv = `resid2'[`i'] if !missing(`rv') { local cumsum = `cumsum' + `rv' / `sigma' qui replace `cusum' = `cumsum' in `i' } } * 5% significance boundaries local a = 0.948 qui gen double `cusum_upper' = `a' * sqrt(`N' - `k_p') + 2 * `a' * (`obs_id' - `k_p') / sqrt(`N' - `k_p') qui gen double `cusum_lower' = -`cusum_upper' twoway (tsline `cusum', lcolor("55 71 133") lwidth(medthick)) /// (tsline `cusum_upper', lcolor("220 95 60") lwidth(medium) lpattern(dash)) /// (tsline `cusum_lower', lcolor("220 95 60") lwidth(medium) lpattern(dash)), /// ytitle("CUSUM", size(medsmall)) /// xtitle("", size(medsmall)) /// title("{bf:CUSUM Stability Test}", size(medlarge), size(medium) color("55 71 133")) /// subtitle("5% Significance Boundaries", size(small) color(gs6)) /// legend(order(1 "CUSUM" 2 "5% Boundary") rows(1) position(6) size(small) /// region(lcolor(gs14) fcolor(white))) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_cusum, replace) qui graph export "`graphdir'/cusum_stability.png", as(png) width(2400) height(1400) replace } * ================================================================ * Graph 6: Dynamic Multiplier Plot * Cumulative effect of a unit change in x over time * ================================================================ capture { qui regress D.`depvar' `ecm_levels' `ecm_diffs' `trendvar', `noconstant' local ecrate_dm = _b[L.`depvar'] preserve clear local nperiods = 30 qui set obs `nperiods' qui gen int period = _n - 1 * For each x variable, compute dynamic multiplier local j = 0 foreach xv of local indepvars { local ++j * Short-run effect (contemporaneous): beta_0 = coeff on D.xv capture local sr = `bmat'[1, colnumb(`bmat', "D.`xv'")] if _rc local sr = 0 * Long-run multiplier local psi_yx = `bmat'[1, colnumb(`bmat', "L.`xv'")] local lr = -`psi_yx' / `ecrate_dm' * Dynamic path: cumulative effect converges from sr to lr * Using: M_h = LRM * (1 - (1+ecr)^h) + sr * (1+ecr)^(h-1) qui gen double dm_`j' = . qui replace dm_`j' = 0 in 1 forvalues h = 1/`=`nperiods'-1' { local cum = `lr' * (1 - (1 + `ecrate_dm')^`h') qui replace dm_`j' = `cum' in `=`h'+1' } } * Build plot command local plotcmd "" local legorder "" local colors `" "55 71 133" "220 95 60" "144 190 109" "178 102 178" "70 130 180" "' local j = 0 foreach xv of local indepvars { local ++j local col : word `j' of `colors' if "`col'" == "" local col "55 71 133" local plotcmd "`plotcmd' (line dm_`j' period, lcolor("`col'") lwidth(medthick))" local legorder "`legorder' `j' `" "`xv'" "'" } twoway `plotcmd', /// ytitle("Cumulative Effect", size(medsmall)) /// xtitle("Periods After Shock", size(medsmall)) /// title("{bf:Dynamic Multiplier Paths}", size(medium) color("55 71 133")) /// subtitle("Cumulative effect of a unit change in each x", size(small) color(gs6)) /// xlabel(0(5)30, labsize(small) grid glcolor(gs14)) /// legend(order(`legorder') rows(1) position(6) size(small) /// region(lcolor(gs14) fcolor(white))) /// scheme(s2color) /// graphregion(color(white) margin(medium)) /// plotregion(margin(small)) /// name(_lrm_dynmult, replace) qui graph export "`graphdir'/dynamic_multipliers.png", as(png) width(2400) height(1400) replace restore } di "" di as res " Graphs saved to: `graphdir'/" di as txt " lrm_forest_plot.png — LRM estimates with 95% CI" di as txt " pss_fbounds.png — F-statistic vs. bounds" di as txt " actual_vs_fitted.png — Actual vs. fitted values" di as txt " residual_diagnostics.png — Residual panel (time series, histogram, QQ)" di as txt " cusum_stability.png — CUSUM stability test" di as txt " dynamic_multipliers.png — Dynamic multiplier paths" end * ============================================================================== * MAIN COMMAND * ============================================================================== program define lrmbounds, rclass version 14.0 syntax varlist(ts min=2) [if] [in], [ /// MAXlag(integer 4) /// LAGSEL(string) /// LAGS(numlist integer >=0) /// ARDL(numlist integer >=0) /// TREND /// NOCONStant /// ROBUST /// BEWLEY /// LEVEL(cilevel) /// NOSTARs /// NODIAGnostics /// GRAPH /// GRAPHDir(string) /// ] marksample touse * Parse variables gettoken depvar indepvars : varlist local nindep : word count `indepvars' local stars = ("`nostars'" == "") * Check tsset qui tsset local timevar "`r(timevar)'" * Count effective observations qui count if `touse' local N_total = r(N) di "" di as txt "{hline 90}" di as res _col(4) "{bf:LRMBOUNDS}" _col(20) as txt /// "Bounds Approach to Inference Using the Long Run Multiplier" di as txt _col(20) "Webb, Linn & Lebo (2019, {it:Political Analysis}; 2020, {it:Journal of Politics})" di as txt "{hline 90}" di "" * ================================================================ * TABLE 1: MODEL SPECIFICATION * ================================================================ di as res " {bf:Model Specification}" di as txt " {hline 60}" di as txt " Dependent variable: " as res "`depvar'" di as txt " Independent variables: " as res "`indepvars'" di as txt " Number of regressors: " as res "`nindep'" di as txt " Observations: " as res "`N_total'" if "`trend'" != "" { di as txt " Deterministics: " as res "Unrestricted constant + trend (Case V)" } else { di as txt " Deterministics: " as res "Unrestricted constant (Case III)" } if "`robust'" != "" { di as txt " Standard errors: " as res "Heteroskedasticity-robust (HC1)" } else { di as txt " Standard errors: " as res "OLS" } di "" * ================================================================ * STEP 1: ESTIMATE THE CONDITIONAL ECM * Webb (2019) eq. (5): * Dy_t = c + psi_yy*y_{t-1} + psi_yx*x_{t-1} + delta*Dz_{t-i} + u_t * ================================================================ * Pass ARDL spec if provided local ardl_opt "" if "`ardl'" != "" { local ardl_opt "ardl(`ardl')" } _lrmbounds_estimate `varlist' if `touse', /// maxlag(`maxlag') lagsel("`lagsel'") /// `trend' `noconstant' `robust' /// lags(`lags') `ardl_opt' local optlag = r(optlag) local ardl_spec = r(ardl_spec) local N = r(N) local k = r(k) local r2 = r(r2) local r2_a = r(r2_a) local rmse = r(rmse) local ll = r(ll) local F_model = r(F_model) local k_total = r(k_total) local ecr = r(ecr) local ecr_se = r(ecr_se) local ecr_t = r(ecr_t) local ecr_p = r(ecr_p) local lagsel_used = r(lagsel) * Store LRM values forvalues j = 1/`nindep' { local lrm_`j' = r(lrm_`j') local lrm_se_`j' = r(lrm_se_`j') local lrm_t_`j' = r(lrm_t_`j') local lrm_p_`j' = r(lrm_p_`j') local psi_yx_`j' = r(psi_yx_`j') local psi_yx_se_`j' = r(psi_yx_se_`j') local xvar_`j' = r(xvar_`j') } local ecm_levels = r(ecm_levels) local ecm_diffs = r(ecm_diffs) local allregs = r(allregs) * ================================================================ * TABLE 2: ECM ESTIMATION RESULTS * ================================================================ di as res " {bf:Table 1. Conditional ECM Estimation Results}" di as txt " {hline 86}" di as txt " ARDL order: " as res "(`ardl_spec')" as txt " (selected by " as res upper("`lagsel_used'") as txt ")" /// as txt _col(50) "Obs = " as res "`N'" /// as txt " R² = " as res %6.4f `r2' di as txt _col(50) "F = " as res %8.3f `F_model' /// as txt " RMSE = " as res %8.4f `rmse' di as txt " {hline 86}" * Re-estimate to display full coefficient table local trendvar "" if "`trend'" != "" { tempvar tvar qui gen double `tvar' = _n if `touse' local trendvar "`tvar'" } local ecm_levels_full "L.`depvar'" foreach xv of local indepvars { local ecm_levels_full "`ecm_levels_full' L.`xv'" } local ecm_diffs_full "" foreach xv of local indepvars { local ecm_diffs_full "`ecm_diffs_full' D.`xv'" } if `optlag' > 1 { forvalues i = 1/`=`optlag'-1' { local ecm_diffs_full "`ecm_diffs_full' L`i'D.`depvar'" foreach xv of local indepvars { local ecm_diffs_full "`ecm_diffs_full' L`i'D.`xv'" } } } if "`robust'" != "" { qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse', `noconstant' vce(robust) } else { qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse', `noconstant' } * Display coefficient table local totw = 86 di as txt " " %~14s "Variable" "{c |}" /// %~12s "Coef." %~12s "Std. Err." %~10s "t" %~10s "P>|t|" %~12s "[95% CI]" di as txt " {hline `totw'}" * Panel A: Long-run (ECM) coefficients di as res _col(4) "{it:Long-run coefficients (Lagged levels)}" * ECR (psi_yy) local ci_lo = `ecr' - invttail(`N' - `k_total', 0.025) * `ecr_se' local ci_hi = `ecr' + invttail(`N' - `k_total', 0.025) * `ecr_se' local star "" if `stars' { _lrmbounds_stars `ecr_p' local star "`s(s)'" } di as txt " " %~14s "L.`depvar'" "{c |}" /// as res %11.5f `ecr' " " %11.5f `ecr_se' " " %9.3f `ecr_t' " " %8.4f `ecr_p' /// " " %10.4f `ci_lo' " " %10.4f `ci_hi' "`star'" * psi_yx for each x forvalues j = 1/`nindep' { local b = `psi_yx_`j'' local se = `psi_yx_se_`j'' local t = `b' / `se' local p = 2 * ttail(`N' - `k_total', abs(`t')) local lo = `b' - invttail(`N' - `k_total', 0.025) * `se' local hi = `b' + invttail(`N' - `k_total', 0.025) * `se' local star "" if `stars' { _lrmbounds_stars `p' local star "`s(s)'" } di as txt " " %~14s "L.`xvar_`j''" "{c |}" /// as res %11.5f `b' " " %11.5f `se' " " %9.3f `t' " " %8.4f `p' /// " " %10.4f `lo' " " %10.4f `hi' "`star'" } * Panel B: Short-run coefficients di as txt " {hline `totw'}" di as res _col(4) "{it:Short-run coefficients (First differences)}" * Contemporaneous Dx terms foreach xv of local indepvars { local b = _b[D.`xv'] local se = _se[D.`xv'] if `se' > 0 { local t = `b' / `se' local p = 2 * ttail(`N' - `k_total', abs(`t')) local lo = `b' - invttail(`N' - `k_total', 0.025) * `se' local hi = `b' + invttail(`N' - `k_total', 0.025) * `se' local star "" if `stars' { _lrmbounds_stars `p' local star "`s(s)'" } di as txt " " %~14s "D.`xv'" "{c |}" /// as res %11.5f `b' " " %11.5f `se' " " %9.3f `t' " " %8.4f `p' /// " " %10.4f `lo' " " %10.4f `hi' "`star'" } } * Lagged difference terms if `optlag' > 1 { forvalues i = 1/`=`optlag'-1' { capture local b = _b[L`i'D.`depvar'] if !_rc { local se = _se[L`i'D.`depvar'] if `se' > 0 { local t = `b' / `se' local p = 2 * ttail(`N' - `k_total', abs(`t')) local lo = `b' - invttail(`N' - `k_total', 0.025) * `se' local hi = `b' + invttail(`N' - `k_total', 0.025) * `se' local star "" if `stars' { _lrmbounds_stars `p' local star "`s(s)'" } di as txt " " %~14s "L`i'D.`depvar'" "{c |}" /// as res %11.5f `b' " " %11.5f `se' " " %9.3f `t' " " %8.4f `p' /// " " %10.4f `lo' " " %10.4f `hi' "`star'" } } foreach xv of local indepvars { capture local b = _b[L`i'D.`xv'] if !_rc { local se = _se[L`i'D.`xv'] if `se' > 0 { local t = `b' / `se' local p = 2 * ttail(`N' - `k_total', abs(`t')) local lo = `b' - invttail(`N' - `k_total', 0.025) * `se' local hi = `b' + invttail(`N' - `k_total', 0.025) * `se' local star "" if `stars' { _lrmbounds_stars `p' local star "`s(s)'" } di as txt " " %~14s "L`i'D.`xv'" "{c |}" /// as res %11.5f `b' " " %11.5f `se' " " %9.3f `t' " " %8.4f `p' /// " " %10.4f `lo' " " %10.4f `hi' "`star'" } } } } } * Constant and trend capture local b = _b[_cons] if !_rc { local se = _se[_cons] if `se' > 0 { local t = `b' / `se' local p = 2 * ttail(`N' - `k_total', abs(`t')) local star "" if `stars' { _lrmbounds_stars `p' local star "`s(s)'" } di as txt " {hline `totw'}" di as txt " " %~14s "_cons" "{c |}" /// as res %11.5f `b' " " %11.5f `se' " " %9.3f `t' " " %8.4f `p' "`star'" } } di as txt " {hline `totw'}" if `stars' di as txt " ***, **, * denote significance at 1%, 5%, 10% levels." di "" * ================================================================ * TABLE 3: PSS BOUNDS TEST RESULTS * F-bounds test (PSS 2001) and ECR t-bounds test * ================================================================ _lrmbounds_ftest `varlist' if `touse', /// optlag(`optlag') `trend' `noconstant' `robust' local F_pss = r(F_pss) local f_decision = r(f_decision) local f_dcode = r(f_dcode) local f_lb_1 = r(f_lb_1) local f_ub_1 = r(f_ub_1) local f_lb_5 = r(f_lb_5) local f_ub_5 = r(f_ub_5) local f_lb_10 = r(f_lb_10) local f_ub_10 = r(f_ub_10) local ecr_t_val = r(ecr_t) local t_decision = r(t_decision) local t_dcode = r(t_dcode) local t_lb_1 = r(t_lb_1) local t_ub_1 = r(t_ub_1) local t_lb_5 = r(t_lb_5) local t_ub_5 = r(t_ub_5) local t_lb_10 = r(t_lb_10) local t_ub_10 = r(t_ub_10) local equil_type = r(equil_type) local equil_code = r(equil_code) local pss_case = r(case) di as res " {bf:Table 2. PSS Bounds Test for Level Relationships}" di as txt " {hline 72}" di "" * F-test panel di as res " {bf:Panel A: F-Bounds Test}" di as txt " H0: No level relationship (psi_yy = psi_yx = 0)" di as txt " k = `nindep', Case `=cond(`pss_case'==5, "V", "III")'" di as txt " {hline 72}" di as txt " " %~14s "I(0) Bound" %~14s "I(1) Bound" di as txt " {hline 72}" di as txt " 10% level " as res %14.3f `f_lb_10' %14.3f `f_ub_10' di as txt " 5% level " as res %14.3f `f_lb_5' %14.3f `f_ub_5' di as txt " 1% level " as res %14.3f `f_lb_1' %14.3f `f_ub_1' di as txt " {hline 72}" di as txt " F-statistic: " as res %14.4f `F_pss' di "" * Color-coded decision if "`f_dcode'" == "reject" { di as res " {bf:Decision:} " as res "`f_decision'" } else if "`f_dcode'" == "fail" { di as err " {bf:Decision:} " as txt "`f_decision'" } else { di as txt " {bf:Decision:} " as res "{bf:`f_decision'}" } di "" * ECR t-test panel di as res " {bf:Panel B: ECR t-Bounds Test}" di as txt " H0: No error correction (psi_yy = 0)" di as txt " {hline 72}" di as txt " " %~14s "I(0) Bound" %~14s "I(1) Bound" di as txt " {hline 72}" di as txt " 10% level " as res %14.3f `t_lb_10' %14.3f `t_ub_10' di as txt " 5% level " as res %14.3f `t_lb_5' %14.3f `t_ub_5' di as txt " 1% level " as res %14.3f `t_lb_1' %14.3f `t_ub_1' di as txt " {hline 72}" di as txt " EC rate (psi_yy): " as res %14.5f `ecr' as txt " t = " as res %8.3f `ecr_t_val' di "" if "`t_dcode'" == "reject" { di as res " {bf:Decision:} " as res "`t_decision'" } else { di as txt " {bf:Decision:} `t_decision'" } di "" * ================================================================ * TABLE 4: DEGENERATE EQUILIBRIUM CHECK * Webb (2019) Table 1 * ================================================================ di as res " {bf:Table 3. Equilibrium Classification (Webb et al. 2019, Table 1)}" di as txt " {hline 72}" di as txt " Type: " as res "`equil_type'" di "" if "`equil_code'" == "valid" { di as res " => Both psi_yy ≠ 0 and psi_yx ≠ 0: Valid long-run equilibrium" di as txt " The variables are in a meaningful long-run relationship." } else if "`equil_code'" == "nonsense" { di as err " => psi_yy = 0 but psi_yx ≠ 0: DEGENERATE (Nonsense) equilibrium" di as txt " y is a unit root but NOT cointegrated with x." di as txt " The apparent relationship is spurious." } else if "`equil_code'" == "degenerate" { di as txt " => psi_yy ≠ 0 but psi_yx = 0: DEGENERATE equilibrium" di as txt " y has its own equilibrium but is independent of x." } else { di as txt " => Unable to determine (F-test null not rejected or neither" di as txt " psi_yy nor psi_yx is individually significant)." } di "" * ================================================================ * TABLE 5: LRM BOUNDS TEST (CORE WEBB 2019 INNOVATION) * Webb, Linn & Lebo (2019) Tables 3-6 * ================================================================ _lrmbounds_lrm `varlist' if `touse', /// optlag(`optlag') ardlspec(`ardl_spaces') `trend' `noconstant' `robust' `bewley' alpha(0.05) local cv_lb_1 = r(cv_lb_1) local cv_ub_1 = r(cv_ub_1) local cv_lb_5 = r(cv_lb_5) local cv_ub_5 = r(cv_ub_5) local cv_lb_10 = r(cv_lb_10) local cv_ub_10 = r(cv_ub_10) local bewley_ok = r(bewley_ok) * Store LRM bounds results forvalues j = 1/`nindep' { local dm_lrm_`j' = r(dm_lrm_`j') local dm_se_`j' = r(dm_se_`j') local dm_t_`j' = r(dm_t_`j') if `bewley_ok' { local bw_lrm_`j' = r(bw_lrm_`j') local bw_se_`j' = r(bw_se_`j') local bw_t_`j' = r(bw_t_`j') } local lrm_decision_`j' = r(lrm_decision_`j') local lrm_dcode_`j' = r(lrm_dcode_`j') } di as res " {bf:Table 4. Long Run Multiplier (LRM) Bounds Test}" di as txt " {hline 86}" di "" * Display Webb bounds di as txt " Critical Value Bounds (|t| on LRM): T = `N', k = `nindep'" di as txt " {hline 50}" di as txt " " %~14s "Lower Bound" %~14s "Upper Bound" di as txt " {hline 50}" di as txt " 10% level " as res %14.3f `cv_lb_10' %14.3f `cv_ub_10' di as txt " 5% level " as res %14.3f `cv_lb_5' %14.3f `cv_ub_5' di as txt " 1% level " as res %14.3f `cv_lb_1' %14.3f `cv_ub_1' di as txt " {hline 50}" di "" * LRM estimates table di as txt " {hline 86}" if `bewley_ok' { di as txt " " %-14s "Variable" "{c |}" /// %~10s "LRM(DM)" %~10s "SE(DM)" %~8s "t(DM)" /// %~10s "LRM(BW)" %~10s "SE(BW)" %~8s "t(BW)" %~12s "Decision" } else { di as txt " " %-14s "Variable" "{c |}" /// %~12s "LRM" %~12s "Std. Err." %~10s "|t|" %~16s "Decision (5%)" } di as txt " {hline 86}" forvalues j = 1/`nindep' { local xv "`xvar_`j''" if `bewley_ok' { * Display both delta method and Bewley results di as txt " " %-14s "`xv'" "{c |}" /// as res %10.4f `dm_lrm_`j'' " " %10.4f `dm_se_`j'' " " %8.3f abs(`dm_t_`j'') /// " " %10.4f `bw_lrm_`j'' " " %10.4f `bw_se_`j'' " " %8.3f abs(`bw_t_`j'') _continue } else { di as txt " " %-14s "`xv'" "{c |}" /// as res %12.5f `dm_lrm_`j'' " " %12.5f `dm_se_`j'' " " %10.3f abs(`dm_t_`j'') _continue } * Color-coded decision if "`lrm_dcode_`j''" == "reject" { di as res " {bf:Signif.}" _continue } else if "`lrm_dcode_`j''" == "fail" { di as txt " Not Signif." _continue } else { di as res " {bf:Incon.}" _continue } di "" } di as txt " {hline 86}" di as txt " DM = Delta Method; BW = Bewley (1979) IV. Decision based on 5% Webb bounds." di as txt " Signif. = |t| > Upper Bound; Not Signif. = |t| < Lower Bound; Incon. = between." di "" * Interpretation di as res " {bf:Interpretation Guide (Webb et al. 2019, 2020):}" di as txt " {hline 72}" di as txt " • If |t_LRM| > Upper Bound: Reject H0 — significant long-run" di as txt " relationship REGARDLESS of whether variables are I(0) or I(1)." di as txt " • If |t_LRM| < Lower Bound: Fail to reject — no evidence of a" di as txt " long-run relationship under ANY integration assumption." di as txt " • If Lower < |t_LRM| < Upper: INCONCLUSIVE — the inference" di as txt " depends on the (unknown) integration order of the variables." di as txt " {hline 72}" di "" * ================================================================ * TABLE 6: DIAGNOSTIC TESTS * ================================================================ if "`nodiagnostics'" == "" { di as res " {bf:Table 5. Diagnostic Tests}" di as txt " {hline 60}" * Re-estimate for diagnostics qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse', `noconstant' * Breusch-Godfrey serial correlation (manual LM test) tempvar resid_diag qui predict double `resid_diag', residuals * BG lag 1 qui regress `resid_diag' `ecm_levels_full' `ecm_diffs_full' `trendvar' L.`resid_diag' if `touse' local bg_n = e(N) local bg_r2_1 = e(r2) local bg_chi2_1 = `bg_n' * `bg_r2_1' local bg_p_1 = chi2tail(1, `bg_chi2_1') di as txt " Breusch-Godfrey (1 lag): chi2 = " as res %8.3f `bg_chi2_1' /// as txt " p = " as res %6.4f `bg_p_1' _continue if `bg_p_1' > 0.05 { di as res " [No serial corr.]" } else { di as err " [Serial corr. detected]" } * BG lag 4 qui regress `resid_diag' `ecm_levels_full' `ecm_diffs_full' `trendvar' /// L(1/4).`resid_diag' if `touse' local bg_r2_4 = e(r2) local bg_chi2_4 = `bg_n' * `bg_r2_4' local bg_p_4 = chi2tail(4, `bg_chi2_4') di as txt " Breusch-Godfrey (4 lags): chi2 = " as res %8.3f `bg_chi2_4' /// as txt " p = " as res %6.4f `bg_p_4' _continue if `bg_p_4' > 0.05 { di as res " [No serial corr.]" } else { di as err " [Serial corr. detected]" } * Breusch-Pagan heteroskedasticity qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse' tempvar bp_resid bp_resid_sq bp_resid_norm qui predict double `bp_resid', residuals qui gen double `bp_resid_sq' = `bp_resid'^2 qui sum `bp_resid_sq', meanonly local sig2 = r(mean) qui gen double `bp_resid_norm' = `bp_resid_sq' / `sig2' qui regress `bp_resid_norm' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse' local bp_n = e(N) local bp_r2 = e(r2) local bp_chi2 = `bp_n' * `bp_r2' / 2 local bp_df = e(rank) - 1 if `bp_df' < 1 local bp_df = 1 local bp_p = chi2tail(`bp_df', `bp_chi2') di as txt " Breusch-Pagan: chi2 = " as res %8.3f `bp_chi2' /// as txt " p = " as res %6.4f `bp_p' _continue if `bp_p' > 0.05 { di as res " [Homoskedastic]" } else { di as err " [Heteroskedastic]" } * Ramsey RESET qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse' tempvar yhat yhat2 yhat3 qui predict double `yhat' qui gen double `yhat2' = `yhat'^2 qui gen double `yhat3' = `yhat'^3 qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' `yhat2' `yhat3' if `touse' local reset_rss_u = e(rss) local reset_n = e(N) local reset_k_u = e(rank) qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse' local reset_rss_r = e(rss) local reset_k_r = e(rank) local reset_f = ((`reset_rss_r' - `reset_rss_u') / 2) / (`reset_rss_u' / (`reset_n' - `reset_k_u')) local reset_p = Ftail(2, `reset_n' - `reset_k_u', `reset_f') di as txt " Ramsey RESET: F = " as res %8.3f `reset_f' /// as txt " p = " as res %6.4f `reset_p' _continue if `reset_p' > 0.05 { di as res " [No misspec.]" } else { di as err " [Misspecification]" } * Jarque-Bera normality qui regress D.`depvar' `ecm_levels_full' `ecm_diffs_full' `trendvar' if `touse', `noconstant' tempvar resid_jb qui predict double `resid_jb', residuals qui sum `resid_jb', detail local jb_n = r(N) local jb_skew = r(skewness) local jb_kurt = r(kurtosis) local jb_stat = (`jb_n'/6) * (`jb_skew'^2 + ((`jb_kurt' - 3)^2)/4) local jb_p = chi2tail(2, `jb_stat') di as txt " Jarque-Bera Normality: JB = " as res %8.3f `jb_stat' /// as txt " p = " as res %6.4f `jb_p' _continue if `jb_p' > 0.05 { di as res " [Normal]" } else { di as err " [Non-normal]" } di as txt " {hline 60}" di "" } * ================================================================ * GRAPHS * ================================================================ if "`graph'" != "" { _lrmbounds_graph, /// depvar(`depvar') indepvars(`indepvars') nindep(`nindep') /// optlag(`optlag') `trend' `noconstant' /// graphdir("`graphdir'") /// fpss(`F_pss') flb(`f_lb_5') fub(`f_ub_5') /// ecrate(`ecr') nobs(`N') } * ================================================================ * SUMMARY BOX * ================================================================ di as txt "{hline 90}" di as res _col(4) "{bf:SUMMARY}" di as txt "{hline 90}" di as txt " PSS F-Bounds Test: " _continue if "`f_dcode'" == "reject" di as res "{bf:Level relationship EXISTS} (F = `: display %6.3f `F_pss'')" else if "`f_dcode'" == "fail" di as err "{bf:No level relationship} (F = `: display %6.3f `F_pss'')" else di as res "{bf:Inconclusive} (F = `: display %6.3f `F_pss'')" di as txt " ECR t-Bounds Test: " _continue if "`t_dcode'" == "reject" di as res "{bf:Error correction confirmed} (ECR = `: display %6.4f `ecr'')" else if "`t_dcode'" == "fail" di as err "{bf:No error correction} (ECR = `: display %6.4f `ecr'')" else di as res "{bf:Inconclusive} (ECR = `: display %6.4f `ecr'')" di as txt " Equilibrium Type: " as res "`equil_type'" di "" di as txt " LRM Bounds Decisions (5% Webb bounds):" forvalues j = 1/`nindep' { di as txt " `xvar_`j'': " _continue if "`lrm_dcode_`j''" == "reject" { di as res "{bf:Significant LRR} (LRM = `: display %8.4f `dm_lrm_`j''', |t| = `: display %6.3f abs(`dm_t_`j'')')" } else if "`lrm_dcode_`j''" == "fail" { di as txt "No LRR (LRM = `: display %8.4f `dm_lrm_`j''', |t| = `: display %6.3f abs(`dm_t_`j'')')" } else { di as res "{bf:Inconclusive} (LRM = `: display %8.4f `dm_lrm_`j''', |t| = `: display %6.3f abs(`dm_t_`j'')')" } } di as txt "{hline 90}" di "" * ================================================================ * RETURN RESULTS * ================================================================ * Estimation return scalar N = `N' return scalar k = `nindep' return scalar optlag = `optlag' return scalar r2 = `r2' return scalar r2_a = `r2_a' return scalar rmse = `rmse' return scalar ll = `ll' * ECR return scalar ecr = `ecr' return scalar ecr_se = `ecr_se' return scalar ecr_t = `ecr_t' * F-bounds return scalar F_pss = `F_pss' return scalar f_lb_5 = `f_lb_5' return scalar f_ub_5 = `f_ub_5' return scalar f_lb_1 = `f_lb_1' return scalar f_ub_1 = `f_ub_1' return scalar f_lb_10 = `f_lb_10' return scalar f_ub_10 = `f_ub_10' * ECR t-bounds return scalar t_lb_5 = `t_lb_5' return scalar t_ub_5 = `t_ub_5' * Webb LRM bounds return scalar cv_lb_5 = `cv_lb_5' return scalar cv_ub_5 = `cv_ub_5' return scalar cv_lb_1 = `cv_lb_1' return scalar cv_ub_1 = `cv_ub_1' return scalar cv_lb_10 = `cv_lb_10' return scalar cv_ub_10 = `cv_ub_10' * LRMs forvalues j = 1/`nindep' { return scalar lrm_`j' = `dm_lrm_`j'' return scalar lrm_se_`j' = `dm_se_`j'' return scalar lrm_t_`j' = `dm_t_`j'' return local xvar_`j' "`xvar_`j''" return local lrm_decision_`j' "`lrm_decision_`j''" return local lrm_dcode_`j' "`lrm_dcode_`j''" } return local depvar "`depvar'" return local indepvars "`indepvars'" return local f_decision "`f_decision'" return local f_dcode "`f_dcode'" return local t_decision "`t_decision'" return local t_dcode "`t_dcode'" return local equil_type "`equil_type'" return local equil_code "`equil_code'" end