* Version:	1.0.0
* Version:	1.0.1 - fixed a bug
* Updated:	01.10.2017
* Author: Narek Ohanyan, Universitat Pompeu Fabra, Email: narek.ohanyan@upf.edu

* ==============================================================================

program define hpfilter, rclass

	version 12.0

	syntax varlist(min=1 max=1) [if] [in] , [trend(string) cycle(string)] [smooth(real 0) optimal] [onesided twosided] [forecast(int 0)]

	di ""

	set type double

	qui tsset
	local timevar = `"`r(timevar)'"'
	local unit = `"`r(unit)'"'
	local time_max_string = `"`r(tmaxs)'"'
	local panel = `"`r(panelvar)'"'

	if `"`panel'"' != `""' {
		di as err "Panel data not supported"
		exit 198
	}

	local y = `"`varlist'"'

	local nopts1 = (`"`trend'"' != "") + (`"`cycle'"' != "")

	if `nopts1' == 0 {
		di as err "At least one of " as result "trend()" as err " and " as result "cycle()" as err " options must be specified"
		exit 198
	}

	local nopts2 = (`"`onesided'"' != "") + (`"`twosided'"' != "")

	if `nopts2' == 0 {
		local twosided = "twosided"
	}
	if `nopts2' == 2 {
		di as err "Either " as result "onesided" as err " or " as result "twosided" as err " option must be specified"
		exit 198
	}

	local nopts3 = (`smooth' != 0) + (`"`optimal'"' != "")

	if `nopts3' == 2 {
		di as err "Either " as result "smooth()" as err " or " as result "optimal" as err " option must be specified"
		exit 198
	}

	if `smooth' < 0 {
		di as result "smooth()" as err " must be positive"
		exit 198
	}

	confirm new variable `trend' `cycle'

	if `"`optimal'"' == `""' {
		if `smooth' == 0 {
			local mode = 0	// default
			local smooth = 1600
			if `"`unit'"' == "daily" {
				local smooth = 1600*(365/4)^4
			}
			if `"`unit'"' == "weekly" {
				local smooth = 1600*(12)^4
			}
			if `"`unit'"' == "monthly" {
				local smooth = 1600*(3)^4
			}
			if `"`unit'"' == "quarterly" {
				local smooth = 1600
			}
			if `"`unit'"' == "halfyearly" {
				local smooth = 1600*(1/2)^4
			}
			if `"`unit'"' == "yearly" {
				local smooth = 1600*(1/4)^4
			}
		}
		if `smooth' != 0 {
			local mode = 1	// user-specified smoothing parameter
		}
	}
	if `"`optimal'"' != `""' {
			local mode = 2	// optimal smoothing parameter
	}

	* =========================== Define constraints ===========================

	constraint drop _all
	* State transition matrix
	constraint define 1 [x1]l.x1 = 2
	constraint define 2 [x1]l.x2 = -1
	constraint define 3 [x2]l.x1 = 1
	constraint define 4 [x2]l.x2 = 0

	* Observation matrix
	constraint define 5 [`y']x1 = 1
	constraint define 6 [`y']x3 = 1

	if `mode' != 2 {
		* Restriction on noise/signal ratio for lambda = sqrt(smooth)
		local noise_to_signal = sqrt(`smooth')
		constraint define 7 [`y']e.`y' = `noise_to_signal'*[x1]e.x1
		constraint define 8 [x3]e.x3 = `noise_to_signal'*[x1]e.x1
	}

	* ============================= Run the model ==============================

	if `mode' == 2 {
		di as text "Finding the optimal smoothing parameter"
	}

	qui sspace (x1 l.x1 l.x2 e.x1, state noconstant) (x2 l.x1 l.x2, state noconstant) (`y' x1 e.`y', noconstant) `if' `in', constraints(1/8)
	local sample_max_string = `"`e(tmaxs)'"'
	local smooth = abs(([`y']e.`y')^2/([x1]e.x1)^2)

	di as text "Setting smoothing parameter = " `smooth'

	* ============================== Predictions ===============================

	di ""
	di as text "Extracting the trend and the cycle"
	di ""

	tempvar tr cy

	if `"`onesided'"' != `""' {
		predict `tr' `if' `in', smethod(filter) equation(x1) states
		qui gen `cy' = `y' - `tr' `if' `in'
		if `"`trend'"' != `""' {
			qui gen `trend' = `tr' `if' `in'
			label var `trend' "Trend of `varlist' (one-sided)"
		}
		if `"`cycle'"' != `""' {
			qui gen `cycle' = `cy' `if' `in'
			label var `cycle' "Cycle of `varlist' (one-sided)"
		}
	}

	if `"`twosided'"' != `""' {
		predict `tr' `if' `in', smethod(smooth) equation(x1) states
		qui gen `cy' = `y' - `tr' `if' `in'
		if `"`trend'"' != `""' {
			qui gen `trend' = `tr' `if' `in'
			label var `trend' "Trend of `varlist' (two-sided)"
		}
		if `"`cycle'"' != `""' {
			qui gen `cycle' = `cy' `if' `in'
			label var `cycle' "Cycle of `varlist' (two-sided)"
		}
	}

	if `"`trend'"' != `""' {
		if `"`forecast'"' != `"0"' {
			tempvar time time_string time_string_sample_max time_string_time_max
			qui tostring `timevar', gen(`time_string') force usedisplayformat
			qui gen `time' = _n
			qui gen `time_string_sample_max' = `time' if `time_string' == `"`sample_max_string'"'
			qui sum `time_string_sample_max'
			local sample_max = `r(max)'
			qui gen `time_string_time_max' = `time' if `time_string' == `"`time_max_string'"'
			qui sum `time_string_time_max'
			local time_max = `r(max)'
			local forecast_begin = `sample_max' + 1
			local forecast_end = `forecast_begin' + `forecast' -1
			if `time_max' < `forecast_end' {
				local addtime = `forecast_end' - `time_max'
				tsappend, add(`addtime')
				qui replace `time' = _n
			}
			qui replace `trend' = L.`trend' + L.D.`trend' if `time' >= `forecast_begin' & `time' <= `forecast_end'
		}
	}

	di as text "Done"

	* ================================= Return =================================

	return scalar smooth = `smooth'
	return local varname = `"`varlist'"'
	return local trendname = `"`trend'"'
	return local cyclename = `"`cycle'"'
	if `"`onesided'"' != `""' {
		return local method = `"Hodrick-Prescott (one-sided)"'
	}
	if `"`twosided'"' != `""' {
		return local method = `"Hodrick-Prescott (two-sided)"'
	}
	return local unit = `"`unit'"'

end