*! _gvar_coint 1.0.1  21aug2026
*! gvar coint -- Johansen trace and maximum-eigenvalue tests for each country
*! model, with I(1) weakly exogenous regressors, and rank selection.
*! Author: Merwan Roudane
*! merwanroudane920@gmail.com
*! https://github.com/merwanroudane
*
* Step -> source map
*   moment matrices, concentrated on Z2      <- Toolbox cointegration_test.m
*   trace = -T sum log(1-lambda)             <- same
*   max eigenvalue = -T log(1-lambda_r)      <- same
*   Pesaran-Shin-Smith 95% critical values,
*   indexed by (case, n-r, #weakly exog)     <- Toolbox Tech/coint_critvalues.xls
*                                               and get_rank.m
*   sequential rank: stop at the first
*   non-rejection, trace by default          <- Toolbox get_rank.m

program define _gvar_coint, rclass
    version 14.0

    syntax [, STAT(string)      ///
              SET               ///
              RANK(string)      ///
              GRaph             ///
              NAME(string)      ///
              noSUMmary ]

    _gvar_require foreign

    if ("`stat'" == "") local stat trace
    local stat = lower("`stat'")
    if ("`stat'" == "trace")       local istat 1
    else if ("`stat'" == "maxeig") local istat 2
    else {
        di as err "stat() must be {bf:trace} or {bf:maxeig}"
        exit 198
    }

    mata: st_local("N",  strofreal(gvar_getN()))
    mata: st_local("cn", invtokens(gvar_getcname()'))

    * -----------------------------------------------------------------------
    * The Pesaran-Shin-Smith critical values, from the engine
    * -----------------------------------------------------------------------
    * These were read from a shipped gvar_cv.dta with findfile, which meant the
    * command failed with rc 601 if that one file went missing.  They are
    * constants, so they now live in gvar_cvtable() in _gvar_mata.ado: no data
    * file, no preserve/restore of the user's data, and nothing to lose.
    * Columns are (dcase, stat, nr, k, cv95), the order mkmat produced.
    tempname CV
    mata: st_matrix("`CV'", gvar_cvtable())

    * -----------------------------------------------------------------------
    * Run the tests
    * -----------------------------------------------------------------------
    tempname R
    mata: st_matrix("`R'", gvar_cointall(st_matrix("`CV'")))

    tempname RK
    mata: st_matrix("`RK'", gvar_rankfrom(st_matrix("`R'"), `istat', `N'))

    * -----------------------------------------------------------------------
    * A unit whose (case, n-r, k) combination is outside the Pesaran-Shin-Smith
    * table gets a MISSING rank, never a fabricated zero.  Stop and say why.
    * -----------------------------------------------------------------------
    local nbad 0
    local badlist ""
    forvalues i = 1/`N' {
        if (`RK'[`i', 1] >= .) {
            local ++nbad
            local u : word `i' of `cn'
            * n and k for this unit sit in columns 8 and 9 of the results
            local kk .
            local nn .
            forvalues q = 1/`=rowsof(`R')' {
                if (`R'[`q', 1] == `i') {
                    local nn = `R'[`q', 8]
                    local kk = `R'[`q', 9]
                    continue, break
                }
            }
            local badlist "`badlist' `u'(n=`nn', k=`kk')"
        }
    }
    if (`nbad' > 0) {
        di as err ""
        di as err "gvar coint: no critical value is available for `nbad' unit(s)."
        di as err "The Pesaran, Shin & Smith (2000) table shipped with the GVAR"
        di as err "Toolbox covers at most 8 weakly exogenous I(1) regressors."
        di as err "Offending units (n = endogenous, k = weakly exogenous):"
        di as err "   `badlist'"
        di as err ""
        di as err "Reduce k for those units, for example with"
        di as err "   {bf:gvar setup ..., noforeign(ep)}      to drop a star variable"
        di as err "   {bf:gvar setup ..., exclude(usa:ep)}    to drop one from one unit"
        di as err "or model the commodity prices with {bf:gvar dominant} instead of"
        di as err "carrying them as weakly exogenous in every country model."
        exit 459
    }

    * -----------------------------------------------------------------------
    * Report
    * -----------------------------------------------------------------------
    if ("`summary'" != "nosummary") {
        _gvar_title "Cointegration tests for the VARX* country models"
        di as text "  Rank chosen sequentially on the " ///
                   as result "`stat'" as text " statistic at 5%."
        di ""
        di as text "  {hline 84}"
        di as text "  Unit" _col(16) "H0" _col(26) "eigenvalue" ///
                   _col(40) "trace" _col(52) "cv95" ///
                   _col(63) "max-eig" _col(76) "cv95"
        di as text "  {hline 84}"

        local nr = rowsof(`R')
        local cur 0
        forvalues q = 1/`nr' {
            local i = `R'[`q', 1]
            local j = `R'[`q', 2]
            if (`i' != `cur') {
                if (`cur' != 0) di as text "  {dup 84:-}"
                local cur = `i'
                local u : word `i' of `cn'
            }
            else {
                local u ""
            }
            local tr  = `R'[`q', 4]
            local tcv = `R'[`q', 5]
            local mx  = `R'[`q', 6]
            local mcv = `R'[`q', 7]
            local t1 " "
            local t2 " "
            if (`tr' < . & `tcv' < . & `tr' > `tcv') local t1 "*"
            if (`mx' < . & `mcv' < . & `mx' > `mcv') local t2 "*"
            di as text "  " %-12s abbrev("`u'", 12)                  ///
               _col(15) as text "r = " as result %2.0f `=`j'-1'      ///
               _col(26) as result %10.4f `=`R'[`q',3]'               ///
               _col(37) as result %10.2f `tr' as text "`t1'"         ///
               _col(49) as result %8.2f  `tcv'                       ///
               _col(60) as result %10.2f `mx' as text "`t2'"         ///
               _col(72) as result %8.2f  `mcv'
        }
        di as text "  {hline 84}"
        di as text "  * marks rejection of H0 at 5% (Pesaran, Shin & Smith 2000"
        di as text "  critical values, indexed by the deterministic case and the"
        di as text "  number of I(1) weakly exogenous regressors)."
        di ""

        di as text "  {hline 50}"
        di as text "  Selected cointegrating ranks"
        di as text "  {hline 50}"
        local line "   "
        forvalues i = 1/`N' {
            local u : word `i' of `cn'
            local rr = `RK'[`i', 1]
            local piece = abbrev("`u'", 9) + "=" + string(`rr')
            if (length("`line'`piece'  ") > 76) {
                di as text "`line'"
                local line "   "
            }
            local line "`line'`piece'  "
        }
        if (trim("`line'") != "") di as text "`line'"
        di as text "  {hline 50}"
        di ""
    }

    * -----------------------------------------------------------------------
    * Store, with optional per-unit overrides
    * -----------------------------------------------------------------------
    if ("`set'" != "" | "`rank'" != "") {
        tempname RR
        matrix `RR' = `RK'
        if ("`rank'" != "") {
            foreach pair of local rank {
                if (strpos("`pair'", "=") == 0) continue
                local nm  = substr("`pair'", 1, strpos("`pair'", "=") - 1)
                local val = substr("`pair'", strpos("`pair'", "=") + 1, .)
                local pos : list posof "`nm'" in cn
                if (`pos' == 0) {
                    di as err "rank(): unknown unit {bf:`nm'}"
                    exit 198
                }
                matrix `RR'[`pos', 1] = `val'
                if ("`summary'" != "nosummary") {
                    di as text "Rank for " as result "`nm'" as text ///
                       " set to " as result `val' as text " by hand."
                }
            }
        }
        mata: gvar_setrank(st_matrix("`RR'"))
        * these three lines were outside the summary guard, so nosummary did
        * not silence the command -- the one thing nosummary exists to do
        if ("`summary'" != "nosummary") {
            di as text "Cointegrating ranks stored in the model."
        }
        return matrix rank = `RR', copy
    }
    else {
        if ("`summary'" != "nosummary") {
            di as text "Add {bf:set} to store these ranks in the model."
        }
        return matrix rank = `RK', copy
    }

    * ---- graph --------------------------------------------------------------
    * Every H0: r = j-1 against its own Pesaran-Shin-Smith critical value.  The
    * cutoff is drawn as a step line because it changes with n-r within each
    * unit, which is the part of the table that is hardest to read as numbers.
    if ("`graph'" != "") {
        local nq = rowsof(`R')
        tempname G
        matrix `G' = J(`nq', 4, .)
        local sc 4
        local cc 5
        if (`istat' == 2) {
            local sc 6
            local cc 7
        }
        forvalues q = 1/`nq' {
            matrix `G'[`q', 1] = `R'[`q', 1]
            matrix `G'[`q', 2] = `R'[`q', `sc']
            matrix `G'[`q', 3] = `R'[`q', `cc']
            matrix `G'[`q', 4] = 1
        }
        local sname "Trace"
        if (`istat' == 2) local sname "Maximal-eigenvalue"
        local sub "each H0: r = j-1 against its 95% Pesaran-Shin-Smith value; marked = reject"
        _gvar_dotplot `G' `nq' "`cn'" ///
            "`sname' cointegration statistics" "`sub'" ///
            "statistic" "`name'" 0
    }

    return matrix coint = `R', copy
    return local  stat  "`stat'"
end