Nothing
#' @importFrom stats qnorm pchisq var dnorm pnorm pt
NULL
#' @title Estimate Forecast Variance via Rolling Window
#'
#' @description Estimates forecast variance from historical forecast errors relative to
#' the benchmark using a rolling window. Used to approximate the time-varying predictive
#' standard deviation for each competing model forecast, required by \code{\link{compute_klic}},
#' \code{\link{compute_zp}}, and \code{\link{compute_kupiec}}.
#'
#' @param forecast_matrix \code{\link[base]{matrix}} of dimension \code{P x K_total},
#' where \code{P} is the number of forecast periods and \code{K_total} is the total
#' number of columns (competing forecasts plus the benchmark).
#' @param benchmark_col Index or name of the benchmark column. Defaults to the last
#' column.
#' @param window_size \code{\link[base]{integer}} rolling window size. For the first
#' \code{window_size} periods, the full available history is used instead (expanding
#' window). From period \code{window_size + 1} onwards, a rolling window of exactly
#' \code{window_size} observations is used.
#'
#' @details
#' For each competing forecast \code{k} and period \code{t}, the forecast error is defined
#' as \eqn{e_{t,k} = \text{benchmark}_t - \hat{y}_{t,k}}. The variance of these errors
#' is estimated over a rolling window:
#' \itemize{
#' \item If \code{t < window_size}: variance is computed over observations
#' \code{1:t} (expanding window).
#' \item If \code{t >= window_size}: variance is computed over observations
#' \code{max(1, t - window_size):t} (rolling window of size \code{window_size}).
#' }
#' For \code{t = 1} the variance of a single observation is undefined (\code{NA}).
#' Estimated variances that are \code{NA}, zero, or negative are replaced by
#' \code{1e-6} to ensure numerical stability in downstream computations.
#' The benchmark column in the returned matrix is set to zero throughout.
#'
#' @return \code{\link[base]{matrix}} of dimension \code{P x K_total} containing
#' estimated variances. Columns correspond to the same forecasts as
#' \code{forecast_matrix}; the benchmark column contains zeros.
#'
#' @seealso \code{\link{compute_klic}}, \code{\link{compute_zp}},
#' \code{\link{compute_kupiec}}
#'
#' @examples
#' data(metals)
#' forecast_variance <- estimate_forecast_variance(metals, benchmark_col = 15,
#' window_size = 20)
#' head(forecast_variance)
#' @export
estimate_forecast_variance <- function(forecast_matrix,
benchmark_col = ncol(forecast_matrix),
window_size = 20) {
if (is.character(benchmark_col))
benchmark_col <- which(colnames(forecast_matrix) == benchmark_col)
if (length(benchmark_col) == 0)
stop("The specified benchmark_col was not found in the matrix.")
P <- nrow(forecast_matrix)
K_total <- ncol(forecast_matrix)
model_cols <- setdiff(seq_len(K_total), benchmark_col)
K_models <- length(model_cols)
benchmark <- forecast_matrix[, benchmark_col]
forecast_variance <- matrix(0, P, K_total)
colnames(forecast_variance) <- colnames(forecast_matrix)
for (i in seq_len(K_models)) {
k <- model_cols[i]
errors <- benchmark - forecast_matrix[, k]
for (t in seq_len(P)) {
if (t < window_size) {
forecast_variance[t, k] <- var(errors[1:t], na.rm = TRUE)
} else {
start_idx <- max(1L, t - window_size)
forecast_variance[t, k] <- var(errors[start_idx:t], na.rm = TRUE)
}
}
}
for (i in seq_len(K_models)) {
k <- model_cols[i]
forecast_variance[
is.na(forecast_variance[, k]) | forecast_variance[, k] <= 0, k
] <- 1e-6
}
return(forecast_variance)
}
#' @title Value-at-Risk (VaR) Unconditional Coverage Test (Kupiec)
#'
#' @description Performs Kupiec's (1995) Unconditional Coverage (UC) test for evaluating
#' Value-at-Risk (VaR) forecasts from competing forecast against realized values.
#'
#' \strong{Hypotheses:}
#' \itemize{
#' \item \strong{H0:} The forecast correctly captures VaR — violations occur with the
#' expected frequency \code{alpha}.
#' \item \strong{H1:} The forecast fails to correctly capture VaR — the observed
#' frequency of violations differs significantly from \code{alpha}.
#' }
#'
#' @param forecast_matrix \code{\link[base]{matrix}} of dimension \code{P x K_total}.
#' Columns contain point forecasts for each model; the benchmark column supplies
#' the realized values.
#' @param forecast_sd_models \code{\link[base]{matrix}} of dimension \code{P x K},
#' where \code{K = K_total - 1}. Contains time-varying forecast standard deviations,
#' typically from \code{\link{estimate_forecast_variance}}.
#' @param benchmark_col Index or name of the benchmark column. Defaults to the last
#' column.
#' @param alpha \code{\link[base]{numeric}} VaR significance level (e.g.,
#' \code{0.05} for 95\% VaR). A violation occurs when the realized value falls below
#' the estimated VaR.
#'
#' @details
#' For each competing forecast \code{k}, the VaR at level \code{alpha} is:
#' \deqn{VaR_{t,k} = \hat{y}_{t,k} + \Phi^{-1}(\alpha) \cdot \hat{\sigma}_{t,k}}
#' where \eqn{\Phi^{-1}} is the standard normal quantile function. A violation occurs
#' when the realized value falls below \eqn{VaR_{t,k}}. The likelihood-ratio statistic
#' \eqn{LR_{UC}} follows a \eqn{\chi^2(1)} distribution under H0 (Kupiec, 1995).
#' \strong{Failing to reject H0} (large p-value) indicates correctly calibrated VaR;
#' \strong{rejecting H0} (small p-value) indicates the forecast under- or over-estimates
#' tail risk.
#'
#' @return A named list (one element per competing forecast) of \code{htest} objects,
#' each containing:
#' \describe{
#' \item{\code{statistic}}{The LR-UC test statistic (\eqn{\chi^2}-distributed under
#' H0).}
#' \item{\code{p.value}}{P-value from the \eqn{\chi^2(1)} distribution. A large
#' p-value indicates correctly calibrated VaR coverage.}
#' \item{\code{actual_exceedances}}{Observed number of VaR violations.}
#' \item{\code{expected}}{Expected number of violations
#' (\code{P * alpha}).}
#' }
#'
#' @seealso \code{\link{estimate_forecast_variance}},
#' \code{\link{run_comprehensive_erc_analysis}}
#'
#' @references
#' Kupiec, P. H. (1995). Techniques for Verifying the Accuracy of Risk Measurement
#' Models. \emph{The Journal of Derivatives}, 3(2), 173--184.
#' \doi{10.3905/jod.1995.407942}
#'
#' @examples
#' data(metals)
#' benchmark_col <- 15
#' K_total <- ncol(metals)
#' comp_cols <- setdiff(seq_len(K_total), benchmark_col)
#' forecast_variance <- estimate_forecast_variance(metals,
#' benchmark_col = benchmark_col, window_size = 20)
#' forecast_sd_models <- sqrt(forecast_variance[, comp_cols])
#' coverage_results <- compute_kupiec(metals, forecast_sd_models,
#' benchmark_col = benchmark_col, alpha = 0.05)
#' print(coverage_results[[1]])
#' @export
compute_kupiec <- function(forecast_matrix, forecast_sd_models,
benchmark_col = ncol(forecast_matrix),
alpha = 0.05) {
P <- nrow(forecast_matrix)
K_total <- ncol(forecast_matrix)
model_cols <- setdiff(seq_len(K_total), benchmark_col)
K_models <- length(model_cols)
if (ncol(forecast_sd_models) != K_models)
stop("forecast_sd_models must have K = K_total - 1 columns (one per competing model).")
benchmark <- forecast_matrix[, benchmark_col]
VaR_matrix <- forecast_matrix
for (i in seq_len(K_models)) {
k <- model_cols[i]
VaR_matrix[, k] <- forecast_matrix[, k] +
qnorm(alpha) * forecast_sd_models[, i]
}
kupiec_results <- list()
for (i in seq_len(K_models)) {
k <- model_cols[i]
violations <- sum(benchmark < VaR_matrix[, k], na.rm = TRUE)
expected <- P * alpha
p_hat <- violations / P
if (p_hat == 0) p_hat <- 1e-10
if (p_hat == 1) p_hat <- 1 - 1e-10
LR_UC_stat <- -2 * log(
(alpha^violations * (1 - alpha)^(P - violations)) /
(p_hat^violations * (1 - p_hat)^(P - violations))
)
p_value_UC <- 1 - pchisq(LR_UC_stat, df = 1)
model_name <- colnames(forecast_matrix)[k]
res <- list(
statistic = c("LR-UC" = LR_UC_stat),
p.value = p_value_UC,
method = paste("Kupiec UC Test for forecast:", model_name),
data.name = paste0("Benchmark: column ", benchmark_col,
if (!is.null(colnames(forecast_matrix)))
paste0(" (", colnames(forecast_matrix)[benchmark_col],
")")
else ""),
null.value = c("expected violation rate" = alpha),
alternative = "violation rate differs from expected",
actual_exceedances = violations,
expected = expected
)
class(res) <- "htest"
kupiec_results[[model_name]] <- res
}
return(kupiec_results)
}
#' @title Compute Kullback-Leibler Information Criterion (KLIC) Negative Log-Likelihood Scores
#'
#' @description Computes the per-period Negative Log-Likelihood Score (NLS) loss matrix
#' under a Gaussian predictive density assumption. The NLS is the loss function
#' corresponding to minimisation of the Kullback-Leibler Information Criterion (KLIC)
#' distance from the true density (Corradi & Swanson, 2006).
#'
#' @param forecast_matrix \code{\link[base]{matrix}} of dimension \code{P x K_total}.
#' The benchmark column supplies the realized values \eqn{y_t}.
#' @param forecast_sd_models \code{\link[base]{matrix}} of dimension
#' \code{P x (K_total - 1)}, containing time-varying forecast standard deviations,
#' typically from \code{\link{estimate_forecast_variance}}.
#' @param benchmark_col Index or name of the benchmark column. Defaults to the last
#' column.
#'
#' @details
#' For each competing forecast \code{k} and period \code{t}:
#' \deqn{NLS_{t,k} = -\log \phi(y_t \mid \hat{y}_{t,k},\, \hat{\sigma}_{t,k})}
#' where \eqn{\phi} denotes the Gaussian density, \eqn{y_t} is the realized value,
#' \eqn{\hat{y}_{t,k}} is the point forecast, and \eqn{\hat{\sigma}_{t,k}} is the
#' forecast standard deviation. Minimising the average NLS is equivalent to minimising
#' the KLIC distance between the forecast's predictive density and the true density
#' (Corradi & Swanson, 2006). \strong{Lower NLS values are better.} The benchmark
#' column in the returned matrix is set to zero.
#'
#' @return \code{\link[base]{matrix}} of dimension \code{P x K_total} containing NLS
#' values. Lower values indicate better density forecast accuracy. The benchmark
#' column is set to zero.
#'
#' @seealso \code{\link{kullback_leibler_test}},
#' \code{\link{estimate_forecast_variance}}
#'
#' @references
#' Corradi, V., & Swanson, N. R. (2006). Predictive density and conditional confidence
#' interval accuracy tests. \emph{Journal of Econometrics}, 135(1--2), 187--228.
#' \doi{10.1016/j.jeconom.2005.07.026}
#'
#' Corradi, V., & Swanson, N. R. (2011). The White Reality Check and some of its
#' recent extensions. In \emph{Festschrift in honor of Halbert L. White}.
#'
#' @examples
#' data(metals)
#' benchmark_col <- 15
#' K_total <- ncol(metals)
#' comp_cols <- setdiff(seq_len(K_total), benchmark_col)
#' forecast_variance <- estimate_forecast_variance(metals,
#' benchmark_col = benchmark_col)
#' forecast_sd_models <- sqrt(forecast_variance[, comp_cols])
#' klic_loss <- compute_klic(metals, forecast_sd_models,
#' benchmark_col = benchmark_col)
#' head(klic_loss)
#' @export
compute_klic <- function(forecast_matrix, forecast_sd_models,
benchmark_col = ncol(forecast_matrix)) {
P <- nrow(forecast_matrix)
K_total <- ncol(forecast_matrix)
model_cols <- setdiff(seq_len(K_total), benchmark_col)
K_models <- length(model_cols)
if (ncol(forecast_sd_models) != K_models)
stop("forecast_sd_models must have K = K_total - 1 columns (one per competing model forecast).")
benchmark <- forecast_matrix[, benchmark_col]
nls_loss <- matrix(NA, P, K_total)
colnames(nls_loss) <- colnames(forecast_matrix)
for (i in seq_len(K_models)) {
k <- model_cols[i]
nls_loss[, k] <- -dnorm(benchmark,
mean = forecast_matrix[, k],
sd = forecast_sd_models[, i],
log = TRUE)
}
nls_loss[, benchmark_col] <- 0
return(nls_loss)
}
#' @title Compute ZP Quantile Loss
#'
#' @description Computes the per-period ZP quantile loss matrix based on the squared
#' difference between the indicator of a tail event and the forecast's predicted
#' probability of that event (Corradi & Swanson, 2006, eq. 7).
#'
#' @param forecast_matrix \code{\link[base]{matrix}} of dimension \code{P x K_total}.
#' The benchmark column supplies the realized values \eqn{y_t}.
#' @param forecast_sd_models \code{\link[base]{matrix}} of dimension \code{P x K},
#' where \code{K = K_total - 1}. Contains time-varying forecast standard deviations,
#' typically from \code{\link{estimate_forecast_variance}}.
#' @param threshold \code{\link[base]{numeric}} tail threshold \eqn{\tau}. The ZP loss
#' measures how well each model predicts the probability of \eqn{y_t \leq \tau}.
#' Typically set to a low quantile of the realized series, e.g.,
#' \code{quantile(realized, 0.05)} for the 5th-percentile left tail.
#' In \code{\link{run_comprehensive_erc_analysis}} this is computed automatically
#' as \code{quantile(realizations, zp_quantile)}.
#' @param benchmark_col Index or name of the benchmark column. Defaults to the last
#' column.
#'
#' @details
#' For each competing forecast \code{k} and period \code{t}:
#' \deqn{ZP_{t,k} = \left(\mathbf{1}(y_t \leq \tau) -
#' \Phi\!\left(\frac{\tau - \hat{y}_{t,k}}{\hat{\sigma}_{t,k}}\right)\right)^2}
#' where \eqn{y_t} is the realized value, \eqn{\tau} is the threshold,
#' \eqn{\hat{y}_{t,k}} is the point forecast, and \eqn{\hat{\sigma}_{t,k}} is the
#' forecast standard deviation. \strong{Lower ZP values are better.}
#'
#' Choosing \eqn{\tau} at the 5th percentile focuses evaluation on whether forecasts
#' correctly predict the risk of falling into the worst 5\% of outcomes. The benchmark
#' column is assigned a point-mass predictive distribution (\eqn{\hat{\sigma} = 10^{-6}}),
#' which approximates the Brier score for the tail indicator and serves as a conservative
#' reference. When the benchmark is the Historical Average (HA), the ZP test thus
#' evaluates whether any competing forecast's calibrated tail probability outperforms the
#' HA's point prediction of the tail event.
#'
#' @return \code{\link[base]{matrix}} of dimension \code{P x K_total} containing ZP
#' loss values. Lower values indicate better left-tail probability calibration.
#' The benchmark column uses \eqn{\hat{\sigma} = 10^{-6}}.
#'
#' @seealso \code{\link{reality_check_zp_test}},
#' \code{\link{estimate_forecast_variance}}
#'
#' @references
#' Corradi, V., & Swanson, N. R. (2006). Predictive density and conditional confidence
#' interval accuracy tests. \emph{Journal of Econometrics}, 135(1--2), 187--228.
#' \doi{10.1016/j.jeconom.2005.07.026}
#'
#' Corradi, V., & Swanson, N. R. (2011). The White Reality Check and some of its
#' recent extensions. In \emph{Festschrift in honor of Halbert L. White}.
#'
#' @examples
#' data(metals)
#' benchmark_col <- 15
#' K_total <- ncol(metals)
#' comp_cols <- setdiff(seq_len(K_total), benchmark_col)
#' forecast_variance <- estimate_forecast_variance(metals,
#' benchmark_col = benchmark_col)
#' forecast_sd_models <- sqrt(forecast_variance[, comp_cols])
#' threshold_val <- quantile(metals[, benchmark_col], 0.10)
#' zp_loss <- compute_zp(metals, forecast_sd_models,
#' threshold = threshold_val,
#' benchmark_col = benchmark_col)
#' head(zp_loss)
#' @export
compute_zp <- function(forecast_matrix, forecast_sd_models, threshold,
benchmark_col = ncol(forecast_matrix)) {
P <- nrow(forecast_matrix)
K_total <- ncol(forecast_matrix)
model_cols <- setdiff(seq_len(K_total), benchmark_col)
K_models <- length(model_cols)
if (ncol(forecast_sd_models) != K_models)
stop("forecast_sd_models must have K = K_total - 1 columns (one per competing model).")
benchmark <- forecast_matrix[, benchmark_col]
zp_loss <- matrix(NA, P, K_total)
colnames(zp_loss) <- colnames(forecast_matrix)
for (t in seq_len(P)) {
actual_violation <- as.numeric(benchmark[t] <= threshold)
for (i in seq_len(K_models)) {
k <- model_cols[i]
prob_k <- pnorm(threshold,
mean = forecast_matrix[t, k],
sd = forecast_sd_models[t, i])
zp_loss[t, k] <- (actual_violation - prob_k)^2
}
prob_benchmark <- pnorm(threshold,
mean = forecast_matrix[t, benchmark_col],
sd = 1e-6)
zp_loss[t, benchmark_col] <- (actual_violation - prob_benchmark)^2
}
return(zp_loss)
}
#' @title Per-Model Diebold-Mariano Test (HAC + MBB Bootstrap)
#'
#' @description Performs a Diebold-Mariano (1995) test for each competing forecast
#' separately, testing whether the predictive accuracy of that forecast differs
#' significantly from the benchmark forecast. The test statistic is the sample
#' mean loss differential standardised by a Newey-West HAC standard error;
#' p-values are provided both analytically and via Moving Block Bootstrap (MBB).
#' The direction of the alternative hypothesis is controlled by the
#' \code{H1} argument: \code{"same"} for a two-sided test (\eqn{H_1: \bar{d}_k \neq 0}),
#' \code{"more"} for the one-sided test that forecast \eqn{k} is more accurate than
#' the benchmark (\eqn{H_1: \bar{d}_k > 0}), and \code{"less"} for the one-sided
#' test that forecast \eqn{k} is less accurate than the benchmark
#' (\eqn{H_1: \bar{d}_k < 0}).
#' @details
#' For each forecast \eqn{k}, the loss differential series is:
#' \deqn{d_{t,k} = g(e_{0,t}) - g(e_{k,t})}
#' where \eqn{g(\cdot)} is the loss function used to construct \code{loss_differences}.
#' In the standard workflow of this package
#' (\code{\link{run_comprehensive_erc_analysis}}), \eqn{g} is the squared error loss:
#' \deqn{d_{t,k} = (y_t - \hat{y}_{t,0})^2 - (y_t - \hat{y}_{t,k})^2}
#' The Diebold-Mariano test statistic is:
#' \deqn{DM_k = \frac{\bar{d}_k}{\hat{SE}_{HAC,k}}}
#' For multi-step forecasts (\eqn{h > 1}), the Harvey, Leybourne & Newbold (1997)
#' small-sample correction is applied:
#' \deqn{DM^*_k = DM_k \times \sqrt{\frac{T + 1 - 2h + \frac{1}{T}h(h-1)}{T}}}
#' For \eqn{h = 1} the correction reduces to \eqn{\sqrt{(T-1)/T}}, which approaches
#' 1 as \eqn{T \to \infty}. For \eqn{h > 1} the correction inflates the statistic,
#' improving finite-sample size control. The corrected statistic \eqn{DM^*_k} is
#' compared to a \eqn{t(T-1)} distribution.
#' The analytic p-value (\code{P_Value}) uses the t-distribution with \eqn{P - 1}
#' degrees of freedom. The bootstrap p-value (\code{P_Value_Boot}) uses MBB
#' resampling (Kunsch, 1989) with recentering at the sample mean \eqn{\bar{d}_k},
#' placing the bootstrap distribution under H0. The Harvey correction is applied
#' consistently to both the analytic and bootstrap statistics. The p-values are
#' computed according to the alternative hypothesis specified by \code{H1}:
#' \subsection{Alternative Hypothesis and P-values (\code{H1})}{
#' \describe{
#' \item{\code{"same"} -- two-sided}{\eqn{p = 2 \cdot P(t_{T-1} < -|DM^*_k|)}}
#' \item{\code{"more"} -- one-sided right}{\eqn{p = P(t_{T-1} > DM^*_k)}}
#' \item{\code{"less"} -- one-sided left}{\eqn{p = P(t_{T-1} < DM^*_k)}}
#' }
#' The bootstrap analogue replaces the t-distribution probability with the
#' empirical proportion of bootstrap statistics falling in the appropriate tail.
#' }
#' where \eqn{T} follows a t-distribution with \eqn{P - 1} degrees of freedom.
#' The bootstrap analogue replaces the t-distribution tail probability with the
#' empirical proportion of bootstrap statistics falling in the appropriate tail.
#'
#' This function performs \eqn{K} individual tests and does \emph{not} control for
#' multiple comparisons. For a joint test controlling the family-wise error rate,
#' use \code{\link{white_reality_check}} or
#' \code{\link{superior_predictive_ability_test}}.
#'
#' \strong{Note on MASE:} When \code{loss_differences} are constructed from
#' Mean Absolute Scaled Errors, the scaling (division by the naive benchmark
#' MAE, i.e. \code{mean(abs(diff(realizations)))}) must be applied
#' \emph{before} passing the loss differentials to this function.
#' \code{compute_per_model_statistics} receives pre-computed loss differentials
#' and applies no internal rescaling — the caller is responsible for ensuring
#' that MASE-based \code{loss_differences} already contain scaled errors.
#' In \code{\link{run_comprehensive_erc_analysis}} this is handled automatically.
#' @param loss_differences \code{\link[base]{matrix}} of dimension \code{P x K},
#' where \code{P} is the number of forecast periods and \code{K} is the number of
#' competing forecasts. Each column \eqn{k} contains the loss differential series
#' \eqn{d_{t,k} = g(e_{0,t}) - g(e_{k,t})} for a generic loss function \eqn{g}.
#' In the standard workflow of this package
#' (\code{\link{run_comprehensive_erc_analysis}}), \eqn{g} is the squared error
#' loss, so
#' \eqn{d_{t,k} = (y_t - \hat{y}_{t,0})^2 - (y_t - \hat{y}_{t,k})^2}.
#' A positive value of \eqn{d_{t,k}} means the forecast from model \eqn{k}
#' is more accurate than the benchmark forecast in period \eqn{t}.
#' @param model_names \code{\link[base]{character}} vector of length \code{K} with
#' names of the competing model forecasts.
#' @param block_length \code{\link[base]{integer}} block length for HAC and MBB.
#' Rule of thumb: \eqn{T^{1/3}}; for \code{P = 165} approximately 5--6.
#' Default is \code{5}.
#' @param n_boot \code{\link[base]{integer}} number of MBB replications for
#' \code{P_Value_Boot}. Default \code{999}; see Davidson & MacKinnon (2000).
#' @param alpha \code{\link[base]{numeric}} significance level. Default \code{0.05}.
#' @param h \code{\link[base]{integer}} forecast horizon (number of steps ahead).
#' Default is \code{1} (one-step-ahead). Passed to the Harvey, Leybourne &
#' Newbold (1997) small-sample correction; see \emph{Details}.
#' @param H1 \code{\link[base]{character}} alternative hypothesis: \code{"same"}
#' (two-sided, default), \code{"more"} (one-sided, forecast \eqn{k} better),
#' or \code{"less"} (one-sided, forecast \eqn{k} worse). See \emph{Details}.
#' @return \code{\link[base]{data.frame}} with one row per competing model forecast:
#' \tabular{ll}{
#' \code{Model} \tab Model name. \cr
#' \code{Mean_Loss_Diff} \tab Sample mean of \eqn{d_{t,k}}. \cr
#' \code{Frac_Better_Than_Benchmark} \tab Fraction of periods where \eqn{d_{t,k} > 0}. \cr
#' \code{T_Stat} \tab Harvey-corrected DM statistic \eqn{DM^*_k}. \cr
#' \code{P_Value} \tab Analytic p-value (t-distribution, \eqn{T-1} df). \cr
#' \code{P_Value_Boot} \tab MBB bootstrap p-value. \cr
#' \code{Significant} \tab \code{TRUE} if \code{P_Value <= alpha}. \cr
#' \code{Significant_Boot} \tab \code{TRUE} if \code{P_Value_Boot <= alpha}. \cr
#' }
#' @seealso \code{\link{white_reality_check}},
#' \code{\link{superior_predictive_ability_test}},
#' \code{\link{estimate_long_run_covariance}}
#'
#' @references
#' Davidson, R., & MacKinnon, J. G. (2000).
#' Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' Diebold, F. X., & Mariano, R. S. (1995). Comparing Predictive Accuracy.
#' \emph{Journal of Business & Economic Statistics}, 13(3), 253--263.
#' \doi{10.1080/07350015.1995.10524599}
#'
#' Harvey, D., Leybourne, S., & Newbold, P. (1997). Testing the equality of
#' prediction mean squared errors. \emph{International Journal of Forecasting},
#' \emph{13}(2), 281--291. \doi{10.1016/S0169-2070(96)00719-4}
#'
#' Kunsch, H. R. (1989). The jackknife and the bootstrap for general stationary
#' observations. \emph{The Annals of Statistics}, 17(3), 1217--1241.
#' \doi{10.1214/aos/1176347265}
#'
#' Newey, W. K., & West, K. D. (1987). A Simple, Positive Semi-Definite,
#' Heteroskedasticity and Autocorrelation Consistent Covariance Matrix.
#' \emph{Econometrica}, 55(3), 703--708. \doi{10.2307/1913610}
#'
#' @examples
#' data(metals)
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1
#' # A small offset (+0.5) avoids degenerate zero loss differences (illustration only).
#' realized <- c(metals[-1, K_total], metals[P, K_total]) + 0.5
#' bench_loss <- (metals[, K_total] - realized)^2
#' model_loss <- (metals[, 1:K] - realized)^2
#' loss_differences <- bench_loss - model_loss
#' model_names <- colnames(metals)[1:K]
#' # Two-sided test (default)
#' result_df <- compute_per_model_statistics(loss_differences, model_names,
#' n_boot = 10)
#' print(result_df)
#' # One-sided test: H1 = forecast is more accurate than benchmark
#' result_more <- compute_per_model_statistics(loss_differences, model_names,
#' n_boot = 10, H1 = "more")
#' print(result_more)
#' @export
compute_per_model_statistics <- function(loss_differences, model_names,
n_boot = 999,
block_length = 5,
alpha = 0.05,
h = 1,
H1 = "same") {
H1 <- match.arg(H1, choices = c("same", "more", "less"))
P <- nrow(loss_differences)
K <- ncol(loss_differences)
per_model_results <- list()
for (k in seq_len(K)) {
model_name <- model_names[k]
loss_k <- loss_differences[, k]
mean_loss <- mean(loss_k, na.rm = TRUE)
frac_better <- mean(loss_k > 0, na.rm = TRUE)
lrc <- estimate_long_run_covariance(matrix(loss_k, ncol = 1),
block_length = block_length)
se_loss <- sqrt(as.numeric(lrc) / sum(!is.na(loss_k)))
if (!is.na(se_loss) && se_loss > 1e-9) {
T_eff <- sum(!is.na(loss_k))
DM <- mean_loss / se_loss * sqrt((T_eff + 1 - 2 * h + (1 / T_eff) * h * (h - 1)) / T_eff)
t_stat <- DM
if (H1 == "same") { p_value <- 2 * min(pt(q = DM, df = T_eff - 1, lower.tail = FALSE),
1 - pt(q = DM, df = T_eff - 1, lower.tail = FALSE)) }
if (H1 == "less") { p_value <- pt(q = DM, df = T_eff - 1, lower.tail = FALSE) }
if (H1 == "more") { p_value <- 1 - pt(q = DM, df = T_eff - 1, lower.tail = FALSE) }
} else {
t_stat <- NA
p_value <- NA
}
loss_k_mat <- matrix(loss_k, ncol = 1)
boot_t_stats <- numeric(n_boot)
for (b in seq_len(n_boot)) {
boot_sample <- mbb_resample_data(loss_k_mat, block_length)[, 1]
boot_centered <- boot_sample - mean_loss
boot_lrc <- estimate_long_run_covariance(
matrix(boot_centered, ncol = 1),
block_length = block_length)
boot_se <- sqrt(as.numeric(boot_lrc) / sum(!is.na(boot_centered)))
boot_t_stats[b] <- if (!is.na(boot_se) && boot_se > 1e-9) {
T_boot <- sum(!is.na(boot_centered))
harvey_boot <- sqrt((T_boot + 1 - 2 * h + (1 / T_boot) * h * (h - 1)) / T_boot)
((mean(boot_sample, na.rm = TRUE) - mean_loss) / boot_se) * harvey_boot
} else {
NA_real_
}
}
p_value_boot <- if (!is.na(t_stat)) {
if (H1 == "same") {
mean(abs(boot_t_stats) >= abs(t_stat), na.rm = TRUE)
} else if (H1 == "more") {
mean(boot_t_stats >= t_stat, na.rm = TRUE)
} else {
mean(boot_t_stats <= t_stat, na.rm = TRUE)
}
} else {
NA_real_
}
per_model_results[[model_name]] <- data.frame(
Model = model_name,
Mean_Loss_Diff = round(mean_loss, 6),
Frac_Better_Than_Benchmark = round(frac_better, 3),
T_Stat = round(ifelse(is.na(t_stat), NA, t_stat), 4),
P_Value = round(ifelse(is.na(p_value), NA, p_value), 4),
P_Value_Boot = round(ifelse(is.na(p_value_boot), NA, p_value_boot), 4),
Significant = ifelse(is.na(p_value), NA, p_value <= alpha),
Significant_Boot = ifelse(is.na(p_value_boot), NA, p_value_boot <= alpha),
stringsAsFactors = FALSE,
row.names = NULL
)
}
result_df <- do.call(rbind, per_model_results)
rownames(result_df) <- NULL
return(result_df)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.