*! version 1.0.4  26nov2021 CFBaum
* 1.0.1: 04feb2003
* 1.0.2: 27jun2006, corrected for calculation of autocovariances using Mata
* 1.0.3: 26apr2011, add Oliver Jones' suggested MAPE criterion
* 1.0.4: 26nov2021, allow onepanel

program define dmariano, rclass
	version 9.2
	syntax varlist(ts min=3 max=3) [if] [in] [, Crit(string) Maxlag(integer -1) Kernel(string)]
	marksample touse
	_ts timevar panelvar `if' `in', sort onepanel
	markout `touse' `timevar'
* MSE default criterion
	local criterion "MSE"
	if upper("`crit'") == "MSE" | upper("`crit'") == "MAE" | upper("`crit'") == "MAPE" {
		local criterion = upper("`crit'")
		}
	else if "`crit'" != "" {
		dis as err ///
			"The accuracy measure you specified is not available."
		exit
		}
* Uniform default kernel
	local window "uniform"
	if lower("`kernel'") == "uniform" | lower("`kernel'") == "bartlett" {
		local window = lower("`kernel'")
		}
	tempvar  e1 e2 d count
	tempname lrvar T J s1 gamma
* get the three variables
	tokenize `varlist'
	local depvar `1'
	local fcvar1 `2'
	local fcvar2 `3'
* set maxlag
	qui	gen `count' = sum(`touse')
	local nobs = `count'[_N]
	if `maxlag' == -1 {
* set maxlag via Schwert criterion (Ng/Perron JASA 1995)
		local maxlag = int(12*(`nobs'/100)^0.25)
		local kmax = "Maxlag = `maxlag' chosen by Schwert criterion"
		}
	else {
		local kmax "Maxlag = `maxlag'"
		}
* calculate e1 and e2
	if "`criterion'" == "MSE" {
		qui gen double `e1' = (`depvar' - `fcvar1')^2 if `touse'
		qui gen double `e2' = (`depvar' - `fcvar2')^2 if `touse'
		}
	else if "`criterion'" == "MAE" {
		qui gen double `e1' = abs(`depvar' - `fcvar1') if `touse'
		qui gen double `e2' = abs(`depvar' - `fcvar2') if `touse'
		}
	else if "`criterion'" == "MAPE" {
		qui gen double `e1' = abs((`depvar' - `fcvar1')/`depvar') if `touse'
		qui gen double `e2' = abs((`depvar' - `fcvar2')/`depvar') if `touse'
		}
	qui {
		gen double `d' = `e1' - `e2' if `touse'
		summ `e1' if `touse', meanonly
		local e1bar = r(mean)
		summ `e2' if `touse', meanonly
		local e2bar = r(mean)
		summ `d' if `touse', meanonly
		local T = r(N)
		local dbar = r(mean)
		}

* generate autocovariances of d series
	local varlist2 L(0/`maxlag').`d'
	tsrevar `varlist2'
	local varlist3 `r(varlist)'
	local ml1 = `maxlag' + 1
	mata:  autocov("`varlist3'",`dbar',"`touse'")
    scalar `J' = __gamma[1,1]
    forv l = 1/`maxlag' {
* uniform kernel is unweighted; otherwise use Bartlett kernel
        local w 1
        if "`window'" == "bartlett" {
        	local w = 1 - (`l'/(`maxlag'+1))
        	}
        scalar `J' = `J' + 2*`w'*__gamma[`l'+1,1]
        }
    scalar `lrvar' = sqrt(`J'/`T')
    if `lrvar' == . {
    	di _n "Long-run variance is non-positive for this kernel and truncation lag."
    	error 506
    	}
*
	qui {
		tsset
		summ `r(timevar)' if `touse'
		}
	return scalar e1bar = `e1bar'
	return scalar e2bar = `e2bar'
 	return scalar dbar = `dbar'
 	return scalar lrsd = `lrvar'
	return scalar s1 = `dbar'/`lrvar'
	return scalar p = 2*normprob(-abs(return(s1)))
	return scalar nobs = `T'
	return scalar maxlag = `maxlag'
	return local criterion  `criterion'
	return local kernel  `window'
	di _n "Diebold-Mariano forecast comparison test for actual : {res:`depvar'}"
*	di in gr "Forecast period : " %tm r(min) " -" %tm r(max) "  N : `nobs'"
	di "Competing forecasts:  {res:`fcvar1'} versus {res:`fcvar2'}"
	di "Criterion: " return(criterion) " over " return(nobs) " observations"
	di in gr "`kmax'   Kernel : " return(kernel)
	di in gr _n "Series" _col(24)  return(criterion)
 	di in gr _dup(30) "_"
	di in ye "{res:`fcvar1'}" _col(20) in gr %9.4g `e1bar'
	di in ye "{res:`fcvar2'}" _col(20) in gr %9.4g `e2bar'
	di "Difference" _col(20) %9.4g `dbar'
	local better `fcvar1'
	if `e1bar'>`e2bar' {
		local better `fcvar2'
		}
	di _n "By this criterion, {res:`better'} is the better forecast"
	di "H0: Forecast accuracy is equal."
	di "S(1) = " %9.4g return(s1)   "  p-value = " %5.4f return(p)  _n
*	di "Forecast comparison statistic : " %9.4g
*	di "H0 : no difference in accuracy, distributed N(0,1) with pval = " %5.4f return(p)
	di _n
*
end

mata:
mata set matastrict on
void autocov(string scalar vname, real scalar dbar, string scalar touse)
{
        real matrix X, gamma
        real scalar T
        string rowvector vars
        string scalar v
   // access the Stata variables in varlist, respecting touse
        vars = tokens(vname)
        v = vars[|1,.|]
        st_view(X,.,v,touse)
// demean by dbar
		X = X :- dbar
// change all missing values to 0, which will be ignored in cross
		_editmissing(X,0)
// apply cross to get the covariances and scale by T
        gamma = cross(X,X) :/ strtoreal(st_local("T"))
// return the __gamma matrix
		st_matrix("__gamma",gamma)
}
end