How much does garch shorten long tails?
Pertinent blog posts include:
- “A practical introduction to garch modeling”
- “The distribution of financial returns made simple”
- “Predictability of kurtosis and skewness in S&P constituents”
Part of the reason that the distributions of returns have long tails is because of volatility clustering. It’s not really the clustering but that volatility changes over time. The returns when volatility is very high will tend to be far in the tails.
One model of volatility clustering is garch. We expect that the standardized residuals from garch models should have shorter tails. That is, be closer to normally distributed.
We look at that with kurtosis. The normal distribution has a kurtosis of 3. Long-tailed means kurtosis is bigger than 3.
We model daily log returns of most of the constituents of the S&P 500 over 1547 days. We fit garch(1,1) models assuming either a normal distribution or a Student’s t distribution where the degrees of freedom are estimated.
Figure 1 compares the kurtosis of the standardized residuals between the two garch specifications. You get standardized residuals by subtracting off the mean estimate and then dividing by the estimated standard deviation at each time point. So each standardized residual theoretically has mean 0 and variance 1.
Figures 2 and 3 compare the garch residual kurtosis to the kurtosis of the returns.
Figure 3: Kurtosis of garch(1,1) residuals assuming a t distribution compared to that of the returns. There are two stocks that are outliers in Figure 2 (and less conspicuous in Figure 3) in that they have much lower return kurtosis than garch kurtosis. The explanation turns out to be that there is a robustness issue. Each of them has standardized residuals on the order of 40 (in absolute value) at a big price jump. The two stocks are HAR and THC.
Don’t want no Short People
from “Short People” by Randy Newman
Here is the analysis in R.
estimate garch and get standardized residuals
require(rugarch) tspec <- ugarchspec(mean.model=list(armaOrder=c(0,0)), distribution="std") normspec <- ugarchspec(mean.model=list(armaOrder=c(0,0)), distribution="norm") garnorm.sp <- pp.multFitgarchStanRes(sp5.ret, normspec) gart.sp <- pp.multFitgarchStanRes(sp5.ret, tspec)
There was one case where the garch estimation didn’t converge, and so there are missing values for that case.
Here we count missing values by column:
> table(colSums(is.na(garnorm.sp))) 0 1547 473 1 > table(colSums(is.na(gart.sp))) 0 474
Now remove that particular stock from both matrices:
garnorm.sp.use <- garnorm.sp[, !is.na(garnorm.sp[1,])] gart.sp.use <- gart.sp[, colnames(garnorm.sp.use)]
require(timeDate) garnorm.kurt <- apply(garnorm.sp.use, 2, kurtosis, method="moment") gart.kurt <- apply(gart.sp.use, 2, kurtosis, method="moment") return.kurt <- apply(sp5.ret[, colnames(garnorm.sp.use)], 2, kurtosis, method="moment")
The functions written for this are in garchkurt_funs.R.