*! version 1.0.0  20mar2026  Dr. Merwan Roudane
*! _lrmbounds_ftest: PSS F-bounds test and ECR t-bounds test
*! Implements the Pesaran, Shin & Smith (2001) bounds testing approach
*! as used in Webb, Linn & Lebo (2019, 2020)

capture program drop _lrmbounds_ftest
program define _lrmbounds_ftest, rclass
    syntax varlist(ts min=2) [if] [in], [     ///
        OPTLAG(integer 1)                      ///
        TREND                                  ///
        NOCONStant                             ///
        ROBUST                                 ///
        ]

    marksample touse

    gettoken depvar indepvars : varlist
    local nindep : word count `indepvars'

    * ================================================================
    *  Step 1: Estimate UNRESTRICTED ECM
    *  Dy_t = c + psi_yy*y_{t-1} + psi_yx*x_{t-1} + short_run + u_t
    * ================================================================
    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 if `touse'
        local trendvar "`tvar'"
    }

    local allregs "`ecm_levels' `ecm_diffs' `trendvar'"

    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)

    * Save ECR coefficient for t-test
    local ecr = _b[L.`depvar']
    local ecr_se = _se[L.`depvar']
    local ecr_t = `ecr' / `ecr_se'

    * ================================================================
    *  Step 2: Estimate RESTRICTED model (drop all lagged levels)
    *  H0: psi_yy = psi_yx = 0 (no level relationship)
    * ================================================================
    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)

    * ================================================================
    *  Step 3: Compute joint F-statistic
    *  F = [(RSS_r - RSS_u) / q] / [RSS_u / (N - k_u)]
    *  where q = number of restrictions = k+1 (psi_yy + k psi_yx's)
    * ================================================================
    local q = `nindep' + 1  /* psi_yy + psi_yx_1 + ... + psi_yx_k */
    local F_pss = ((`rss_r' - `rss_u') / `q') / (`rss_u' / `df_u')

    * ================================================================
    *  Step 4: Look up F-bounds 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)
    }

    * F-test decision at 5% level
    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"
    }

    * ================================================================
    *  Step 5: ECR t-bounds test (PSS Table CII)
    *  Tests H0: psi_yy = 0 (no error correction)
    * ================================================================
    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)
    }

    * ECR t-test decision at 5%
    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"
    }

    * ================================================================
    *  Step 6: Degenerate equilibrium check (Webb 2019, Table 1)
    *  After rejecting F-test, check which alternative holds
    * ================================================================
    local ecr_sig = (abs(`ecr_t') > `t_ub_5')

    * Check if any psi_yx is significant
    local any_psi_sig = 0
    local j = 0
    foreach xv of local indepvars {
        local ++j
        * Re-estimate to get coefficients
        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"
    }

    * ================================================================
    *  Return results
    * ================================================================
    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'

    * F-bounds
    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'

    * ECR t-test
    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'

    * Decisions
    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