*! _boundedur_m v1.0.0 Dr. Merwan Roudane 02Feb2026
*! M tests for bounded time series (internal routine)
*! Implements MZ_alpha, MZ_t, and MSB tests from Cavaliere & Xu (2014)

program define _boundedur_m, rclass
    version 14.0

    syntax varlist(max=1 ts) [if] [in], ///
        lags(integer) ///
        test(string) ///
        sigma2(real) ///
        [clower(real -999)] ///
        [cupper(real 999)] ///
        [nsim(integer 499)] ///
        [nstep(integer 0)] ///
        [detrend(string)] ///
        [nosimulation]

    marksample touse

    * Defaults
    if "`detrend'" == "" local detrend "constant"
    local test = lower("`test'")

    * Get sample size
    qui count if `touse'
    local T = r(N)

    * De-mean the data
    tempvar Xhat
    if "`detrend'" == "constant" {
        qui reg `varlist' if `touse'
        qui predict `Xhat' if `touse', residuals
    }
    else {
        qui gen `Xhat' = `varlist' if `touse'
    }

    * Sort data by time variable
    qui tsset
    local timevar `r(timevar)'
    sort `timevar'

    * Compute components for M statistics
    * We need: X_hat^2_T, X_hat^2_0, sum(X_hat^2_{t-1}), s^2_AR(k)

    tempname Xhat_T Xhat_0 sum_Xhat2_lag s2_AR

    * Get first and last values
    qui sum `Xhat' if `touse' & _n == 1
    scalar `Xhat_0' = r(mean)
    qui sum `Xhat' if `touse' & _n == `T'
    scalar `Xhat_T' = r(mean)

    * Sum of squared lagged values
    tempvar Xhat_lag2
    qui gen `Xhat_lag2' = L.`Xhat'^2 if `touse'
    qui sum `Xhat_lag2' if `touse'
    scalar `sum_Xhat2_lag' = r(sum)

    * Long-run variance (spectral AR estimator already computed)
    scalar `s2_AR' = `sigma2'

    * Compute test statistics
    if "`test'" == "mzalpha" {
        * MZ_alpha = (T^{-1}*Xhat_T^2 - T^{-1}*Xhat_0^2 - s^2_AR) / (2*T^{-2}*sum(Xhat^2_{t-1}))
        tempname test_stat
        scalar `test_stat' = (`Xhat_T'^2/`T' - `Xhat_0'^2/`T' - `s2_AR') / ///
                              (2 * `sum_Xhat2_lag' / (`T'^2))
        return scalar mz_alpha = `test_stat'
    }
    else if "`test'" == "msb" {
        * MSB = sqrt(T^{-2}*sum(Xhat^2_{t-1}) / s^2_AR)
        tempname test_stat
        scalar `test_stat' = sqrt(`sum_Xhat2_lag' / (`T'^2 * `s2_AR'))
        return scalar msb = `test_stat'
    }
    else if "`test'" == "mzt" {
        * MZ_t = MZ_alpha * MSB
        tempname mz_alpha msb
        scalar `mz_alpha' = (`Xhat_T'^2/`T' - `Xhat_0'^2/`T' - `s2_AR') / ///
                             (2 * `sum_Xhat2_lag' / (`T'^2))
        scalar `msb' = sqrt(`sum_Xhat2_lag' / (`T'^2 * `s2_AR'))

        tempname test_stat
        scalar `test_stat' = `mz_alpha' * `msb'
        return scalar mz_t = `test_stat'
    }

    * If simulation requested, compute p-value
    if "`nosimulation'" == "" {

        * Set discretization step
        if `nstep' == 0 {
            local nstep = `T'
        }

        * Monte Carlo simulation
        tempname mc_stat pval count_below
        scalar `count_below' = 0

        forvalues b = 1/`nsim' {
            * Generate i.i.d. N(0,1) innovations
            tempvar epsilon_star
            qui gen `epsilon_star' = rnormal() if _n <= `nstep'

            * Generate bounded process
            tempvar X_star
            qui gen `X_star' = 0 if _n == 1

            * Recursive generation with reflection at bounds
            forvalues t = 2/`nstep' {
                qui replace `X_star' = ///
                    cond(L.`X_star' + `epsilon_star'/sqrt(`nstep') > `cupper', `cupper', ///
                    cond(L.`X_star' + `epsilon_star'/sqrt(`nstep') < `clower', `clower', ///
                    L.`X_star' + `epsilon_star'/sqrt(`nstep'))) ///
                    if _n == `t'
            }

            * De-mean simulated series
            qui sum `X_star' in 1/`nstep'
            local X_star_mean = r(mean)
            tempvar Xstar_tilde
            qui gen `Xstar_tilde' = `X_star' - `X_star_mean' if _n <= `nstep'

            * Compute MC statistic components
            qui sum `Xstar_tilde' if _n == 1
            local Xstar_0 = r(mean)
            qui sum `Xstar_tilde' if _n == `nstep'
            local Xstar_T = r(mean)

            tempvar Xstar_lag2
            qui gen `Xstar_lag2' = L.`Xstar_tilde'^2 if _n <= `nstep'
            qui sum `Xstar_lag2'
            local sum_Xstar2 = r(sum)

            * Compute MC statistic (using unit long-run variance for simulated data)
            if "`test'" == "mzalpha" {
                scalar `mc_stat' = (`Xstar_T'^2/`nstep' - `Xstar_0'^2/`nstep' - 1) / ///
                                    (2 * `sum_Xstar2' / (`nstep'^2))
            }
            else if "`test'" == "msb" {
                scalar `mc_stat' = sqrt(`sum_Xstar2' / (`nstep'^2))
            }
            else if "`test'" == "mzt" {
                local mz_alpha_star = (`Xstar_T'^2/`nstep' - `Xstar_0'^2/`nstep' - 1) / ///
                                       (2 * `sum_Xstar2' / (`nstep'^2))
                local msb_star = sqrt(`sum_Xstar2' / (`nstep'^2))
                scalar `mc_stat' = `mz_alpha_star' * `msb_star'
            }

            * Count if MC statistic is in rejection region
            if "`test'" == "msb" {
                * MSB rejects for small values
                if `mc_stat' < `test_stat' {
                    scalar `count_below' = `count_below' + 1
                }
            }
            else {
                * MZ_alpha and MZ_t reject for large negative values
                if `mc_stat' < `test_stat' {
                    scalar `count_below' = `count_below' + 1
                }
            }

            * Clean up
            drop `epsilon_star' `X_star' `Xstar_tilde' `Xstar_lag2'
        }

        * Compute p-value
        scalar `pval' = `count_below' / `nsim'
        return scalar pval = `pval'
    }

end