--- title: "A new innovation distribution: bimodal GEV from the bgev package" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{A new innovation distribution: bimodal GEV from the bgev package} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") has_bgev <- requireNamespace("bgev", quietly = TRUE) knitr::opts_chunk$set(eval = has_bgev) library(GEVStableGarch) ``` The built-in innovations (stable, GEV, GAt) are created with the same constructor, `gs_dist()`, that is offered to users. This vignette shows the point of that design: a distribution from **another CRAN package** is dropped in as the innovation of an ARMA-GARCH/APARCH model and immediately gets the full machinery — estimation, simulation, stationarity and forecasting — with no changes to the package. We use the **bimodal GEV** (BGEV) of Otiniano, Oliveira, Vila and Dias (2025), provided by the CRAN package [`bgev`](https://CRAN.R-project.org/package=bgev). It extends the GEV with a second shape parameter `delta` that reshapes the body and the tail. ```{r check-bgev, eval = !has_bgev, echo = FALSE, results = "asis"} cat("> The `bgev` package is not installed, so the code below is shown but not run.\n") ``` ## Wrapping bgev as an innovation distribution An innovation is standardized to location 0 and scale 1; the conditional scale `sigma_t` of the model carries the scale. So we fix bgev's `mu = 0`, `sigma = 1` and expose its two shape parameters. The only bookkeeping: * bgev's second shape is called `delta`, which would clash with the APARCH power `delta`, so we rename the innovation parameters `bgev_xi` and `bgev_delta`; * `max_power` must say where moments stop existing. The BGEV paper (Remark 1) gives, for `xi > 0`, `E|z|^k < Inf` if and only if `k < (delta + 1) / xi`; * `mean` must return `E[z]`. BGEV is not centered, exactly like the built-in GEV, and the machinery accounts for it; we compute the mean numerically. ```{r build} bgev_mean <- function(xi, delta) integrate(function(p) bgev::qbgev(p, 0, 1, xi, delta), 0, 1, subdivisions = 400L, rel.tol = 1e-6)$value bgev_innov <- gs_dist( name = "bgev", log_density = function(z, par) { d <- bgev::dbgev(z, 0, 1, par[["bgev_xi"]], par[["bgev_delta"]]) ifelse(d > 0, log(d), -Inf) }, random = function(n, par) bgev::rbgev(n, 0, 1, par[["bgev_xi"]], par[["bgev_delta"]]), cdf = function(q, par) bgev::pbgev(q, 0, 1, par[["bgev_xi"]], par[["bgev_delta"]]), quantile = function(p, par) bgev::qbgev(p, 0, 1, par[["bgev_xi"]], par[["bgev_delta"]]), par_names = c("bgev_xi", "bgev_delta"), lower = c(bgev_xi = 0.02, bgev_delta = 0.5), upper = c(bgev_xi = 0.45, bgev_delta = 5), start = c(bgev_xi = 0.20, bgev_delta = 2), default_delta = 2, # GARCH mode: variance recursion max_power = function(par) (par[["bgev_delta"]] + 1) / par[["bgev_xi"]], mean = function(par) bgev_mean(par[["bgev_xi"]], par[["bgev_delta"]])) ``` The constructor runs a self-check so that a distribution that declares something inconsistent (densities that do not integrate to one, a wrong `max_power`, a `quantile` that does not invert the `cdf`, ...) fails loudly rather than silently biasing the fit: ```{r check} gs_check_dist(bgev_innov) ``` ## Fit, simulate, forecast From here it is an ordinary model. We simulate a GARCH(1,1) with BGEV innovations and estimate it back. ```{r fit} spec <- gs_spec(garch = c(1, 1), dist = bgev_innov) model <- gs_model(spec, mu = 0, omega = 0.1, alpha = 0.1, beta = 0.6, dist_par = c(bgev_xi = 0.1, bgev_delta = 4)) gs_stationarity(model) x <- gs_sim(model, n = 3000, seed = 7)$y fit <- gs_fit(x, spec) fit ``` The estimates sit close to the true values (`omega = 0.1`, `alpha1 = 0.1`, `beta1 = 0.6`, `bgev_xi = 0.1`, `bgev_delta = 4`) — note the shape parameters are recovered from the default start `(bgev_xi = 0.2, bgev_delta = 2)`, far from the truth. Forecasting and Value-at-Risk work unchanged; with a non-centered innovation the conditional mean uses the declared `mean`: ```{r forecast} predict(fit, n_ahead = 5, level = c(0.01, 0.05), n_sim = 5000, seed = 1) ``` And the probability integral transform of the residuals is uniform under a correct model, which is the usual goodness-of-fit check: ```{r pit} u <- residuals(fit, type = "pit") round(quantile(u, c(0.1, 0.5, 0.9)), 3) ``` That is the whole contribution on display: the pluggable interface turns any documented density — here a third party's, reused rather than reimplemented — into a fully functional heavy-tailed ARMA-GARCH/APARCH model. ## Reference Otiniano, C. E. G., Oliveira, B. S., Vila, R., Dias, M. (2025). *The bimodal GEV distribution*. (CRAN package `bgev`.)