Nothing
#' @importFrom stats dnorm pnorm pchisq pt quantile var sd
#' @importFrom utils head
#' @title Compute Continuous Ranked Probability Score (CRPS)
#'
#' @description Calculates the Continuous Ranked Probability Score (CRPS) using the
#' energy score (Monte Carlo) approximation for a single forecast period.
#'
#' @param forecast_density \code{\link[base]{numeric}} vector of simulated forecasts
#' (density samples) representing the predictive distribution for a single time period.
#' @param target_realization \code{\link[base]{numeric}} scalar representing the realized
#' value against which the forecast density is evaluated.
#'
#' @details
#' The CRPS is a strictly proper scoring rule that jointly rewards calibration and sharpness
#' of a probabilistic forecast. It is computed via the energy score identity:
#' \deqn{CRPS = E|X - y| - \frac{1}{2} E|X - X'|}
#' where \eqn{X, X'} are independent draws from the forecast distribution and \eqn{y} is
#' the realization. \strong{Lower values are better}: a CRPS of 0 indicates a perfect
#' point-mass forecast at the true realization.
#'
#' @return \code{\link[base]{numeric}} scalar representing the CRPS loss, or \code{NA} if
#' input is invalid. Lower values indicate better probabilistic forecast accuracy.
#'
#' @references
#' Gneiting, T., & Raftery, A. E. (2007). Strictly Proper Scoring Rules, Prediction, and
#' Estimation. \emph{Journal of the American Statistical Association}, 102(477), 359--378.
#' \doi{10.1198/016214506000001437}
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#'
#' # CRPS for forecast 1, period 1:
#' # Use the cross-sectional spread of all competing forecasts at period t=1 as the density
#' density_samples <- as.numeric(metals[1, 1:14])
#' realized_value <- metals[1, 15]
#' compute_crps(density_samples, realized_value)
#'
#' # In practice, iterate over all forecasts and periods.
#' # For forecast k and period t, the predictive density is approximated by shifting the
#' # cross-sectional spread of all K competing forecasts so that it is centred at the
#' # cross-sectional mean of forecasts at period t. Specifically, for each forecast k:
#' # density_samples_tk = (forecasts of all K forecast at t) - forecast_k(t) +
#' mean # (all K forecasts at t)
#' # This preserves the spread (diversity) across forecasts while recentring around the
#' # cross-sectional mean rather than around forecast k's own point forecast. It is an
#' # empirical approximation to the predictive distribution when no parametric density
#' # is available.
#' P <- nrow(metals)
#' K <- ncol(metals) - 1L # 14 competing forecasts
#' crps_matrix <- matrix(NA_real_, nrow = P, ncol = K,
#' dimnames = list(NULL, colnames(metals)[1:K]))
#' for (t in seq_len(P)) {
#' for (k in seq_len(K)) {
#' density_samples_tk <- as.numeric(metals[t, 1:K]) - metals[t, k] + mean(metals[t, 1:K])
#' crps_matrix[t, k] <- compute_crps(as.numeric(density_samples_tk),
#' target_realization = metals[t, ncol(metals)])
#' }
#' }
#' head(crps_matrix)
#' @export
compute_crps <- function(forecast_density, target_realization) {
if (is.na(target_realization) || length(forecast_density) == 0 || all(is.na(forecast_density))) {
return(NA)
}
clean_density <- forecast_density[is.finite(forecast_density) & !is.na(forecast_density)]
n_sim <- length(clean_density)
if (n_sim < 1) return(NA)
f_sorted <- sort(clean_density)
crps_val <- mean(abs(f_sorted - target_realization)) - 0.5 * mean(abs(outer(f_sorted, f_sorted, `-`)))
return(crps_val)
}
#' @title Long-Run Covariance Estimator via Bartlett Kernel (HAC)
#'
#' @description Estimates the long-run covariance matrix using the Newey-West (1987) approach
#' with a Bartlett kernel. Provides Heteroskedasticity and Autocorrelation Consistent (HAC)
#' variance estimates used for studentizing Reality Check test statistics.
#'
#' @param loss_differences A \code{\link[base]{numeric}} matrix (\code{P x K}) of loss
#' differences (benchmark loss minus forecast loss), where \code{P} is the number of
#' forecast periods and \code{K} is the number of competing forecasts.
#' @param block_length \code{\link[base]{integer}}. The truncation lag \eqn{l} for the
#' Bartlett kernel, numerically set equal to the MBB block length used elsewhere in
#' this package for consistency. In HAC estimation this controls how many autocovariance
#' lags are included; in MBB it controls block size -- both capture the same dependence
#' horizon. A commonly used rule of thumb is \eqn{l \approx T^{1/3}}
#' (Politis & Romano, 1994). For \code{P = 165} this gives approximately 5--6.
#'
#' @details
#' Implements the Newey-West (1987) HAC covariance matrix estimator with Bartlett kernel
#' weights \eqn{w_j = 1 - j / (l + 1)} for lags \eqn{j = 1, \ldots, l}, where \eqn{l}
#' denotes the truncation lag (following the notation of Newey & West, 1987, and
#' Politis & Romano, 1994), here set equal to \code{block_length}. This is essential
#' for accounting for serial dependence in time-series forecast evaluations.
#'
#' @return A symmetric positive semi-definite \code{\link[base]{matrix}} of dimensions
#' \code{K x K} representing the estimated long-run covariance.
#'
#' @references
#' 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}
#'
#' Politis, D. N., & Romano, J. P. (1994). The stationary bootstrap.
#' \emph{Journal of the American Statistical Association}, 89(428), 1303--1313.
#' \doi{10.1080/01621459.1994.10476870}
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' # A small offset (+0.5) is added to the lagged benchmark to avoid degenerate zero
#' # loss differences when forecasts equal the realized value exactly (illustration only).
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1 # 14 competing forecasts
#' realized <- c(metals[-1, K_total], metals[P, K_total]) + 0.5
#' benchmark_loss <- (metals[, K_total] - realized)^2
#' model_loss <- (metals[, 1:K] - realized)^2
#' loss_diff <- benchmark_loss - model_loss
#' lrc_result <- estimate_long_run_covariance(loss_diff, block_length = 5)
#' print(round(lrc_result[1:3, 1:3], 6))
#' @export
estimate_long_run_covariance <- function(loss_differences, block_length) {
T_obs <- nrow(loss_differences)
K_models <- ncol(loss_differences)
data_centered <- loss_differences - matrix(colMeans(loss_differences, na.rm = TRUE),
nrow = T_obs, ncol = K_models, byrow = TRUE)
V_hat <- matrix(0, K_models, K_models)
V_hat <- V_hat + crossprod(data_centered, data_centered) / T_obs
for (j in 1:(T_obs - 1)) {
if (j > block_length) break
w <- 1 - j / (block_length + 1)
Gamma_j <- crossprod(data_centered[(j+1):T_obs, , drop = FALSE],
data_centered[1:(T_obs-j), , drop = FALSE]) / T_obs
V_hat <- V_hat + w * (Gamma_j + t(Gamma_j))
}
return(V_hat)
}
#' @title Moving Block Bootstrap (MBB) Resampler
#'
#' @description Generates a bootstrap resample of a time series matrix using the Moving
#' Block Bootstrap (MBB) method of Kunsch (1989).
#'
#' @param data_series \code{\link[base]{matrix}} where rows are observations (\code{P}) and
#' columns are variables (\code{K}).
#' @param block_length \code{\link[base]{integer}} block length for the resampler. Overlapping
#' blocks of this length are sampled with replacement. A commonly used rule of thumb is
#' \code{block_length} \eqn{\approx T^{1/3}} (Politis & Romano, 1994). For \code{P = 165},
#' this gives approximately 5--6.
#'
#' @details
#' Resamples overlapping blocks of \code{block_length} consecutive rows with replacement,
#' then concatenates them to produce a bootstrap sample of the same length \code{P} as
#' the original series. This preserves the short-run autocorrelation structure of the data,
#' which is required for valid inference in the Reality Check and SPA-type tests.
#'
#' @return \code{\link[base]{matrix}} of bootstrap resampled data with the same dimensions
#' as \code{data_series}.
#'
#' @references
#' 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}
#'
#' Politis, D. N., & Romano, J. P. (1994). The stationary bootstrap.
#' \emph{Journal of the American Statistical Association}, 89(428), 1303--1313.
#' \doi{10.1080/01621459.1994.10476870}
#'
#' 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}.
#'
#' Liu, R. Y., & Singh, K. (1992). Moving blocks jackknife and bootstrap capture weak
#' dependence. In R. LePage & L. Billard (Eds.),
#' \emph{Exploring the Limits of Bootstrap} (pp. 225--248). Wiley.
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-3 are the first three competing forecasts
#' mbb_resample_data(metals[, 1:3], block_length = 5)
#' @export
mbb_resample_data <- function(data_series, block_length) {
P <- nrow(data_series)
num_blocks <- ceiling(P / block_length)
start_indices <- sample(1:(P - block_length + 1), num_blocks, replace = TRUE)
boot_indices <- unlist(sapply(start_indices, function(i) i:(i + block_length - 1)))
boot_indices <- boot_indices[1:P]
return(data_series[boot_indices, , drop = FALSE])
}
#' @title White's Reality Check (WRC)
#'
#' @description Implements White's (2000) Reality Check (WRC) for comparing forecast
#' accuracy of multiple competing forecasts against a benchmark forecast based on mean
#' loss differences. The test controls the family-wise error rate across all forecast
#' comparisons simultaneously, avoiding data-snooping bias.
#'
#' \strong{Hypotheses:}
#' \itemize{
#' \item \strong{H0:} \eqn{\max_{k} E[g(u_{0,t}) - g(u_{k,t})] \leq 0} -- no competing
#' forecast produces a strictly lower expected loss than the benchmark forecast.
#' \item \strong{H1:} At least one competing forecast has strictly lower expected loss
#' than the benchmark forecast.
#' }
#'
#' @param loss_differences A \code{\link[base]{numeric}} matrix (\code{P x K}) of loss
#' differences (benchmark loss minus forecast loss), where \code{P} is the number of
#' forecast periods and \code{K} is the number of competing forecasts.
#' A positive entry means the competing forecast outperforms the benchmark
#' forecast in that period.
#' @param n_simulations \code{\link[base]{integer}}. The number of MBB bootstrap
#' replications. Default \code{999}; see Davidson & MacKinnon (2000).
#' @param block_length \code{\link[base]{integer}}. The block length for the Moving Block
#' Bootstrap (MBB). A commonly used rule of thumb is \eqn{T^{1/3}} (Politis & Romano,
#' 1994). For \code{P = 165}, this gives approximately 5--6.
#' @param alpha \code{\link[base]{numeric}}. The significance level
#' (default \code{0.05}).
#'
#' @details
#' The test statistic is \eqn{\hat{S}_P = \max_k \overline{d}_k}, where
#' \eqn{\overline{d}_k} is the sample mean of the loss differential series for forecast
#' \eqn{k} (White, 2000, eq. 2). Bootstrap p-values are obtained via the MBB of
#' Kunsch (1989) by recentring each bootstrap statistic at the sample mean, following
#' the procedure in Corradi & Swanson (2011). This is an \emph{unstudentized} test; for
#' a studentized version with improved power against irrelevant forecasts, see
#' \code{\link{superior_predictive_ability_test}}.
#'
#' @return An object of class \code{"htest"}. The printed output shows the
#' test statistic (maximum mean loss differential), the bootstrap p-value, and the
#' test name. A small p-value (below \code{alpha}) leads to rejection of H0,
#' indicating that at least one competing forecast is significantly more accurate
#' than the benchmark forecast.
#'
#' @references
#' White, H. (2000). A reality check for data snooping. \emph{Econometrica}, 68(5),
#' 1097--1126. \doi{10.1111/1468-0262.00152}
#'
#' 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}
#'
#' Davidson, R., & MacKinnon, J. G. (2000). Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' 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)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' # A small offset (+0.5) is added to the lagged benchmark to avoid degenerate zero
#' # loss differences when forecasts equal the realized value exactly (illustration only).
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1 # 14 competing forecasts
#' realized <- c(metals[-1, K_total], metals[P, K_total]) + 0.5
#' benchmark_loss <- (metals[, K_total] - realized)^2
#' model_loss <- (metals[, 1:K] - realized)^2
#' loss_diff <- benchmark_loss - model_loss
#' res <- white_reality_check(loss_diff, block_length = 5, n_simulations = 50)
#' print(res)
#' @export
white_reality_check <- function(loss_differences,n_simulations = 999,
block_length = 5, alpha = 0.05) {
SP_k <- colMeans(loss_differences, na.rm = TRUE)
test_statistic <- max(SP_k, na.rm = TRUE)
bootstrap_stats <- numeric(n_simulations)
for (b in 1:n_simulations) {
boot_sample <- mbb_resample_data(loss_differences, block_length)
boot_SP_k <- colMeans(boot_sample, na.rm = TRUE)
boot_SP_k_centered <- boot_SP_k - SP_k
bootstrap_stats[b] <- max(boot_SP_k_centered, na.rm = TRUE)
}
p_value <- mean(bootstrap_stats > test_statistic, na.rm = TRUE)
res <- list(
statistic = c("max mean loss diff" = test_statistic),
p.value = p_value,
method = "White's Reality Check (WRC)",
data.name = deparse(substitute(loss_differences)),
null.value = c("max mean loss differential" = 0),
alternative = "at least one competing forecast outperforms the benchmark forecast",
reject_null = p_value <= alpha
)
class(res) <- "htest"
return(res)
}
#' @title Superior Predictive Ability (SPA) Test
#'
#' @description Implements the Hansen (2005) Superior Predictive Ability (SPA) test, a
#' studentized extension of White's (2000) Reality Check that corrects for the
#' inclusion of irrelevant (poor) forecasts to reduce conservatism.
#'
#' \strong{Hypotheses:}
#' \itemize{
#' \item \strong{H0:} \eqn{\max_{k} E[g(u_{0,t}) - g(u_{k,t})] \leq 0} -- no competing
#' forecast produces strictly lower expected loss than the benchmark forecast.
#' \item \strong{H1:} At least one competing forecast has strictly lower expected loss
#' than the benchmark forecast.
#' }
#'
#' @param loss_differences A \code{\link[base]{numeric}} matrix (\code{P x K}) of loss
#' differences (benchmark loss minus forecast loss).
#' @param block_length \code{\link[base]{integer}}. The block length for MBB and HAC
#' estimation. A commonly used rule of thumb is \eqn{T^{1/3}} (Politis & Romano,
#' 1994). For \code{P = 165}, this gives approximately 5--6.
#' @param num_bootstrap_replications \code{\link[base]{integer}} number of MBB
#' bootstrap replications. Default \code{999}; use at least \code{999} for
#' reliable inference (Davidson & MacKinnon, 2000).
#' @param alpha \code{\link[base]{numeric}}. The significance level
#' (default \code{0.05}).
#' @details
#' The SPA statistic studentizes each mean loss differential by its HAC standard
#' deviation (estimated via \code{\link{estimate_long_run_covariance}}), then takes
#' the maximum across forecasts. Two p-values are returned, corresponding to two choices
#' of the null distribution (Hansen, 2005, Section 3):
#' \itemize{
#' \item \code{p_consistent}: uses the sample-dependent null estimator
#' \eqn{\hat{\mu}^c}, which recentres the bootstrap statistic at the sample mean
#' \eqn{\bar{d}_k} for each forecast. This is the \strong{recommended} p-value.
#' \item \code{p_conservative}: uses the Least Favourable Configuration (LFC)
#' \eqn{\hat{\mu}^u = 0} for all forecasts -- equivalent to White's (2000) Reality
#' Check bootstrap, where no recentring is applied. This provides an upper bound
#' on the true p-value and is always \eqn{\geq} \code{p_consistent}.
#' }
#' @return An object of class \code{"htest"}. Additionally contains
#' \code{p_consistent} and \code{p_conservative} for the two SPA bootstrap variants.
#' @references
#' Hansen, P. R. (2005). A Test for Superior Predictive Ability.
#' \emph{Journal of Business & Economic Statistics}, 23(4), 365--380.
#' \doi{10.1198/073500105000000063}
#'
#' 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}.
#'
#' Davidson, R., & MacKinnon, J. G. (2000).
#' Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' 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}
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' # A small offset (+0.5) is added to the lagged benchmark to avoid degenerate zero
#' # loss differences when forecasts equal the realized value exactly (illustration only).
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1 # 14 competing forecasts
#' realized <- c(metals[-1, K_total], metals[P, K_total]) + 0.5
#' benchmark_loss <- (metals[, K_total] - realized)^2
#' model_loss <- (metals[, 1:K] - realized)^2
#' loss_diff <- benchmark_loss - model_loss
#' res <- superior_predictive_ability_test(loss_diff, block_length = 5,
#' num_bootstrap_replications = 50,
#' alpha = 0.05)
#' print(res)
#' @export
superior_predictive_ability_test <- function(loss_differences, block_length,
num_bootstrap_replications, alpha) {
P <- nrow(loss_differences)
K <- ncol(loss_differences)
SP_k <- colMeans(loss_differences, na.rm = TRUE)
V_hat_full <- estimate_long_run_covariance(loss_differences, block_length)
V_k <- diag(V_hat_full) / P
V_k[V_k <= 1e-10] <- 1e-10
std_dev_k <- sqrt(V_k)
T_k_unscaled <- SP_k / std_dev_k
T_SPA_Consistent <- max(T_k_unscaled)
bootstrap_consistent_all_k <- matrix(NA, nrow = num_bootstrap_replications, ncol = K)
bootstrap_conservative_all_k <- matrix(NA, nrow = num_bootstrap_replications, ncol = K)
for (b in 1:num_bootstrap_replications) {
boot_sample <- mbb_resample_data(loss_differences, block_length)
boot_SP_k <- colMeans(boot_sample, na.rm = TRUE)
boot_T_k <- boot_SP_k / std_dev_k
bootstrap_consistent_all_k[b, ] <- boot_T_k - T_k_unscaled
bootstrap_conservative_all_k[b, ] <- boot_T_k
}
T_C_boot_max <- apply(bootstrap_consistent_all_k, 1, max, na.rm = TRUE)
T_R_boot_max <- apply(bootstrap_conservative_all_k, 1, max, na.rm = TRUE)
p_value_consistent <- mean(T_C_boot_max > T_SPA_Consistent, na.rm = TRUE)
p_value_conservative <- mean(T_R_boot_max > T_SPA_Consistent, na.rm = TRUE)
res <- list(
statistic = c("T-SPA" = T_SPA_Consistent),
p.value = p_value_consistent,
method = "Superior Predictive Ability (SPA) Test",
data.name = deparse(substitute(loss_differences)),
alternative = "at least one competing forecast outperforms the benchmark forecast",
p_consistent = p_value_consistent,
p_conservative = p_value_conservative,
reject_null = p_value_consistent <= alpha
)
class(res) <- "htest"
return(res)
}
#' @title Conditional Predictive Ability (CPA) Reality Check Test
#'
#' @description Implements the Conditional Predictive Ability (CPA) test of Giacomini &
#' White (2006), extended to a multiple-forecast setting via a studentized Reality Check
#' statistic. Tests whether any competing forecast's predictive advantage over the benchmark
#' is state-dependent, i.e., predictable from a conditioning variable \eqn{h_t} known
#' at the time the forecast is made.
#'
#' \strong{Hypotheses:}
#' \itemize{
#' \item \strong{H0:} \eqn{E[h_t \cdot (g(u_{0,t}) - g(u_{k,t}))] = 0} for all
#' \eqn{k = 1,\ldots,K} -- no competing forecast's loss differential with the benchmark
#' is predictable using the conditioning information \eqn{h_t}.
#' \item \strong{H1:} At least one forecast's loss differential
#' \eqn{d_{k,t} = g(u_{0,t}) - g(u_{k,t})} is predictable by \eqn{h_t}, i.e.,
#' \eqn{E[h_t \cdot d_{k,t}] \neq 0} for some \eqn{k}.
#' }
#'
#' @param loss_differences A \code{\link[base]{numeric}} matrix (\code{P x K}) of loss
#' differences (benchmark loss minus forecast loss), where \code{P} is the number of
#' forecast periods and \code{K} is the number of competing forecasts.
#' A positive entry means the competing forecast outperforms the benchmark
#' forecast in that period.
#' @param weighting_vector \code{\link[base]{numeric}} vector of length \code{P}
#' serving as the conditioning instrument \eqn{h_t} in the CPA test. At each
#' period \eqn{t}, the test checks whether the loss differential \eqn{d_{k,t}}
#' covaries with \eqn{h_t}, i.e., whether \eqn{E[h_t \cdot d_{k,t}] \neq 0}.
#' See the \emph{Conditioning Instrument} section in Details for interpretation,
#' requirements, and recommended choices.
#' @param block_length \code{\link[base]{integer}}. The block length for MBB and HAC
#' estimation. A commonly used rule of thumb is \eqn{T^{1/3}} (Politis & Romano,
#' 1994). For \code{P = 165}, this gives approximately 5--6.
#' @param num_bootstrap_replications \code{\link[base]{integer}} number of MBB
#' bootstrap replications. Default \code{999}; see Davidson & MacKinnon (2000).
#' @param alpha \code{\link[base]{numeric}}. The significance level
#' (default \code{0.05}).
#' @details
#' The test multiplies each column of \code{loss_differences} element-wise by
#' \code{weighting_vector} to form the weighted loss differential series
#' \eqn{h_t \cdot d_{k,t}}. The unconditional mean of this product,
#' \eqn{E[h_t \cdot d_{k,t}]}, equals zero under H0 by the law of iterated
#' expectations when \eqn{h_t} is a valid instrument. The test statistic is the
#' maximum studentized mean across all \eqn{K} forecasts:
#' \deqn{\hat{T}_{CPA} = \max_{k} \frac{\frac{1}{P}\sum_t h_t d_{k,t}}
#' {\hat{\sigma}_{k,h}}}
#' where \eqn{\hat{\sigma}_{k,h}} is the HAC standard deviation of \eqn{h_t d_{k,t}}
#' estimated via \code{\link{estimate_long_run_covariance}}. Bootstrap p-values are
#' obtained via the MBB of Kunsch (1989) with recentring, following the SPA-type
#' procedure of Hansen (2005) applied to the weighted series.
#' \subsection{Conditioning Instrument (\code{weighting_vector})}{
#'
#' A significant result means that knowing \eqn{h_t} allows one to predict which
#' forecast will perform better in period \eqn{t} -- the benchmark's advantage (or
#' disadvantage) is state-dependent and potentially exploitable. This is a strictly
#' stronger statement than the unconditional WRC: a forecast can fail the WRC (no
#' unconditional improvement) yet pass the CPA test (conditional improvement in
#' specific states).
#'
#' \eqn{h_t} must be measurable with respect to the information set available at
#' time \eqn{t} (Giacomini & White, 2006, Assumption 1) -- it must not use
#' information from period \eqn{t+1} or later. The scale of \eqn{h_t} does not
#' affect the test result because the statistic is studentized by its own HAC
#' standard deviation.
#'
#' Recommended choices:
#' \describe{
#' \item{\code{abs(realized)} -- absolute realised values}{
#' Tests whether forecast performance depends on outcome magnitude -- a natural
#' proxy for market volatility or economic uncertainty. Default in
#' \code{\link{run_comprehensive_erc_analysis}}.}
#' \item{\code{c(realized[1], realized[-length(realized)])} -- lagged realised values}{
#' Tests whether the previous period's outcome predicts which forecast wins next
#' period. Relevant when forecast errors are autocorrelated.}
#' \item{\code{rep(1, P)} -- constant vector}{
#' The product \eqn{h_t \cdot d_{k,t}} reduces to \eqn{d_{k,t}}, making the
#' CPA test equivalent to the unconditional WRC. Use as a sanity check: results
#' should be consistent with \code{\link{white_reality_check}}.}
#' \item{External economic indicator}{
#' E.g., a recession dummy, VIX level, lagged interest rate spread, or monetary
#' policy stance dummy. Tests whether one forecast systematically outperforms
#' during specific regimes. Must be lagged one period to ensure \eqn{h_t} is
#' in the information set at the time of the forecast.}
#' }
#' }
#' The scale of \code{weighting_vector} has no effect on inference because both the
#' test statistic and its bootstrap distribution are studentized by the same
#' \eqn{\hat{\sigma}_{k,h}}.
#'
#' @return An object of class \code{"htest"} with the following components:
#' \tabular{ll}{
#' \code{statistic} \tab Maximum studentized weighted mean loss differential
#' across all \eqn{K} forecasts, labelled \code{"T-CPA"}. \cr
#' \code{p.value} \tab Bootstrap p-value from the MBB procedure. \cr
#' \code{method} \tab \code{"Conditional Predictive Ability (CPA) Test"}. \cr
#' \code{null.value} \tab Named scalar \code{"max studentized weighted mean
#' loss differential"} = 0. \cr
#' \code{alternative} \tab Direction of the alternative hypothesis. \cr
#' \code{reject_null} \tab Logical: \code{TRUE} if \code{p.value <= alpha}. \cr
#' }
#' A small p-value indicates that at least one forecast's loss differential is
#' predictable from the conditioning variable \eqn{h_t}. Failure to reject H0
#' means no evidence of state-dependent predictive ability for the chosen instrument.
#'
#' @references
#' Giacomini, R., & White, H. (2006). Tests of Conditional Predictive Ability.
#' \emph{Econometrica}, 74(6), 1545--1578. \doi{10.1111/j.1468-0262.2006.00718.x}
#'
#' Hansen, P. R. (2005). A Test for Superior Predictive Ability.
#' \emph{Journal of Business & Economic Statistics}, 23(4), 365--380.
#' \doi{10.1198/073500105000000063}
#'
#' 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}.
#'
#' Davidson, R., & MacKinnon, J. G. (2000).
#' Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' 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}
#'
#' Politis, D. N., & Romano, J. P. (1994). The stationary bootstrap.
#' \emph{Journal of the American Statistical Association}, 89(428), 1303--1313.
#' \doi{10.1080/01621459.1994.10476870}
#'
#' @seealso
#' \code{\link{white_reality_check}} for the unconditional WRC test (equivalent to
#' CPA with a constant \code{weighting_vector});
#' \code{\link{superior_predictive_ability_test}} for the studentized unconditional test.
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' # A small offset (+0.5) is added to the lagged benchmark to avoid degenerate zero
#' # loss differences when forecasts equal the realized value exactly (illustration only).
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1L # 14 competing forecasts
#' realized <- c(metals[-1, K_total], metals[P, K_total]) + 0.5
#' benchmark_loss <- (metals[, K_total] - realized)^2
#' model_loss <- (metals[, 1:K] - realized)^2
#' loss_diff <- benchmark_loss - model_loss
#'
#' # Example 1: absolute realised values as conditioning variable (volatility proxy)
#' res1 <- white_reality_check_conditional(
#' loss_differences = loss_diff,
#' weighting_vector = abs(realized),
#' block_length = 5,
#' num_bootstrap_replications = 50,
#' alpha = 0.05
#' )
#' print(res1)
#'
#' # Example 2: constant vector - should give results consistent with white_reality_check()
#' res2 <- white_reality_check_conditional(
#' loss_differences = loss_diff,
#' weighting_vector = rep(1, P),
#' block_length = 5,
#' num_bootstrap_replications = 50,
#' alpha = 0.05
#' )
#' print(res2)
#' @export
white_reality_check_conditional <- function(loss_differences, weighting_vector,
block_length, num_bootstrap_replications,
alpha) {
P <- nrow(loss_differences)
if (length(weighting_vector) != P)
stop("Weighting vector length must match the number of forecast periods P.")
weighted_diff_series <- loss_differences * weighting_vector
V_hat_full_h <- estimate_long_run_covariance(weighted_diff_series, block_length)
V_k_h <- diag(V_hat_full_h) / P
V_k_h[V_k_h <= 1e-10] <- 1e-10
std_dev_k_h <- sqrt(V_k_h)
SP_k_h <- colMeans(weighted_diff_series, na.rm = TRUE)
T_k_unscaled_h <- SP_k_h / std_dev_k_h
T_CPA_statistic <- max(T_k_unscaled_h, na.rm = TRUE)
bootstrap_stats_all_k <- matrix(NA, nrow = num_bootstrap_replications,
ncol = ncol(loss_differences))
for (b in 1:num_bootstrap_replications) {
boot_weighted_diff <- mbb_resample_data(weighted_diff_series, block_length)
boot_SP_k_h <- colMeans(boot_weighted_diff, na.rm = TRUE)
boot_T_k_unscaled_h <- boot_SP_k_h / std_dev_k_h
boot_T_k_h_centered <- boot_T_k_unscaled_h - T_k_unscaled_h
bootstrap_stats_all_k[b, ] <- boot_T_k_h_centered
}
T_CPA_Consistent_boot_max <- apply(bootstrap_stats_all_k, 1, max, na.rm = TRUE)
p_value_consistent <- mean(T_CPA_Consistent_boot_max > T_CPA_statistic, na.rm = TRUE)
res <- list(
statistic = c("T-CPA" = T_CPA_statistic),
p.value = p_value_consistent,
method = "Conditional Predictive Ability (CPA) Test",
data.name = deparse(substitute(loss_differences)),
null.value = c("max studentized weighted mean loss differential" = 0),
alternative = "at least one forecast's loss differential is predictable by the conditioning variable",
reject_null = p_value_consistent <= alpha
)
class(res) <- "htest"
return(res)
}
#' @title White's Reality Check via Expected Loss CDF Comparison (CDF-RC)
#'
#'@description Implements a studentized Reality Check test that compares competing
#' \strong{forecasts} against a benchmark across the entire distribution of loss
#' differences, not only the mean. For each forecast \eqn{k} and each quantile
#' threshold \eqn{x_\tau} (derived from the pooled empirical distribution of loss
#' differences), the test evaluates
#' whether the empirical CDF of \strong{forecast \eqn{k}'s} loss differences lies
#' uniformly above that of the benchmark, indicating stochastic dominance of the
#' benchmark's loss distribution over the \strong{forecast's} loss distribution.
#' \strong{Hypotheses:}
#' \itemize{
#' \item \strong{H0:} \eqn{\max_{k,j} E[\mathbf{1}(d_{k,t} \leq x_{\tau_j})] \leq 0}
#' for all \eqn{k = 1,\ldots,K} and all quantile thresholds
#' \eqn{x_{\tau_j},\ j = 1,\ldots,J} -- no competing forecast has a uniformly higher
#' empirical CDF of loss differences than the benchmark at any evaluation point.
#' \item \strong{H1:} At least one competing forecast has a significantly higher
#' empirical CDF of loss differences than the benchmark at some quantile threshold
#' \eqn{x_{\tau_j}}, i.e., the benchmark is stochastically dominated in terms of
#' loss differences.
#' }
#'
#' @param loss_differences A \code{\link[base]{numeric}} matrix (\code{P x K}) of loss
#' differences (benchmark loss minus forecast forecast loss), where \code{P} is the number of
#' forecast periods and \code{K} is the number of competing forecasts. A positive entry
#' means the competing forecast is more accurate than the benchmark forecast in that period.
#' @param block_length \code{\link[base]{integer}}. The block length for the Moving Block
#' Bootstrap (MBB) and for HAC variance estimation via
#' \code{\link{estimate_long_run_covariance}}. A commonly used rule of thumb is
#' \eqn{T^{1/3}} (Politis & Romano, 1994). For \code{P = 165}, this gives
#' approximately 5--6.
#' @param num_bootstrap_replications \code{\link[base]{integer}} number of MBB
#' bootstrap replications. Default \code{999}; see Davidson & MacKinnon (2000).
#' @param alpha \code{\link[base]{numeric}}. The significance level (default \code{0.05}).
#'
#' @details
#' The test proceeds in three steps.
#'
#' \strong{Step 1 — Quantile grid.} A grid of \eqn{J = 9} evaluation points
#' \eqn{x_{\tau_1}, \ldots, x_{\tau_9}} is constructed as the
#' \eqn{\tau_j \in \{0.1, 0.2, \ldots, 0.9\}} quantiles of the \emph{pooled}
#' empirical distribution of all loss differences (across all forecasts and all periods).
#' Using quantiles of the data rather than a fixed grid ensures that the evaluation
#' points are always in the support of the observed loss differences.
#'
#' \strong{Step 2 — Indicator matrix.} For each forecast \eqn{k} and each threshold
#' \eqn{x_{\tau_j}}, the binary indicator
#' \deqn{G_{k,j,t} = \mathbf{1}(d_{k,t} \leq x_{\tau_j})}
#' is formed, where \eqn{d_{k,t}} is the loss difference for forecast \eqn{k} at
#' period \eqn{t}. This yields a \eqn{P \times (K \cdot J)} matrix \code{Gdata}
#' with \eqn{K \times J = 14 \times 9 = 126} columns (for the \code{metals} dataset).
#' The column mean \eqn{\bar{G}_{k,j} = \frac{1}{P}\sum_t G_{k,j,t}} estimates the
#' empirical CDF of forecast \eqn{k}'s loss differences evaluated at \eqn{x_{\tau_j}}.
#' A higher CDF value means a larger fraction of the forecast's loss differences fall
#' below \eqn{x_{\tau_j}}, i.e., the competing forecast more frequently outperforms the
#' benchmark forecast (since a positive loss difference means the competing forecast is more accurate).
#'
#' \strong{Step 3 — Studentized test statistic.} Each column mean is studentized by
#' its HAC standard deviation (from \code{\link{estimate_long_run_covariance}}),
#' and the test statistic is the maximum studentized CDF indicator mean across all
#' \eqn{K \times J} columns:
#' \deqn{\hat{T} = \max_{k,j} \frac{\bar{G}_{k,j}}{\hat{\sigma}_{k,j}}}
#' Bootstrap p-values are obtained via the MBB of Kunsch (1989) with recentring,
#' following the WRC procedure of White (2000) and Corradi & Swanson (2011).
#'
#' \strong{Relationship to Corradi & Swanson (2006).} This test is a loss-difference
#' analogue of the predictive CDF comparison in Corradi & Swanson (2006, Section 4).
#' Rather than comparing forecast CDFs against the true conditional distribution
#' (as in the ZP test), it compares empirical CDFs of \emph{loss differences} against
#' zero, assessing stochastic dominance of the benchmark over each competing forecast
#' in terms of loss. It complements \code{\link{white_reality_check}} (which tests
#' only the mean) by detecting cases where one forecast is better in the tails but
#' not on average.
#'
#' \strong{Lower p-values are more informative:} rejection of H0 indicates that at
#' least one forecast stochastically dominates the benchmark at some point of the loss
#' distribution. Failure to reject does not preclude dominance at specific quantiles
#' -- it means no single \eqn{(k,j)} combination is significant after controlling
#' for multiple comparisons.
#'
#' @return An object of class \code{"htest"} with the following components:
#' \tabular{ll}{
#' \code{statistic} \tab Maximum studentized CDF indicator mean across all
#' \eqn{K \times J} forecast-quantile combinations,
#' labelled \code{"KS-type"}. \cr
#' \code{p.value} \tab Bootstrap p-value from the MBB procedure. \cr
#' \code{method} \tab \code{"Expected Loss CDF Comparison Test"}. \cr
#' \code{null.value} \tab Named scalar
#' \code{"max studentized CDF indicator mean
#' (benchmark minus forecast)"} = 0. \cr
#' \code{alternative} \tab Description of the alternative hypothesis. \cr
#' }
#' A small p-value (below \code{alpha}) leads to rejection of H0, indicating that
#' at least one competing forecast has a significantly higher empirical CDF of loss
#' differences than the benchmark at some quantile threshold -- i.e., the competing forecast
#' more frequently produces smaller losses than the benchmark forecast in some region of the
#' loss distribution.
#'
#' @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}
#'
#' Davidson, R., & MacKinnon, J. G. (2000).
#' Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' White, H. (2000). A reality check for data snooping. \emph{Econometrica},
#' 68(5), 1097--1126. \doi{10.1111/1468-0262.00152}
#'
#' 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}.
#'
#' 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}
#'
#' Politis, D. N., & Romano, J. P. (1994). The stationary bootstrap.
#' \emph{Journal of the American Statistical Association}, 89(428), 1303--1313.
#' \doi{10.1080/01621459.1994.10476870}
#'
#' @seealso
#' \code{\link{white_reality_check}} for the mean-based WRC test;
#' \code{\link{reality_check_zp_test}} for a distributional test based on the
#' true conditional CDF; \code{\link{superior_predictive_ability_test}} for the
#' studentized mean-based SPA test.
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' # A small offset (+0.5) is added to the lagged benchmark to avoid degenerate zero
#' # loss differences when forecasts equal the realized value exactly (illustration only).
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1L # 14 competing forecasts
#' realized <- c(metals[-1, K_total], metals[P, K_total]) + 0.5
#' benchmark_loss <- (metals[, K_total] - realized)^2
#' model_loss <- (metals[, 1:K] - realized)^2
#' loss_diff <- benchmark_loss - model_loss
#' res <- white_reality_check_cdf_approx(loss_diff,
#' block_length = 5,
#' num_bootstrap_replications = 50)
#' print(res)
#' @export
white_reality_check_cdf_approx <- function(loss_differences, block_length,
num_bootstrap_replications, alpha = 0.05) {
P <- nrow(loss_differences)
K <- ncol(loss_differences)
quantile_grid <- seq(0.1, 0.9, by = 0.1)
pooled_loss <- as.vector(loss_differences)
pooled_loss <- pooled_loss[!is.na(pooled_loss)]
x_tau_points <- quantile(pooled_loss, probs = quantile_grid, na.rm = TRUE)
J <- length(x_tau_points)
G_data <- matrix(NA, nrow = P, ncol = K * J)
for (k in 1:K) {
for (j in 1:J) {
G_index <- (k - 1) * J + j
G_data[, G_index] <- as.numeric(loss_differences[, k] <= x_tau_points[j])
}
}
na_rows <- apply(loss_differences, 1, anyNA)
G_data <- G_data[!na_rows, , drop = FALSE]
P_clean <- nrow(G_data)
if (P_clean == 0) return(NULL)
S_mean <- colMeans(G_data, na.rm = TRUE)
V_hat_full <- estimate_long_run_covariance(G_data, block_length)
V_k_new <- diag(V_hat_full) / P_clean
V_k_new[V_k_new <= 1e-10] <- 1e-10
std_dev_k_new <- sqrt(V_k_new)
T_k_unscaled <- S_mean / std_dev_k_new
T_max_ks <- max(T_k_unscaled)
bootstrap_stats <- numeric(num_bootstrap_replications)
for (b in 1:num_bootstrap_replications) {
boot_sample <- mbb_resample_data(G_data, block_length)
boot_S_mean <- colMeans(boot_sample, na.rm = TRUE)
boot_T_unscaled <- boot_S_mean / std_dev_k_new
boot_T_centered <- boot_T_unscaled - T_k_unscaled
bootstrap_stats[b] <- max(boot_T_centered, na.rm = TRUE)
}
p_value <- mean(bootstrap_stats > T_max_ks, na.rm = TRUE)
res <- list(
statistic = c("KS-type" = T_max_ks),
p.value = p_value,
method = "Expected Loss CDF Comparison Test",
data.name = deparse(substitute(loss_differences)),
null.value = c("max studentized CDF indicator mean (benchmark minus forecast)" = 0),
alternative = "at least one competing forecast has a higher CDF of loss differences at some quantile threshold"
)
class(res) <- "htest"
return(res)
}
#' @title Kullback-Leibler Information Criterion (KLIC) Test
#'
#' @description Implements the Reality Check using Negative Log-Likelihood Scores (NLS) to
#' evaluate predictive densities in terms of their Kullback-Leibler divergence from the
#' true density. Based on Corradi & Swanson (2006).
#'\itemize{
#' \item \strong{H0:} \eqn{\max_k E[\log f_1(y_t) - \log f_k(y_t)] \leq 0} -- no
#' competing forecast achieves a higher average log-likelihood (lower KLIC distance)
#' than the benchmark density \eqn{f_1}.
#' \item \strong{H1:} At least one competing forecast achieves strictly higher average
#' log-likelihood than the benchmark.
#'}
#' @param log_likelihood_differences A \code{\link[base]{numeric}} matrix (\code{P x K})
#' of Negative Log-Likelihood Score (NLS) differences: benchmark NLS minus forecast NLS.
#' A positive entry means the forecast's density assigns higher probability to the
#' observed outcome than the benchmark density does.
#' @param block_length \code{\link[base]{integer}}. The block length for MBB and HAC
#' estimation. A commonly used rule of thumb is
#' \code{block_length} \eqn{\approx T^{1/3}}, where \eqn{T} is the number of
#' observations (Politis & Romano, 1994). For \code{P = 165}, this gives
#' approximately 5--6.
#' @param num_bootstrap_replications \code{\link[base]{integer}} number of MBB
#' bootstrap replications. Default \code{999}; see Davidson & MacKinnon (2000).
#' @param alpha \code{\link[base]{numeric}}. The significance level (default \code{0.05}).
#'
#' @details
#' The KLIC between the true density \eqn{f_0} and a forecast density \eqn{f_k} is
#' \eqn{E[\log f_0(y) - \log f_k(y)]}. Minimising KLIC is equivalent to maximising
#' expected log-likelihood. This test therefore selects the forecast with the smallest
#' KLIC distance from the true density. \strong{Lower NLS values are better}: a forecast
#' with lower NLS assigns higher average probability to events that actually occurred
#' (Corradi & Swanson, 2006).
#'
#' The NLS loss matrix is constructed via \code{\link{compute_klic}} assuming normal
#' predictive densities parameterised by a point forecast and a rolling-window standard
#' deviation. The test statistic is the maximum studentized mean NLS differential,
#' with p-values obtained via MBB following the SPA bootstrap of Hansen (2005).
#'
#' @return An object of class \code{"htest"}. A small p-value (below \code{alpha})
#' leads to rejection of H0, indicating that at least one competing forecast has a
#' lower Kullback-Leibler distance from the true density than the benchmark.
#' Failure to reject H0 suggests no forecast provides significantly better density
#' fit than the benchmark.
#' @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}.
#'
#' Davidson, R., & MacKinnon, J. G. (2000). Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' Hansen, P. R. (2005). A Test for Superior Predictive Ability.
#' \emph{Journal of Business & Economic Statistics}, 23(4), 365--380.
#' \doi{10.1198/073500105000000063}
#'
#' Politis, D. N., & Romano, J. P. (1994). The stationary bootstrap.
#' \emph{Journal of the American Statistical Association}, 89(428), 1303--1313.
#' \doi{10.1080/01621459.1994.10476870}
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' benchmark_col <- 15
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1 # 14 competing forecasts
#' forecast_variance <- estimate_forecast_variance(metals, benchmark_col = K_total,
#' window_size = 20)
#' comp_cols <- setdiff(seq_len(K_total), benchmark_col)
#' forecast_sd_models <- sqrt(forecast_variance[, comp_cols])
#' nls_loss <- compute_klic(metals, forecast_sd_models, benchmark_col = K_total)
#' nls_diff <- nls_loss[, K_total] - nls_loss[, comp_cols]
#' kullback_leibler_test(nls_diff, block_length = 5, num_bootstrap_replications = 50)
#' @export
kullback_leibler_test <- function(log_likelihood_differences, block_length,
num_bootstrap_replications, alpha = 0.05) {
P <- nrow(log_likelihood_differences)
K <- ncol(log_likelihood_differences)
Mean_L_k <- colMeans(log_likelihood_differences, na.rm = TRUE)
V_hat_full <- estimate_long_run_covariance(log_likelihood_differences, block_length)
V_k <- diag(V_hat_full) / P
V_k[V_k <= 1e-10] <- 1e-10
std_dev_k <- sqrt(V_k)
T_k_unscaled <- Mean_L_k / std_dev_k
T_KLIC_statistic <- max(T_k_unscaled, na.rm = TRUE)
bootstrap_stats_all_k <- matrix(NA, nrow = num_bootstrap_replications, ncol = K)
for (b in 1:num_bootstrap_replications) {
boot_sample <- mbb_resample_data(log_likelihood_differences, block_length)
boot_Mean_L_k <- colMeans(boot_sample, na.rm = TRUE)
boot_T_k_unscaled <- boot_Mean_L_k / std_dev_k
boot_T_k_centered <- boot_T_k_unscaled - T_k_unscaled
bootstrap_stats_all_k[b, ] <- boot_T_k_centered
}
T_C_boot_max <- apply(bootstrap_stats_all_k, 1, max, na.rm = TRUE)
p_value_consistent <- mean(T_C_boot_max > T_KLIC_statistic, na.rm = TRUE)
res <- list(
statistic = c("T-KLIC" = T_KLIC_statistic),
p.value = p_value_consistent,
method = "Kullback-Leibler Information Criterion (KLIC) Test",
data.name = deparse(substitute(log_likelihood_differences)),
null.value = c("max studentized mean NLS differential" = 0),
alternative = "at least one forecast has lower Kullback-Leibler distance from the true density than the benchmark"
)
class(res) <- "htest"
return(res)
}
#' @title ZP Quantile Loss Reality Check Test
#'
#' @description Implements the studentized Reality Check test for comparing predictive
#' densities based on the ZP quantile loss function of Corradi & Swanson (2006). The test
#' evaluates whether any competing forecast more accurately predicts the probability of the
#' outcome falling below a specified threshold than the benchmark forecast.
#' \itemize{
#' \item \strong{H0:} \eqn{\max_k E[\mu^2_1(u) - \mu^2_k(u)] \leq 0} -- no competing
#' forecast has a lower expected squared probability forecast error at threshold \eqn{u}
#' than the benchmark (Corradi & Swanson, 2006, eq. 7).
#' \item \strong{H1:} At least one competing forecast has lower expected ZP-loss than the
#' benchmark.
#' }
#' @param zp_loss_differences A \code{\link[base]{numeric}} matrix (\code{P x K}) of
#' ZP-loss differences (benchmark ZP-loss minus forecast ZP-loss), where a positive
#' entry means the competing forecast outperforms the benchmark forecast at that period.
#' @param block_length \code{\link[base]{integer}}. The block length for MBB and HAC
#' estimation. A commonly used rule of thumb is
#' \code{block_length} \eqn{\approx T^{1/3}}, where \eqn{T} is the number of
#' observations (Politis & Romano, 1994). For \code{P = 165}, this gives
#' approximately 5--6.
#' @param num_bootstrap_replications \code{\link[base]{integer}} number of MBB
#' bootstrap replications. Default \code{999}; see Davidson & MacKinnon (2000).
#' @param alpha \code{\link[base]{numeric}}. The significance level (default \code{0.05}).
#'
#' @details
#' The ZP loss for forecast \eqn{k} at period \eqn{t} is
#' \deqn{ZP_{t,k} = \left(\mathbf{1}(y_t \leq \tau) - F_k(\tau \mid \hat{y}_{t,k},
#' \hat{\sigma}_{t,k})\right)^2}
#' where \eqn{\tau} is a threshold, \eqn{F_k(\cdot)} is the forecast's predictive CDF at
#' period \eqn{t}, and \eqn{\mathbf{1}(y_t \leq \tau)} is the indicator for a tail event.
#' Interpretively, the threshold \eqn{\tau} defines a tail event of interest (e.g., the
#' 5th percentile of realizations). The ZP loss penalises the squared difference between
#' the predicted probability of this event and whether it actually occurred. A forecast with
#' \strong{lower} ZP loss more accurately calibrates the left-tail probability. The
#' threshold is typically set to a quantile of the realized series (e.g.,
#' \code{quantile(realized, 0.05)}); a lower threshold focuses the test more sharply on
#' extreme left-tail events.
#'
#' The benchmark is treated as a degenerate (point-mass) predictive distribution
#' with \eqn{\hat{\sigma} = 10^{-6}}, which is a conservative choice ensuring the
#' benchmark's ZP loss approximates the Brier score for the tail indicator.
#'
#' Two p-values are returned: \code{p_consistent} and \code{p_conservative}, analogous
#' to those in \code{\link{superior_predictive_ability_test}}.
#'
#' @return An object of class \code{"htest"}. Lower p-values indicate that
#' at least one competing forecast is significantly better calibrated in the
#' left-tail than the benchmark forecast.
#' Also contains \code{p_consistent} and \code{p_conservative}.
#' Failure to reject H0 suggests no forecast provides significantly better density
#' fit than the benchmark forecast.
#' @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}.
#'
#' Davidson, R., & MacKinnon, J. G. (2000). Bootstrap tests: How many bootstraps?
#' \emph{Econometric Reviews}, 19(1), 55--68.
#' \doi{10.1080/07474930008800459}
#'
#' Hansen, P. R. (2005). A Test for Superior Predictive Ability.
#' \emph{Journal of Business & Economic Statistics}, 23(4), 365--380.
#' \doi{10.1198/073500105000000063}
#'
#' Politis, D. N., & Romano, J. P. (1994). The stationary bootstrap.
#' \emph{Journal of the American Statistical Association}, 89(428), 1303--1313.
#' \doi{10.1080/01621459.1994.10476870}
#'
#' @examples
#' data(metals)
#' # metals: 165 x 15; columns 1-14 are competing forecasts, column 15 is the benchmark
#' benchmark_col <- 15
#' P <- nrow(metals)
#' K_total <- ncol(metals)
#' K <- K_total - 1 # 14 competing forecasts
#' forecast_variance <- estimate_forecast_variance(metals, benchmark_col = K_total,
#' window_size = 20)
#' comp_cols <- setdiff(seq_len(K_total), benchmark_col)
#' forecast_sd_models <- sqrt(forecast_variance[, comp_cols])
#' threshold_val <- quantile(metals[, K_total], 0.05)
#' zp_loss <- compute_zp(metals, forecast_sd_models,
#' threshold = threshold_val, benchmark_col = K_total)
#' zp_diff <- zp_loss[, K_total] - zp_loss[, comp_cols]
#' reality_check_zp_test(zp_diff, block_length = 5, num_bootstrap_replications = 50)
#' @export
reality_check_zp_test <- function(zp_loss_differences, block_length,
num_bootstrap_replications, alpha = 0.05) {
P <- nrow(zp_loss_differences)
K <- ncol(zp_loss_differences)
SP_k <- colMeans(zp_loss_differences, na.rm = TRUE)
V_hat_full <- estimate_long_run_covariance(zp_loss_differences, block_length)
V_k <- diag(V_hat_full) / P
V_k[V_k <= 1e-10] <- 1e-10
std_dev_k <- sqrt(V_k)
T_k_unscaled <- SP_k / std_dev_k
T_ZP_Consistent <- max(T_k_unscaled, na.rm = TRUE)
bootstrap_consistent_all_k <- matrix(NA, nrow = num_bootstrap_replications, ncol = K)
bootstrap_conservative_all_k <- matrix(NA, nrow = num_bootstrap_replications, ncol = K)
for (b in 1:num_bootstrap_replications) {
boot_sample <- mbb_resample_data(zp_loss_differences, block_length)
boot_SP_k <- colMeans(boot_sample, na.rm = TRUE)
boot_T_k <- boot_SP_k / std_dev_k
bootstrap_consistent_all_k[b, ] <- boot_T_k - T_k_unscaled
bootstrap_conservative_all_k[b, ] <- boot_T_k
}
T_C_boot_max <- apply(bootstrap_consistent_all_k, 1, max, na.rm = TRUE)
T_R_boot_max <- apply(bootstrap_conservative_all_k, 1, max, na.rm = TRUE)
p_value_consistent <- mean(T_C_boot_max > T_ZP_Consistent, na.rm = TRUE)
p_value_conservative <- mean(T_R_boot_max > T_ZP_Consistent, na.rm = TRUE)
res <- list(
statistic = c("T-ZP" = T_ZP_Consistent),
p.value = p_value_consistent,
method = "ZP Quantile Loss Test (Corradi & Swanson, 2006)",
data.name = deparse(substitute(zp_loss_differences)),
null.value = c("max studentized mean ZP-loss differential" = 0),
alternative = "at least one forecast better calibrates the left-tail probability than the benchmark",
p_consistent = p_value_consistent,
p_conservative = p_value_conservative,
reject_consistent_zp = p_value_consistent <= alpha
)
class(res) <- "htest"
return(res)
}
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.