The following examples cover the main stationary time series processes presented in the lecture. Model generation, identification, estimation, forecasts and residual analysis examples are provided via R
.
We can generate the stationary process:
with the following code:
set.seed(1)
par(mfrow = c(1, 1))
x1 <- rnorm(200, mean = 0, sd = 1)
plot.ts(x1, main = "WN, mean = 0")
abline(0, 0, col = "red")
By drawing the red horizontal line we can visually inspect whether the time series fluctuates around the mean.
We will generate the three examples of non-stationary time series:
n = 150
set.seed(123)
#Set custom plot layout
m <- rbind(c(1, 1), c(2, 3))
layout(m)
#layout.show(3) #See how would three figures look in this layout
ns1 = rnorm(n) + 1:n
plot.ts(ns1, main = "non stationary in mean")
ns2 = rnorm(n)*(1:n)
plot.ts(ns2, main = "non stationary in variance")
ns3 = cumsum(sample(c(-1,1), n, replace = TRUE))
plot.ts(ns3, main = "no clear tendency")
The three main cases of a non-stationary process: a non-constant mean, a changing variance. The last example is also non-stationary though it is not immediately apparent from a small sample size.
We can check how this kind of process would look like with different sample sizes:
set.seed(123)
ns31 = cumsum(sample(c(-1,1), 1000, replace = TRUE))
par(mfrow = c(2, 2))
plot.ts(ns31[1:100])
plot.ts(ns31[1:300])
plot.ts(ns31[1:600])
plot.ts(ns31[1:1000])
We can see that, as we increase the sample size, the variance of the data increases.
Because we know that for this type of process \(Var(Y_t) = t\), we can check this using a Monte Carlo simulation:
set.seed(123)
Y <- matrix(NA, nrow = 1000, ncol = 11)
colnames(Y) <- paste0("X", 1:ncol(Y))
for(j in 1:nrow(Y)){
Y[j, ] <- cumsum(sample(c(-1,1), ncol(Y), replace = TRUE))
}
#Our variance:
apply(Y, 2, var)
## X1 X2 X3 X4 X5 X6 X7
## 1.000324 1.968368 3.058735 3.939684 4.809894 5.835836 7.098102
## X8 X9 X10 X11
## 8.151816 8.989389 9.808384 10.675792
We can see that as \(t\) increases and we look at \(X_1, X_2, ..., X_{11}\), the variance also increases, which is in line with the theoretical calculations.
Here are some of the sample series plotted with different colors:
plot.ts(t(Y[c(1, 3, 5, 8),]), plot.type = "single", col = 1:4)
And here is how all of the series look like on the same plot:
plot.ts(t(Y), plot.type = "single") # , col = 1:ncol(Y)
We see that we get a plot that resembles a board. If we increase our simulation number from 1000 to more (5000, 10000, etc.) then our plot would have some additional directories, which were not present in the simulation.
Plot the sample ACF and PACF of the non-stationary time series:
par(mfrow = c(1, 3))
acf(ns1, main = "non stationary in mean")
acf(ns2, main = "non stationary in variance")
acf(ns3, main = "no clear tendency")
pacf(ns1, main = "non stationary in mean")
pacf(ns2, main = "non stationary in variance")
pacf(ns3, main = "no clear tendency")
We can also draw the confidence intervals by hand:
par(mfrow = c(1, 2))
n = length(x1)
acf(x1, main = "WN, mean = 0")
abline(h = qnorm(c(0.025, 0.975))/sqrt(n), col = "red")
pacf(x1, main = "WN, mean = 0")
abline(h = qnorm(c(0.025, 0.975))/sqrt(n), col = "red")
As an example, read the quarterly Canadian employment index data. We can quickly view the series plot, its ACF and PACF by using the tsdisplay
function from the forecast
package:
suppressPackageStartupMessages({require("forecast")})
txt1 <- "http://uosis.mif.vu.lt/~rlapinskas/(data%20R&GRETL/caemp.txt"
caemp <- read.csv(url(txt1), header = TRUE, as.is = TRUE)
caemp <- ts(caemp, start = c(1960, 1), freq = 4)
tsdisplay(caemp, lwd = 2)
We can test the WN hypothesis by running the Ljung-Box test statistic. Our null hypothesis:
\[H_0: \rho(1) = 0, ..., \rho(k) = 0\] Let us test it for ACF up to lag 5, one by one:
for(k in 1:5){
print(Box.test(caemp, lag = k, type = "Ljung-Box"))
}
##
## Box-Ljung test
##
## data: caemp
## X-squared = 127.73, df = 1, p-value < 2.2e-16
##
##
## Box-Ljung test
##
## data: caemp
## X-squared = 240.45, df = 2, p-value < 2.2e-16
##
##
## Box-Ljung test
##
## data: caemp
## X-squared = 337.32, df = 3, p-value < 2.2e-16
##
##
## Box-Ljung test
##
## data: caemp
## X-squared = 418.26, df = 4, p-value < 2.2e-16
##
##
## Box-Ljung test
##
## data: caemp
## X-squared = 484.16, df = 5, p-value < 2.2e-16
In all cases, we reject the null hypothesis that all of the autocorrelation functions up to k
are zero.
Note that, if we reject the null hypothesis \(H_0: \rho(1) = 0, ..., \rho(k) = 0\) - this means that at least one ACF is non-zero - this does not necessarily mean that all are non-zero.
An example of performing the Ljung-Box test on \(\epsilon_t \sim \mathcal{N}(0,1)\):
set.seed(123)
Box.test(rnorm(100, mean = 0, sd = 1), lag = 1, type = "Ljung-Box")
##
## Box-Ljung test
##
## data: rnorm(100, mean = 0, sd = 1)
## X-squared = 0.067503, df = 1, p-value = 0.795
p-value = 0.795 > 0.05
- we do not reject the null hypothesis \(H_0: \rho(1) = 0\).
We will generate three MA models with \(\epsilon_t \sim \mathcal{N}(0,1)\):
We can generate the processes in three ways: manually
or using arima.sim
from the forecast
package. Generating manually involves writing the code equivalent of the previously provided mathematical formulas:
set.seed(100)
n = 5000
epsilon <- rnorm(n, mean = 0, sd = 1)
MA.m1 <- 3 + epsilon + 0.5 * c(0, epsilon[-n])
MA.m2 <- epsilon + 1.3 * c(0, epsilon[-n])
MA.m3 <- epsilon - 1.3 * c(0, epsilon[-n]) + 0.4 * c(0, 0, epsilon[-((n-1):n)])
The mean of each sample is:
mean(MA.m1)
## [1] 3.002095
mean(MA.m2)
## [1] 0.003159988
mean(MA.m3)
## [1] 0.0001168011
which is what we expect (Note: you can check this by calculating \(\mathbb{E}Y_t\) for each case).
The variance of the second process:
var(MA.m2)
## [1] 2.685624
is very close to the theoretical value \(Var(Y_t) = (1 + \theta^2)\sigma^2 = 1 + 1.3^2 = 2.69\).
The ACF and PACF or the three different models:
par(mfrow = c(2,3))
acf(MA.m1, main = expression(paste(Y[t], " = 3 + ", epsilon[t], " + 0.5", epsilon[t-1])))
acf(MA.m2, main = expression(paste(Y[t], " = ", epsilon[t], " + 1.3", epsilon[t-1])))
acf(MA.m3, main = expression(paste(Y[t], " = ",
epsilon[t], " - 1.3", epsilon[t-1], " + 0.4", epsilon[t-2])))
pacf(MA.m1, main = expression(paste(Y[t], " = 3 + ", epsilon[t], " + 0.5", epsilon[t-1])))
pacf(MA.m2, main = expression(paste(Y[t], " = ", epsilon[t], " + 1.3", epsilon[t-1])))
pacf(MA.m3, main = expression(paste(Y[t], " = ",
epsilon[t], " - 1.3", epsilon[t-1], " + 0.4", epsilon[t-2])))
For MA(1) the ACF has a sharp cutoff after lag = 1
, for MA(2) - the sharp cutoff is after lag = 2
. The PACF of the MA(q) process is slowly decaying to zero. Note that the second equation does not meet the requirement that \(|\theta| < 1\). Therefore, it is not invertible, i.e. it cannot be expressed as an infinite-order AR process.
We will generate three AR models with \(\epsilon_t \sim \mathcal{N}(0,1)\):
We will also give an example of model generation using arima.sim
function:
set.seed(100)
n = 5000
epsilon <- rnorm(n, mean = 0, sd = 1)
AR.m1 <- AR.m2 <- NULL
AR.m1[1] <- 3 + epsilon[1]
AR.m2[1] <- epsilon[1]
for(j in 2:n){
AR.m1[j] <- 3 + 0.5 * AR.m1[j-1] + epsilon[j]
AR.m2[j] <- 1.2 * AR.m2[j-1] + epsilon[j]
}
#We can simulate an AR, MA or ARIMA model using arima.sim:
AR.m3 <- arima.sim(list(order = c(2,0,0), ar = c(1.2, -0.5)), n = n)
Note: The ARMA model is checked for stationarity when using arima.sim
!.
To make it easier to analyse the second model, we will reduce the sample of the generated data:
AR.m2 <- AR.m2[1:50]
plot.ts(AR.m2)
Because the coefficient \(\phi = 1.2 \geq 1\) the time series grows exponentially.
The mean of each sample is:
mean(AR.m1)
## [1] 6.001387
mean(AR.m2)
## [1] 106.433
mean(AR.m3)
## [1] 0.02753754
Remember that for the generalized AR(1) case (with constant component) the mean is \(m = \alpha/(1-\phi)\). For the first model, we have that \(\alpha = 3\) and \(\phi = 0.5\) \(\Rightarrow\) \(m = 6\) (which is similar to what the sample mean of the first model is).
Note that the second model violates the requirement that \(|\phi| < 1\). This kind of AR process is not stationary.
Trying to generate this kind of model using arima.sim
would result in an error:
arima.sim(list(order = c(1,0,0), ar = c(1.2)), n = n)
## Error in arima.sim(list(order = c(1, 0, 0), ar = c(1.2)), n = n): 'ar' part of model is not stationary
This is on of the differences when generating data manually and using arima.sim
- sometimes we want to generate non-stationary data and see how our R
script would handle this situation.
The ACF and PACF:
par(mfrow = c(2,3))
acf(AR.m1, main = expression(paste(Y[t], " = 3 + 0.5", Y[t-1], " + ", epsilon[t])))
acf(AR.m2, main = expression(paste(Y[t], " = 1.2", Y[t-1], " + ", epsilon[t])))
acf(AR.m3, main = expression(paste(Y[t], " = 1.2", Y[t-1], " - 0.5", Y[t-2], " + ", epsilon[t])))
pacf(AR.m1, main = expression(paste(Y[t], " = 3 + 0.5", Y[t-1], " + ", epsilon[t])))
pacf(AR.m2, main = expression(paste(Y[t], " = 1.2", Y[t-1], " + ", epsilon[t])))
pacf(AR.m3, main = expression(paste(Y[t], " = 1.2", Y[t-1], " - 0.5", Y[t-2], " + ", epsilon[t])))
For the AR(p) models, the ACF decays gradually to zero. For the AR(1), the PACF has a sharp cutoff after lag = 1
. For the AR(2), the PACF has a sharp cutoff after lag = 2
. Note that even though the second model is not stationary, the PACF is cut-off after the first lag and the ACF is slowly decaying (because the coefficient \(\phi > 1\) the decay is slower compared to the first model with \(\phi = 0.5\))
We will generate an ARMA model:
set.seed(100)
n = 5000
ARMA.m1 <- arima.sim(list(order = c(1,0,1), ar = 0.4, ma = 0.6), n = n)
The ACF and PACF:
acf(ARMA.m1, main = expression(paste(Y[t], " = 0.4",
Y[t-1], " + ", epsilon[t], " + 0.6", epsilon[t-1])))
pacf(ARMA.m1, main = expression(paste(Y[t], " = 0.4",
Y[t-1], " + ", epsilon[t], " + 0.6", epsilon[t-1])))
Both ACF and PACF decline for the ARMA process.
Arima
Let’s say we do not know the data generating process that generated out previously mentioned AR, MA and ARMA models. If we examined the ACF and PACF graphs and we have an idea of what our underlying model might be, we can use the Arima
function from the forecast
package (NOT arima
from stats
package) to estimate our model.
MA.m1.est <- Arima(MA.m1, order = c(0, 0, 1), include.mean = TRUE)
MA.m2.est <- Arima(MA.m2, order = c(0, 0, 1), include.mean = FALSE)
MA.m3.est <- Arima(MA.m3, order = c(0, 0, 2), include.mean = FALSE)
AR.m1.est <- Arima(AR.m1, order = c(1, 0, 0), include.mean = TRUE)
AR.m2.est <- Arima(AR.m2, order = c(1, 0, 0), include.mean = FALSE, method = "ML")
AR.m3.est <- Arima(AR.m3, order = c(2, 0, 0), include.mean = FALSE)
ARMA.m1.est<-Arima(ARMA.m1, order = c(1, 0, 1), include.mean = FALSE)
The summary statistics of our models:
summary(MA.m1.est)
## Series: MA.m1
## ARIMA(0,0,1) with non-zero mean
##
## Coefficients:
## ma1 mean
## 0.5083 3.0021
## s.e. 0.0122 0.0213
##
## sigma^2 estimated as 0.9999: log likelihood=-7093.67
## AIC=14193.33 AICc=14193.34 BIC=14212.88
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 4.945214e-05 0.9997648 0.8003394 -31.77517 66.31426 0.8106853
## ACF1
## Training set -0.009559869
To only leave the relevant info, we only take the coefficients and their standard errors:
print(data.frame(coef = coef(MA.m2.est), "s.e" = sqrt(diag(vcov(MA.m2.est)))))
## coef s.e
## ma1 0.7712797 0.008735223
print(data.frame(coef = coef(MA.m3.est), `s.e` = sqrt(diag(vcov(MA.m3.est)))))
## coef s.e
## ma1 -1.3100158 0.01311805
## ma2 0.4017394 0.01315534
We can see that our estimated coefficients are close to the actual values, except for the second process, which is not invertible.
For the AR
models:
print(data.frame(coef = coef(AR.m1.est), "s.e" = sqrt(diag(vcov(AR.m1.est)))))
## coef s.e
## ar1 0.4854857 0.01237243
## intercept 6.0010377 0.02749717
print(data.frame(coef = coef(AR.m2.est), "s.e" = sqrt(diag(vcov(AR.m2.est)))))
## coef s.e
## ar1 0.9979733 0.002871269
print(data.frame(coef = coef(AR.m3.est), "s.e" = sqrt(diag(vcov(AR.m3.est)))))
## coef s.e
## ar1 1.1678721 0.01241928
## ar2 -0.4781098 0.01242592
Note that the second process is not stationary. As such the estimated value of \(\phi\) is close to 1. We also use method = "ML"
when estimating the non-stationary series to get a stationary model. If we do not use this option, then we get an error that our time series is not stationary:
Arima(AR.m2, order = c(1, 0, 0), include.mean = FALSE)
## Error in stats::arima(x = x, order = order, seasonal = seasonal, include.mean = include.mean, : non-stationary AR part from CSS
When estimating AR, MA or ARMA processes we assume that they are stationary (if they are not, then we need to create such models or transform the data such that it would be stationary).
Note that the intercept
coefficient of the first \(AR(1)\) model is close to the process mean m = 6
.
summary(ARMA.m1.est)
## Series: ARMA.m1
## ARIMA(1,0,1) with zero mean
##
## Coefficients:
## ar1 ma1
## 0.3735 0.6199
## s.e. 0.0160 0.0131
##
## sigma^2 estimated as 0.9996: log likelihood=-7093.15
## AIC=14192.3 AICc=14192.31 BIC=14211.85
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 0.001018066 0.9995867 0.8001791 -79.8131 424.9294 0.8285089
## ACF1
## Training set 0.005094493
The ARIMA model coefficients are also close to the actual values used when generating the model.
auto.arima
If we are not sure what order of AR, MA or ARMA model would best fit our data, we can use the auto.arima
function.
MA.m1.auto <- auto.arima(MA.m1)
MA.m2.auto <- auto.arima(MA.m2)
MA.m3.auto <- auto.arima(MA.m3)
AR.m1.auto <- auto.arima(AR.m1)
AR.m2.auto <- auto.arima(AR.m2)
AR.m3.auto <- auto.arima(AR.m3)
ARMA.m1.auto<-auto.arima(ARMA.m1)
We can see, that for simple AR or MA processes auto.arima
works fine, however, in some cases the best model differs from the actual model used when generating the data:
summary(MA.m1.auto)
## Series: MA.m1
## ARIMA(0,0,1) with non-zero mean
##
## Coefficients:
## ma1 mean
## 0.5083 3.0021
## s.e. 0.0122 0.0213
##
## sigma^2 estimated as 0.9999: log likelihood=-7093.67
## AIC=14193.33 AICc=14193.34 BIC=14212.88
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 4.945214e-05 0.9997648 0.8003394 -31.77517 66.31426 0.8106853
## ACF1
## Training set -0.009559869
print(data.frame(coef = coef(MA.m2.auto), "s.e" = sqrt(diag(vcov(MA.m2.auto)))))
## coef s.e
## ma1 0.7712797 0.008735223
print(data.frame(coef = coef(MA.m3.auto), "s.e" = sqrt(diag(vcov(MA.m3.auto)))))
## coef s.e
## ar1 0.002381094 0.06571518
## ar2 -0.024920403 0.03035926
## ma1 -1.304041029 0.06444188
## ma2 0.398838343 0.05764883
For the MA process, the first two models are identical to the Arima
specified models. However, the last model is different.
Similar results can be seen for the AR(2) models:
print(data.frame(coef = coef(AR.m1.auto), "s.e" = sqrt(diag(vcov(AR.m1.auto)))))
## coef s.e
## ar1 0.49872222 0.01414085
## ar2 -0.02732634 0.01415136
## intercept 6.00100454 0.02675482
print(data.frame(coef = coef(AR.m2.auto), "s.e" = sqrt(diag(vcov(AR.m2.auto)))))
## coef s.e
## ma1 0.8915756 0.1164140
## ma2 1.0606306 0.1011557
## ma3 0.4085473 0.1054677
print(data.frame(coef = coef(AR.m3.auto), "s.e" = sqrt(diag(vcov(AR.m3.auto)))))
## coef s.e
## ar1 1.1678721 0.01241928
## ar2 -0.4781098 0.01242592
Because the second model is not stationary, auto.arima
tries to fit the best stationary model.
summary(ARMA.m1.auto)
## Series: ARMA.m1
## ARIMA(2,0,3) with non-zero mean
##
## Coefficients:
## ar1 ar2 ma1 ma2 ma3 mean
## -0.6637 0.3137 1.6620 0.7175 0.0481 0.0058
## s.e. 0.0469 0.0450 0.0484 0.0836 0.0378 0.0359
##
## sigma^2 estimated as 0.9997: log likelihood=-7091.58
## AIC=14197.16 AICc=14197.19 BIC=14242.78
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set -0.001232387 0.9992723 0.7999059 -92.63542 440.1049 0.828226
## ACF1
## Training set 0.0004421623
The ARMA model differs the most from the actual model even though we used arima.sim
to generate the model.
Having estimated the models, we would like to create our time series forecasts:
Lets say we want to forecast 10 periods into the future. Since we have 5000 historic data points we would also want to reduce the historic data in the plot to, say 100.
Our code would look like:
#?plot.forecast
plot(forecast(MA.m1.auto, h = 10), include = 100)
Some other forecast plots:
par(mfrow = c(2, 2))
plot(forecast(MA.m3.auto, h = 10), include = 100)
plot(forecast(AR.m2.auto, h = 10), include = 100)
plot(forecast(AR.m3.auto, h = 10), include = 100)
plot(forecast(ARMA.m1.auto, h = 10), include = 100)
AR and MA model forecasts perform as we would expect: MA forecasts quickly tend to the mean, whereas AR forecasts tend to the average but never reach it.
After estimating our models, it is also important to check if the residuals are WN. To get the model residuals use the residuals
function:
res1.MA <- residuals(MA.m1.auto)
res2.MA <- residuals(MA.m2.auto)
res3.MA <- residuals(MA.m3.auto)
res1.AR <- residuals(AR.m1.auto)
res2.AR <- residuals(AR.m2.auto)
res3.AR <- residuals(AR.m3.auto)
res1.ARMA <- residuals(ARMA.m1.auto)
We can plot the residuals (we will plot the last 100):
par(mfrow = c(1,2))
plot.ts(tail(res2.AR, 100))
plot.ts(tail(res1.ARMA, 100))
We can check their ACF and PACF:
par(mfrow = c(2,2))
acf(res2.AR, 100)
acf(res1.ARMA, 100)
pacf(res2.AR, 100)
pacf(res1.ARMA, 100)
And perform the Ljung-Box test:
Box.test(res2.AR, lag = 20, type = "Ljung-Box")
##
## Box-Ljung test
##
## data: res2.AR
## X-squared = 50.262, df = 20, p-value = 0.0002031
Because p-value < 0.05
, we reject the null hypothesis of no serial correlation, i.e. the residuals are not white noise. This is to be expected, because the second model is estimated on non-stationary data.
Box.test(res1.ARMA, lag = 20, type = "Ljung-Box")$p.value
## [1] 0.7141348
We do not reject the null hypothesis that the first 20 lag autocorrelations among the residuals are zero (p-value > 0.05) for the ARMA model.
We can also perform the Box-Pierce
test:
Box.test(res2.AR, lag = 20, type = "Box-Pierce")
##
## Box-Pierce test
##
## data: res2.AR
## X-squared = 41.936, df = 20, p-value = 0.002819
Box.test(res1.ARMA, lag = 20, type = "Box-Pierce")
##
## Box-Pierce test
##
## data: res1.ARMA
## X-squared = 15.993, df = 20, p-value = 0.7171
Which lets us draw the same conclusions as the Ljung-Box
test results.
Remember, the null hypothesis is:
\[H_0: \rho(1) = 0, ..., \rho(k) = 0\]
The test statistics:
are calculated differently but have the same critical region - if \(Q > \chi_{K}^2\), we reject the null hypothesis of independently distributed residuals.
Another example, this time by fitting an incorrect model.
Remember our MA(1) model which is not invertible \(Y_t = \epsilon_t + 1.3 \epsilon_{t-1}\)?
Let us try to fit an AR(1) model and check its residuals:
par(mfrow = c(1,2))
bad.model <- Arima(MA.m2, order = c(1,0,0))
acf(residuals(bad.model))
pacf(residuals(bad.model))
Box.test(residuals(bad.model), lag = 10, type = "Ljung-Box")
##
## Box-Ljung test
##
## data: residuals(bad.model)
## X-squared = 633.87, df = 10, p-value < 2.2e-16
The ACF and PACF graphs show that the residuals exhibit autocorrelation. The Ljung-Box test rejects the null hypothesis (because p-value < 0.05) that the first 10 lag autocorrelations among residuals are zero. We can conclude that the residuals are not WN, which indicates that the specified model is not correct.
To correct this, with our current knowledge of time series, we would need to specify a different model or try to leverage the results of auto.arima
.
Lets say our \(ARMA(1,1)\) model is of the following general form: \[Y_t = \alpha + \phi_1 Y_{t-1} + \epsilon_t + \theta_1 \epsilon_{t-1}\]
Let \(\mathbb{E}Y_t = \mu\), then we have:
\[\begin{align} \mathbb{E}(Y_t) &= \alpha + \phi_1 \mathbb{E} Y_{t-1} + \mathbb{E}\epsilon_t + \theta_1 \mathbb{E}\epsilon_{t-1}\\ \mu &= \alpha + \phi_1 \mu \\ \mu &= \dfrac{\alpha}{1 - \phi_1} \end{align}\]
Which is the same as the generalized \(AR(1)\) case.
Let’s take an example with \(\alpha = 2\), \(\phi_1 = 0.2\) and \(\theta_1 = 0.3\). then our \(\mu\) is:
2/(1 - 0.2)
## [1] 2.5
Why would we need tho separately calculate the mean? Because arima.sim
generates a model which has a zero mean. To get around this, we either generate the model manually
:
set.seed(1)
general.arima.v1 <- NULL
epsilon <- rnorm(mean = 0, sd = 1, n = 2500)
general.arima.v1[1] <- 2 + epsilon[1]
for(j in 2:length(epsilon)){
general.arima.v1[j] <- 2 + 0.2 * general.arima.v1[j-1] + epsilon[j] + 0.3 * epsilon[j-1]
}
mean(general.arima.v1)
## [1] 2.484964
or we can generate the data using arima.sim
:
set.seed(1)
general.arima.v2 <- arima.sim(list(order = c(1,0,1), ar = 0.2, ma = 0.3), n = 2500)
mean(general.arima.v2)
## [1] -0.01640406
and add the mean to the series:
general.arima.v2 <- 2.5 + general.arima.v2
mean(general.arima.v2)
## [1] 2.483596
We can plot the first 100 observation the two series side by side:
plot.ts(cbind(general.arima.v1, general.arima.v2)[1:100, ])
Next, we can estimate the two different datasets:
mdl.arma.v1 <- Arima(general.arima.v1, order = c(1, 0, 1), include.mean = TRUE)
mdl.arma.v2 <- Arima(general.arima.v2, order = c(1, 0, 1), include.mean = TRUE)
coef(mdl.arma.v1)
## ar1 ma1 intercept
## 0.1860932 0.3085092 2.4844429
coef(mdl.arma.v2)
## ar1 ma1 intercept
## 0.1911828 0.3046751 2.4837918
We note that the intercept
in both cases is close to the process mean. The difference is in the naming - intercept
is not the \(\alpha\) coefficient, it is the \(\mu\)!
Interestingly, if we print the summary of the model:
summary(mdl.arma.v2)
## Series: general.arima.v2
## ARIMA(1,0,1) with non-zero mean
##
## Coefficients:
## ar1 ma1 mean
## 0.1912 0.3047 2.4838
## s.e. 0.0419 0.0407 0.0335
##
## sigma^2 estimated as 1.077: log likelihood=-3638.9
## AIC=7285.8 AICc=7285.81 BIC=7309.09
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 3.903516e-05 1.037248 0.8270324 -90.77095 179.704 0.8361583
## ACF1
## Training set 0.0007538847
The intercept
is now labeled as the mean
! This one of the problems when using very generalized functions for model estimation - since they are written in such a way that they cover a variety of models and cases, their output is not always as intuitive as it may seem. This is further discussed here and here
If an \(MA(q)\) process is invertible, then:
\[Y_t = \epsilon_t + \theta \epsilon_{t-1}\]
Can be written as an \(AR(\infty )\) process:
\[Y_t = \sum_{j = 1}^\infty \tilde{\phi}_j Y_{t-j} + \epsilon_t\]
Since the \(MA\) process is a finite approximation of the Wold representation, we could also approximate its infinite \(AR\) representation reasonably well. This could also be the case for the infinite \(MA\) process approximation.
This means that, in practice, we may come across cases where more than one model could be used, e.g. a lower order \(ARMA\) process, which is stationary and invertible, could be approximated by a higher order \(MA\) or \(AR\) process, especially if the sample size is small.
We will provide an example using data generated from an \(ARMA(2, 3)\) process:
set.seed(100)
# We will use a smaller data sample:
n = 100
# We will generate an additional 5 periods ahead, which we will use to compare the predictions to:
ARMA_data <- arima.sim(list(order = c(2,0,3), ar = c(0.3, -0.2), ma = c(0.4, 0.2, -0.1)), n = n + 5)
# Save the last five periods:
actual_data = ts(ARMA_data[(n+1):(n+5)], start = n + 1)
# Exclude the last 5 values from the data:
ARMA_data <- ARMA_data[1:n]
We will estimate 4 models:
Arma
;auto.arima
;auto.arima
;auto.arima
;ARMA_model = Arima(ARMA_data, order = c(2,0,3), include.mean = FALSE)
ARMA_auto_arma = auto.arima(ARMA_data)
ARMA_auto_ar = auto.arima(ARMA_data, max.p = 10, max.q = 0)
ARMA_auto_ma = auto.arima(ARMA_data, max.p = 0, max.q = 10)
The resulting models:
print(data.frame(ARMA = ARMA_model$coef))
## ARMA
## ar1 0.53388006
## ar2 -0.05686963
## ma1 -0.11185555
## ma2 -0.09679085
## ma3 -0.34581676
print(data.frame(ARMA = ARMA_auto_arma$coef))
## ARMA
## ar1 0.8294162
## ar2 -0.6277636
## ma1 -0.4047657
## ma2 0.3237735
print(data.frame(AR = ARMA_auto_ar$coef))
## AR
## ar1 0.4009224
## ar2 -0.1016042
## ar3 -0.2739891
print(data.frame(MA = ARMA_auto_ma$coef))
## MA
## ma1 0.42249089
## ma2 0.05592508
## ma3 -0.33983869
## ma4 -0.14555316
Gives us different models:
auto.arima
is an \(ARMA(2, 2)\);auto.arima
is an \(AR(3)\);auto.arima
is an \(MA(4)\);The residuals ACF and PACF plots:
par(mfrow = c(2, 2))
Acf(ARMA_model$residuals);Pacf(ARMA_model$residuals)
Acf(ARMA_auto_arma$residuals);Pacf(ARMA_auto_arma$residuals)
par(mfrow = c(2, 2))
Acf(ARMA_auto_ar$residuals);Pacf(ARMA_auto_ar$residuals)
Acf(ARMA_auto_ma$residuals);Pacf(ARMA_auto_ma$residuals)
We can see that the residuals are not autocorrelated in either of these models, so each of them is a valid specification for our data sample.
And from the model AIC values:
print(paste0("True ARMA AIC = ", ARMA_model$aic))
## [1] "True ARMA AIC = 294.427693105794"
print(paste0("AUTO ARMA AIC = ", ARMA_auto_arma$aic))
## [1] "AUTO ARMA AIC = 293.348716616519"
print(paste0("AUTO AR AIC = ", ARMA_auto_ar$aic))
## [1] "AUTO AR AIC = 290.187669396479"
print(paste0("AUTO MA AIC = ", ARMA_auto_ma$aic))
## [1] "AUTO MA AIC = 292.598059624327"
we can see that:
Our true \(ARMA(2,3)\) model has the largest AIC, therefore it is the worst one of the 4 possible models.
The \(ARMA(2, 2)\) model estimated via auto.arima
is the second-worst as it has the second largest AIC from the estimated models.
The \(AR(3)\) model has the lowest \(AIC\) value from the estimated models.
Note: we can also examine their BIC values with $bic
but we will get the same results.
So, the best model for our data sample is the \(AR(3)\) model. Let’s compare the forecasts for 5 periods ahead:
forc_1 = forecast(ARMA_model, h = 5)$mean
forc_2 = forecast(ARMA_auto_arma, h = 5)$mean
forc_3 = forecast(ARMA_auto_ar, h = 5)$mean
forc_4 = forecast(ARMA_auto_ma, h = 5)$mean
We can check how the fitted data looks like for each model:
plot.ts(ARMA_data)
lines(fitted(ARMA_model), col = "red", lty = 2)
lines(fitted(ARMA_auto_arma), col = "blue", lty = 2)
lines(fitted(ARMA_auto_ar), col = "darkgreen", lty = 2)
lines(fitted(ARMA_auto_ma), col = "darkorange", lty = 2)
legend("bottomleft", #x = 0, y = -1,
legend = c("Actual", "ARMA_true", "ARMA_auto", "AR_auto", "MA_auto"),
col = c("black", "red", "blue", "darkgreen", "darkorange"),
lty = c(1, 2, 2, 2, 2), cex = 0.8)
Graphically, there is little difference between the models. This can also be said for their forecasts:
plot.ts(ARMA_data, xlim = c(98, 105))
lines(actual_data, col = "black", lty = 2)
lines(forc_1, col = "red", lty = 2)
lines(forc_2, col = "blue", lty = 2)
lines(forc_3, col = "darkgreen", lty = 2)
lines(forc_4, col = "darkorange", lty = 2)
legend("bottomleft", #x = 0, y = -1,
legend = c("Historical", "Forecast_actual", "ARMA_true", "ARMA_auto", "AR_auto", "MA_auto"),
col = c("black", "black", "red", "blue", "darkgreen", "darkorange"),
lty = c(1, 2, 2, 2, 2, 2), cex = 0.8)
The following data contains:
Ret
over the period 1954:1-2001:9
;Inf.
DT.bill
.txt1 <- "http://uosis.mif.vu.lt/~rlapinskas/(data%20R&GRETL/"
txt2 <- "S&P.txt"
#Read it as an excel file
SP <- read.table(paste0(txt1, txt2), header = TRUE)
SP <- read.table(paste0(txt1, txt2), header = TRUE)
SP <- ts(SP, start = 1954, freq = 12)
We begin by modelling the return series Ret
as a function of a constant, one lag of returns Ret
, one lag of the inflation rate Inf
and one lag of the first-difference of the three-month T-bill rate DT.bill
. We expect our model to be of the form:
\[ \begin{cases} Y_t &= \gamma + \phi_1 Y_{t-1} + ... \beta_1 X_{1,t} + ... + \epsilon_t \\ \epsilon_t &= \sigma_t w_t \\ \sigma_t^2 &= \omega + \sum_{j = 1}^q \alpha_j \epsilon_{t-j}^2 + \sum_{i = 1}^p \beta_i \sigma_{t-i}^2 \end{cases} \]
where:
We will begin by adding the first lags of the aforementioned variables and create the usual OLS model. Then we will test the null hypothesis:
\[H_0: \text{residuals of the model are normal}\]
In conventional regression models, rejection of the null hypothesis would lead us to change the model specification. However, non-normality is an inherent feature of the errors from the regression models for financial data. As such, robust standard errors are needed.
suppressPackageStartupMessages({library(dynlm)})
mean_mdl <- dynlm(Ret ~ L(Ret) + L(Inf.) + L(DT.bill), data = SP)
summary(mean_mdl)
##
## Time series regression with "ts" data:
## Start = 1954(2), End = 2001(8)
##
## Call:
## dynlm(formula = Ret ~ L(Ret) + L(Inf.) + L(DT.bill), data = SP)
##
## Residuals:
## Min 1Q Median 3Q Max
## -12.1199 -1.8907 0.1141 2.0607 10.3608
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.17715 0.21004 5.604 3.26e-08 ***
## L(Ret) 0.20799 0.04102 5.070 5.39e-07 ***
## L(Inf.) -1.17945 0.44805 -2.632 0.00871 **
## L(DT.bill) -1.25007 0.29135 -4.291 2.10e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.259 on 567 degrees of freedom
## Multiple R-squared: 0.1011, Adjusted R-squared: 0.0963
## F-statistic: 21.25 on 3 and 567 DF, p-value: 4.688e-13
As mentioned before, we need to correct the standard errors of our model:
suppressPackageStartupMessages({
library(sandwich)
library(lmtest)
})
coeftest(mean_mdl, df = Inf, vcov = vcovHC(mean_mdl, type = "HC"))
##
## z test of coefficients:
##
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.177149 0.228283 5.1565 2.516e-07 ***
## L(Ret) 0.207991 0.048629 4.2771 1.893e-05 ***
## L(Inf.) -1.179451 0.521536 -2.2615 0.0237285 *
## L(DT.bill) -1.250071 0.328941 -3.8003 0.0001445 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
where:
type = "HC"
means that the standard errors are corrected to be Heteroskedasticity-Consistent.coeftest
- a z-test (using a normal approximation (df = Inf
)).As we can see, all the coefficients are statistically significant because their p-values are less than 0.05.
We can test the residual normality with the Shapiro-Wilk
test:
shapiro.test(mean_mdl$residuals)
##
## Shapiro-Wilk normality test
##
## data: mean_mdl$residuals
## W = 0.97873, p-value = 2.233e-07
# tseries::jarque.bera.test(mdl.1$residuals)
Since p-value < 0.05
we reject the null hypothesis that our residuals are normal.
Let us look at the residual correlograms:
suppressPackageStartupMessages({library(forecast)})
tsdisplay(mean_mdl$residuals)
Box.test(mean_mdl$residuals, lag = 15)
##
## Box-Pierce test
##
## data: mean_mdl$residuals
## X-squared = 23.766, df = 15, p-value = 0.06917
We do not reject the null hypothesis that the autocorrelations are not statistically significantly different from zero.
Let us test the same with the squared
residuals:
tsdisplay(mean_mdl$residuals^2)
Box.test(mean_mdl$residuals^2, lag = 6)
##
## Box-Pierce test
##
## data: mean_mdl$residuals^2
## X-squared = 17.567, df = 6, p-value = 0.007412
We reject the null hypothesis, which means that our squared residuals are autocorrelated.
From the PACF of the squared residuals we see that the an \(ARCH(6)\) model might be appropriate. So, we will now test the residuals for \(ARCH(6)\):
suppressPackageStartupMessages({library(fGarch)})
mdl.res <- mean_mdl$residuals
mdl.arch <- garchFit(formula = ~ garch(6, 0), trace = FALSE, data = mdl.res)
summary(mdl.arch)
##
## Title:
## GARCH Modelling
##
## Call:
## garchFit(formula = ~garch(6, 0), data = mdl.res, trace = FALSE)
##
## Mean and Variance Equation:
## data ~ garch(6, 0)
## <environment: 0x000000003bbf2f48>
## [data = mdl.res]
##
## Conditional Distribution:
## norm
##
## Coefficient(s):
## mu omega alpha1 alpha2 alpha3
## -1.5103e-16 5.6728e+00 1.2585e-01 1.2245e-02 1.2945e-01
## alpha4 alpha5 alpha6
## 1.1232e-02 9.3974e-02 1.2805e-01
##
## Std. Errors:
## based on Hessian
##
## Error Analysis:
## Estimate Std. Error t value Pr(>|t|)
## mu -1.510e-16 1.286e-01 0.000 1.0000
## omega 5.673e+00 1.072e+00 5.294 1.2e-07 ***
## alpha1 1.259e-01 5.986e-02 2.102 0.0355 *
## alpha2 1.225e-02 3.999e-02 0.306 0.7595
## alpha3 1.294e-01 6.952e-02 1.862 0.0626 .
## alpha4 1.123e-02 3.852e-02 0.292 0.7706
## alpha5 9.397e-02 6.653e-02 1.412 0.1578
## alpha6 1.280e-01 5.793e-02 2.210 0.0271 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Log Likelihood:
## -1468.767 normalized: -2.572272
##
## Description:
## Mon Feb 19 23:28:51 2018 by user: Andrius
##
##
## Standardised Residuals Tests:
## Statistic p-Value
## Jarque-Bera Test R Chi^2 28.08916 7.952716e-07
## Shapiro-Wilk Test R W 0.9883611 0.0001652405
## Ljung-Box Test R Q(10) 10.2739 0.416801
## Ljung-Box Test R Q(15) 22.56042 0.09392936
## Ljung-Box Test R Q(20) 27.36542 0.1252926
## Ljung-Box Test R^2 Q(10) 1.268283 0.9994941
## Ljung-Box Test R^2 Q(15) 6.411728 0.9719713
## Ljung-Box Test R^2 Q(20) 9.804126 0.9715862
## LM Arch Test R TR^2 2.103424 0.9992301
##
## Information Criterion Statistics:
## AIC BIC SIC HQIC
## 5.172564 5.233473 5.172179 5.196327
We see that alpha6
coefficient is statistically significant and we reject the null hypothesis \(H_0: \alpha_1 =...=\alpha_6=0\) in:
\[
\begin{cases}
\epsilon_t = \sigma_t w_t \\
\sigma_t^2 = \omega + \alpha_1 \epsilon_{t-1}^2 + ... + \alpha_6 \epsilon_{t-6}^2
\end{cases}
\] The LM Arch Test
on the standardized residuals from this ARCH model says that we do not reject the null hypothesis of uncorrelated residuals (i.e. the standardized residuals from the ARCH model do not have any ARCH effects).
Thus, the residuals of our model make \(ARCH(6)\). The \(ARCH(6)\) model has many parameters and one common advice is to replace the model by \(GARCH(1, 1)\):
mdl.garch <- garchFit(formula = ~ garch(1, 1), trace = FALSE, data = mdl.res)
summary(mdl.garch)
##
## Title:
## GARCH Modelling
##
## Call:
## garchFit(formula = ~garch(1, 1), data = mdl.res, trace = FALSE)
##
## Mean and Variance Equation:
## data ~ garch(1, 1)
## <environment: 0x000000001db25b60>
## [data = mdl.res]
##
## Conditional Distribution:
## norm
##
## Coefficient(s):
## mu omega alpha1 beta1
## -1.5103e-16 1.1511e+00 1.1392e-01 7.8510e-01
##
## Std. Errors:
## based on Hessian
##
## Error Analysis:
## Estimate Std. Error t value Pr(>|t|)
## mu -1.510e-16 1.292e-01 0.000 1.00000
## omega 1.151e+00 5.183e-01 2.221 0.02636 *
## alpha1 1.139e-01 4.002e-02 2.846 0.00442 **
## beta1 7.851e-01 6.584e-02 11.925 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Log Likelihood:
## -1472.623 normalized: -2.579025
##
## Description:
## Mon Feb 19 23:28:51 2018 by user: Andrius
##
##
## Standardised Residuals Tests:
## Statistic p-Value
## Jarque-Bera Test R Chi^2 36.679 1.084565e-08
## Shapiro-Wilk Test R W 0.9851709 1.483964e-05
## Ljung-Box Test R Q(10) 10.01125 0.4395069
## Ljung-Box Test R Q(15) 22.05404 0.1063948
## Ljung-Box Test R Q(20) 27.39449 0.1245256
## Ljung-Box Test R^2 Q(10) 7.909245 0.6377014
## Ljung-Box Test R^2 Q(15) 13.07198 0.5967378
## Ljung-Box Test R^2 Q(20) 17.00733 0.6524979
## LM Arch Test R TR^2 8.202932 0.7690776
##
## Information Criterion Statistics:
## AIC BIC SIC HQIC
## 5.172060 5.202514 5.171962 5.183941
From the summary we see that Shapiro-Wilk Test
and Jarque-Bera Test
reject the null hypothesis that residuals are normal.
The Ljung-Box Test
does not reject the null hypothesis that residuals form \(WN\). They also do not reject the null hypothesis that the squared residuals R^2
are WN.
We should also note that the output from \(GARCH\) models is quite large. If we want to only get the coefficients, the fit statistics, we can do the following:
mdl.garch@fit$matcoef
## Estimate Std. Error t value Pr(>|t|)
## mu -1.510320e-16 0.12923004 -1.168707e-15 1.00000000
## omega 1.151120e+00 0.51831280 2.220897e+00 0.02635790
## alpha1 1.139233e-01 0.04002464 2.846328e+00 0.00442266
## beta1 7.851049e-01 0.06583855 1.192470e+01 0.00000000
mdl.garch@fit$ics
## AIC BIC SIC HQIC
## 5.172060 5.202514 5.171962 5.183941
The value of the garchFit
function is an not a list (S3
object), but an S4
object; its components are accessed not with the $
symbol but with @
. Each accessed component can be a list
, vector
, data.frame
, etc. which is why we also used the $
symbol to extract the coefficient matrix.
If need be, we can also use capture.output
to save each output from the summary
function into a vector of strings. Then we can print any summary lines that we need to reduce the output clutter:
capture.output(summary(mdl.garch))[39:49]
## [1] "Standardised Residuals Tests:"
## [2] " Statistic p-Value "
## [3] " Jarque-Bera Test R Chi^2 36.679 1.084565e-08"
## [4] " Shapiro-Wilk Test R W 0.9851709 1.483964e-05"
## [5] " Ljung-Box Test R Q(10) 10.01125 0.4395069 "
## [6] " Ljung-Box Test R Q(15) 22.05404 0.1063948 "
## [7] " Ljung-Box Test R Q(20) 27.39449 0.1245256 "
## [8] " Ljung-Box Test R^2 Q(10) 7.909245 0.6377014 "
## [9] " Ljung-Box Test R^2 Q(15) 13.07198 0.5967378 "
## [10] " Ljung-Box Test R^2 Q(20) 17.00733 0.6524979 "
## [11] " LM Arch Test R TR^2 8.202932 0.7690776 "
As a final step, we combine the regression model for conditional mean and the GARCH model for conditional variance:
\[ \begin{cases} \text{Ret}_t &= \gamma + \gamma_1\cdot \text{Ret}_{t-1} + \gamma_2\cdot \text{Inf}_{t-1} + \gamma_3\cdot \text{DT.bill}_{t-1} + \epsilon_t \\ \epsilon_t &= \sigma_t \cdot w_t \\ \sigma_t^2 &= \omega + \alpha_1 \epsilon_{t-1}^2 + \beta_1 \sigma_{t-1}^2 \end{cases} \]
Save the data subset of exogenous variables. Because we need to include their lags in the right-hand side - we need to exclude the last row:
spec.dt <- as.matrix(SP)[-nrow(SP), 1:3]
head(spec.dt)
## Ret Inf. DT.bill
## [1,] 2.678492 0.3710579 -0.21
## [2,] 2.584550 -0.3710579 0.06
## [3,] 4.448416 0.0000000 -0.06
## [4,] 4.420518 0.0000000 -0.21
## [5,] 1.221139 0.0000000 -0.12
## [6,] 4.459217 0.0000000 0.08
Specify the model in R
:
suppressPackageStartupMessages({library(rugarch)})
mdl.spec.garch <- ugarchspec(
variance.model = list(garchOrder = c(1, 1)),
mean.model = list(armaOrder = c(0,0),
include.mean = TRUE,
external.regressors = spec.dt)
)
mdl.spec.garch
##
## *---------------------------------*
## * GARCH Model Spec *
## *---------------------------------*
##
## Conditional Variance Dynamics
## ------------------------------------
## GARCH Model : sGARCH(1,1)
## Variance Targeting : FALSE
##
## Conditional Mean Dynamics
## ------------------------------------
## Mean Model : ARFIMA(0,0,0)
## Include Mean : TRUE
## GARCH-in-Mean : FALSE
## Exogenous Regressor Dimension: 3
##
## Conditional Distribution
## ------------------------------------
## Distribution : norm
## Includes Skew : FALSE
## Includes Shape : FALSE
## Includes Lambda : FALSE
where:
variance.model
is the \(GARCH(1,1)\) model specification for our residual volatility;mean.model
is the mean equation for our returns with:
armaOrder
- an \(ARMA\) model in case we need to specify one;include.mean
- if we would like to include a constant/intercept in our model;external.regressors
- we specify the exogenous variables. Note that we are including the lag of the endogenous regressor as well, so it also needs to be included in the model.Because we are including lags, our endogenous data will start from \(t = 2,...,T\), so that the lagged exogenous data would be from \(t = 1,..., T-1\):
mdl.spec.garch.fit <- ugarchfit(data = SP[-1, "Ret"], spec = mdl.spec.garch)
mdl.spec.garch.fit
##
## *---------------------------------*
## * GARCH Model Fit *
## *---------------------------------*
##
## Conditional Variance Dynamics
## -----------------------------------
## GARCH Model : sGARCH(1,1)
## Mean Model : ARFIMA(0,0,0)
## Distribution : norm
##
## Optimal Parameters
## ------------------------------------
## Estimate Std. Error t value Pr(>|t|)
## mu 1.31263 0.204030 6.4335 0.000000
## mxreg1 0.18528 0.045487 4.0731 0.000046
## mxreg2 -1.31787 0.456021 -2.8899 0.003853
## mxreg3 -1.19690 0.309909 -3.8621 0.000112
## omega 1.19923 0.536503 2.2353 0.025400
## alpha1 0.11996 0.041406 2.8970 0.003767
## beta1 0.77559 0.068169 11.3775 0.000000
##
## Robust Standard Errors:
## Estimate Std. Error t value Pr(>|t|)
## mu 1.31263 0.230400 5.6972 0.000000
## mxreg1 0.18528 0.043918 4.2187 0.000025
## mxreg2 -1.31787 0.547165 -2.4085 0.016016
## mxreg3 -1.19690 0.361031 -3.3152 0.000916
## omega 1.19923 0.598210 2.0047 0.044996
## alpha1 0.11996 0.047766 2.5113 0.012028
## beta1 0.77559 0.069713 11.1254 0.000000
##
## LogLikelihood : -1472.298
##
## Information Criteria
## ------------------------------------
##
## Akaike 5.1814
## Bayes 5.2347
## Shibata 5.1811
## Hannan-Quinn 5.2022
##
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.08685 0.7682
## Lag[2*(p+q)+(p+q)-1][2] 0.26510 0.8155
## Lag[4*(p+q)+(p+q)-1][5] 1.49164 0.7420
## d.o.f=0
## H0 : No serial correlation
##
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.002882 0.9572
## Lag[2*(p+q)+(p+q)-1][5] 1.322472 0.7834
## Lag[4*(p+q)+(p+q)-1][9] 4.093256 0.5731
## d.o.f=2
##
## Weighted ARCH LM Tests
## ------------------------------------
## Statistic Shape Scale P-Value
## ARCH Lag[3] 0.0002154 0.500 2.000 0.9883
## ARCH Lag[5] 0.8432633 1.440 1.667 0.7800
## ARCH Lag[7] 3.7926024 2.315 1.543 0.3775
##
## Nyblom stability test
## ------------------------------------
## Joint Statistic: 0.8629
## Individual Statistics:
## mu 0.13023
## mxreg1 0.06168
## mxreg2 0.13919
## mxreg3 0.02595
## omega 0.11633
## alpha1 0.09033
## beta1 0.15680
##
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic: 1.69 1.9 2.35
## Individual Statistic: 0.35 0.47 0.75
##
## Sign Bias Test
## ------------------------------------
## t-value prob sig
## Sign Bias 0.8170 0.41426
## Negative Sign Bias 0.8844 0.37684
## Positive Sign Bias 0.7499 0.45362
## Joint Effect 8.1065 0.04386 **
##
##
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
## group statistic p-value(g-1)
## 1 20 27.77 0.08787
## 2 30 47.65 0.01597
## 3 40 55.76 0.03990
## 4 50 59.56 0.14348
##
##
## Elapsed time : 0.7686911
We get a large output. Let’s extract only the relevant information.
The coefficients:
round(mdl.spec.garch.fit@fit$robust.matcoef, 5)
## Estimate Std. Error t value Pr(>|t|)
## mu 1.31263 0.23040 5.69719 0.00000
## mxreg1 0.18528 0.04392 4.21873 0.00002
## mxreg2 -1.31787 0.54716 -2.40855 0.01602
## mxreg3 -1.19690 0.36103 -3.31523 0.00092
## omega 1.19923 0.59821 2.00469 0.04500
## alpha1 0.11996 0.04777 2.51132 0.01203
## beta1 0.77559 0.06971 11.12540 0.00000
Here we extracted the coefficients with Robust Standard Errors
- unbiased standard errors of coefficients under the hypothesis of residual heteroscedasticity (i.e. when the residual variance is not the same across all observation points). We see that all the p-values
are less than 0.05, so all of the coefficients are statistically significant.
We also get Ljung-Box
tests on the standardized (squared) residuals we conclude that the residuals are \(WN\) p-value > 0.05
for all cases).
The Weighted ARCH LM Tests
for ARCH effects testing conclude that the model residuals do not have any more ARCH effects.
So, we have finalized our model. Now, we can also create its forecasts:
forc.garch.mdl <- ugarchforecast(mdl.spec.garch.fit, n.ahead = 5)
forc.garch.mdl
##
## *------------------------------------*
## * GARCH Model Forecast *
## *------------------------------------*
## Model: sGARCH
## Horizon: 5
## Roll Steps: 0
## Out of Sample: 0
##
## 0-roll forecast [T0=1971-07-26 02:00:00]:
## Series Sigma
## T+1 1.313 5.580
## T+2 1.313 5.393
## T+3 1.313 5.220
## T+4 1.313 5.059
## T+5 1.313 4.912
Where we forecast both the return Ret
and the volatility Sigma
. Finally, we can plot the data:
par(mfrow = c(2, 1))
plot(forc.garch.mdl, which = 1)
plot(forc.garch.mdl, which = 3)
Note - if we do not specify which = ...
, we will be greeted with the following prompt:
Make a plot selection (or 0 to exit):
1: Time Series Prediction (unconditional)
2: Time Series Prediction (rolling)
3: Sigma Prediction (unconditional)
4: Sigma Prediction (rolling)
where we would have to input the number 1-4
to specify which plot we would like to see.
Regarding the rolling forecasts - they are used for cross-validation. From the documentation:
The forecast is based on the expected value of the innovations and hence the density chosen. One step ahead forecasts are based on the value of the previous data, while n-step ahead (n>1) are based on the unconditional expectation of the models.
The ability to roll the forecast 1 step at a time is implemented with the n.roll argument which controls how many times to roll the n.ahead forecast. The default argument of n.roll = 0 denotes no rolling and returns the standard n.ahead forecast. Critically, since n.roll depends on data being available from which to base the rolling forecast, the ugarchfit function needs to be called with the argument out.sample being at least as large as the n.roll argument, or in the case of a specification being used instead of a fit object, the out.sample argument directly in the forecast function.
Some info on rolling forecasts can be found here. The general idea is:
This allows us to examine the forecast accuracy of the model.
A similar test for the coefficients and their confidence intervals of the model lets us test the model stability over time.