library(reshape2)
source("common/functions.r",chdir=TRUE)
library(rugarch)
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(zoo))17 When things go wrong
The estimation in the previous chapter went through without any problems, but we are not always so lucky. Since we are maximising a non-linear function, many things can go wrong, often with hard-to-diagnose problems, as the code can fail with an incomprehensible error message. There can be many plausible explanations. There might be errors in data, the algorithms might be buggy, or your code might have mistakes.
This chapter covers common estimation failures in GARCH modeling and provides practical solutions for troubleshooting these issues. We examine numerical precision problems, convergence failures, and data scaling issues, along with techniques for handling errors in automated estimation procedures.
These problems can arise because of the trade-off between safe code and speed. If we want to ensure that nothing goes wrong, we need many checks and robust algorithms, which are slow, even very slow. Consequently, we opt for algorithms that are fast and usually work but sometimes fail.
17.1 Data and libraries
data=ProcessRawData()
y=data$sp500$y
y=y-mean(y)17.1.1 Define rugarch specs
spec.g = ugarchspec(
variance.model = list(garchOrder = c(1,1)),
mean.model = list(armaOrder = c(0,0), include.mean = FALSE)
)
spec.t = ugarchspec(
variance.model = list(garchOrder = c(1,1)),
mean.model = list(armaOrder = c(0,0), include.mean = FALSE),
distribution.model = "std"
)17.2 Failure
Estimation failures can occur for various reasons, ranging from numerical precision issues to inappropriate model specifications. We begin with a specific example that illustrates typical failure modes and their underlying causes.
When I tried to estimate the tGARCH model over a particular 2000-day estimation window, y[720:2719], the estimation failed. Other estimation windows also failed. This happens on the computer I am using now, but the estimation might be successful on a different computer, especially if it has a different version of R or other key libraries.
Here is what we do:
Res = ugarchfit(spec = spec.t, data = y[720:2719])
When we run this, we get the following error message:
Error in robustvcv(fun = f, pars = ipars[estidx, 1], nlag = nlag, hess = fit$hessian, : object 'B' not found
Traceback:
1. ugarchfit(spec = spec.t, data = data)
2. ugarchfit(spec = spec.t, data = data)
3. .sgarchfit(spec = spec, data = data, out.sample = out.sample,
. solver = solver, solver.control = solver.control, fit.control = fit.control,
. numderiv.control = default.numd)
4. .makefitmodel(garchmodel = "sGARCH", f = .sgarchLLH, T = T, m = m,
. timer = timer, convergence = convergence, message = sol$message,
. hess = hess, arglist = arglist, numderiv.control = numderiv.control)
5. robustvcv(fun = f, pars = ipars[estidx, 1], nlag = nlag, hess = fit$hessian,
. n = T, arglist = arglist)
17.2.1 Why does it fail?
Understanding the cause of estimation failures helps determine the appropriate solution. Let us examine the error message for clues about what went wrong.
This error message might seem incomprehensible, but there are some clues in it. It fails in something called robustvcv, which is not something we need for our purpose. It is simply a one-day-ahead volatility forecast, but it is required to get the confidence bounds of the parameters. In particular, vcv relates to variance-covariance-matrix and robust to its calculation in a way that is not overly sensitive to distributional assumptions.
The likely explanation is that the error has to do with some overflow of numerical values. Some returns are very small, while others are quite large, and when we take them to power two, the differences amplify. The covariance matrix is obtained from the inverse hessian, which is the matrix of second derivatives at the top of the likelihood function. It is tricky to estimate this hessian, especially if we want it done quickly.
It comes down to limited numerical precision. Most calculations are done with what is known as double precision floating point, called float64, a number that uses 64 bits in computer memory. A floating point number such as \(0.0234=2.34\times 10^{-2}\) is written in code as 2.34e-2.
The smallest number is \(2^{-1022}\) and largest \(2^{1023}\). There are two parts to a floating point number: the exponent (or power) and the mantissa, which contains the precision bits of the number in front of the power. Fifty-two bits represent the mantissa.
This means that if we do calculations with numbers that are very different from each other, one can end up with a very incorrect answer. For example, if we want to sum many numbers that are very different in size, we will get a different answer whether we start adding the smallest numbers or the largest numbers. Also, as we see in Chapter 22, we can get a different answer depending on whether we do a log of a product or the sum of logs.
I think this is what happened here: some sort of numerical precision problem. There are some ways to solve it, some easy, others a bit more complicated.
17.2.1.1 solver = "hybrid" does not help
The obvious first attempt might be to use alternative optimisation methods, but this particular failure has deeper causes.
We could try
Res = ugarchfit(spec = spec.t, data = data,solver = "hybrid")
But that also fails for obvious reasons.
17.3 Re-scaling might work
Data scaling issues are among the most common causes of estimation failure. Re-scaling the data can often resolve numerical precision problems by ensuring that calculations involve numbers of similar magnitudes.
The first thing to try is rescaling the data.
17.3.1 De-meaning
The simplest rescaling approach is to remove the mean from the estimation window rather than the entire dataset.
When we imported the data, we removed the mean from all the observations. If, however, we de-mean the estimation window, the calculation succeeds.
data=y[720:2719]
data=data-mean(data)
Res = ugarchfit(spec = spec.t, data = data,)
coef(Res) omega alpha1 beta1 shape
3.512907e-07 5.443387e-02 9.444364e-01 5.758712e+00
17.3.2 Normalising variance
An alternative approach is to standardise the data to have unit variance, though this requires rescaling the estimated parameters.
Normalising the variance to be one also works, but then we need to re-scale \(\omega\) back since \[
\sigma^2 = \frac{\omega}{1-\alpha-\beta}
\] and so if we do data=data/sd(data) then need to do \[
\hat{\omega}\times var(y)
\]
data=y[720:2719]
scale=1/sd(data)
data=data*scale
Res = ugarchfit(spec = spec.t, data = data)
coef(Res) omega alpha1 beta1 shape
0.003949835 0.053105631 0.945524952 5.769406825
omega=coef(Res)[1] / scale^2
omega omega
3.561103e-07
17.3.3 Do both
Combining both approaches often provides the most robust solution to numerical precision issues.
Usually, it is best to do both: de-mean and normalise the data.
17.3.4 Why did re-scaling work?
The effectiveness of rescaling stems from how it affects the numerical range of calculations.
The reason why the de-mean and normalisation works is because it makes most of the numbers be around one, which means that when we square them, the difference between the big and the small numbers does not become excessively large, so we do not get the numerical overflow we discussed above.
17.4 Boundary constraint problems
GARCH parameters must satisfy certain constraints: \(\omega > 0\), \(\alpha_i \geq 0\), \(\beta_j \geq 0\), and sometimes \(\sum(\alpha_i + \beta_j) < 1\) for stationarity. When the optimiser pushes parameters to these boundaries, estimation can fail or produce unreliable results.
Solutions:
- Use
variance.targeting = TRUEto fix unconditional variance - Impose tighter constraints: modify
fixed.parsorstart.pars - Try simpler model specifications (e.g., GARCH(1,1) instead of higher orders)
- Check for structural breaks that might violate stationarity assumptions
17.5 Starting value problems
Poor initial parameter values can cause the optimiser to converge to local maxima or fail entirely. This is particularly problematic for complex models with many parameters.
Solutions:
- Use
start.parsto provide better initial values based on simpler model estimates - Estimate ARCH first, then use results to initialise GARCH
- Try multiple random starting points and compare results
- Use grid search over plausible parameter ranges for robust starting values
17.6 Model specification problems
Sometimes estimation fails because the chosen model is fundamentally inappropriate for the data characteristics.
Solutions:
- Start with simple specifications (normal GARCH) before adding complexity
- Check residual diagnostics from simpler models to guide specification choices
- Use information criteria (AIC/BIC) to compare nested models systematically
- Consider alternative model families (GJR-GARCH for asymmetry, FIGARCH for long memory)
- Examine data for structural breaks that require regime-switching models
17.7 Data quality problems
Poor data quality can cause estimation failures through outliers, missing values, or inappropriate frequency.
Solutions:
- Implement outlier detection and treatment (winsorisation, trimming)
- Handle missing values appropriately (interpolation vs. exclusion)
- Check for data recording errors (decimal point shifts, sign errors)
- Verify appropriate sampling frequency (daily vs. intraday considerations)
- Test data stability over time to detect structural changes
17.8 Detecting failures — try()
When conducting automated analyses such as backtesting, estimation failures can interrupt the entire process. The try() function provides a mechanism for catching errors and continuing execution even when individual estimations fail.
Suppose the rescaling does not work, and the estimation still fails. If doing one estimation, it is easy to spot the problem and do something else. But what if we are doing backtesting, and we want to generate a large number of one-day risk forecasts? Then, it becomes annoying to have the code fail.
Here, an R statement called try() can be particularly useful. try() is designed to catch errors, so you try a calculation, and either you get an answer back or a message saying the estimation failed.
Suppose you want to take a logarithm of x and log(x). The x needs to be a positive number, and if you pass a string, the code crashes.
log("string")Error in log("string"): non-numeric argument to mathematical function
But what if you do a try?
res1=try(log(1))
res2=try(log("string"),silent = TRUE)Both ran without error, but the second, of course, failed. We can see that by
res1[1] 0
class(res1)[1] "numeric"
res2[1] "Error in log(\"string\") : non-numeric argument to mathematical function\n"
attr(,"class")
[1] "try-error"
attr(,"condition")
<simpleError in log("string"): non-numeric argument to mathematical function>
class(res2)[1] "try-error"
And detect it by
class(res2)=="try-error"[1] TRUE
So after running the try() function we check the output, and if its class is "try-error", we know the calculation failed, and we should deal with that.
17.9 Exercise
Apart from rugarch, which we used in this chapter, there are two popular alternatives in R that estimate time series models: fGarch and tseries. Fit a GARCH(1,1) model to the S&P 500 return series (y) using these three packages. Are the results identical? If not, what can account for the difference? Discuss.
# Answer
library(fGarch)
library(tseries)
spec1 = ugarchspec(
variance.model = list(garchOrder = c(1,1)),
mean.model = list(armaOrder = c(0,0), include.mean = FALSE))
ru.fit = ugarchfit(spec = spec1, data = y)
fg.fit = garchFit(y~garch(1,1),data=y,include.mean = FALSE)
ts.fit = garch(y)
rst=c()
rst=rbind(c(as.numeric(coef(ru.fit)),likelihood(ru.fit)),
c(as.numeric(coef(fg.fit)),as.numeric(-fg.fit@fit$llh)),
c(as.numeric(ts.fit$coef),as.numeric(logLik(ts.fit))))
colnames(rst)=c("Omega", "Alpha", "Beta", "Log Likelihood")
rownames(rst)=c("rugarch", "fGarch", "tseries")
rst
# rugarch and fGarch give almost identical results, with very little difference. The log-likelihood of the model estimated by tseries is lower. Possible explanations are:
# Different solvers. rugarch uses solnp by default, but it also provides several alternatives, such as nlminb, lbfgs, etc. fGarch uses nlminb by default, while tseries uses a Quasi-Newton optimiser.
# Variance initialisation. The most common choice is the unconditional variance, but the tseries documentation did not clearly mention the value it selects.
# Other factors, such as initial values of parameters, number of iterations, etc.
# Previous studies show that the performance of `tseries` is inferior to the other two packages. For more discussion on this topic, see "On The Accuracy of GARCH Estimation in R Packages" by Hill and McCullough (2019).