*! rwolf2: Romano Wolf stepdown hypothesis testing algorithm, alternative syntax
*! Version 1.0.0 july 5, 2021 @ 23:56:11
*! Author: Damian Clarke
*! Department of Economics
*! University of Chile
*! dclarke@fen.uchile.cl

/*
version highlights:
1.0.0:[05/07/2021]:
*/


cap program drop rwolf2
program rwolf2, eclass
vers 11.0

*-------------------------------------------------------------------------------
*--- (0) Parsing of equations
*-------------------------------------------------------------------------------
local stop 0
local i = 0
while !`stop' {
    gettoken eqn 0 : 0,  parse(" ,[") match(paren)
    if `"`paren'"'=="(" {
        local ++i
        local eqn`i' `eqn'
    }
    else {
        local stop 1
    }
}
local 0 ", `0'"
local numeqns = `i'

*-------------------------------------------------------------------------------
*--- (1) Syntax
*-------------------------------------------------------------------------------
#delimit ;
syntax , indepvars(string)
[
  reps(integer 100)
  Verbose
  seed(numlist integer >0 max=1)
  holm
  graph
  onesided(name)
  varlabels
  nodots
  nulls(numlist)
  strata(varlist)
  CLuster(varlist)
  idcluster(string)
  usevalid
  noplusone
];
#delimit cr

*-------------------------------------------------------------------------------
*--- (2) Unpacking baseline options and error check
*-------------------------------------------------------------------------------
if length(`"`verbose'"')==0 local q qui
cap set seed `seed'

local jj=1
foreach rdvar in RD_Estimate Conventional Bias-corrected Robust {
    cap gen `rdvar' = .
    if _rc==0 local DROP`jj' = 1
    else      local DROP`jj' = 0
    local ++jj
}



if length(`"`onesided'"')!=0 {
    if `"`onesided'"'!="positive"&`"`onesided'"'!="negative" {
        local W1 "When specifying onesided(), only onesided(positive)"
        dis as error "`W1' or onesided(negative) are permissible"
        exit 200
    }
}

if length(`"`nulls'"')!=0 {
    local j = 0
    foreach null of numlist `nulls' {
        local ++j
        local beta0`j' = `null'
    }
    local nnulls = `j'
}

tempname nullvals
tempfile nullfile
file open `nullvals' using "`nullfile'", write all

tokenize `"`indepvars'"', parse(",")
local NVARS = 0
foreach equation of numlist 1(1)`numeqns' {
    foreach var of varlist ``equation'' {
        local ++NVARS
    }
    macro shift
}


local nsets=0
tokenize `"`indepvars'"', parse(",")
while length("`1'")!=0 {
    macro shift
    local ++nsets
}
if `numeqns'*2-1!=`nsets' {
    local NEff = (`nsets'+1)/2
    dis as error "`numeqns' equations are specified, and `NEff' set(s) of indepdent variables are specified."
    dis as error "Ensure that indepvars includes a list of variables of interest for each equation."
    error 200
}

local bopts
if length(`"`strata'"')!=0  local bopts `bopts' strata(`strata')
if length(`"`cluster'"')!=0 local bopts `bopts' cluster(`cluster')
if length(`"`idcluster'"')!=0 {
    if length(`"`cluster'"')==0 {
        dis as error "The idcluster option requires that cluster is also specified"
        exit 200
    }
    local bopts `bopts' idcluster(`idcluster')
    foreach equation of numlist 1(1)`numeqns' {
        local RE1 = "cluster\(.*\)"
        local RE2 = "vce\( *[cluster].*\)"
        local beqn`equation' = regexr("`eqn`equation''" ,"`RE1'","cluster(`idcluster')")
        local beqn`equation' = regexr("`beqn`equation''","`RE2'","vce(cluster `idcluster')")
    }
}

*-------------------------------------------------------------------------------
*--- (3) Parsing of independent variables and estimating models
*-------------------------------------------------------------------------------
tokenize `"`indepvars'"', parse(",")

local inft = 0
local j    = 0
local diswidth = 15

local allvars
foreach equation of numlist 1(1)`numeqns' {
    `q' dis "Equation `equation' is `eqn`equation''"
    `q' dis "Variables to correct are ``equation''"
    `q' `eqn`equation''

    local y`equation' `e(depvar)'
    local y`equation'length = length("`y`equation''")
    local diswidth = max(`diswidth',`y`equation'length')
    local xvar`equation'
    foreach var of varlist ``equation'' {
        local xvar`equation' `xvar`equation'' `var'
        local allvars `allvars' "(`y`equation''-`var')"

        local ++j
        if length(`"`nulls'"')==0 local beta0`j' = 0

        if length(`"`onesided'"')==0 {
            local t`j' = abs((_b[`var']-`beta0`j'')/_se[`var'])
        }
        else {
            local t`j' = (_b[`var']-`beta0`j'')/_se[`var']
            local inft = min(`inft',`t`j'')
        }
        local beta`j' = _b[`var']
        qui test _b[`var'] = `beta0`j''
        local pv`j' = string(r(p), "%6.4f")
        local pv`j's= r(p)
        local label`j' `: var label `var' '


        if `"`onesided'"'=="positive" {
            qui test `var'=`beta0`j''
            local sgn = sign(_b[`var'])
            if length(`"`r(F)'"')!=0 {
                local pv`j'  = string(1-ttail(r(df_r),`sgn'*sqrt(r(F))), "%6.4f")
                local pv`j's = (1-ttail(r(df_r),`sgn'*sqrt(r(F))))
            }
            else {
                local pv`j's=normal(`sgn'*sqrt(r(chi2)))
            }
        }
        if `"`onesided'"'=="negative" {
            qui test `var'=`beta0`j''
            local sgn = sign(_b[`var'])
            if length(`"`r(F)'"')!=0 {
                local pv`j'  = string(ttail(r(df_r),`sgn'*sqrt(r(F))), "%6.4f")
                local pv`j's = (ttail(r(df_r),`sgn'*sqrt(r(F))))
            }
            else {
                local pv`j's= 1-normal(`sgn'*sqrt(r(chi2)))
            }
        }
        local cand `cand' `j'
        file write `nullvals' "b`j'; se`j';"
    }
    macro shift
}



*-------------------------------------------------------------------------------
*--- (4) Bootstrap replications
*-------------------------------------------------------------------------------
dis "Bootstrap replications (`reps'). This may take some time."
if length(`"`dots'"')==0 {
    dis "----+--- 1 ---+--- 2 ---+--- 3 ---+--- 4 ---+--- 5"
}
forvalues i=1/`reps' {
    local bmess1 "There has been an issue with a bootstrap replicate"
    local bmess2 "in bootstrap `i'.  Taking next bootstrap sample..."
    local j=0
    preserve
    bsample, `bopts'
    if length(`"`dots'"')==0 {
        display in smcl "." _continue
        if mod(`i',50)==0 dis "     `i'"
    }

    foreach equation of numlist 1(1)`numeqns' {
        if length(`"`usevalid'"')==0 {
            if length(`"`idcluster'"')!=0 qui `beqn`equation''
            else qui `eqn`equation''
            local k=1
            foreach var of varlist `xvar`equation'' {
                local ++j
                if `j'==1&`k'==1 file write `nullvals' _n "`= _b[`var']';`= _se[`var']'"
                else file write `nullvals' ";`= _b[`var']';`= _se[`var']'"
                local ++k
            }
        }
        if length(`"`usevalid'"')!=0 {
            if length(`"`idcluster'"')!=0 cap `beqn`equation''
            else cap `eqn`equation''

            local RET = _rc
            local k=1
            foreach var of varlist `xvar`equation'' {
                if `RET'!=0 {
                    local Bbeta = .
                    local Bstde = .
                }
                else {
                    local Bbeta = _b[`var']
                    local Bstde = _se[`var']
                }
                local ++j
                if `j'==1&`k'==1 file write `nullvals' _n "`Bbeta';`Bstde'"
                else file write `nullvals' ";`Bbeta';`Bstde'"
                local ++k
            }
        }
    }
    restore
}


preserve
file close `nullvals'
qui insheet using `nullfile', delim(";") names clear case

matrix pvalues = J(`j',3,.)
if length(`"`holm'"')!=0 matrix pvalues = J(`j',4,.)


*-------------------------------------------------------------------------------
*--- (5) Create null t-distribution
*-------------------------------------------------------------------------------
local P=0
foreach num of numlist 1(1)`j' {
    qui gen     t`num'=(b`num'-`beta`num'')/se`num'
    qui replace b`num'=abs((b`num'-`beta`num'')/se`num')

    qui count if t`num'!=.
    if r(N)!=`reps' local ++P
}
if length(`"`usevalid'"')!=0 {
    egen NMISS = rowmiss(t*)
    qui drop if NMISS!=0
    qui count
    local reps = r(N)
}
else {
    if `P'!=0 {
        dis as error "Not all bootstrap replicates have produced defined t-statistics for each variable"
        dis as error "It is suggested that the usevalid option should be specified to discard invalid bootstrap replicates"
    }
}

*-------------------------------------------------------------------------------
*--- (6) Create stepdown value in descending order based on t-stats
*-------------------------------------------------------------------------------
local maxt = `inft'-10
local pval = 0
local rank
local Holm = `j'
local prmsm1

tempvar empiricalDist
tokenize `allvars'
local ii=0
while length("`cand'")!=0 {
    local ++ii
    local donor_tvals

    if length(`"`onesided'"')==0|`"`onesided'"'=="negative" {
        foreach var of local cand {
            if `t`var''>`maxt' {
                local maxt = `t`var''
                local maxv `var'
                if length(`"`onesided'"')==0  local ovar  b`var'
                if `"`onesided'"'=="negative" local ovar  t`var'
            }
            *dis "Maximum t among remaining candidates is `maxt' (variable `maxv')"
            if length(`"`onesided'"')==0  {
                local donor_tvals `donor_tvals' b`var'
            }
            if `"`onesided'"'=="negative" {
                local donor_tvals `donor_tvals' t`var'
            }
        }
        qui egen `empiricalDist' = rowmax(`donor_tvals')
        qui count if `empiricalDist'>=`maxt'  & `empiricalDist' != .
        local cnum = r(N)

        if length(`"`plusone'"')!=0      local pval = (`cnum')/(`reps')
        else if length(`"`plusone'"')==0 local pval = (`cnum'+1)/(`reps'+1)

        qui count if `ovar'>=`maxt' & `ovar'!=.
        local cnum = r(N)

        if length(`"`plusone'"')!=0      local pvalBS = `cnum'/`reps'
        else if length(`"`plusone'"')==0 local pvalBS = (`cnum'+1)/(`reps'+1)

        local pbs`maxv's= `pvalBS'
        local prm`maxv's= `pval'
        local prh`maxv's= min(`pvalBS'*`Holm',1)

        if length(`"`prmsm1'"')!=0 {
            local prm`maxv's=max(`prm`maxv's',`prmsm1')
        }

        local prmsm1 = `prm`maxv's'
        if length(`"`graph'"')!=0 {
            gen tDist`ii' = `empiricalDist'
            local tt`ii' = `t`maxv''
            local ll`ii' "`label`maxv''"
            local yy`ii' ``maxv''
            local pp`ii' = string(`prm`maxv's',"%6.4f")
        }
    }
    else {
        local mint = .
        foreach var of local cand {
            if `t`var''<`mint' {
                local mint = `t`var''
                local minv `var'
                local ovar t`var'
            }
            *dis "Minimum t among remaining candidates is `mint' (variable `minv')"
            local donor_tvals `donor_tvals' t`var'
        }
        qui egen `empiricalDist' = rowmin(`donor_tvals')
        qui count if `empiricalDist' <= `mint' & `empiricalDist' != .
        local cnum = r(N)

        if length(`"`plusone'"')!=0  local pval = (`cnum')/(`reps')
        else  local pval = (`cnum'+1)/(`reps'+1)
        qui count if `ovar'<=`mint' & `ovar'!=.
        local cnum = r(N)
        if length(`"`plusone'"')!=0  local pvalBS = (`cnum')/(`reps')
        else local pvalBS = (`cnum'+1)/(`reps'+1)

        local pbs`minv's = `pvalBS'
        local prm`minv's = `pval'
        local prh`minv's = min(`pvalBS'*`Holm',1)

        if length(`"`prmsm1'"')!=0 {
            local prm`minv's = max(`prm`minv's',`prmsm1')
        }

        local prmsm1 = `prm`minv's'
        if length(`"`graph'"')!= 0 {
            gen tDist`ii' = `empiricalDist'
            local tt`ii' = `t`minv''
            local ll`ii' "`label`minv''"
            local yy`ii' ``minv''
            local pp`ii' = string(`prm`minv's',"%6.4f")
        }
    }
    drop `empiricalDist'
    local rank `rank' `maxv' `minv'
    local candnew
    foreach c of local cand {
        local match = 0
        foreach r of local rank {
            if `r'==`c' local match = 1
        }
        if `match'==0 local candnew `candnew' `c'
    }
    local cand `candnew'
    local maxt = `inft'-10
    local maxv = 0
    local --Holm
}


*-------------------------------------------------------------------------------
*--- (7) Graphing null distributions
*-------------------------------------------------------------------------------
if length(`"`graph'"')!= 0 {
    local graphs
    if length(`"`onesided'"')==0 {
        foreach num of numlist 1(1)`ii' {
            local title "Combination `yy`num''"
            if length(`"`varlabels'"')!=0 local title "`ll`num''"

            #delimit ;
            twoway hist tDist`num', bcolor(gs12) ||
            function y=sqrt(2)/sqrt(c(pi))*exp(-x^2/2), range(0 6)
            xline(`tt`num'', lcolor(red)) name(g`num', replace)
            scheme(sj) nodraw legend(off) title(`title')
            note("p-value = `pp`num''");
            #delimit cr
            local graphs `graphs' g`num'
        }
    }
    else {
        foreach num of numlist 1(1)`ii' {
            local title "Combination `yy`num''"
            if length(`"`varlabels'"')!=0 local title "`ll`num''"
            #delimit ;
            twoway hist tDist`num', bcolor(gs12) ||
            function y=1/sqrt(2*c(pi))*exp(-x^2/2), range(-4 4)
            xline(`tt`num'', lcolor(red)) name(g`num', replace)
            scheme(sj) nodraw legend(off) title(`title')
            note("p-value = `pp`num''");
            #delimit cr
            local graphs `graphs' g`num'
        }
    }
    graph combine `graphs', scheme(sj)
}
restore


*-------------------------------------------------------------------------------
*--- (8) Report and export p-values
*-------------------------------------------------------------------------------
ereturn clear
local vardisplay
local linelength=0

foreach equation of numlist 1(1)`numeqns' {
    foreach var of varlist `xvar`equation'' {
        local linelength=`linelength'+length("`var'")
        if `linelength'>57 {
            local vardisplay "`vardisplay'" _newline _col(22) "`var'"
            local linelength=length("`var'")
        }
        else local vardisplay "`vardisplay' `var'"
    }
}
local crit = (100-c(level))/100
local lev  = c(level)
if length(`"`usevalid'"')!=0 {
    local NREP "Number of valid resamples: `reps'"
}
else local NREP "Number of resamples: `reps'"

dis _newline
dis "Romano-Wolf step-down adjusted p-values"
dis "`NREP'"
dis _newline
dis "{hline 78}"


if length(`"`holm'"')==0 {
    dis "{dup `diswidth': } | Model p-value    Resample p-value    Romano-Wolf p-value"
    dis "{hline `=`diswidth'+1'}+{hline `=76-`diswidth''}"
}
else {
    dis "{dup `diswidth': } |  Model        Resample       Romano-Wolf        Holm"
    dis "{dup `diswidth': } | p-value       p-value         p-value         p-value"
    dis "{hline `=`diswidth'+1'}+{hline `=76-`diswidth''}"
}

local j = 0
foreach equation of numlist 1(1)`numeqns' {
    local DUP = `diswidth' - `y`equation'length'
    display as text "`y`equation'' {dup `DUP': }{c |}     "
    if length(`"`holm'"')==0 {
        foreach var of varlist `xvar`equation'' {
            local ++j
            display as text %`diswidth's abbrev("`var'",19) " {c |}     "   /*
            */  as result %6.4f `pv`j's' "             "                    /*
            */  as result %6.4f `pbs`j's' "              "                  /*
            */  as result %6.4f `prm`j's'
            ereturn scalar rw_`y`equation''_`var'=`prm`j's'
            matrix pvalues[`j',1]=`pv`j's'
            matrix pvalues[`j',2]=`pbs`j's'
            matrix pvalues[`j',3]=`prm`j's'
        }
    }
    else {
        foreach var of varlist `xvar`equation'' {
            local ++j
            display as text %`diswidth's abbrev("`var'",19) " {c |}  "   /*
            */  as result %6.4f `pv`j's' "        "                    /*
            */  as result %6.4f `pbs`j's' "          "                  /*
            */  as result %6.4f `prm`j's' "          "                  /*
            */  as result %6.4f `prh`j's'
            ereturn scalar rw_`y`equation''_`var'=`prm`j's'
            matrix pvalues[`j',1]=`pv`j's'
            matrix pvalues[`j',2]=`pbs`j's'
            matrix pvalues[`j',3]=`prm`j's'
            matrix pvalues[`j',4]=`prh`j's'
        }
    }
    dis "{hline 78}"
}
dis _newline

*-------------------------------------------------------------------------------
*--- (9) Clean up and Return
*-------------------------------------------------------------------------------
local jj=1
foreach rdvar in RD_Estimate Conventional Bias-corrected Robust {
    if `DROP`jj''==1 {
        drop `rdvar'
    }
    local ++jj
}


ereturn matrix RW=pvalues
end