*! version 1.0.3  07oct2022  I I Bolotov
program define xtarimau, rclass
	version 16.0
	/*
		Finds the best [S]ARIMA[X] models in heterogeneous panels with the help
		of (SSC) arimaauto. The user can run a command or program (= multiple
		commands at a time) prior to and after the estimation, generating
		eventual new variables. The estimates can be exported to a .ster file.

		Author: Ilya Bolotov, MBA, Ph.D.
		Date: 15 January 2022
	*/
	tempname timevar panelvar panelval ictests icarima limits models		///
			 title rspec cspec tmp
	tempfile tmpf
	// check for third-party packages from SSC
	qui which arimaauto
	// replay last result
	if replay() {
		if _by() {
			error 190
		}
		cap confirm mat r(models)
		if _rc {
			di as err "results of xtarimau not found"
			exit 301
		}
		/* copy return values                                                 */
		loc `ictests' `=r(ictests)'
		loc `icarima' `=r(icarima)'
		mat `limits'  = r(limits)
		mat `models'  = r(models)
		/* print output                                                       */
		cap confirm mat `models'
		if ! _rc {
			di as res _n "Best models for each time series:"
			loc `rspec' = "& - `= "& " * rowsof(`models')'"
			loc `cspec' = "& %12s | %5.0f & %5.0f & %5.0f & %5.0f & " +		///
						  "%5.0f & %5.0f & %6.0f | %12.4f &"
			matlist `models', title(``title'') rspec(``rspec'') cspec(``cspec'')
		}
		/* return output                                                      */
		cap ret        loc ictests  ``ictests''
		cap ret        loc icarima  ``icarima''
		cap ret hidden mat limits = `limits'
		cap ret        mat models = `models'
		exit 0
	}
	// syntax
	syntax																	///
	[varlist(ts fv)] [if] [in] [iw] [,										///
		PREestimation(string asis) POSTestimation(string asis)				///
		export(string asis) SDtest *										///
	]
	// adjust and preprocess options
	loc `tmp' = ustrregexm(`"`options'"', ".*ic\((\w+)\).*", 1)
	loc ic    = cond(ustrregexs(1) == "", "aic", ustrregexs(1))
	// get timevar, panelvar and panelval
	qui tsset, noq
	if "`r(panelvar)'" == "" {						 // examine data
		di as err "command requires panel data"
		exit 459
	}
	loc `timevar'  = r(timevar)
	loc `panelvar' = r(panelvar)
	qui levelsof ``panelvar'', matrow(`panelval')
	loc `tmp'      = r(r)
	// check if system limit is not exceeded
	qui estimates dir
	if (`: word count `r(names)'' + ``tmp'') > 300 & trim(`"`export'"') == "" {
		di as err															///
		"operation will exceed system limit, please add an export() option"
		exit 1000
	}
	// perform estimation
	qui ds ``panelvar'' ``timevar'', not			 // get the initial varlist
	loc `tmp' = r(varlist)
	cap estimates drop ts_*							 // drop xtarimau estimates
	mata: MS = J(0,8,.)								 // MS -> r(models)
	preserve
	forv i = 1/`=rowsof(`panelval') + 1' {
		/* restore, add eventual variables to the initial varlist             */
		restore
		if trim("`r(varlist)'") != "" {
			qui merge 1:1 ``panelvar'' ``timevar'' using `tmpf', update nogen
		}
		/* preserve, reduce the dataset to 1 time series and re-set the data  */
		preserve
		qui {
			keep ``panelvar'' ``timevar'' ``tmp''	 // the initial dataset
			keep if ``panelvar'' == `panelval'[`i',1]
			if `=_N' {								 // continue or break loop
				tsset, clear
				tsset ``timevar''
			}
			else {
				continue, break
			}
		}
		/* run preestimation command/program (= multiple commands)            */
		if trim(`"`preestimation'"') != "" {
			`=cond(! strpos(`"`options'"', "trace"), "qui", "")'			///
			`preestimation'
		}
		/* run arimaauto, store/save estimates and fill MS -> r(models)       */
		`=cond(! strpos(`"`options'"', "trace"), "qui", "")'				///
		arimaauto `varlist' `if' `in' [`weight'`exp'], `options'
		mata: t  = st_matrix("r(tests)"); ms = st_matrix("r(models)");
		mata: u  = rows(t) ?                                                ///
		           (mod(rows(t), 2) ? (floor((colsum(t[,1]) - 1) / 2),1)    ///
		                            : (floor(colsum(t[,1])       / 2),0))   ///
		         : (0,0)
		mata: c  = ("`ic'":==("llf", "aic","sic")) * (6::8)
		mata: r  = selectindex(ms[,c] :== (c == 6 ? max(ms[,c])             ///
		                                          : min(ms[,c])))
		mata: MS = MS\(ms[r,1],u[1],ms[r,2..3],u[2],ms[r,4..5],ms[r,c])
		loc `ictests' `=r(ictests)'					 // read from r(...)
		loc `icarima' `=r(icarima)'
		mat `limits'  = r(limits)
		if trim(`"`export'"') == "" {
			qui estimates store ts_`i'
		}
		else {
			qui estimates save `"`export'"', append
		}
		cap drop _*									 // drop ancillary vars
		/* run postestimation command/program (= multiple commands)           */
		if trim(`"`postestimation'"') != "" {
			`=cond(! strpos(`"`options'"', "trace"), "qui", "")'			///
			`postestimation'
		}
		/* pass eventual new variables to the dataset                         */
		qui {
			drop ``tmp''
			ds ``panelvar'' ``timevar'', not
			save `tmpf', replace
		}
		/* draw a dot                                                         */
		if ! strpos(`"`options'"', "trace") {
			di as txt "." _continue
		}
	}
	qui {
		tsset, clear								 // re-set the data
		tsset ``panelvar'' ``timevar''
	}
	// get models
	mata: st_matrix("`models'", MS)
	mata: if (rows(MS)) st_matrixrowstripe(                                 ///
		"`models'", (J(rows(MS),1,""),strofreal(1::rows(MS)))               ///
	);;
	mata: if (rows(MS)) st_matrixcolstripe(                                 ///
		"`models'", (J(8,1,""),("p","d","q","P","D","Q","const",            ///
		                        strupper("`ic'"))')                         ///
	);;
	// print output
	cap confirm mat `models'
	if ! _rc {
		di as res _n "Best models for each time series:"
		loc `rspec' = "& - `= "& " * rowsof(`models')'"
		loc `cspec' = "& %12s | %5.0f & %5.0f & %5.0f & %5.0f & " +			///
					  "%5.0f & %5.0f & %6.0f | %12.4f &"
		matlist `models', title(``title'') rspec(``rspec'') cspec(``cspec'')
	}
	// print tests
	if trim(`"`sdtest'"') != "" {
		mata: p = (! strpos(st_local("options"), "stat") ? 1..3 :    (1,3)), /*
			   */ (! strpos(st_local("options"), "seas") ? 4..6 : J(1,0,.))
		mata: for(i = 1; i <= cols(p); i++) {;                              ///
			  printf("\n{res}"  + ("p","d","q","P","D","Q")[p[i]]         + ///
			         " {txt}in {res}" + (p[i] <= 3 ? "arima(#p,#d,#q)"      ///
			                                       : "sarima(#P,#D,#Q)")    ///
			);                                                              ///
			  (void) _stata("sdtesti "                                    + ///
			                strofreal(rows(st_matrix("`models'")))        + ///
			                "      . "                                    + ///
			                strofreal(sqrt(variance(                        ///
			                               st_matrix("`models'")[.,p[i]]    ///
			                ))) + " 0"                                      ///
			); };
		mata: mata drop p i
	}
	// return output
	cap ret        loc ictests  ``ictests''
	cap ret        loc icarima  ``icarima''
	cap ret hidden mat limits = `limits'
	cap ret        mat models = `models'
	// clear memory
	estimates drop .
	mata: mata drop MS t ms u c r
end