| Title: | GARCH-Informed LSTM Model for Volatility Forecasting |
|---|---|
| Description: | The proposed Generalized Autoregressive Conditional Heteroskedasticity (GARCH)-informed Long Short-Term Memory (LSTM) model follows the concept of physics-informed machine learning (PIML) by integrating established econometric knowledge of price volatility into a data-driven forecasting framework. In the model, conditional volatility estimated from the GARCH process is incorporated as an additional explanatory signal or volatility-based weighting component within the LSTM architecture. This enables the LSTM to learn nonlinear temporal dependencies while remaining informed by the underlying characteristics of agricultural price series, including volatility clustering, heteroscedasticity and market uncertainty. The optimized weighting parameter, lambda, controls the contribution of the GARCH-derived volatility information to the final prediction. Thus, the model combines the statistical interpretability of GARCH with the nonlinear learning capability of LSTM, producing a hybrid PIML framework that is more responsive to both normal price movements and periods of extreme market volatility. The methodology is motivated by hybrid forecasting framework proposed by Yeasin and Paul (2024) <doi:10.1007/s11227-023-05542-3>. |
| Authors: | Md Yeasin [aut], Ranjit Kumar Paul [aut, cre], Manojit Mandal [aut], Pushkar Bora [aut] |
| Maintainer: | Ranjit Kumar Paul <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-09-22 10:24:59 UTC |
| Source: | https://github.com/cran/GARCHInfoLSTM |
Compares GINN conditional variance forecasts against realized variance computed from actual data that arrived after training. Returns RMSE, MAE, R-squared and SMAPE on variance units.
AccuracyGINN(fit, actual, lambda = NULL, verbose = TRUE)AccuracyGINN(fit, actual, lambda = NULL, verbose = TRUE)
fit |
A GINN object returned by GINN(). |
actual |
Numeric vector of actual DATA observed after training. Their returns and realized variance are computed internally. Needs at least 2 values. |
lambda |
Lambda value to use. NULL = best lambda. |
verbose |
Logical. If TRUE (default), prints a formatted accuracy report to the console. Set to FALSE to suppress console output and only receive the returned data frame. |
Data frame with columns: Lambda, h, RMSE, MAE, R2, SMAPE (all in variance units). Also prints a formatted accuracy table to the console. The data frame has a "comparison" attribute with step-by-step Actual_Var vs Forecast_Var vs Error columns.
prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.5), verbose = FALSE) # Simulate actual future prices new_prices <- tail(prices, 1) * cumprod(1 + rnorm(12, 0.001, 0.02)) acc <- AccuracyGINN(fit, actual = new_prices) print(acc) # Step-by-step comparison attr(acc, "comparison")prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.5), verbose = FALSE) # Simulate actual future prices new_prices <- tail(prices, 1) * cumprod(1 + rnorm(12, 0.001, 0.02)) acc <- AccuracyGINN(fit, actual = new_prices) print(acc) # Step-by-step comparison attr(acc, "comparison")
Trains an LSTM on conditional variance sequences using the GINN composite loss:
Loss = (1-lambda) x MSE(sigma2_t, sigma2_hat_LSTM)
+ lambda x MSE(sigma2_GARCH, sigma2_hat_LSTM)
Trains on FULL data – no train/test split. Best lambda selected by Training RMSE. Use predictGINN() for out-of-sample volatility forecasting.
Pipeline:
Returns:
AR(p) on full returns gives mean return mu_t
GARCH(p,q) on full returns gives sigma2_GARCH
Ground-truth variance: sigma2_t = (r_t - mu_t)^2
LSTM trained on sigma2_t sequences with GINN loss
GINN( data, mode = c("auto", "manual"), ar_max_lag = 5L, ar_lag = NULL, garch_p_grid = 1:3, garch_q_grid = 1:3, garch_mean_grid = c("constant", "ar", "arma"), garch_dist_grid = c("norm", "std", "ged", "snorm", "sstd", "sged"), garch_p = NULL, garch_q = NULL, garch_mean = NULL, garch_dist = NULL, lag, hidden_size = 32, num_layers = 1, lr = 0.001, dropout = 0.0, epochs = 500, patience = 30, tune_epochs = 50, batch_size = 16, lambda_list = seq(0.1, 0.9, 0.1), hidden_grid = c(8, 16, 24, 32, 40), layers_grid = c(1, 2), lr_grid = c(0.001, 0.003, 0.005), dropout_grid = c(0.0, 0.1, 0.2, 0.3), verbose = TRUE, seed = 42L )GINN( data, mode = c("auto", "manual"), ar_max_lag = 5L, ar_lag = NULL, garch_p_grid = 1:3, garch_q_grid = 1:3, garch_mean_grid = c("constant", "ar", "arma"), garch_dist_grid = c("norm", "std", "ged", "snorm", "sstd", "sged"), garch_p = NULL, garch_q = NULL, garch_mean = NULL, garch_dist = NULL, lag, hidden_size = 32, num_layers = 1, lr = 0.001, dropout = 0.0, epochs = 500, patience = 30, tune_epochs = 50, batch_size = 16, lambda_list = seq(0.1, 0.9, 0.1), hidden_grid = c(8, 16, 24, 32, 40), layers_grid = c(1, 2), lr_grid = c(0.001, 0.003, 0.005), dropout_grid = c(0.0, 0.1, 0.2, 0.3), verbose = TRUE, seed = 42L )
data |
Numeric data vector. ALL observations used for training. GINN internally computes returns and conditional variance. |
mode |
|
ar_max_lag |
Max AR lag to search (auto mode). Default 5. |
ar_lag |
Fixed AR lag (manual mode). NULL = auto. |
garch_p_grid |
ARCH order grid (auto mode). Default 1:3. |
garch_q_grid |
GARCH order grid (auto mode). Default 1:3. |
garch_mean_grid |
Mean model candidates for grid search: "constant" (intercept only), "ar" (AR(p) mean), "arma" (ARMA mean). Default c("constant", "ar", "arma"). |
garch_dist_grid |
Error distribution candidates: "norm" (normal), "std" (Student-t), "ged" (GED), "snorm" (skew-normal), "sstd" (skew-t), "sged" (skew-GED). Default includes all six. |
garch_p |
Fixed ARCH order (manual mode). |
garch_q |
Fixed GARCH order (manual mode). |
garch_mean |
Fixed mean model (manual mode): "constant", "ar", or "arma". |
garch_dist |
Fixed distribution (manual mode). |
lag |
Variance sequence look-back length. COMPULSORY – no default. User must specify. Example: lag = 5 uses the last 5 variance values to predict the next one. |
|
LSTM hidden units (manual mode). Default 32. |
|
num_layers |
Stacked LSTM layers (manual mode). Default 1. |
lr |
Learning rate (manual mode). Default 0.001. |
dropout |
Dropout rate (manual mode). Default 0.0. |
epochs |
Max training epochs. Default 500. |
patience |
Early-stopping patience. Default 30. |
tune_epochs |
Epochs per grid-search combo (auto mode). Default 50. |
batch_size |
Mini-batch size. Default 16. |
lambda_list |
Lambda values to test. Default seq(0.1, 0.9, 0.1). lambda = 0.0: Standard LSTM (GT variance only, no GARCH guidance). lambda = 0.5: Balanced GINN (equal GT and GARCH weight). lambda = 1.0: GINN-1 (GARCH variance only). |
|
Hidden-unit candidates (auto mode). |
|
layers_grid |
Layer-count candidates (auto mode). |
lr_grid |
Learning-rate candidates (auto mode). |
dropout_grid |
Dropout candidates (auto mode). |
verbose |
Print progress messages. Default TRUE. |
seed |
Random seed. Default 42. |
S3 object of class "GINN" with elements:
Named list per lambda. Each entry contains: tr_rmse, tr_mae, tr_r2, tr_preds, tr_actuals (all in VARIANCE units), val_rmse, loss_hist, time, lambda, model.
Complete best lambda summary: lambda, hyperparams, ar_lag, garch_config, metrics table, predictions (Actual_Var vs Predicted_Var data frame), loss_history, model object, train_time.
GARCH config, sigma2_all (full in-sample variance), coefs.
AR lag and fitted mean returns mu_all.
Full return series r_t.
Ground-truth variance sigma2_t (all observations).
Best LSTM hyperparameters found.
Settings: lag, lambda_list, mode, best_lam_nm, selection = "Train RMSE".
prices, N, N_ret, var_scaler, last_var_seq_sc, lag, mu_all.
prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 # -- Fast manual mode (fixed GARCH order + small LSTM) ------- fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.5), verbose = FALSE) print(fit) summary(fit) # -- Forecast 12 steps of conditional variance -------------- fc <- predictGINN(fit, h = 12) print(fc) # Volatility = sqrt(variance) sqrt(fc$forecast_var) # -- Diagnostic plots --------------------------------------- plotGINN(fit, h = 12) # actual + predicted + forecast plotlambda(fit) # Training RMSE by lambda # -- Best model details ------------------------------------- fit$best_model fit$best_model$lambda fit$best_model$hyperparams fit$best_model$metrics fit$best_model$predictions # Actual_Var vs Predicted_Var # -- Manual mode with a couple of lambdas -------------------- fit2 <- GINN( prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "ar", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, dropout = 0.0, lambda_list = c(0.3, 0.7), verbose = FALSE ) summary(fit2) # -- Accuracy vs actual future prices ---------------------- new_prices <- tail(prices, 1) * cumprod(1 + rnorm(12, 0.001, 0.02)) AccuracyGINN(fit, actual = new_prices) # Plot with actual future variance overlaid in green plotGINN(fit, h = 12, actual = new_prices)prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 # -- Fast manual mode (fixed GARCH order + small LSTM) ------- fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.5), verbose = FALSE) print(fit) summary(fit) # -- Forecast 12 steps of conditional variance -------------- fc <- predictGINN(fit, h = 12) print(fc) # Volatility = sqrt(variance) sqrt(fc$forecast_var) # -- Diagnostic plots --------------------------------------- plotGINN(fit, h = 12) # actual + predicted + forecast plotlambda(fit) # Training RMSE by lambda # -- Best model details ------------------------------------- fit$best_model fit$best_model$lambda fit$best_model$hyperparams fit$best_model$metrics fit$best_model$predictions # Actual_Var vs Predicted_Var # -- Manual mode with a couple of lambdas -------------------- fit2 <- GINN( prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "ar", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, dropout = 0.0, lambda_list = c(0.3, 0.7), verbose = FALSE ) summary(fit2) # -- Accuracy vs actual future prices ---------------------- new_prices <- tail(prices, 1) * cumprod(1 + rnorm(12, 0.001, 0.02)) AccuracyGINN(fit, actual = new_prices) # Plot with actual future variance overlaid in green plotGINN(fit, h = 12, actual = new_prices)
Plots conditional variance (volatility) in one chart showing:
Actual realized variance sigma2_t on full training data (dark line).
GINN in-sample predicted variance (blue line).
Out-of-sample variance forecast (red solid line).
If actual future prices are provided, their realized variance is computed and overlaid as a green line for direct comparison with the red forecast.
plotGINN(fit, h = 12, lambda = NULL, actual = NULL, newdata = NULL, n_hist = NULL)plotGINN(fit, h = 12, lambda = NULL, actual = NULL, newdata = NULL, n_hist = NULL)
fit |
A GINN object returned by GINN(). |
h |
Forecast horizon (steps ahead in variance space). Default 12. |
lambda |
Lambda to use. NULL = best lambda. |
actual |
Optional numeric vector of actual future PRICES. Their variance is computed and overlaid in green. |
newdata |
Optional new prices to update starting sequence. |
n_hist |
Number of historical variance points to show. NULL = all. |
A ggplot2 object (invisibly).
prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.5), verbose = FALSE) # Plot variance forecast only plotGINN(fit, h = 12) # Show last 50 historical variance points only plotGINN(fit, h = 12, n_hist = 50) # With actual future prices overlaid (green = actual variance) new_prices <- tail(prices, 1) * cumprod(1 + rnorm(12, 0.001, 0.02)) plotGINN(fit, h = 12, actual = new_prices)prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.5), verbose = FALSE) # Plot variance forecast only plotGINN(fit, h = 12) # Show last 50 historical variance points only plotGINN(fit, h = 12, n_hist = 50) # With actual future prices overlaid (green = actual variance) new_prices <- tail(prices, 1) * cumprod(1 + rnorm(12, 0.001, 0.02)) plotGINN(fit, h = 12, actual = new_prices)
Plot Training RMSE Across Lambda Values (GINN)
plotlambda(fit)plotlambda(fit)
fit |
A GINN object. |
ggplot2 object (invisibly).
Forecasts future conditional variance (volatility) h steps ahead using recursive one-step prediction on variance sequences. Returns single-point forecasts only.
predictGINN(fit, h = 1, lambda = NULL, newdata = NULL)predictGINN(fit, h = 1, lambda = NULL, newdata = NULL)
fit |
A GINN object returned by GINN(). |
h |
Number of steps to forecast ahead. Default 1. |
lambda |
Lambda value to use. NULL = best (lowest Train RMSE). Use 0.0 for Standard LSTM (GT variance only, no GARCH guidance). |
newdata |
Optional numeric vector of new DATA that arrived after training. Their variance is computed and used to update the starting sequence before forecasting. |
Data frame with columns:
Forecast horizon step (1, 2, ..., h).
Time index in return space (N_ret + 1, ..., N_ret + h).
Point forecast of conditional variance sigma2_t.
prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.3, 0.7), verbose = FALSE) # Forecast 12 steps of conditional variance fc <- predictGINN(fit, h = 12) print(fc) # Volatility = sqrt(variance) sqrt(fc$forecast_var) # Specific lambda fc3 <- predictGINN(fit, h = 6, lambda = 0.3) # With new prices to update starting sequence new_prices <- tail(prices, 1) * cumprod(1 + rnorm(5, 0.001, 0.02)) fc_new <- predictGINN(fit, h = 12, newdata = new_prices)prices <- cumprod(1 + rnorm(60, 0.001, 0.02)) * 100 fit <- GINN(prices, mode = "manual", lag = 5, ar_lag = 1, garch_p = 1, garch_q = 1, garch_mean = "constant", garch_dist = "norm", hidden_size = 4, num_layers = 1, lr = 0.01, epochs = 20, patience = 5, lambda_list = c(0.3, 0.7), verbose = FALSE) # Forecast 12 steps of conditional variance fc <- predictGINN(fit, h = 12) print(fc) # Volatility = sqrt(variance) sqrt(fc$forecast_var) # Specific lambda fc3 <- predictGINN(fit, h = 6, lambda = 0.3) # With new prices to update starting sequence new_prices <- tail(prices, 1) * cumprod(1 + rnorm(5, 0.001, 0.02)) fc_new <- predictGINN(fit, h = 12, newdata = new_prices)