ARCH/GARCH Models
The engine for Volatility Discovery. This model audits the 'Clustering' of uncertainty, revealing how current volatility depends on previous shocks and previous variance, essential for high-fidelity risk forensics.
What is it?
ARCH/GARCH Models analyzes sequences of data points ordered chronologically over time to extract patterns, model trends, and make forecasts.
The engine for Volatility Discovery. This model audits the 'Clustering' of uncertainty, revealing how current volatility depends on previous shocks and previous variance, essential for high-fidelity risk forensics.
Goals & Indications
- Volatility Pulse Audit: Decipher the 'Persistence' of uncertainty—identifying periods where high variance clusters together.
- Risk Forensics Mapping: Construct a mathematical engine that forecasts the 'Width' of future outcomes rather than just the average.
- Heteroscedastic Neutralization: Mathematically model time-varying variance to ensure the reliability of p-values in unstable temporal fields.
Core Idea Diagram
Claims tested
How it works
- Fit mean equation (ARIMA or constant) to time series and obtain residuals.
- Check squared residuals for autocorrelation to confirm ARCH effects.
- Fit ARCH/GARCH model parameters using Maximum Likelihood Estimation.
- Compute predicted conditional variances to capture volatility bounds.
Assumptions
Important Note
ARCH/GARCH models focus on modeling conditional variance (volatility) of returns. Key insight: volatility is predictable and clusters over time - high volatility periods tend to be followed by high volatility. ARCH(q): variance depends on q past squared residuals. GARCH(p,q): variance depends on p past variances + q past squared residuals (more parsimonious). GARCH(1,1) most common: σ²_t = ω + α·ε²_{t-1} + β·σ²_{t-1}. Extensions: EGARCH (asymmetry/leverage), GJR-GARCH (threshold), TGARCH. Typical workflow: (1) Model mean equation (ARIMA/regression), (2) Test residuals for ARCH effects (LM test), (3) Fit GARCH to conditional variance, (4) Forecast volatility and VaR. Unlike ARIMA which models conditional mean, GARCH models conditional variance.
Worked Example
| Parameter | Estimate | Std. Error | t-stat | p-value |
|---|---|---|---|---|
| omega (ω) | 0.051 | 0.012 | 4.25 | <0.001 |
| alpha (α) | 0.152 | 0.045 | 3.38 | <0.001 |
| beta (β) | 0.783 | 0.062 | 12.63 | <0.001 |
Volatility Clustering & GARCH(1,1) Laboratory
Observe how the GARCH parameters controls persistence. Higher parameters (α + β) lead to prolonged clusters of high/low returns volatility.
Hypotheses
Pragmatic null and alternative hypotheses defined in mathematical notation.
H₀: No ARCH effects present (constant conditional variance)
Hₐ: ARCH effects present (time-varying conditional variance with volatility clustering)
ARCH/GARCH models focus on modeling conditional variance (volatility) of returns. Key insight: volatility is predictable and clusters over time - high volatility periods tend to be followed by high volatility. ARCH(q): variance depends on q past squared residuals. GARCH(p,q): variance depends on p past variances + q past squared residuals (more parsimonious). GARCH(1,1) most common: σ²_t = ω + α·ε²_{t-1} + β·σ²_{t-1}. Extensions: EGARCH (asymmetry/leverage), GJR-GARCH (threshold), TGARCH. Typical workflow: (1) Model mean equation (ARIMA/regression), (2) Test residuals for ARCH effects (LM test), (3) Fit GARCH to conditional variance, (4) Forecast volatility and VaR. Unlike ARIMA which models conditional mean, GARCH models conditional variance.
Assumptions
The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.
Diagnostics
Checking residual plots and indices to examine model deviations and ensure standard error integrity.
- ARCH-LM test on residuals to verify the presence of volatility clustering.
- Ljung-Box test on squared standardized residuals to ensure volatility modeling is complete.
- Significance audit of Alpha (shock) and Beta (persistence) coefficients.
- Log-Likelihood comparison between GARCH(1,1) and higher-order models.
- Nyblom test for parameter stability over time.
- Distributional fit audit: Comparing Normal vs. Student-t vs. GED error models.
- Value-at-Risk (VaR) Backtesting using Kupiec’s Proportion of Failures test.
- News Impact Curve analysis to audit symmetry of shock reactions.
- Robustness check against EGARCH or GJR-GARCH if leverage effects are suspected.
- Standardized residual Q-Q plot to identify 'Fat Tails' (Leptokurtosis).
Applied Minds
Review concrete study examples, data layout guidelines, and copy executable syntax scripts.
Stock Return Volatility Modeling with GARCH(1,1)
Model daily stock return volatility (n=1500 daily returns, ≈6 years) using GARCH framework. Demonstrates complete volatility modeling workflow: return calculation, exploratory volatility analysis, ARCH effect testing, mean equation specification (ARMA), GARCH(1,1) estimation with normal and Student-t distributions, extended models (EGARCH, GJR-GARCH for leverage effects), comprehensive residual diagnostics (ARCH-LM test, standardized residuals), volatility forecasting, VaR estimation and backtesting, news impact curves for asymmetry. Compares symmetric vs asymmetric GARCH models, tests leverage hypothesis, validates VaR predictions using Kupiec and Christoffersen tests.
# ============================================================================
# ARCH/GARCH MODELS: Stock Return Volatility Modeling
# ============================================================================
# Demonstrates: GARCH(1,1), EGARCH, GJR-GARCH, VaR forecasting
# Data: 1500 daily returns (≈6 years) with volatility clustering and leverage
# ============================================================================
library(rugarch) # GARCH estimation and forecasting
library(FinTS) # ARCH test
library(forecast) # ARMA models
library(ggplot2) # visualization
library(gridExtra) # multiple plots
set.seed(123)
# ============================================================================
# 1. DATA GENERATION: Stock returns with volatility clustering
# ============================================================================
cat("=", rep("=", 79), "\n", sep="")
cat("GARCH MODELS: Stock Return Volatility Analysis\n")
cat("=", rep("=", 79), "\n\n", sep="")
# Simulate GARCH(1,1) process with leverage (GJR-GARCH)
n <- 1500 # Daily returns (≈6 years of trading days)
# True parameters (GJR-GARCH to create leverage effect)
omega <- 0.00001
alpha <- 0.08
beta <- 0.90
gamma <- 0.04 # Leverage: negative shocks increase volatility more
returns <- numeric(n)
variance <- numeric(n)
variance[1] <- omega / (1 - alpha - beta - gamma/2) # Unconditional variance
returns[1] <- rnorm(1, 0, sqrt(variance[1]))
for (t in 2:n) {
# GJR-GARCH variance equation
I_negative <- ifelse(returns[t-1] < 0, 1, 0)
variance[t] <- omega + alpha * returns[t-1]^2 +
gamma * returns[t-1]^2 * I_negative +
beta * variance[t-1]
returns[t] <- rnorm(1, 0, sqrt(variance[t]))
}
# Convert to percentage returns
returns <- returns * 100
volatility <- sqrt(variance) * 100
# Create time series
date_seq <- seq.Date(from = as.Date("2018-01-01"), by = "day", length.out = n)
returns_ts <- xts::xts(returns, order.by = date_seq)
cat("Data generated: n =", n, "daily returns\n")
cat("Mean return:", round(mean(returns), 4), "%\n")
cat("SD(volatility):", round(sd(returns), 4), "%\n")
cat("Skewness:", round(moments::skewness(returns), 3), "\n")
cat("Kurtosis:", round(moments::kurtosis(returns), 3), "(>3 = fat tails)\n\n")
# ============================================================================
# 2. EXPLORATORY ANALYSIS
# ============================================================================
cat("=== EXPLORATORY VOLATILITY ANALYSIS ===\n\n")
# Visualizations
par(mfrow = c(3, 2), mar = c(4, 4, 3, 1))
# Returns over time
plot(date_seq, returns, type="l", col="steelblue", lwd=0.5,
main="Daily Returns", xlab="Date", ylab="Return(%)")
abline(h = 0, col = "red", lty = 2)
# Squared returns (proxy for volatility)
plot(date_seq, returns^2, type="l", col="darkred", lwd=0.5,
main="Squared Returns(Volatility Proxy)", xlab="Date", ylab="Squared Return")
# Histogram
hist(returns, breaks = 50, col = "lightblue", probability = TRUE,
main = "Return Distribution", xlab = "Return(%)")
curve(dnorm(x, mean(returns), sd(returns)), add = TRUE, col = "red", lwd = 2)
legend("topright", "Normal", col="red", lwd=2, bty="n")
# Q-Q plot
qqnorm(returns, main = "Q-Q Plot: Returns vs Normal")
qqline(returns, col = "red", lwd = 2)
# ACF of returns
acf(returns, lag.max = 40, main = "ACF: Returns")
# ACF of squared returns (ARCH test)
acf(returns^2, lag.max = 40, main = "ACF: Squared Returns(ARCH effects)")
par(mfrow = c(1, 1))
cat("Visual inspection:\n")
cat(" - Returns show volatility clustering(episodes of high/low volatility)\n")
cat(" - Squared returns show autocorrelation(ARCH effects)\n")
cat(" - Fat tails evident in histogram and Q-Q plot\n\n")
# ============================================================================
# 3. TEST FOR ARCH EFFECTS
# ============================================================================
cat("=== ARCH EFFECT TESTING ===\n\n")
# Ljung-Box test on squared returns
lb_squared <- Box.test(returns^2, lag = 20, type = "Ljung-Box")
cat("Ljung-Box test on squared returns(lag=20):\n")
cat(" Test statistic:", round(lb_squared$statistic, 4), "\n")
cat(" p-value:", format.pval(lb_squared$p.value, digits=4), "\n")
if (lb_squared$p.value < 0.05) {
cat(" Conclusion: ARCH effects PRESENT(p<0.05) - GARCH appropriate\n\n")
} else {
cat(" Conclusion: No ARCH effects - constant variance adequate\n\n")
}
# ARCH-LM test
arch_test <- ArchTest(returns, lags = 12)
cat("ARCH-LM test(Engle 1982):\n")
print(arch_test)
if (arch_test$p.value < 0.05) {
cat("\n Conclusion: ARCH effects PRESENT - proceed with GARCH\n\n")
}
# ============================================================================
# 4. MEAN EQUATION SPECIFICATION
# ============================================================================
cat("=== MEAN EQUATION SPECIFICATION ===\n\n")
# Check if returns have autocorrelation
lb_returns <- Box.test(returns, lag = 20, type = "Ljung-Box")
cat("Ljung-Box test on returns(lag=20):\n")
cat(" p-value:", format.pval(lb_returns$p.value, digits=4), "\n")
if (lb_returns$p.value < 0.05) {
cat(" Autocorrelation present - use ARMA mean equation\n")
# Auto-select ARMA order
arma_model <- auto.arima(returns, max.p=5, max.q=5, seasonal=FALSE,
ic="aic", stepwise=TRUE, trace=FALSE)
arma_order <- arimaorder(arma_model)
cat(" Selected ARMA order:", paste0("(", arma_order[1], ",", arma_order[3], ")\n\n"))
mean_spec <- list(armaOrder = c(arma_order[1], arma_order[3]))
} else {
cat(" No significant autocorrelation - use constant mean\n\n")
mean_spec <- list(armaOrder = c(0, 0))
}
# ============================================================================
# 5. GARCH(1,1) ESTIMATION - Normal Distribution
# ============================================================================
cat("=== GARCH(1,1) ESTIMATION ===\n\n")
# Specify GARCH(1,1) with normal distribution
spec_garch11 <- ugarchspec(
variance.model = list(model = "sGARCH", garchOrder = c(1, 1)),
mean.model = mean_spec,
distribution.model = "norm"
)
# Fit model
fit_garch11 <- ugarchfit(spec = spec_garch11, data = returns)
cat("GARCH(1,1) with Normal distribution:\n")
print(fit_garch11)
# Extract parameters
params <- coef(fit_garch11)
omega_est <- params["omega"]
alpha_est <- params["alpha1"]
beta_est <- params["beta1"]
cat("\nKey parameters:\n")
cat(" omega(ω):", round(omega_est, 8), "\n")
cat(" alpha(α):", round(alpha_est, 4), "(ARCH effect)\n")
cat(" beta(β):", round(beta_est, 4), "(GARCH effect)\n")
cat(" α + β:", round(alpha_est + beta_est, 4), "(persistence)\n")
# Unconditional variance
if (alpha_est + beta_est < 1) {
uncond_var <- omega_est / (1 - alpha_est - beta_est)
cat(" Unconditional variance:", round(uncond_var, 6), "\n")
cat(" Unconditional volatility:", round(sqrt(uncond_var), 4), "%\n")
}
# Half-life
half_life <- log(0.5) / log(alpha_est + beta_est)
cat(" Half-life of shocks:", round(half_life, 2), "days\n\n")
# Information criteria
cat("Model fit:\n")
cat(" Log-likelihood:", round(likelihood(fit_garch11), 2), "\n")
cat(" AIC:", round(infocriteria(fit_garch11)[1], 2), "\n")
cat(" BIC:", round(infocriteria(fit_garch11)[2], 2), "\n\n")
# ============================================================================
# 6. GARCH(1,1) with Student-t Distribution
# ============================================================================
cat("=== GARCH(1,1) with Student-t ===\n\n")
# Specify with Student-t
spec_garch_t <- ugarchspec(
variance.model = list(model = "sGARCH", garchOrder = c(1, 1)),
mean.model = mean_spec,
distribution.model = "std" # Student-t
)
fit_garch_t <- ugarchfit(spec = spec_garch_t, data = returns)
cat("GARCH(1,1) with Student-t distribution:\n")
cat(" Degrees of freedom:", round(coef(fit_garch_t)["shape"], 2), "\n")
cat(" AIC:", round(infocriteria(fit_garch_t)[1], 2), "\n")
cat(" BIC:", round(infocriteria(fit_garch_t)[2], 2), "\n\n")
if (infocriteria(fit_garch_t)[1] < infocriteria(fit_garch11)[1]) {
cat(" Student-t provides BETTER fit(lower AIC)\n")
cat(" Fat tails present - t-distribution recommended\n\n")
}
# ============================================================================
# 7. EGARCH for Leverage Effects
# ============================================================================
cat("=== EGARCH(1,1) - Asymmetric Model ===\n\n")
# Specify EGARCH
spec_egarch <- ugarchspec(
variance.model = list(model = "eGARCH", garchOrder = c(1, 1)),
mean.model = mean_spec,
distribution.model = "std"
)
fit_egarch <- ugarchfit(spec = spec_egarch, data = returns)
cat("EGARCH(1,1) results:\n")
print(fit_egarch)
gamma_egarch <- coef(fit_egarch)["gamma1"]
cat("\nLeverage parameter(γ):", round(gamma_egarch, 4), "\n")
if (gamma_egarch < 0) {
cat(" γ < 0: Negative returns INCREASE volatility more(leverage effect)\n")
}
cat(" AIC:", round(infocriteria(fit_egarch)[1], 2), "\n\n")
# ============================================================================
# 8. GJR-GARCH for Leverage Effects
# ============================================================================
cat("=== GJR-GARCH(1,1) - Threshold Model ===\n\n")
# Specify GJR-GARCH
spec_gjr <- ugarchspec(
variance.model = list(model = "gjrGARCH", garchOrder = c(1, 1)),
mean.model = mean_spec,
distribution.model = "std"
)
fit_gjr <- ugarchfit(spec = spec_gjr, data = returns)
cat("GJR-GARCH(1,1) results:\n")
print(fit_gjr)
gamma_gjr <- coef(fit_gjr)["gamma1"]
cat("\nAsymmetry parameter(γ):", round(gamma_gjr, 4), "\n")
if (gamma_gjr > 0) {
cat(" γ > 0: Negative returns INCREASE volatility more\n")
cat(" Total effect of negative shock: α + γ =",
round(coef(fit_gjr)["alpha1"] + gamma_gjr, 4), "\n")
}
cat(" AIC:", round(infocriteria(fit_gjr)[1], 2), "\n\n")
# ============================================================================
# 9. MODEL COMPARISON
# ============================================================================
cat("=== MODEL COMPARISON ===\n\n")
model_comp <- data.frame(
Model = c("GARCH(1,1)-Normal", "GARCH(1,1)-t", "EGARCH(1,1)-t", "GJR-GARCH(1,1)-t"),
LogLik = c(likelihood(fit_garch11), likelihood(fit_garch_t),
likelihood(fit_egarch), likelihood(fit_gjr)),
AIC = c(infocriteria(fit_garch11)[1], infocriteria(fit_garch_t)[1],
infocriteria(fit_egarch)[1], infocriteria(fit_gjr)[1]),
BIC = c(infocriteria(fit_garch11)[2], infocriteria(fit_garch_t)[2],
infocriteria(fit_egarch)[2], infocriteria(fit_gjr)[2])
)
print(model_comp, row.names=FALSE)
best_model <- which.min(model_comp$AIC)
cat("\nBest model by AIC:", model_comp$Model[best_model], "\n")
cat("Recommendation: Use GJR-GARCH with Student-t for leverage effects\n\n")
# Use GJR-GARCH for further analysis
final_model <- fit_gjr
# ============================================================================
# 10. DIAGNOSTIC CHECKING
# ============================================================================
cat("=== MODEL DIAGNOSTICS ===\n\n")
# Standardized residuals
std_resid <- residuals(final_model, standardize = TRUE)
# ARCH-LM test on standardized residuals
arch_test_resid <- ArchTest(std_resid, lags = 12)
cat("ARCH-LM test on standardized residuals:\n")
cat(" Test statistic:", round(arch_test_resid$statistic, 4), "\n")
cat(" p-value:", format.pval(arch_test_resid$p.value, digits=4), "\n")
if (arch_test_resid$p.value > 0.05) {
cat(" Conclusion: No remaining ARCH effects - model adequate\n\n")
} else {
cat(" Conclusion: ARCH effects remain - consider higher order\n\n")
}
# Ljung-Box on standardized residuals
lb_std <- Box.test(std_resid, lag = 20, type = "Ljung-Box")
cat("Ljung-Box test on standardized residuals:\n")
cat(" p-value:", format.pval(lb_std$p.value, digits=4), "\n")
if (lb_std$p.value > 0.05) {
cat(" Conclusion: No autocorrelation - good\n\n")
}
# Ljung-Box on squared standardized residuals
lb_std_sq <- Box.test(std_resid^2, lag = 20, type = "Ljung-Box")
cat("Ljung-Box test on squared standardized residuals:\n")
cat(" p-value:", format.pval(lb_std_sq$p.value, digits=4), "\n")
if (lb_std_sq$p.value > 0.05) {
cat(" Conclusion: No remaining ARCH effects - model captures volatility\n\n")
}
# Residual statistics
cat("Standardized residual statistics:\n")
cat(" Mean:", round(mean(std_resid), 4), "(should be ~0)\n")
cat(" SD:", round(sd(std_resid), 4), "(should be ~1)\n")
cat(" Skewness:", round(moments::skewness(std_resid), 3), "\n")
cat(" Kurtosis:", round(moments::kurtosis(std_resid), 3), "\n")
cat(" |z| > 3:", sum(abs(std_resid) > 3), "observations\n\n")
# Plot diagnostics
par(mfrow = c(2, 3))
plot(std_resid, type="l", main="Standardized Residuals", ylab="z_t")
abline(h = c(-3, 0, 3), col = c("red", "black", "red"), lty = c(2, 1, 2))
acf(std_resid, lag.max = 30, main = "ACF: Standardized Residuals")
acf(std_resid^2, lag.max = 30, main = "ACF: Squared Std Residuals")
hist(std_resid, breaks = 50, probability = TRUE, col = "lightblue",
main = "Histogram: Std Residuals", xlab = "z_t")
curve(dnorm(x, 0, 1), add = TRUE, col = "red", lwd = 2)
qqnorm(std_resid, main = "Q-Q Plot: Std Residuals")
qqline(std_resid, col = "red", lwd = 2)
# Conditional volatility
vol_fitted <- sigma(final_model)
plot(date_seq, vol_fitted, type="l", col="darkred", lwd=1,
main="Fitted Conditional Volatility", xlab="Date", ylab="Volatility(%)")
par(mfrow = c(1, 1))
# ============================================================================
# 11. NEWS IMPACT CURVE
# ============================================================================
cat("=== NEWS IMPACT CURVE ===\n\n")
ni_curve <- newsimpact(final_model)
plot(ni_curve$zx, ni_curve$zy, type="l", lwd=2, col="darkblue",
main="News Impact Curve(GJR-GARCH)",
xlab="Shock(standardized return)", ylab="Next Period Variance")
abline(v = 0, col = "gray", lty = 2)
grid()
cat("News impact curve shows asymmetry:\n")
cat(" Negative shocks(left) have LARGER impact on variance than positive\n")
cat(" This is the leverage effect\n\n")
# ============================================================================
# 12. VOLATILITY FORECASTING
# ============================================================================
cat("=== VOLATILITY FORECASTING ===\n\n")
# Forecast 20 days ahead
forecast_horizon <- 20
forc <- ugarchforecast(final_model, n.ahead = forecast_horizon)
cat("Volatility forecast(next", forecast_horizon, "days):\n")
vol_forecast <- sigma(forc)
print(head(vol_forecast, 10))
# Plot forecast
par(mfrow = c(1, 1))
vol_historical <- as.numeric(sigma(final_model))
vol_dates <- date_seq[1:length(vol_historical)]
forc_dates <- seq.Date(from = max(vol_dates) + 1, by = "day", length.out = forecast_horizon)
plot(vol_dates, vol_historical, type="l", col="steelblue", lwd=1.5,
xlim=range(c(vol_dates, forc_dates)), ylim=range(c(vol_historical, vol_forecast)),
main="Volatility Forecast", xlab="Date", ylab="Volatility(%)")
lines(forc_dates, vol_forecast, col="darkred", lwd=2, lty=2)
abline(v = max(vol_dates), col = "gray", lty = 3)
legend("topright", c("Historical", "Forecast"), col=c("steelblue", "darkred"),
lwd=c(1.5, 2), lty=c(1, 2), bty="n")
cat("\nVolatility forecasts generated.\n")
cat("Mean forecast volatility:", round(mean(vol_forecast), 4), "%\n\n")
# ============================================================================
# 13. VALUE-AT-RISK (VaR) ESTIMATION
# ============================================================================
cat("=== VALUE-AT-RISK(VaR) ESTIMATION ===\n\n")
# Calculate VaR from GARCH forecasts
alpha_var <- 0.05 # 5% VaR
df_param <- coef(final_model)["shape"] # t-distribution df
# Quantile from Student-t
q_t <- qt(alpha_var, df = df_param)
# VaR forecast: μ + σ * quantile
mu_forecast <- fitted(forc) # Mean forecast
var_forecast <- mu_forecast + vol_forecast * q_t
cat("5% VaR forecast(next 10 days):\n")
print(head(var_forecast, 10))
cat("\nInterpretation:\n")
cat(" 5% probability that return will be BELOW VaR threshold\n")
cat(" Example: VaR =", round(var_forecast[1], 3), "% means 5% chance of losing more than",
round(abs(var_forecast[1]), 3), "%\n\n")
# ============================================================================
# 14. VaR BACKTESTING
# ============================================================================
cat("=== VaR BACKTESTING ===\n\n")
# Use rolling window for backtesting
window_size <- 1000
test_size <- 500
var_violations <- numeric(test_size)
var_estimates <- numeric(test_size)
for (i in 1:test_size) {
# Rolling window
train_data <- returns[i:(window_size + i - 1)]
test_return <- returns[window_size + i]
# Fit GJR-GARCH
fit_roll <- ugarchfit(spec = spec_gjr, data = train_data, solver="hybrid")
# 1-day ahead forecast
forc_roll <- ugarchforecast(fit_roll, n.ahead = 1)
# VaR estimate
mu_1 <- fitted(forc_roll)[1]
sig_1 <- sigma(forc_roll)[1]
df_1 <- coef(fit_roll)["shape"]
var_1 <- mu_1 + sig_1 * qt(alpha_var, df = df_1)
var_estimates[i] <- var_1
var_violations[i] <- ifelse(test_return < var_1, 1, 0)
}
violation_rate <- mean(var_violations)
expected_rate <- alpha_var
cat("VaR Backtesting Results(5% VaR):\n")
cat(" Expected violation rate:", expected_rate * 100, "%\n")
cat(" Actual violation rate:", round(violation_rate * 100, 2), "%\n")
cat(" Number of violations:", sum(var_violations), "out of", test_size, "\n\n")
# Kupiec test (unconditional coverage)
kupiec_stat <- -2 * (log(expected_rate^sum(var_violations) *
(1-expected_rate)^(test_size-sum(var_violations))) -
log(violation_rate^sum(var_violations) *
(1-violation_rate)^(test_size-sum(var_violations))))
kupiec_p <- 1 - pchisq(kupiec_stat, df = 1)
cat("Kupiec Test(Unconditional Coverage):\n")
cat(" Test statistic:", round(kupiec_stat, 4), "\n")
cat(" p-value:", format.pval(kupiec_p, digits=4), "\n")
if (kupiec_p > 0.05) {
cat(" Conclusion: VaR model is ADEQUATE(p>0.05)\n\n")
} else {
cat(" Conclusion: VaR model INADEQUATE - violation rate differs from 5%\n\n")
}
# Plot VaR violations
test_dates <- date_seq[(window_size + 1):(window_size + test_size)]
test_returns <- returns[(window_size + 1):(window_size + test_size)]
plot(test_dates, test_returns, type="l", col="steelblue",
main="VaR Backtesting: 5% VaR Violations",
xlab="Date", ylab="Return(%)")
lines(test_dates, var_estimates, col="red", lwd=1.5, lty=2)
points(test_dates[var_violations==1], test_returns[var_violations==1],
col="darkred", pch=19, cex=1.2)
abline(h = 0, col = "gray", lty = 3)
legend("topright", c("Returns", "5% VaR", "Violations"),
col=c("steelblue", "red", "darkred"), lwd=c(1, 1.5, NA),
lty=c(1, 2, NA), pch=c(NA, NA, 19), bty="n")
# ============================================================================
# 15. SUMMARY
# ============================================================================
cat("\n=== FINAL SUMMARY ===\n\n")
cat("Data: 1500 daily stock returns\n")
cat("Best model: GJR-GARCH(1,1) with Student-t distribution\n\n")
cat("Key findings:\n")
cat(" - ARCH effects present(p<0.05): volatility clustering confirmed\n")
cat(" - Leverage effect detected(γ>0): negative returns increase volatility more\n")
cat(" - High persistence(α+β≈0.95): shocks decay slowly\n")
cat(" - Student-t fits better than normal: fat tails in returns\n")
cat(" - VaR backtesting successful: violation rate ≈ 5%\n\n")
cat("Model parameters(GJR-GARCH):\n")
cat(" α (ARCH):", round(coef(final_model)["alpha1"], 4), "\n")
cat(" β (GARCH):", round(coef(final_model)["beta1"], 4), "\n")
cat(" γ (Leverage):", round(coef(final_model)["gamma1"], 4), "\n")
cat(" df(Student-t):", round(coef(final_model)["shape"], 2), "\n\n")
cat("Applications:\n")
cat(" - Portfolio risk management(VaR, ES)\n")
cat(" - Option pricing(volatility forecasts)\n")
cat(" - Risk-adjusted returns\n")
cat(" - Regulatory capital requirements\n\n")
cat("GARCH analysis complete.\n")
# ============================================================================
# END
# ============================================================================Alternatives
Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.
- EGARCH Strike — Pivot to the Exponential GARCH to model why 'Negative News' creates more noise than 'Positive News'.
- GJR-GARCH — An alternative standard for modeling leverage effects in volatile systems.
- Student-t GARCH — The mandatory pivot when residuals follow a heavy-tailed 'Leptokurtic' distribution.
- GED-GARCH — Use the Generalized Error Distribution to capture extreme clinical shocks.
- GARCH-in-Mean (GARCH-M) — Explicitly model how the 'Risk' level influences the 'Return' or mean trajectory.
Post-hoc
Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.
- Test standardized residuals for remaining ARCH effects
- Compare different error distributions (normal, t, GED)
- Ljung-Box test on squared standardized residuals
- Examine persistence: alpha + beta close to 1 indicates high persistence
- Value-at-Risk backtesting for risk applications
ARCH/GARCH models volatility clustering. Post-hoc involves model comparison and diagnostics.
Effect Size
Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.
Sample Size
Guidelines for minimum sample requirements and power analysis parameters.
The 'Massive-D' Requirement: A minimum of 500-1000 timepoints is essential. Volatility models (GARCH) are extremely data-hungry; they must 'See' several clusters of high and low variance to stabilize the α/β estimates.
| Effect Size | Parameters | Required n |
|---|---|---|
| Small Effect | Low Persistence (α+β=0.5) | n ≈ 2000 |
| Medium Effect | Moderate Persistence (α+β=0.8) | n ≈ 1000 |
| Large Effect | High Persistence (α+β=0.95) | n ≈ 500 |
The 'Non-Normal' Tax: If your temporal data has 'Heavy Tails' (extreme shocks), the GARCH math becomes even more unstable. Quadruple your sample size or use a Student-t error distribution to maintain statistical authority.
Reporting
How to compile statistical results into publication prose matching APA and journal style guides.
Manuscript Lab
Copy standard summary tables and forensic reporting grids to outline analysis details.
| Model Part | Term | Estimate | SE | t | p |
|---|---|---|---|---|---|
| Mean Equation | Constant (mu) | 0.005 | 0.002 | 2.50 | .012 |
| Variance Equation | Omega (Baseline) | 0.012 | 0.004 | 3.00 | .003 |
| Variance Equation | Alpha (Shock) | 0.150 | 0.045 | 3.33 | .002 |
| Variance Equation | Beta (Persistence) | 0.820 | 0.052 | 15.77 | < .001 |
The 'Sensitivity' to News. Measures how much current volatility spikes in response to a sudden market swing.
The 'Memory' of Fear. Measures how long high volatility lasts before settling back to normal. Values near 1.0 indicate long-lasting market turbulence.
Command Center
Syntax libraries and function parameters for executing calculations in stats packages.
# 1. Specify GARCH(1,1) Model
spec <- rugarch::ugarchspec(variance.model = list(model = 'sGARCH', garchOrder = c(1, 1)))
# 2. Fit Model
model <- rugarch::ugarchfit(spec = spec, data = returns)
print(model)
# 3. Forecast Future Volatility
forecast <- rugarch::ugarchforecast(model, n.ahead = 10)
plot(forecast)Traditional models assume returns are 'Normal'. They aren't. Always use a 'Student-t' or 'skew-GED' error distribution in GARCH to account for 'Fat Tails' (extreme market crashes).
# Audit for Fat Tails
# Re-specify model with distribution = 'std'Common Mistakes
Analytical caveats and corrections to maintain modeling integrity.
References
Scholarly lineage and citation keys grounding the statistical framework.