////////////////////////////////////////////////////////////////////////////////
// STATA FOR CHIANG, HANSEN, SASAKI
////////////////////////////////////////////////////////////////////////////////
!* version 14.2  12may2022
program define xtregtwo, eclass
    version 14.2

    syntax varlist(numeric) [if] [in] [, NOConstant FE]
    marksample touse

	qui xtset
	local panelid = r(panelvar)
	local timeid  = r(timevar)

    gettoken depvar indepvars : varlist
    _fv_check_depvar `depvar'
    fvexpand `indepvars'
    local cnames `r(varlist)'

    tempname b V N T NT M

	local const = 0
	if "`noconstant'" == "" {
	  local const = 1
	}

	local fixedeffect = 1
	if "`fe'" == "" {
	  local fixedeffect = 0
	}

	mata: estimation("`depvar'","`cnames'","`panelid'","`timeid'",		 ///
					 "`touse'","`b'","`V'","`N'","`T'","`NT'","`M'",     ///
					 `const',`fixedeffect')

	local cnames `cnames'
	if "`noconstant'" == "" & "`fe'" == ""{
		local cnames "`cnames' _cons"
	}
	matrix colnames `b' = `cnames'
	matrix colnames `V' = `cnames'
	matrix rownames `V' = `cnames'

	ereturn post `b' `V', esample(`touse') buildfvinfo
	ereturn scalar N    = `N'
	ereturn scalar T    = `T'
	ereturn scalar NT   = `NT'
	ereturn scalar M    = `M'
	ereturn local  cmd  "xtregtwo"
	ereturn display
	di "Reference: Chiang, H.D., B.E. Hansen, and Y. Sasaki (2022) Standard Errors for"
	di "Two-Way Clustering with Serially Correlated Time Effects. Working Paper."
end
////////////////////////////////////////////////////////////////////////////////

mata:
////////////////////////////////////////////////////////////////////////////////
// Estimation Function
void estimation(string scalar depvar, 	string scalar indepvars, 			 ///
                string scalar panelid, 	string scalar timeid,    			 ///
				string scalar touse, 	string scalar bname,	 			 ///
				string scalar Vname,   	string scalar nname,	 			 ///
				string scalar tname,	string scalar ntname,				 ///
				string scalar mname,	real scalar constant,				 ///
				real scalar fe)
{
    Y = st_data(., depvar, touse)
    X = st_data(., indepvars, touse)
    t = st_data(., timeid, touse)
	i = st_data(., panelid, touse)
	uniq_i = uniqrows(i)
	uniq_t = uniqrows(t)
	N = rows(uniq_i)
	T = rows(uniq_t)

	if( constant > 0.5 & fe < 0.5){
		ones = J(rows(X),1,1)
		X = X,ones
	}

////////////////////////////////////////////////////////////////////////////////
// Within-Transformation If Fixed Effect
	if( fe > 0.5 ){
		for( uidx = 1 ; uidx <= N ; uidx++ ){
			idx = uniq_i[uidx,1]
			for( jdx = 1 ; jdx <= cols(X) ; jdx++ ){
				X[select((1..rows(X))',i:==idx),jdx] = X[select((1..rows(X))',i:==idx),jdx] :- mean(X[select((1..rows(X))',i:==idx),jdx])
			}
			Y[select((1..rows(X))',i:==idx),  1] = Y[select((1..rows(X))',i:==idx),  1] :- mean(Y[select((1..rows(X))',i:==idx),  1])
		}
		//X = X[select((1..rows(X))',t:!=uniq_t[1,1]),]
		//Y = Y[select((1..rows(Y))',t:!=uniq_t[1,1]),]
		//i = i[select((1..rows(i))',t:!=uniq_t[1,1]),]
		//t = t[select((1..rows(t))',t:!=uniq_t[1,1]),]
		uniq_i = uniqrows(i)
		uniq_t = uniqrows(t)
		N = rows(uniq_i)
		T = rows(uniq_t)
	}

////////////////////////////////////////////////////////////////////////////////
// OLS and Residual
	beta = luinv(X'*X)*X'*Y
	Uhat = Y :- X*beta

////////////////////////////////////////////////////////////////////////////////
// Variance Estimation

	// Omega Hat 1st Term
	Omega_hat_1 = J(cols(X),cols(X),0)
	for( uidx = 1 ; uidx <= N ; uidx++ ){
		idx = uniq_i[uidx,1]
		for( jdx = 1 ; jdx <= cols(X) ; jdx++ ){
			for( kdx = 1 ; kdx <= jdx ; kdx++ ){
				Omega_hat_1[jdx,kdx] = Omega_hat_1[jdx,kdx] + sum((X[select((1..rows(X))',i:==idx),jdx]:*Uhat[select((1..rows(X))',i:==idx),1]) * (X[select((1..rows(X))',i:==idx),kdx]:*Uhat[select((1..rows(X))',i:==idx),1])')
				Omega_hat_1[kdx,jdx] = Omega_hat_1[jdx,kdx]
	}}}
	Omega_hat_1 = min((N,T)')/N/(N*T^2) :* Omega_hat_1

	// Omega Hat 2nd Term
	Omega_hat_2 = J(cols(X),cols(X),0)
	for( utdx = 1 ; utdx <= T ; utdx++ ){
		tdx = uniq_t[utdx,1]
		for( jdx = 1 ; jdx <= cols(X) ; jdx++ ){
			for( kdx = 1 ; kdx <= jdx ; kdx++ ){
				Omega_hat_2[jdx,kdx] = Omega_hat_2[jdx,kdx] + sum((X[select((1..rows(X))',t:==tdx),jdx]:*Uhat[select((1..rows(X))',t:==tdx),1]) * (X[select((1..rows(X))',t:==tdx),kdx]:*Uhat[select((1..rows(X))',t:==tdx),1])')
				Omega_hat_2[kdx,jdx] = Omega_hat_2[jdx,kdx]
	}}}
	Omega_hat_2 = min((N,T)')/T/(N^2*T) :* Omega_hat_2

	// Omega Hat 3rd Term
	Omega_hat_3 = J(cols(X),cols(X),0)
	for( jdx = 1 ; jdx <= cols(X) ; jdx++ ){
		for( kdx = 1 ; kdx <= jdx ; kdx++ ){
			Omega_hat_3[jdx,kdx] = (X[,jdx]:*Uhat[,1])' * (X[,kdx]:*Uhat[,1])
			Omega_hat_3[kdx,jdx] = Omega_hat_3[jdx,kdx]
	}}
	Omega_hat_3 = min((N,T)')/(N*T)/(N*T) :* Omega_hat_3

	// Select M Hat
	rho_hat = J(cols(X),1,0)
	for( kdx = 1 ; kdx <= cols(X) ; kdx++ ){
		utdx = 2
		tdx = uniq_t[utdx,1]
		tdx_lag = uniq_t[utdx-1,1]
		S = X[select((1..rows(X))',t:==tdx),kdx]:*Uhat[select((1..rows(X))',t:==tdx),1]
		S_lag = X[select((1..rows(X))',t:==tdx_lag),kdx]:*Uhat[select((1..rows(X))',t:==tdx_lag),1]
		for( utdx = 3 ; utdx <= T ; utdx++ ){
			tdx = uniq_t[utdx,1]
			tdx_lag = uniq_t[utdx-1,1]
			S = S \ X[select((1..rows(X))',t:==tdx),kdx]:*Uhat[select((1..rows(X))',t:==tdx),1]
			S_lag = S_lag \ X[select((1..rows(X))',t:==tdx_lag),kdx]:*Uhat[select((1..rows(X))',t:==tdx_lag),1]
		}
		rho_hat[kdx] = ( luinv((J(rows(S_lag),1,1),S_lag)' * (J(rows(S_lag),1,1),S_lag)) * (J(rows(S_lag),1,1),S_lag)'*S )[2,1]
	}
	m = floor( 1.8171 * ( sum(rho_hat:^2:/(1:-rho_hat):^4) / sum((1:-rho_hat:^2):^2:/(1:-rho_hat):^4) )^(1/3) * T^(1/3) )

	// Omega Hat 4th Term
	Omega_hat_4 = J(cols(X),cols(X),0)
	for( j = 1 ; j <= m ; j++ ){
		temp_Omega_hat_4 = J(cols(X),cols(X),0)
		w = 1 - (j/(m+1))
		for( utdx = (j+1) ; utdx <= T ; utdx++ ){
			tdx = uniq_t[utdx,1]
			tdxminus = uniq_t[utdx-j,1]
			for( jdx = 1 ; jdx <= cols(X) ; jdx++ ){
				for( kdx = 1 ; kdx <= cols(X) ; kdx++ ){
					temp_Omega_hat_4[jdx,kdx] = temp_Omega_hat_4[jdx,kdx] + sum((X[select((1..rows(X))',t:==tdx),jdx]:*Uhat[select((1..rows(X))',t:==tdx),1]) * (X[select((1..rows(X))',t:==tdxminus),kdx]:*Uhat[select((1..rows(X))',t:==tdxminus),1])')
		}}}
		temp_Omega_hat_4 = w / (N^2*T) * temp_Omega_hat_4
		Omega_hat_4 = Omega_hat_4 + temp_Omega_hat_4
	}
	Omega_hat_4 = min((N,T)')/T :* Omega_hat_4

	// Omega Hat 5th Term
	Omega_hat_5 = J(cols(X),cols(X),0)
	for( j = 1 ; j <= m ; j++ ){
		temp_Omega_hat_5 = J(cols(X),cols(X),0)
		w = 1 - (j/(m+1))
		for( utdx = (j+1) ; utdx <= T ; utdx++ ){
			tdx = uniq_t[utdx,1]
			tdxminus = uniq_t[utdx-j,1]
			for( jdx = 1 ; jdx <= cols(X) ; jdx++ ){
				for( kdx = 1 ; kdx <= cols(X) ; kdx++ ){
					temp_Omega_hat_5[jdx,kdx] = temp_Omega_hat_5[jdx,kdx] + sum((X[select((1..rows(X))',t:==tdxminus),jdx]:*Uhat[select((1..rows(X))',t:==tdxminus),1]) * (X[select((1..rows(X))',t:==tdx),kdx]:*Uhat[select((1..rows(X))',t:==tdx),1])')
		}}}
		temp_Omega_hat_5 = w / (N^2*T) * temp_Omega_hat_5
		Omega_hat_5 = Omega_hat_5 + temp_Omega_hat_5
	}
	Omega_hat_5 = min((N,T)')/T :* Omega_hat_5

	// Add the 5 Terms to Get Omega Hat
	Omega_hat = Omega_hat_1 :+ Omega_hat_2 :- Omega_hat_3 :+ Omega_hat_4 :+ Omega_hat_5

	// Q Hat
	Q_hat = X' * X :/ rows(X)

	// Variance
	V = luinv(Q_hat) * Omega_hat * luinv(Q_hat) :/ min((N,T)')

////////////////////////////////////////////////////////////////////////////////
// Output
    st_matrix(bname, beta')
    st_matrix(Vname, V)
    st_numscalar(nname, N)
	st_numscalar(tname, T)
	st_numscalar(ntname, N*T)
	st_numscalar(mname, m)
}
end
////////////////////////////////////////////////////////////////////////////////