*! xtevent.ado 3.1.0 July 11, 2024

version 13

program define xtevent, eclass

	* Replay routine
	if replay() {
		if "`e(cmd2)'"!="xtevent" exit 301
		else {
			loc rep = e(cmd)
			`rep'
		}
		exit
	}

	#d;
	syntax varlist(fv ts numeric) [aw fw pw] [if] [in] , /* Proxy for eta and covariates go in varlist. Can add fv ts later */	
	POLicyvar(varname) /* Policy variable */	
	[
	Window(string) /* Estimation window */
	pre(numlist >=0 min=1 max=1 integer) /* Pre-event time periods where anticipation effects are allowed */
	post(numlist >=0 min=1 max=1 integer) /* Post-event time periods where dynamic effects are allowed */
	overidpre(numlist >=0 min=1 max=1 integer) /* Pre-event time periods for overidentification */
	overidpost(numlist >=0 min=1 max=1 integer) /* Post-event time periods for overidentification */
	Panelvar(varname) /* Panel variable */
	Timevar(varname) /* Time variable */
	proxyiv(string) /* Instruments. For FHS set ins equal to leads of the policy */
	proxy (varlist numeric) /* Proxy variable */		
	TRend(string) /*trend(a -1) Include a linear trend from time a to -1. Method can be either GMM or OLS*/
	SAVek(string) /* Generate the time-to-event dummies, trend, and cohort-relative time interactions and keep them in the dataset */
	STatic /* Estimate static model */			
	reghdfe /* Estimate with reghdfe */
	addabsorb(string) /* Absorb additional variables in reghdfe */
	norm(numlist integer max=1) /* Normalization */
	REPeatedcs /*indicate that the input data is a repeated cross-sectional dataset*/
	cohort(string) /* create or variable varname, where varname is categorical variable indicating cohort */
	control_cohort(string) /* dummy variable to indicate cohort to be used as control in SA estimation*/
	SUNABraham /* Alias for cohort(create) */
	plot /* Produce plot */
	*
	/*
	These options passed to subcommands
	
	nofe /* No fixed effects */
	note /* No time effects */
	Kvars(string) /* Use previously generated dummies */
	IMPute(string) /* impute policyvar */
	*/
	]
	;
	#d cr

	
	
	* Capture errors
	
	if "`addabsorb'"!="" & "`reghdfe'"=="" {
		di as err "option {bf:addabsorb} only allowed with option {bf:reghdfe}"
		exit 198
	}	

	if "`proxy'" == "" & "`proxyiv'" != "" {
		di as err _n "With instruments, you must specify a proxy variable"
		exit 198
	}
	
	* If xtset, don't need panelvar and timevar	
	cap xtset
	if _rc==459 {
		if "`panelvar'"=="" & "`timevar'"!="" | "`panelvar'"!="" & "`timevar'"=="" | "`panelvar'"=="" & "`timevar'"=="" {
			di as err _n "If data have not been xtset, you must specify options {bf:panelvar} and {bf:timevar}"
			exit 198
		}
	}
	else if ("`panelvar'"!="" & "`panelvar'"!=r(panelvar)) | ("`timevar'"!="" & "`timevar'"!=r(timevar)) {
		di as err _n "Data have been xtset, and you specified options {bf:panelvar} or {bf:timevar} with variables different from those previously set. Run {cmd:xtset,clear}  or {cmd:xtset} your data again"
		exit 198
	}
	else if ("`panelvar'"=="" & "`timevar'"=="") | ("`panelvar'"!="" & "`timevar'"=="") | ("`panelvar'"=="" & "`timevar'"!="")  {
		di as txt _n "Using options {bf:panelvar} and {bf:timevar} from {cmd:xtset}"
		loc panelvar=r(panelvar)
		loc timevar=r(timevar)
	}

	if "`trend'"!="" & "`proxy'"!="" {
		di as err _n "options {bf:proxy} and {bf:trend} not allowed simultaneously"
		exit 198
	}
	
	if "`trend'"!="" & "`static'"!="" {
		di as err _n "options {bf:static} and {bf:trend} not allowed simultaneously"
		exit 198
	}
		
	* Always need window unless static is specified
	if "`window'"=="" & ("`static'"=="" & ("`pre'"=="" | "`post'"=="" | "`overidpre'"=="" | "`overidpost'"=="")) {
		di as err _n "option {bf:window} is required unless option {bf:static}, or options {bf:pre},{bf:post},{bf:overidpre}, and {bf:overidpost} are specified"
		exit 198
	}
	if "`window'"!="" & "`static'"!="" {
		di as err _n "option {bf:window} not allowed with option {bf:static}"
		exit 198
	}
	if "`window'"!="" & ("`static'"!="" | ("`pre'"!="" | "`post'"!="" | "`overidpre'"!="" | "`overidpost'"!="")) {
		di as err _n "option {bf:window} not allowed with options {bf:static},{bf:pre},{bf:post},{bf:overidpre}, or {bf:overidpost}"
		exit 198
	}
	if ("`static'"!="" & ("`pre'"!="" | "`post'"!="" | "`overidpre'"!="" | "`overidpost'"!="")) {
		di as err _n "option {bf:static} not allowed with options {bf:pre},{bf:post},{bf:overidpre}, or {bf:overidpost}"
		exit 198
	}
			
	if "`savek'"=="_k" {
		di as err _n "_k reserved for internal variables. Please choose a different stub"
		exit 198
	}
	
	if "`reghdfe'" != "" {
		foreach p in reghdfe ftools {
			cap which `p'
			if _rc {
				di as err _n "option {bf:reghdfe} requires {cmd: `p'} to be installed"
				exit 199
			}
		}
		if "`proxy'"!="" {
			foreach p in ivreghdfe ivreg2 ranktest avar {
				cap which `p'
				if _rc {
					di as err _n "option {bf:reghdfe} and IV estimation requires {cmd: `p'} to be installed"
					exit 199
				}
			}
		}
	}
	
	*inform panel variables in case of data is repeated cross-sectional
	if "`repeatedcs'"!=""{
		di as txt _n "Option {bf:repeatedcs} was specified. Using {bf:`panelvar'} as the panel variable and {bf:`timevar'} as the time variable."
	}
	
	if "`cohort'"!="" {
		cap which avar 
		if _rc {
			di as err _n "Sun-and-Abraham estimation requires {cmd: avar} to be installed"
			exit 199
		}
	}

	if "`control_cohort'"!="" & "`cohort'"=="" {
		di as err _n "{bf:control_cohort} requires {bf:cohort} to be specified"
		exit 198
	}

	if "`sunabraham'"!="" {		
		if "`cohort'"=="" loc cohort "create"		
	}

	* SA estimation not implemented with IV estimation yet
	if ("`cohort'"!="" | "`control_cohort'"!="" | "`sunabraham'"!="") & ("`proxy'"!="" | "`proxyiv'"!="") {
		di as err _n "Sun-and-Abraham estimation not allowed with proxy or instruments"
		exit 198
	}

	* Keep old vars that have reserved names to avoid dropping them if cleanup
	loc oldvars ""
	foreach x in _k_eq* _ttrend* __k* _f* _interact* _cohort _control_cohort {
		cap unab oldvarsadd: `x'
		loc oldvars "`oldvars' `oldvarsadd'"
		loc oldvarsadd ""
	}	

	tempvar tousegen
	
	* Do not mark variables, only if in here
	
	mark `tousegen' `if' `in'
	
	loc flagerr=0
	
	
	* first parsing of window 
	if "`window'"!="" {
		parsewindow `window'
		loc swindow = r(window)
		loc w_type = r(w_type)	
	}

	* Set norm to -1 if not specified
	if "`norm'"=="" loc norm = -1

	if "`static'"=="" {
		if "`window'"!="" {
			* Parse window
			loc nw : word count `window'
			
			* if window is numeric 
			if "`w_type'"=="numeric"{
				if `nw'==1 {
					loc lwindow = -`window'
					loc rwindow = `window'
				}
				else if `nw'==2 {
					loc lwindow : word 1 of `window'
					loc rwindow : word 2 of `window'
				}
			}
			* if window is string (max or balanced)
			if "`w_type'"=="string"{
				loc lwindow : word 1 of `window'
				loc rwindow : word 1 of `window'
			}
			
			if "`w_type'"=="numeric" {
				if  (-`lwindow'<0 | `rwindow'<0) {
					di as err _n "Window can not be negative"
					exit 198
				}
			}
		}
		else if "`window'"=="" & ("`pre'"!="" & "`post'"!="" & "`overidpre'"!="" & "`overidpost'"!="") {
			loc lwindow = `pre' + `overidpre'
			loc lwindow = -`lwindow'
			loc rwindow = `post' + `overidpost' -1 
			loc w_type = "numeric"
		}
		
		* If allowing for anticipation effects, change the normalization if norm is missing, or warn the user
		if ("`pre'"!="0" & "`pre'"!="") {
			if `norm'==-1 {
				loc norm = -`pre'-1
				di as text _n "You allowed for anticipation effects `pre' periods before the event, so the coefficient at `norm' was selected to be normalized to zero. Use options {bf:norm} and {bf:window} to override this."
			}
		}
		
		* Check that normalization is in window
		if "`w_type'"=="numeric" { 
			if (`norm' < `=`lwindow'-1' | `norm' > `rwindow') {
				di as err _n "The coefficient to be normalized to 0 is outside of the estimation window"
				exit 498
			}
		}
		* Do not allow norm and trend 
		if "`norm'" !="-1" & "`trend'" != "" {
			di as err _n "Option {bf:trend} not allowed with a value for option {bf:norm} different from -1."
			exit 198
		}
		*user			
		*if "`trend'"!="" loc trend "trend(`trend')"
		*else loc trend ""

		* Estimate
	
		if "`proxy'" == "" & "`proxyiv'" == "" {
			di as txt _n "No proxy or instruments provided. Implementing OLS estimator"
			cap noi _eventols `varlist' [`weight'`exp'] if `tousegen', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') lwindow(`lwindow') rwindow(`rwindow') w_type(`w_type') trend(`trend') savek(`savek') norm(`norm') `reghdfe' addabsorb(`addabsorb') `repeatedcs' cohort(`cohort') control_cohort(`control_cohort') `options' 
			if _rc {
				errpostest `oldvars'
			}
		}
		else {
			di as txt _n "Proxy for the confound specified. Implementing FHS estimator"
			cap noi _eventiv `varlist' [`weight'`exp'] if `tousegen', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') lwindow(`lwindow') rwindow(`rwindow') w_type(`w_type') proxyiv(`proxyiv') proxy (`proxy') savek(`savek')    norm(`norm') `reghdfe' addabsorb(`addabsorb') `repeatedcs' `options' 		
			if _rc {
				errpostest `oldvars'
			}
		}		
		* if window was max or balanced, return the found limits 
		if "`w_type'"=="string" {
			loc lwindow = r(lwindow)
			loc rwindow = r(rwindow)
		}
	}
	else if "`static'"=="static" {
		loc lwindow=.
		loc rwindow=.
		di as txt _n "option {bf:static} specified. Estimating static model"
		di as txt _n "Plotting options ignored"
		if "`proxy'" == "" & "`proxyiv'" == "" {
			di as txt _n "No proxy or instruments provided. Implementing OLS estimator"
			cap noi _eventolsstatic `varlist' [`weight'`exp'] if `tousegen', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') `reghdfe' addabsorb(`addabsorb') `repeatedcs' `options' `static'
			if _rc {
				errpostest `oldvars'
			}
		}
		
		else {
			di as txt _n "Proxy for the confound specified. Implementing FHS estimator"
			
			cap noi _eventivstatic `varlist' [`weight'`exp'] if `tousegen', panelvar(`panelvar') timevar(`timevar') policyvar(`policyvar') proxyiv(`proxyiv') proxy (`proxy') `reghdfe' addabsorb(`addabsorb') `repeatedcs' `options' `static'
			if _rc {
				errpostest `oldvars'
			}
		}
	}
	
	*don't try returning matrices and macros if noestimate is specified 
	loc noestimate = r(noestimate)
	if "`noestimate'"=="." loc noestimate ""
	if "`noestimate'"!="" exit 
	
	if `=r(flagerr)'!=1  {
	
		loc noestimate=r(noestimate)
		if "`noestimate'"=="." loc noestimate ""
		if "`noestimate'"!="" {
			loc savek = r(savek)
			*clear previous estimates, so it will not mix them with the new ones
			ereturn clear 
		}
		
		ereturn scalar lwindow= `lwindow'
		ereturn scalar rwindow=`rwindow'
		if "`pre'"!="" {
			ereturn scalar pre = `pre'
			ereturn scalar post = `post'
			ereturn scalar overidpre = `overidpre'
			ereturn scalar overidpost = `overidpost'
		}
		ereturn local cmdline `"xtevent `0'"' /*"*/
		ereturn local cmd2 "xtevent"
		ereturn local stub = "`savek'"
		ereturn local noestimate = "`noestimate'"
		ereturn local ambiguous = r(ambiguous)
		*don't return the remaining if the user indicated not to estimate  
		if "`noestimate'"!="" exit
	
		mat delta=r(delta)
		mat Vdelta=r(Vdelta)
		mat b = r(b)
		mat V = r(V)
		ereturn repost b=b V=V, esample(`tousegen')
		ereturn matrix delta = delta
		ereturn matrix Vdelta = Vdelta
		if "`=r(method)'"=="iv" {
			mat deltaxsc = r(deltaxsc)
			mat deltaov = r(deltaov)
			mat Vdeltaov = r(Vdeltaov)
			mat deltax = r(deltax)
			mat Vdeltax = r(Vdeltax)
			ereturn matrix deltaxsc = deltaxsc
			ereturn matrix deltaov = deltaov
			ereturn matrix Vdeltaov = Vdeltaov
			ereturn matrix deltax = deltax
			ereturn matrix Vdeltax = Vdeltax
			if `=r(x1)'!=. ereturn local x1 = r(x1)
			
		}
		loc sun_abraham = r(sun_abraham)
		if "`sun_abraham'"=="." loc sun_abraham ""
		if "`sun_abraham'"!="" {
			mat b_ir = r(b_ir)
			mat V_ir = r(V_ir)
			mat b_interact = r(b_interact)
			mat V_interact = r(V_interact)
			mat ff_w = r(ff_w)
			mat Sigma_ff = r(Sigma_ff)
			ereturn matrix b_ir = b_ir
			ereturn matrix V_ir = V_ir
			ereturn matrix b_interact = b_interact
			ereturn matrix V_interact = V_interact
			ereturn matrix ff_w = ff_w
			ereturn matrix Sigma_ff = Sigma_ff
		}
		
		loc saveov = r(saveov)
		if "`saveov'"=="." loc saveov ""
		if "`saveov'"!="" {
		
			mat mattrendy = r(mattrendy)
			mat mattrendx = r(mattrendx)
			mat deltaov = r(deltaov)			
			mat Vdeltaov = r(Vdeltaov)
			ereturn matrix mattrendy = mattrendy
			ereturn matrix mattrendx = mattrendx
			ereturn matrix deltaov = deltaov
			ereturn matrix Vdeltaov = Vdeltaov
			ereturn local trendsaveov = r(trendsaveov)
		}
		if "`trend'"!="" {
			ereturn local trendmethod = r(trendmethod)
			ereturn local trend = r(trend)
		}
		
		ereturn local names=r(names)
		loc cmd = r(cmd)
		ereturn local cmd = r(cmd)
		ereturn local df = r(df)
		ereturn local komit = r(komit)
		ereturn local kmiss = r(kmiss)
		ereturn local y1 = r(y1)
		ereturn local method = r(method)
		ereturn local depvar = r(depvar)
	}
	else {
		exit 198
	}
	
	if "`plot'"!="" xteventplot

end

* Program to parse window 
program define parsewindow, rclass

	syntax [anything] 
		
	tokenize "`anything'"
	loc nwwindow = wordcount("`anything'")
	if !inlist(`nwwindow',1,2) {
		di as err _n "{bf:window} can only have one or two elements."
		exit 198
	}
	
	*check that all words are numeric or string 
	loc isnum = 0
	forvalues i=1/`nwwindow'{
		cap confirm number ``i''
		if !_rc loc ++ isnum 
	}
	if `isnum'>0 & `isnum'<`nwwindow'{
		di as err _n "Invalid {bf:window} option."
		exit 198
	}
	
	* tell if all words are numeric or strings 
	if `isnum' == 0 loc w_type ="string"
	if `isnum' == `nwwindow' loc w_type ="numeric"
	
	* if all words are numbers, check that they are integers 
	if "`w_type'"=="numeric" {
		loc isnotint = 0
		forvalues i=1/`nwwindow'{
			cap confirm integer number ``i''
			if _rc!=0 loc ++ isnotint 
		}
		if `isnotint'>0 {
			di as err _n "Number in {bf:window} must be integer."
			exit 126
		}
	}
	
	* if all words are string, check that it is only one word and it is a valid option name 
	if "`w_type'"=="string" {
		
		if `nwwindow'>1 {
			di as err _n "If string, {bf:window} must have only one element."
			exit 198
		}
		
		if `nwwindow'==1 {
			if !inlist("`anything'","max","balanced"){
				di as err _n "{bf:window} must be {bf:max} or {bf:balanced}."
				exit 198
			}
		}
		
	}

	return local swindow "`anything'"
	return local w_type "`w_type'"
end	

program define cleanup

	syntax [anything]

	loc oldvars = "`anything'"

	foreach x in _k_eq* _ttrend* __k* _f* _interact* _cohort _control_cohort {
		cap unab todrop: `x'		
		loc todrop: list local todrop - oldvars
		cap drop `todrop'
		loc todrop ""	
		
	}
	cap _estimates clear
end

program define errpostest, rclass
	
	syntax [anything]

	cleanup `anything' _rc	
	return local flagerr=1
end