*! Attaullah Shah ;  Email: attaullah.shah@imsciences.edu.pk ; * Support website: www.FinTechProfessor.com

*!Version 4.8 : Jul 02, 2023 : Time labels were incorrectly displayed with fmb first options. Fixed now.

* Version 4.7: Jan 07, 2022 : When data had duplicates, the fitted and residual were not calculated for each obs. This has been fixed.
* Version 4.6 : Oct 13, 2021 : Added [aweights] and noconstant to fmb
* Version 4.5 : Feb 17, 2021 : Shanken license updated
* Version 4.4 : Feb 09, 2021 : Issue with by groups regressions solved : Also, removed the select index pointer
* Version 4.3 : Feb 05, 2021 : label issue with the fmb, first regression is fixed.
* Version 4.2 : Jan 31, 2021 : Option exclude(keyvar low high) added
* Version 4.1 : Nov 02, 2020 : New system for shanken correction license
* Version 4.0 : Oct 29, 2020 : Added flexible window : window(year -40 10) etc.
* Version 3.4 : Jul 09, 2019 : Added adjusted r-squared to fmb
* Version 3.3 : Dec 19, 2018 : Fixed a bug in  function ASREG4s1f1()
* Version 3.2 : Jul 01, 2018 : Fixed bugs in newey()
* Added noConstant and Robust options
* Added Fama and MacBeth regressions and newey SE
* Multiple functions for se and fitted values


cap prog drop asreg
prog asreg, byable(onecall) sortpreserve eclass
	version 11

	syntax       	    ///
		varlist(min=1)  ///
		[aw/]            /// works only with fmb
		[in] [if],      ///
		[Window(string) ///
		MINimum(real 0) ///
		by(varlist)     ///
		FITted          ///
		SE            	///
		RMSE			///
		RECursive		///
		FMB				///
		newey(int 0)    ///
		first    		///
		save(string)	///
		KEEP(string)	///
		NOConstant      ///
		Robust			///
		shanken(str)	///
		EXclude(str)	///
		]

	if "`shanken'" != "" {
		if "`fmb'" == "" {
			dis as error "Option Shanken() can be used only with option fmb"
			exit
		}
		else {
			cap confirm matrix `shanken'
			if _rc {
				dis as error "Option Shanken needs a matrix of covariances among the right-hand side variables"
				dis as text "asreg could not find such a matrix"
				exit
			}
			else {
				loc colS = colsof(`shanken')
				dis "`varlist'"
				loc nRHS : word count `varlist'


				if `colS' != `=`nRHS'-1' {
					dis as error "The covariance matrix `shanken' has `colS' columns"
					dis as error "whereas you have entered `=`nRHS'-1' right hand-side variables"
					dis as error "Columns of matrix `shanken' and the right hand-side variables should be equal in number"
					exit
				}

			}
		}

	}
	marksample touse
	if "`fmb'" == "" {
		if "`window'"!=""{
		    loc window = subinstr("`window'", ",", " ", .)
			local nwindow : word count `window'
			if !inrange(`nwindow', 2, 3) {
				dis ""
				display as error "Option window must have either two arguments: rangevar and length of the rolling window, e.g., {opt window(year 10)}"
				dis as error " Or three arguments : rangeevar, length of the backward, and the forward windows e.g., {opt window(year -10 20)}"
				exit
			}
			else if `nwindow'==2 {
				tokenize `window'
				gettoken    rangevar window : window
				gettoken  bw window : window
				confirm number `bw'
				confirm numeric variable `rangevar'
			}
			else if `nwindow' == 3 {
				local rangevar : word 1 of `window'
				local bw : word 2 of `window'
				local fw : word 3 of `window'
				confirm number `bw'
				confirm number `fw'
				confirm numeric variable `rangevar'

				if (`bw' >= `fw') {
					display as error "The lower bound of the window is either equal to or less than the upper bound."
					display as error "Rolling window calculations are not possible when this is the case!"
					exit
				}

				if `fw' == 0 {
					loc fw
					loc bw = abs(`bw')
				}
			}



			if "`recursive'"~=""{
				local recursive = 1000000
			}
			else{
				local recursive = 0
			}

			local bw = `bw' + `recursive'

		}

		if "`exclude'" != "" {
		    loc exclude = subinstr("`exclude'", ",", " ", .)
			local nexclude : word count `exclude'
			if !inlist(`nexclude', 3) {
				dis as error "Error in the option exclude()!"
				display as text "Option exclude must have three arguments : keyvar, low and high range., {opt exclude(year -2 2)}"
				exit
			}
			local keyvar : word 1 of `exclude'
			cap confirm numeric variable `keyvar'
			if _rc {
				di as error "Error in the option exclude!"
				di as text "The first argument must be a numeric variable"
				exit
			}
			local xlow : word 2 of `exclude'
			local xhigh : word 3 of `exclude'
			confirm number `xlow'
			confirm number `xhigh'
			if `xlow' > `xhigh' {
				dis as error "Invalide exclude range!"
				dis as text "The second argument of exclude should be lower than the third argument"
				exit
			}


		}

		gettoken lhsvar rhs : varlist
		loc varlist "`lhsvar' `rhs'"
		qui {
			if "`rmse'"!="" {
				qui gen double _rmse = .
				label var _rmse "Root-mean-squared error"
				local _b_rmse _rmse
			}

			gen  _Nobs 	= .
			label var _Nobs "No of observatons"
			gen double _R2 	= .
			label var _R2 "R-squared"
			gen double _adjR2	= .
			label var _adjR2 "Adjusted R-squared"

			if "`rhs'" != "" {

				foreach var of varlist `rhs'{
					gen double _b_`var'=.
					label var _b_`var' "Coefficient of `var'"
					local b_rvsvars "`b_rvsvars' _b_`var'"

				}
			}
			if `newey' != 0 {
				loc mindif = `newey' - `minimum' +1
				loc minimum = `mindif'
			}

			if "`noconstant'" == "" {
				tempvar _CONS
				qui gen `_CONS' = 1
				gen double _b_cons = .
				label var _b_cons "Constant of the regression"
				loc _b_cons _b_cons
			}

			if "`newey'" ! = "0" | "`robust'" ! = "" loc se se

			if "`se'"!=""{
				if `newey' != 0 & "`robust'" != "" {
					dis as error "Option newey() and robust cannot be combined"
					exit
				}
				if `newey' != 0 local se_text "Newey adj. Std. errors of "
				else if "`robust'" != "" local se_text "Robust Std. errors of "
				else local se_text "Standard Std. errors of "

				if "`rhs'" != "" {
					foreach var of varlist `rhs'{
						gen _se_`var'=.
						label var _se_`var' "`se_text'`var'"
						local _se_rvsvars "`_se_rvsvars' _se_`var'"
					}
				}

				if "`noconstant'" == "" {
					gen _se_cons = .
					label var _se_cons "`se_text'constant"
					loc _se_cons _se_cons
				}
				local _se_rvsvars "`_se_rvsvars' `_se_cons'"
			}
			if ("`fitted'" != "") {
				gen double  _fitted =.
				gen double  _residuals =.
				local fitres "_fitted _residuals"
			}

			local ResultsVars "_Nobs _R2 _adjR2   `b_rvsvars' `_b_cons' `_se_rvsvars'  `fitres' `_b_rmse'"
		}

		if "`timevar'" != "" {
			local by "`timevar'"
		}

		if "`_byvars'"!="" {
			local by "`_byvars'"
		}

		if "`by'"=="" {
			tempvar by
			qui gen `by' = 1
		}

		local cversion =`c(version)'
		tempvar __GByVars __000first __0dIf
		qui bysort `by' (`rangevar'): gen  `__000first' = _n == 1
		qui gen `__GByVars'=sum(`__000first')
		qui drop `__000first'

		qui by `by' : gen `__0dIf' = `rangevar' - `rangevar'[_n-1]

		loc dt = 2
		cap qui assert `__0dIf' == 1 | `__0dIf' == .
		if _rc == 0 loc dt = 1
		else {
		    cap assert `__0dIf' == 0  if `__0dIf' < 1, fast null
			if _rc != 8 & _rc != 9 {
				loc dt = 3
				tempvar touse2

				if "`fw'" == "" qui bysort `__GByVars' `rangevar' : gen `touse2' = _n == _N
				else            qui bysort `__GByVars' `rangevar' : gen `touse2' = _n == 1
			}
		}




		if "`bw'" != "" {

			if "`fw'" == "" {
				mata: ASREGW(				///
					"`varlist' `_CONS'",  	///
					"`__GByVars'" ,		   	///
					"`ResultsVars'" , 	 	///
					`bw',		  			///
					`minimum', 	       		///
					`newey' ,			  	///
					"`rangevar'",	      	///
					"`__0dIf'"	,			///
					"`se'",           		///
					"`fitted'",      		///
					`c(version)' ,  		///
					"`rmse'",				///
					"`robust'",     		///
					"`noconstant'",  		///
					"`touse'",	    		///
					"`touse2'",				///
					"`exclude'"				///
					)

				if "`dt'" == "3" {

					if "`fitted'" != "" {
						loc removeFitted _fitted
						loc removeResiduals _residuals

						local  ResultsVars : list ResultsVars - removeFitted
						local  ResultsVars : list ResultsVars - removeResiduals
					}

					foreach v of varlist `ResultsVars' {
						qui bys `__GByVars' `rangevar': replace `v' =`v'[_N]
					}

					if "`fitted'" != "" gen_residual_fitted `rhs', depvar(`lhsvar') constant(`_b_cons')

				}


			}
			else  {
				mata: ASREGFW(				///
					"`varlist' `_CONS'",  	///
					"`__GByVars'" ,		   	///
					"`ResultsVars'" , 	 	///
					`bw',		   			///
					`fw', 					///
					`minimum', 	       		///
					`newey' ,			  	///
					"`rangevar'",	      	///
					"`dt'"	,				///
					"`se'",           		///
					"`fitted'",      		///
					`c(version)' ,  		///
					"`rmse'",				///
					"`robust'",     		///
					"`noconstant'",  		///
					"`touse'",	    		///
					"`touse2'",				///
					"`exclude'"				///
					)
				if "`dt'" == "3" {
					foreach v of varlist `ResultsVars' {
						qui bys `__GByVars' `rangevar': replace `v' =`v'[1]
					}
				}
			}


		}

		else{

			mata: ASREGNW(         		///
				"`varlist' `_CONS'",    ///
				"`__GByVars'" ,			///
				"`ResultsVars'" , 		///
				"`se'", 				///
				"`fitted'",				///
				`minimum',				///
				`newey' , 				///
				"`rmse'",				///
				"`robust'",		    	///
				"`noconstant'",  		///
				"`touse'",	         	///
				"`exclude'"				///
				)
		}
		cap qui label variable `generate' "`stat' of `varlist' in a rol. window"
		if "`keep'"!="" {
			local keep _fitted
			qui cap unab v : _se_*
			cap confirm var _fitted
			if _rc==0 local var `var' _fitted _residuals
			cap noi unab b : _b_*
			local all `b' `var' _R2 _adjR2 _Nobs
			local drop : list all - keep
			drop `drop'
		}
	}

	else {
		marksample touse
		preserve
		qui _xt
		local timevar `r(tvar)'
		sort `timevar'
		local nvars : word count `varlist'

		if `newey'< 0 {
			di in red `"newey(`newey') invalid lag selected"'
			exit 198
		}

		gettoken lhsvar rhs : varlist

		tsrevar `lhsvar'
		loc lhsvar  `r(varlist)'

		// tsrevar, list

		if (strmatch("`rhs'", "*.*")) {
			tsrevar `rhs'
			loc rhsvars  `r(varlist)'
			tsrevar `rhs', list
			loc rhs  `r(varlist)'
		}
		else loc rhsvars `rhs'

		loc varlist "`lhsvar' `rhsvars'"

		qui count if `touse'
		local observations = r(N)

		loc IFaweightExists 0

		if ("`weight'" == "aweight") {
		    loc aweight `exp'
			loc IFaweightExists 1
			loc __aweight __aweight
		    asreg_aweights `varlist', aweight(`aweight') `noconstant'

		}

		qui {
			foreach i of varlist `rhs'{

				gen double _b_`i'=.

				local b_rvsvars "`b_rvsvars' _b_`i'"
			}

			cap drop _TimeVar obs R2 cons

			gen _R2      = .
			gen _adjR2   = .
			gen _TimeVar = .
			gen _obs     = .

			if "`noconstant'" == "" {
				gen _Cons = .
				loc _constant _Cons
				loc cons ":cons"
			}

			local ResultsVars "_TimeVar _obs _R2 _adjR2  `b_rvsvars' `_constant'"

		}

		if `IFaweightExists' {

		    mata: fmb_first_stage_aw("`varlist' `__aweight'",   ///
				"`timevar'" , "`ResultsVars'", "`aweight'" ,        ///
				"`noconstant'", "`touse'")
		}

		else mata: fmb_first_stage("`varlist'", "`timevar'" , "`ResultsVars'" ,  ///
			"`noconstant'", "`newey'", "`touse'")

		qui count if _obs != .

		loc periods = r(N)

		if ("`save'" != "") {

			keep if _obs != .

			qui save "`save'", replace
		}

		if ("`first'" != "") report_first_stage_fmb `ResultsVars', timevar(`timevar')

		if (`newey' == 0) 	mata: fmb_with_se("`b_rvsvars'", "`noconstant'")

		else mata: fmb_with_newey("`b_rvsvars' `_constant'", `newey')

		foreach var of local rhsvars {

			local Labels "`Labels' :`var'"
		}

		qui sum _R2
		ereturn local avgr2 = r(mean)
		qui sum _adjR2, meanonly

		ereturn local r2_a = r(mean)
		restore

		if "`shanken'" != "" {

			mata: shanken_se("v", "b", "`shanken'", `periods',  "`c(os)'")
		}
		matrix rownames b = `lhsvar'
		matrix colnames b = `Labels' `cons'
		matrix rownames v = `Labels' `cons'
		matrix colnames v = `Labels' `cons'

		ereturn clear
		ereturn post b v, esample(`touse') depname("`lhsvar'")

		ereturn scalar N = `observations'
		ereturn scalar N_g = `periods'
		ereturn scalar df_m = wordcount("`rhsvars'")
		ereturn scalar df_r = `periods' - (wordcount("`rhsvars'")+1)
		local df_r = `periods' - (wordcount("`rhsvars'")+1)

		cast_e_here df_r `df_r'


		qui if "`rhsvars'"!=""  test `rhsvars', min
		ereturn scalar F = r(F)

		ereturn scalar r2 = `avgR2'
		ereturn scalar r2_a = `adjR2'

		if `newey' == 0 {
			if "`shanken'" == "" {
				ereturn local vcetype "Fama-MacBeth"
				local title "Fama-MacBeth (1973) Two-Step procedure"
			}
			else {
				ereturn local vcetype "Shanken"
				local title "Fama-MacBeth procedure with Shanken Correction"
			}

		}
		else {
			ereturn local vcetype "Newey-FMB"
			ereturn local title "Fama-MacBeth Two-Step procedure (Newey SE)"
			local title "Fama-MacBeth Two-Step procedure (Newey SE)"
			local Newey_Text "(Newey-West adj. Std. Err. using lags(`newey'))"

		}

		ereturn local depvar "`lhsvar'"
		ereturn local method "Fama-MacBeth Two-Step procedure"
		ereturn local cmd "asreg"
		local R2text "avg. R-squared    =    "
		local adjR2text "Adj. R-squared    =    "
		#delimit ;
		disp _n
		in green `"`title'"'
		_col(50) in green `"Number of obs     ="' in yellow %10.0f `observations' _n
		in green "`Newey_Text'"
		_col(50) in green `"Num. time periods ="' in yellow %10.0f e(N_g) _n
		_col(50) in green `"F("' in yellow %3.0f e(df_m) in green `","' in yellow %6.0f e(df_r)
		in green `")"' _col(68) `"="' in yellow %10.2f e(F) _n
		_col(50) in green `"Prob > F          =    "'
		in yellow %6.4f fprob(e(df_m),e(df_r),e(F)) _n
		_col(1) in green `"`SE_Text'"'
		_col(50) in green `"`R2text'"' in yellow %5.4f `avgR2' _n
		_col(50) in green `"`adjR2text'"' in yellow %5.4f `adjR2'
		;
		#delimit cr
		ereturn display, level(95)


		if "`save'" != "" {

			preserve

			use "`save'", clear
			qui keep `ResultsVars'
			capture qui rename _TimeVar `timevar'
			qui save "`save'", replace
			di as smcl `"First stage regression results saved in {browse "`save'.dta"}"'
		}
	}
end

program define cast_e_here, eclass
	ereturn scalar `1' = `2'
end

* gen_residual_fitted: Version 1.0 Jan 7, 2021
program define gen_residual_fitted

	syntax varlist, depvar(str) [constant(str) ]
	if "`constant'" == "" local constant 0


	local sum_notation `constant'

	foreach v of local varlist {
		local sum_notation `sum_notation' + (`v' * _b_`v')
	}
	qui cap replace _fitted    = `sum_notation'     if _fitted       == .
	qui cap replace _residuals = `depvar' - _fitted if _residuals    == .
end

* asreg_aweights: Version 1.0 Oct 13, 2021
program define asreg_aweights

	syntax varlist [if] [in], AWeight(str) [NOConstant]

	if ("`noconstant'" == "") loc __aweight __aweight

	quietly gen __aweight = sqrt(`aweight')

	foreach v of varlist `varlist' {

		qui replace `v' = `v' * __aweight

	}

end


* report first stage fmb regressions: Version 1.0 July 2, 2023
program define report_first_stage_fmb

	syntax varlist, TIMEvar(varlist)
	preserve
	qui keep `varlist'
	qui drop if _obs == .
	foreach v of varlist `varlist' {
		qui format %8.0g `v'
	}

	display "{title:First stage Fama-McBeth regression results}"
	capture qui rename _TimeVar `timevar'
	capture qui rename _Cons constant
	capture qui rename _R2 R²
	capture qui rename _adjR2 adjR²
	capture qui rename _obs N

	qui ds _b_*
	loc betas `r(varlist)'

	foreach var of varlist `betas' {
		loc newName = subinstr("`var'", "_b_", "β_", 1)
		qui rename `var' `newName'
		loc varlist = subinstr("`varlist'", "`var'", "`newName'", 1)
		dis "`newName'"

	}

	loc varlist = subinstr("`varlist'", "_TimeVar", "`timevar'", 1)
	loc varlist = subinstr("`varlist'", "_Cons", "constant", 1)
	loc varlist = subinstr("`varlist'", "_R2", "R²", 1)
	loc varlist = subinstr("`varlist'", "_adjR2", "adjR²", 1)
	loc varlist = subinstr("`varlist'", "_obs", "N", 1)


	list `varlist'  , noobs mean separator(0)
	restore
end