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)
An R package implementing the GARCH-Informed Neural Network (GINN) hybrid framework for volatility (variance) forecasting.
Step 1: y_t → r_t = (y_t - y_{t-1}) / y_{t-1} [returns]
Step 2: AR(p) on r_t → μ̂_t [mean return]
Step 3: GARCH(p,q) on r_t → σ²̂_GARCH [conditional variance]
Step 4: σ²_t = (r_t - μ̂_t)² [ground-truth variance]
Step 5: LSTM trained on σ²_t sequences with GINN loss:
Loss = λ × MSE(σ²_t, σ²̂_LSTM) [vs ground truth]
+ (1-λ) × MSE(σ²̂_GARCH, σ²̂_LSTM) [vs GARCH — Eq. 17]
Lambda interpretation:
λ = 1.0 → Standard LSTM (ground-truth variance only, no GARCH guidance)λ = 0.5 → Balanced GINN (equal weight)λ = 0.0 → GINN-0 (LSTM only learns from GARCH)install.packages(c("torch", "rugarch", "ggplot2", "cli", "coro", "devtools"))
torch::install_torch() # one-time ~500 MB download
devtools::install("path/to/GINN")
library(GINN)
# Simulated prices
prices <- cumprod(1 + rnorm(150, 0.001, 0.02)) * 100
# ── AUTO mode ─────────────────────────────────────────────────
# AR lag, GARCH config, LSTM hyperparameters all auto-selected
result <- GINN(
data = prices,
mode = "auto",
seq_len = 5,
lambda_list = seq(0.1, 0.9, 0.1)
)
print(result)
plotGINN(result, h = 12)
predictGINN(result, h = 12)
plotlambda(result)
accuracy.GINN(result) # this one stays # full metrics table
# ── MANUAL mode ───────────────────────────────────────────────
result_m <- GINN(
data = prices,
mode = "manual",
ar_lag = 1,
garch_p = 1, garch_q = 1,
garch_mean = "zero", garch_dist = "norm",
seq_len = 5, hidden_size = 32,
num_layers = 1, lr = 0.001, dropout = 0.0,
lambda_list = c(0.1, 0.5, 0.9)
)
print(result_m)
| Parameter | Default | Description |
|---|---|---|
data |
required | Numeric price/yield vector |
mode |
"auto" |
"auto" or "manual" |
ar_max_lag |
5 |
Max AR lag to search (auto) |
ar_lag |
NULL |
Fixed AR lag (manual) |
garch_p_grid |
1:3 |
ARCH order grid (auto) |
garch_q_grid |
1:3 |
GARCH order grid (auto) |
garch_mean_grid |
c("zero","arma") |
Mean model grid (auto) |
garch_dist_grid |
c("norm","std") |
Distribution grid (auto) |
garch_p/q/mean/dist |
NULL |
Fixed GARCH config (manual) |
seq_len |
5 |
Variance sequence length |
hidden_size |
32 |
LSTM hidden units (manual) |
num_layers |
1 |
Stacked layers (manual) |
lr |
0.001 |
Learning rate (manual) |
dropout |
0.0 |
Dropout rate (manual) |
epochs |
500 |
Max training epochs |
patience |
30 |
Early-stopping patience |
tune_epochs |
50 |
Epochs per grid combo (auto) |
batch_size |
16 |
Mini-batch size |
lambda_list |
0.1…0.9 |
Lambda values to test |
hidden_grid |
c(8,16,24,32,40) |
Hidden grid (auto) |
layers_grid |
c(1,2) |
Layers grid (auto) |
lr_grid |
c(0.001,0.003,0.005) |
LR grid (auto) |
dropout_grid |
c(0.0,0.1,0.2,0.3) |
Dropout grid (auto) |
GINN class)result$results # list — one per lambda (RMSE, MAE, R², preds, loss_hist)
result$garch # GARCH config, coefs, sigma²_train/test, metrics
result$ar # AR lag, fitted values, forecast
result$returns # computed return series r_t
result$variance # ground-truth variance σ²_t (train/test/all)
result$best_model # complete best model summary
result$best_hp # best LSTM hyperparameters
result$tuning_log # grid-search log (auto mode)
result$meta # settings
result$data_info # original data, split, N
print(result) # compact table (all lambdas)
summary(result) # full data frame with all metrics
plotGINN(result, h = 12) # actual + predicted + forecast
plotGINN(result, h = 12, actual = new_prices) # with actual future data
plotlambda(result) # Training RMSE bar chart
predictGINN(result, h = 12) # variance forecast
AccuracyGINN(result, actual = new_prices) # accuracy vs actual
plot(result, "loss") # training loss (unchanged)
accuracy.GINN(result) # accuracy data frame
torch — LSTM (native R, no Python required)rugarch — GARCH fittingggplot2 — plotscli — progress messagescoro — dataloader iteration