Portfolio & Market Risk

VaR Backtesting Calculator: Kupiec & Christoffersen Tests

Calculate exception counts and p-values for Kupiec coverage, Christoffersen independence and joint conditional coverage using reproducible simulated profit and loss (P&L).

How to use the VaR backtesting calculator

Set a positive one-day Value at Risk (VaR) forecast, confidence level, sample length and seed. An exception occurs when the simulated loss exceeds the forecast. Increase realised volatility or add an initial stress window to explore a misspecified forecast.

The results show whether the exception rate matches the forecast (Kupiec), whether exceptions depend on the previous day (Christoffersen independence), and whether those two conditions hold jointly (conditional coverage). See the worked example and p-value interpretation.

The controls generate a zero-mean Gaussian P&L simulation; they do not upload historical P&L or a daily forecast series. The default results are precomputed. After changing inputs, choose “Run the backtest” to calculate new results.

VaR forecast
Realised market (how well the forecast held up)Optional
1.0 = model well-calibrated
days of extra-stressed vol, from day 0

The seeded series uses Java's Random generator; seed, forecast and stress settings are visible in the source.

Java source
VarBacktestRiskStudio.java

This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.

import com.nablatensor.quant.analytic.Normal;
import java.util.Random;

public final class VarBacktestRiskStudio {
  // Java demo using nablatensor normal-distribution helpers: simulate P&L, then test exception frequency and timing.
  public static void main(String[] args) {
    // VaR is a positive loss threshold; alpha is its confidence as a probability.
    double forecast = 2000000, alpha = 99 / 100.0;
    // Multipliers control realised volatility and the extra volatility in the initial stress window.
    double volMult = 1, clusterMult = 2;
    int n = 500, stressDays = 0;
    // A fixed seed makes this constructed simulation reproducible.
    long seed = 7L;
    // For zero-mean Gaussian P&L, VaR = sigma * Phi^-1(alpha).
    // Normal.inverseCdf(0.99) is about 2.32635: the standard-normal 99th percentile.
    double sigma = forecast / Normal.inverseCdf(alpha);
    Random rng = new Random(seed);
    // hit[t] is true when day t's loss exceeds the forecast; x counts these exceptions.
    boolean[] hit = new boolean[n];
    int x = 0;
    for (int t = 0; t < n; t++) {
      // Stress affects the first stressDays observations; afterwards use ordinary realised volatility.
      double scale = sigma * volMult * (t < stressDays ? clusterMult : 1.0);
      // nextGaussian draws a standard normal; positive P&L is a gain, negative P&L a loss.
      double pnl = scale * rng.nextGaussian();
      hit[t] = -pnl > forecast;
      if (hit[t]) x++;
      // Return this run's daily loss, forecast, exception flag and expected running count for the graphs.
      // Display days from 1; array indices still start at 0.
      System.out.println("DAY|" + (t + 1) + "|" + (-pnl) + "|" + forecast + "|" + (hit[t] ? 1 : 0)
          + "|" + ((t + 1) * (1 - alpha)));
    }
    // Kupiec checks the exception rate against the forecast probability 1-alpha.
    double kupiec = pof(n, x, 1 - alpha);
    // Christoffersen checks whether exception probability depends on the previous day's outcome.
    double independence = independence(hit);
    // Conditional coverage combines the frequency and independence likelihood-ratio statistics.
    double cc = kupiec + independence;
    // Output: days, exceptions, expected exceptions, then each statistic and its p-value.
    // The combined test uses two chi-square degrees of freedom: survival = exp(-cc/2).
    System.out.println("RESULT|" + n + "|" + x + "|" + (n * (1 - alpha)) + "|" + kupiec + "|" + chi1(kupiec)
        + "|" + independence + "|" + chi1(independence) + "|" + cc + "|" + Math.exp(-0.5 * cc));
  }
  // Proportion-of-failures test: compare the forecast rate p with the fitted observed rate q.
  static double pof(int n, int x, double p) {
    // Boundary cases avoid evaluating log(0) multiplied by zero.
    if (x == 0) return -2.0 * n * Math.log(1 - p);
    if (x == n) return -2.0 * n * Math.log(p);
    double q = (double) x / n;
    // -2 * (forecast log-likelihood - fitted log-likelihood).
    return -2 * ((n - x) * Math.log(1 - p) + x * Math.log(p) - (n - x) * Math.log(1 - q) - x * Math.log(q));
  }
  // A count-weighted log probability; an unobserved event contributes zero even if p=0.
  static double xl(int n, double p) {
    return n == 0 ? 0 : n * Math.log(p);
  }
  // First-order independence test over adjacent days, using 0=no exception and 1=exception.
  static double independence(boolean[] h) {
    // nij counts transitions from yesterday's state i to today's state j.
    int n00 = 0, n01 = 0, n10 = 0, n11 = 0;
    // Start at day 1 because day 0 has no preceding observation in this sample.
    for (int t = 1; t < h.length; t++) {
      // 0 -> 0: two consecutive days without an exception.
      if (!h[t - 1] && !h[t]) n00++;
      // 0 -> 1: an exception following a day without one.
      else if (!h[t - 1]) n01++;
      // 1 -> 0: a non-exception following an exception.
      else if (!h[t]) n10++;
      // 1 -> 1: consecutive exceptions (one indication of clustering).
      else n11++;
    }
    // Row totals: how many transitions began in state 0 and state 1.
    int n0 = n00 + n01;
    int n1 = n10 + n11;
    // There are h.length-1 transitions; ones counts exceptions at their destinations.
    int total = n0 + n1;
    int ones = n01 + n11;
    // No transitions, or constant destinations: both fitted likelihoods agree, so LR=0.
    // This convention does not establish independence; a sparse sample offers little evidence.
    if (total == 0 || ones == 0 || ones == total) return 0;
    // Null model: one common exception probability, independent of yesterday's state.
    double pi = (double) ones / total;
    // Alternative model: separate exception probabilities after state 0 and state 1.
    // An empty row uses 0 as a placeholder; xl makes all its contributions zero.
    double pi0 = n0 == 0 ? 0 : (double) n01 / n0;
    double pi1 = n1 == 0 ? 0 : (double) n11 / n1;
    // Null log-likelihood: all destination exceptions/non-exceptions share the pooled rate.
    double pooled = xl(ones, pi) + xl(total - ones, 1 - pi);
    // Alternative log-likelihood: each transition uses its row's fitted probability.
    double split = xl(n01, pi0) + xl(n00, 1 - pi0) + xl(n11, pi1) + xl(n10, 1 - pi1);
    // Larger LR means stronger evidence against independence; clamp round-off below zero.
    return Math.max(0, -2 * (pooled - split));
  }
  // Approximate asymptotic p-value for a chi-square statistic with one degree of freedom.
  // If Z is standard normal, Z^2 is chi-square(1): P(Z^2>s)=2*(1-Phi(sqrt(s))).
  // Small samples and rare exceptions can make this asymptotic calibration unreliable.
  // Normal.cdf(x) returns P(Z <= x); nablatensor owns the numerical approximation.
  static double chi1(double s) {
    return s <= 0 ? 1 : Math.max(0, Math.min(1, 2 * (1 - Normal.cdf(Math.sqrt(s)))));
  }
}
TeaVM compiles and runs the Java source above in this browser.

Results from a precomputed Java run.

Exceptions4expected 5.0 of 500 days
Kupiec p-value0.641statistic 0.217
Christoffersen p-value0.799statistic 0.065
Conditional coverageacceptp=0.869, 5% sig. (statistic 0.282)

Daily loss versus VaR

Simulated losses in the forecast’s monetary units; negative losses are gains. Larger dots mark exceptions above the dashed VaR line. Hover over a dot for its day and loss.

Daily loss versus VaR500 simulated days with 4 exceptions. The solid line shows losses, the dashed line the VaR forecast, and larger dots mark breaches.-3.39M-1.76M-139.94K1.48M3.11M0125250375500Simulation dayDay 1: loss -726,637.73; VaR 2,000,000Day 2: loss -784,814.85; VaR 2,000,000Day 3: loss 246,806.28; VaR 2,000,000Day 4: loss -646,386.07; VaR 2,000,000Day 5: loss -1,148,128.95; VaR 2,000,000Day 6: loss 816,712.71; VaR 2,000,000Day 7: loss -515,013.17; VaR 2,000,000Day 8: loss -1,035,589.52; VaR 2,000,000Day 9: loss -2,133,824.81; VaR 2,000,000Day 10: loss 648,183.46; VaR 2,000,000Day 11: loss 1,209,802.05; VaR 2,000,000Day 12: loss 578,185.22; VaR 2,000,000Day 13: loss -597,210.36; VaR 2,000,000Day 14: loss 617,850.06; VaR 2,000,000Day 15: loss 344,573.13; VaR 2,000,000Day 16: loss -675,677.27; VaR 2,000,000Day 17: loss 799,239.51; VaR 2,000,000Day 18: loss 118,459.4; VaR 2,000,000Day 19: loss -46,793.26; VaR 2,000,000Day 20: loss 822,042.17; VaR 2,000,000Day 21: loss -497,628.63; VaR 2,000,000Day 22: loss -1,269,061.8; VaR 2,000,000Day 23: loss 313,074.84; VaR 2,000,000Day 24: loss -969,175.92; VaR 2,000,000Day 25: loss 1,434,766.19; VaR 2,000,000Day 26: loss 2,015,484.58; VaR 2,000,000; exceptionDay 27: loss -366,106.03; VaR 2,000,000Day 28: loss 1,088,759.53; VaR 2,000,000Day 29: loss -1,288,599.54; VaR 2,000,000Day 30: loss -1,335,871.89; VaR 2,000,000Day 31: loss -8,966.02; VaR 2,000,000Day 32: loss -646,162.08; VaR 2,000,000Day 33: loss 71,239.49; VaR 2,000,000Day 34: loss -226,678.32; VaR 2,000,000Day 35: loss 290,663.44; VaR 2,000,000Day 36: loss -1,347,534.3; VaR 2,000,000Day 37: loss 690,360.96; VaR 2,000,000Day 38: loss 1,063,718.61; VaR 2,000,000Day 39: loss 1,600,279.86; VaR 2,000,000Day 40: loss -729,343.8; VaR 2,000,000Day 41: loss 325,992.37; VaR 2,000,000Day 42: loss 1,109,561.98; VaR 2,000,000Day 43: loss -472,560.92; VaR 2,000,000Day 44: loss -759,615.69; VaR 2,000,000Day 45: loss 559,159.54; VaR 2,000,000Day 46: loss 285,198.69; VaR 2,000,000Day 47: loss -873,411.81; VaR 2,000,000Day 48: loss 821,316.93; VaR 2,000,000Day 49: loss 731,354.14; VaR 2,000,000Day 50: loss -977,758.42; VaR 2,000,000Day 51: loss -329,506.58; VaR 2,000,000Day 52: loss 469,971.44; VaR 2,000,000Day 53: loss 1,910,916.4; VaR 2,000,000Day 54: loss 337,168.75; VaR 2,000,000Day 55: loss -754,003.78; VaR 2,000,000Day 56: loss 561,958.92; VaR 2,000,000Day 57: loss -1,287,869.27; VaR 2,000,000Day 58: loss -491,661.17; VaR 2,000,000Day 59: loss -399,495.42; VaR 2,000,000Day 60: loss -147,351.99; VaR 2,000,000Day 61: loss -1,236,143.82; VaR 2,000,000Day 62: loss 313,800.36; VaR 2,000,000Day 63: loss 2,077,620.33; VaR 2,000,000; exceptionDay 64: loss -368,650.44; VaR 2,000,000Day 65: loss -671,101.91; VaR 2,000,000Day 66: loss 1,240,024.69; VaR 2,000,000Day 67: loss 50,331.25; VaR 2,000,000Day 68: loss -354,119.96; VaR 2,000,000Day 69: loss 296,163.94; VaR 2,000,000Day 70: loss 2,164,723.85; VaR 2,000,000; exceptionDay 71: loss 337,750.53; VaR 2,000,000Day 72: loss 1,111,917.59; VaR 2,000,000Day 73: loss -329,637.79; VaR 2,000,000Day 74: loss -2,013,386.87; VaR 2,000,000Day 75: loss -410,809.38; VaR 2,000,000Day 76: loss -968,323.72; VaR 2,000,000Day 77: loss 263,077.95; VaR 2,000,000Day 78: loss 181,380.55; VaR 2,000,000Day 79: loss 94,420.92; VaR 2,000,000Day 80: loss -1,374,637.25; VaR 2,000,000Day 81: loss -570,437.3; VaR 2,000,000Day 82: loss -550,429.75; VaR 2,000,000Day 83: loss 437,773.41; VaR 2,000,000Day 84: loss 474,822.2; VaR 2,000,000Day 85: loss 298,415.13; VaR 2,000,000Day 86: loss -595,129.5; VaR 2,000,000Day 87: loss 172,204.23; VaR 2,000,000Day 88: loss -723,680.72; VaR 2,000,000Day 89: loss 388,086.98; VaR 2,000,000Day 90: loss 89,224.07; VaR 2,000,000Day 91: loss 1,544,012.46; VaR 2,000,000Day 92: loss 1,150,310.49; VaR 2,000,000Day 93: loss -1,378,622.84; VaR 2,000,000Day 94: loss 44,039.45; VaR 2,000,000Day 95: loss 1,651,696.69; VaR 2,000,000Day 96: loss 176,549.1; VaR 2,000,000Day 97: loss 176,570.32; VaR 2,000,000Day 98: loss 204,969.25; VaR 2,000,000Day 99: loss 561,970.33; VaR 2,000,000Day 100: loss -1,010,883.14; VaR 2,000,000Day 101: loss -1,036,626.83; VaR 2,000,000Day 102: loss 764,717.47; VaR 2,000,000Day 103: loss 1,553,234.15; VaR 2,000,000Day 104: loss -1,023,555.82; VaR 2,000,000Day 105: loss -614,860.05; VaR 2,000,000Day 106: loss -23,381.15; VaR 2,000,000Day 107: loss 1,387,652.33; VaR 2,000,000Day 108: loss -1,332,251.76; VaR 2,000,000Day 109: loss -1,213,874.05; VaR 2,000,000Day 110: loss 927,805.17; VaR 2,000,000Day 111: loss -489,166.52; VaR 2,000,000Day 112: loss -1,014,564.46; VaR 2,000,000Day 113: loss -27,075.38; VaR 2,000,000Day 114: loss 645,607.58; VaR 2,000,000Day 115: loss -1,724,996.26; VaR 2,000,000Day 116: loss -784,152.77; VaR 2,000,000Day 117: loss 1,442,144.67; VaR 2,000,000Day 118: loss -694,243.49; VaR 2,000,000Day 119: loss 762,735.7; VaR 2,000,000Day 120: loss 41,654.26; VaR 2,000,000Day 121: loss -782,097.29; VaR 2,000,000Day 122: loss -607,588.6; VaR 2,000,000Day 123: loss 368,439.26; VaR 2,000,000Day 124: loss 907,458.66; VaR 2,000,000Day 125: loss -1,456,440.02; VaR 2,000,000Day 126: loss -512,283.3; VaR 2,000,000Day 127: loss 1,052,111.62; VaR 2,000,000Day 128: loss 133,626.01; VaR 2,000,000Day 129: loss 980,353.53; VaR 2,000,000Day 130: loss -279,568.51; VaR 2,000,000Day 131: loss -131,479.09; VaR 2,000,000Day 132: loss -43,319.79; VaR 2,000,000Day 133: loss -758,477.66; VaR 2,000,000Day 134: loss -683,996.26; VaR 2,000,000Day 135: loss -579,180.49; VaR 2,000,000Day 136: loss 590,586.65; VaR 2,000,000Day 137: loss -1,065,142.76; VaR 2,000,000Day 138: loss 113,003.7; VaR 2,000,000Day 139: loss 256,477.91; VaR 2,000,000Day 140: loss -384,197.12; VaR 2,000,000Day 141: loss 246,430.78; VaR 2,000,000Day 142: loss -422,666.19; VaR 2,000,000Day 143: loss 155,169.36; VaR 2,000,000Day 144: loss 66,336.82; VaR 2,000,000Day 145: loss 1,113,508.12; VaR 2,000,000Day 146: loss -1,130,818.15; VaR 2,000,000Day 147: loss 371,484.4; VaR 2,000,000Day 148: loss 303,088.43; VaR 2,000,000Day 149: loss -579,247.46; VaR 2,000,000Day 150: loss 101,417.16; VaR 2,000,000Day 151: loss -746,213.64; VaR 2,000,000Day 152: loss 682,904.07; VaR 2,000,000Day 153: loss 615,701; VaR 2,000,000Day 154: loss -227.78; VaR 2,000,000Day 155: loss -590,992.16; VaR 2,000,000Day 156: loss 885,197.37; VaR 2,000,000Day 157: loss 695,227.17; VaR 2,000,000Day 158: loss 381,437.82; VaR 2,000,000Day 159: loss -896,841.47; VaR 2,000,000Day 160: loss -1,105,012.05; VaR 2,000,000Day 161: loss 1,520,131.62; VaR 2,000,000Day 162: loss 267,765.11; VaR 2,000,000Day 163: loss -991,780.05; VaR 2,000,000Day 164: loss 1,110,246.7; VaR 2,000,000Day 165: loss 1,310,801.87; VaR 2,000,000Day 166: loss 1,293,069.17; VaR 2,000,000Day 167: loss 194,972.89; VaR 2,000,000Day 168: loss -109,320.18; VaR 2,000,000Day 169: loss -1,104,785.14; VaR 2,000,000Day 170: loss 694,433.84; VaR 2,000,000Day 171: loss -1,050,516.6; VaR 2,000,000Day 172: loss -547,306.61; VaR 2,000,000Day 173: loss 868,363.48; VaR 2,000,000Day 174: loss 402,152.38; VaR 2,000,000Day 175: loss -437,189.45; VaR 2,000,000Day 176: loss -129,993; VaR 2,000,000Day 177: loss -372,560.27; VaR 2,000,000Day 178: loss -233,960.01; VaR 2,000,000Day 179: loss 324,765.13; VaR 2,000,000Day 180: loss -519,818.85; VaR 2,000,000Day 181: loss -907,458.82; VaR 2,000,000Day 182: loss 24,875.84; VaR 2,000,000Day 183: loss -608,807.14; VaR 2,000,000Day 184: loss -672,503.78; VaR 2,000,000Day 185: loss 1,705,723.67; VaR 2,000,000Day 186: loss 1,516,470.44; VaR 2,000,000Day 187: loss 1,512,665.85; VaR 2,000,000Day 188: loss 581,259.87; VaR 2,000,000Day 189: loss -1,185,617.91; VaR 2,000,000Day 190: loss 784,602.57; VaR 2,000,000Day 191: loss -1,603,241.09; VaR 2,000,000Day 192: loss 957,831.19; VaR 2,000,000Day 193: loss -407,025.79; VaR 2,000,000Day 194: loss -594,983.3; VaR 2,000,000Day 195: loss 346,847.89; VaR 2,000,000Day 196: loss -240,165.56; VaR 2,000,000Day 197: loss 781,246.41; VaR 2,000,000Day 198: loss -288,352.82; VaR 2,000,000Day 199: loss 590,040.18; VaR 2,000,000Day 200: loss -349,632.57; VaR 2,000,000Day 201: loss -812,685.82; VaR 2,000,000Day 202: loss -925,003.92; VaR 2,000,000Day 203: loss 323,945.12; VaR 2,000,000Day 204: loss 124,896.09; VaR 2,000,000Day 205: loss -427,600.43; VaR 2,000,000Day 206: loss -724,705.74; VaR 2,000,000Day 207: loss 845,104.69; VaR 2,000,000Day 208: loss -658,082.8; VaR 2,000,000Day 209: loss -290,424.42; VaR 2,000,000Day 210: loss 975,412.93; VaR 2,000,000Day 211: loss -1,462,321.62; VaR 2,000,000Day 212: loss -1,080,435.88; VaR 2,000,000Day 213: loss -1,787,677.22; VaR 2,000,000Day 214: loss 138,248.44; VaR 2,000,000Day 215: loss 496,153.45; VaR 2,000,000Day 216: loss 222,186.72; VaR 2,000,000Day 217: loss 1,188,948.65; VaR 2,000,000Day 218: loss -798,980.97; VaR 2,000,000Day 219: loss 817,024.23; VaR 2,000,000Day 220: loss 1,435,954.37; VaR 2,000,000Day 221: loss -350,833.72; VaR 2,000,000Day 222: loss -1,207,959.09; VaR 2,000,000Day 223: loss 959,798.89; VaR 2,000,000Day 224: loss -682,341.54; VaR 2,000,000Day 225: loss 1,040,706.38; VaR 2,000,000Day 226: loss -234,134.67; VaR 2,000,000Day 227: loss -817,988.46; VaR 2,000,000Day 228: loss -1,021,290.45; VaR 2,000,000Day 229: loss 490,092.83; VaR 2,000,000Day 230: loss -1,492,826.65; VaR 2,000,000Day 231: loss 33,295.35; VaR 2,000,000Day 232: loss -1,138,028.9; VaR 2,000,000Day 233: loss -222,622.47; VaR 2,000,000Day 234: loss -721,209.73; VaR 2,000,000Day 235: loss -154,970.65; VaR 2,000,000Day 236: loss 977,674.98; VaR 2,000,000Day 237: loss -495,136.94; VaR 2,000,000Day 238: loss -139,059.58; VaR 2,000,000Day 239: loss 377,202.53; VaR 2,000,000Day 240: loss 793,303.76; VaR 2,000,000Day 241: loss -1,686,329.56; VaR 2,000,000Day 242: loss -881,911; VaR 2,000,000Day 243: loss -155,614.12; VaR 2,000,000Day 244: loss -1,116,318.49; VaR 2,000,000Day 245: loss 1,629,606.71; VaR 2,000,000Day 246: loss 290,209.76; VaR 2,000,000Day 247: loss 1,263,129.63; VaR 2,000,000Day 248: loss 1,651,938.22; VaR 2,000,000Day 249: loss -921,749.98; VaR 2,000,000Day 250: loss 193,560.29; VaR 2,000,000Day 251: loss -157,265.15; VaR 2,000,000Day 252: loss -443,185.93; VaR 2,000,000Day 253: loss 131,167.7; VaR 2,000,000Day 254: loss -285,237.68; VaR 2,000,000Day 255: loss 801,545.82; VaR 2,000,000Day 256: loss -748,650.25; VaR 2,000,000Day 257: loss -1,078,475.26; VaR 2,000,000Day 258: loss -73,599.54; VaR 2,000,000Day 259: loss -126,123.23; VaR 2,000,000Day 260: loss 773,504.56; VaR 2,000,000Day 261: loss -163,938.51; VaR 2,000,000Day 262: loss -707,047.31; VaR 2,000,000Day 263: loss 169,761.1; VaR 2,000,000Day 264: loss -758,527.3; VaR 2,000,000Day 265: loss -446,827.25; VaR 2,000,000Day 266: loss 1,142,941.85; VaR 2,000,000Day 267: loss -535,453.2; VaR 2,000,000Day 268: loss 507,939.29; VaR 2,000,000Day 269: loss 648,886.67; VaR 2,000,000Day 270: loss -144,155.15; VaR 2,000,000Day 271: loss -753,326.09; VaR 2,000,000Day 272: loss -266,879.43; VaR 2,000,000Day 273: loss -832,565.44; VaR 2,000,000Day 274: loss -894,739.91; VaR 2,000,000Day 275: loss 219,037.76; VaR 2,000,000Day 276: loss 15,024.65; VaR 2,000,000Day 277: loss -403,656.76; VaR 2,000,000Day 278: loss -105,605.11; VaR 2,000,000Day 279: loss 720,619.21; VaR 2,000,000Day 280: loss -1,056,253.1; VaR 2,000,000Day 281: loss -716,687.29; VaR 2,000,000Day 282: loss 536,777.89; VaR 2,000,000Day 283: loss -854,075.54; VaR 2,000,000Day 284: loss -222,607.8; VaR 2,000,000Day 285: loss 958,283.25; VaR 2,000,000Day 286: loss -410,002.66; VaR 2,000,000Day 287: loss 818,448.78; VaR 2,000,000Day 288: loss 695,200.01; VaR 2,000,000Day 289: loss 1,009,154.26; VaR 2,000,000Day 290: loss 752,951.36; VaR 2,000,000Day 291: loss 153,338.31; VaR 2,000,000Day 292: loss 1,162,700.24; VaR 2,000,000Day 293: loss -601,117.67; VaR 2,000,000Day 294: loss 193,562.01; VaR 2,000,000Day 295: loss -910,439.65; VaR 2,000,000Day 296: loss 836,084.96; VaR 2,000,000Day 297: loss -173,276.97; VaR 2,000,000Day 298: loss 157,332.99; VaR 2,000,000Day 299: loss 418,913.07; VaR 2,000,000Day 300: loss 1,505,698.26; VaR 2,000,000Day 301: loss -1,541,952.42; VaR 2,000,000Day 302: loss 217,376.28; VaR 2,000,000Day 303: loss 97,806.87; VaR 2,000,000Day 304: loss 840,063.33; VaR 2,000,000Day 305: loss -401,075.56; VaR 2,000,000Day 306: loss 260,533.16; VaR 2,000,000Day 307: loss -451,404.37; VaR 2,000,000Day 308: loss -333,770.43; VaR 2,000,000Day 309: loss 532,053; VaR 2,000,000Day 310: loss 525,316.56; VaR 2,000,000Day 311: loss 224,952.84; VaR 2,000,000Day 312: loss -1,177,610.59; VaR 2,000,000Day 313: loss 523,130.74; VaR 2,000,000Day 314: loss -780,982.4; VaR 2,000,000Day 315: loss 208,629.94; VaR 2,000,000Day 316: loss 459,936.68; VaR 2,000,000Day 317: loss -177,044.86; VaR 2,000,000Day 318: loss -2,938,675.56; VaR 2,000,000Day 319: loss 114,273.83; VaR 2,000,000Day 320: loss -766,840.92; VaR 2,000,000Day 321: loss 559,107.4; VaR 2,000,000Day 322: loss -1,070,865.28; VaR 2,000,000Day 323: loss -791,992.49; VaR 2,000,000Day 324: loss 16,567; VaR 2,000,000Day 325: loss 208,122.87; VaR 2,000,000Day 326: loss -508,235.91; VaR 2,000,000Day 327: loss -182,630.02; VaR 2,000,000Day 328: loss 229,548.93; VaR 2,000,000Day 329: loss -22,441.88; VaR 2,000,000Day 330: loss 14,687.27; VaR 2,000,000Day 331: loss -307,917.4; VaR 2,000,000Day 332: loss -168,812.77; VaR 2,000,000Day 333: loss -1,225,947.84; VaR 2,000,000Day 334: loss 796,392.85; VaR 2,000,000Day 335: loss 2,658,788.63; VaR 2,000,000; exceptionDay 336: loss -687,884.27; VaR 2,000,000Day 337: loss 1,575,241.55; VaR 2,000,000Day 338: loss 441,716.55; VaR 2,000,000Day 339: loss 489,390.33; VaR 2,000,000Day 340: loss -47,921.72; VaR 2,000,000Day 341: loss -549,728.21; VaR 2,000,000Day 342: loss -634,351.88; VaR 2,000,000Day 343: loss 1,661,163.2; VaR 2,000,000Day 344: loss 1,498,492.09; VaR 2,000,000Day 345: loss 763,748.02; VaR 2,000,000Day 346: loss -106,354.27; VaR 2,000,000Day 347: loss -1,190,024.83; VaR 2,000,000Day 348: loss 1,039,972.88; VaR 2,000,000Day 349: loss -787,105.36; VaR 2,000,000Day 350: loss -85,698.21; VaR 2,000,000Day 351: loss -85,902.38; VaR 2,000,000Day 352: loss 444,231.1; VaR 2,000,000Day 353: loss -73,272.25; VaR 2,000,000Day 354: loss 1,050,879.13; VaR 2,000,000Day 355: loss 716,596.59; VaR 2,000,000Day 356: loss -755,538.43; VaR 2,000,000Day 357: loss 431,672.49; VaR 2,000,000Day 358: loss -1,241,613.71; VaR 2,000,000Day 359: loss 865,251.52; VaR 2,000,000Day 360: loss 299,837.02; VaR 2,000,000Day 361: loss -858,005.21; VaR 2,000,000Day 362: loss 398,906.21; VaR 2,000,000Day 363: loss 985,573.45; VaR 2,000,000Day 364: loss -329,442.26; VaR 2,000,000Day 365: loss 1,368,671.77; VaR 2,000,000Day 366: loss -96,277.01; VaR 2,000,000Day 367: loss -1,027,301.23; VaR 2,000,000Day 368: loss -1,639,936.8; VaR 2,000,000Day 369: loss 372,892.47; VaR 2,000,000Day 370: loss 399,769.18; VaR 2,000,000Day 371: loss 313,435.77; VaR 2,000,000Day 372: loss 891,206.19; VaR 2,000,000Day 373: loss -239,511.06; VaR 2,000,000Day 374: loss -441,583.57; VaR 2,000,000Day 375: loss -43,781.68; VaR 2,000,000Day 376: loss 838,132.71; VaR 2,000,000Day 377: loss -303,654.93; VaR 2,000,000Day 378: loss -959,461.94; VaR 2,000,000Day 379: loss 270,755.51; VaR 2,000,000Day 380: loss -719,121.95; VaR 2,000,000Day 381: loss 698,278.31; VaR 2,000,000Day 382: loss -339,105.4; VaR 2,000,000Day 383: loss -755,664.87; VaR 2,000,000Day 384: loss -1,065,339.3; VaR 2,000,000Day 385: loss 1,532,521.96; VaR 2,000,000Day 386: loss 1,649,643.03; VaR 2,000,000Day 387: loss 360,572.12; VaR 2,000,000Day 388: loss -334,883.92; VaR 2,000,000Day 389: loss -796,775.26; VaR 2,000,000Day 390: loss -386,829.13; VaR 2,000,000Day 391: loss -265,985.81; VaR 2,000,000Day 392: loss -1,299,253.15; VaR 2,000,000Day 393: loss -1,174,396.01; VaR 2,000,000Day 394: loss -342,916.79; VaR 2,000,000Day 395: loss -349,138.25; VaR 2,000,000Day 396: loss 191,677.38; VaR 2,000,000Day 397: loss 44,289.88; VaR 2,000,000Day 398: loss -659,164.05; VaR 2,000,000Day 399: loss -87,768.64; VaR 2,000,000Day 400: loss 508,445.65; VaR 2,000,000Day 401: loss -516,500.88; VaR 2,000,000Day 402: loss -961,756.06; VaR 2,000,000Day 403: loss 1,599,276.05; VaR 2,000,000Day 404: loss -1,989,078.45; VaR 2,000,000Day 405: loss -352,995.08; VaR 2,000,000Day 406: loss -1,809,082.97; VaR 2,000,000Day 407: loss -1,043,496.97; VaR 2,000,000Day 408: loss -496,378.75; VaR 2,000,000Day 409: loss -546,730.92; VaR 2,000,000Day 410: loss -620,425.01; VaR 2,000,000Day 411: loss 385,087.96; VaR 2,000,000Day 412: loss -1,117,951.62; VaR 2,000,000Day 413: loss 584,207.71; VaR 2,000,000Day 414: loss -970,916.93; VaR 2,000,000Day 415: loss -644,907.82; VaR 2,000,000Day 416: loss 311,429.15; VaR 2,000,000Day 417: loss -147,285.76; VaR 2,000,000Day 418: loss -1,752,270.48; VaR 2,000,000Day 419: loss 776,271.55; VaR 2,000,000Day 420: loss 1,264,913.13; VaR 2,000,000Day 421: loss 806,797.3; VaR 2,000,000Day 422: loss -728,654.74; VaR 2,000,000Day 423: loss -865,951.18; VaR 2,000,000Day 424: loss -26,511.12; VaR 2,000,000Day 425: loss -112,154.18; VaR 2,000,000Day 426: loss 315,082; VaR 2,000,000Day 427: loss -794,326.07; VaR 2,000,000Day 428: loss -253,826.19; VaR 2,000,000Day 429: loss -394,373.67; VaR 2,000,000Day 430: loss 1,716,915.9; VaR 2,000,000Day 431: loss -1,734,787.09; VaR 2,000,000Day 432: loss 547,786.28; VaR 2,000,000Day 433: loss 1,558,031.96; VaR 2,000,000Day 434: loss -423,521.56; VaR 2,000,000Day 435: loss 676,024.71; VaR 2,000,000Day 436: loss 39,461.79; VaR 2,000,000Day 437: loss 330,921.46; VaR 2,000,000Day 438: loss -343,939.26; VaR 2,000,000Day 439: loss 474,087.13; VaR 2,000,000Day 440: loss 536,486.18; VaR 2,000,000Day 441: loss -440,355.97; VaR 2,000,000Day 442: loss 1,116,948.43; VaR 2,000,000Day 443: loss 341,756.26; VaR 2,000,000Day 444: loss 491,423.81; VaR 2,000,000Day 445: loss 65,820.41; VaR 2,000,000Day 446: loss 11,135.48; VaR 2,000,000Day 447: loss 69,175.48; VaR 2,000,000Day 448: loss -195,231.2; VaR 2,000,000Day 449: loss -187,692.25; VaR 2,000,000Day 450: loss 663,017.78; VaR 2,000,000Day 451: loss 908,457.98; VaR 2,000,000Day 452: loss 372,277.32; VaR 2,000,000Day 453: loss 380,929.78; VaR 2,000,000Day 454: loss 1,037,096.24; VaR 2,000,000Day 455: loss -1,609,133.36; VaR 2,000,000Day 456: loss -823,633.72; VaR 2,000,000Day 457: loss -1,334,464.89; VaR 2,000,000Day 458: loss 640,863.81; VaR 2,000,000Day 459: loss -171,148.9; VaR 2,000,000Day 460: loss -127,510.45; VaR 2,000,000Day 461: loss 55,095.57; VaR 2,000,000Day 462: loss 672,933.05; VaR 2,000,000Day 463: loss 396,046.27; VaR 2,000,000Day 464: loss -899,016.66; VaR 2,000,000Day 465: loss 391,930.92; VaR 2,000,000Day 466: loss -1,185,037.46; VaR 2,000,000Day 467: loss -197,940.41; VaR 2,000,000Day 468: loss -2,091,447.34; VaR 2,000,000Day 469: loss 497,852.72; VaR 2,000,000Day 470: loss 1,515,187.13; VaR 2,000,000Day 471: loss 92,005.26; VaR 2,000,000Day 472: loss -1,442,875.53; VaR 2,000,000Day 473: loss -669,975.61; VaR 2,000,000Day 474: loss 41,243.71; VaR 2,000,000Day 475: loss -29,195.37; VaR 2,000,000Day 476: loss -1,055,892.75; VaR 2,000,000Day 477: loss -584,987.04; VaR 2,000,000Day 478: loss 1,285,637.14; VaR 2,000,000Day 479: loss 568,328.07; VaR 2,000,000Day 480: loss -838,088.64; VaR 2,000,000Day 481: loss 682,924.58; VaR 2,000,000Day 482: loss -951,503.27; VaR 2,000,000Day 483: loss 548,319.42; VaR 2,000,000Day 484: loss -992,622.38; VaR 2,000,000Day 485: loss 458,331.3; VaR 2,000,000Day 486: loss -624,948.87; VaR 2,000,000Day 487: loss -739,814.35; VaR 2,000,000Day 488: loss 366,734.69; VaR 2,000,000Day 489: loss -126,148.63; VaR 2,000,000Day 490: loss 1,027,674.35; VaR 2,000,000Day 491: loss 168,416.23; VaR 2,000,000Day 492: loss 454,395.74; VaR 2,000,000Day 493: loss 314,213.73; VaR 2,000,000Day 494: loss 829,420.87; VaR 2,000,000Day 495: loss 909,002.49; VaR 2,000,000Day 496: loss 139,007.2; VaR 2,000,000Day 497: loss 202,641.05; VaR 2,000,000Day 498: loss -705,895.5; VaR 2,000,000Day 499: loss 112,287; VaR 2,000,000Day 500: loss -338,383.39; VaR 2,000,000

Cumulative exceptions versus expected

Solid steps count observed exceptions; the dashed line shows the expected count under the forecast confidence. This is a reference expectation, not a confidence band or a separate test.

Cumulative exceptions versus expectedAfter 500 simulated days, 4 exceptions were observed versus 5.00 expected.0.01.42.84.15.50125250375500Simulation dayDay 26: 1 observed; 0.26 expectedDay 63: 2 observed; 0.63 expectedDay 70: 3 observed; 0.70 expectedDay 335: 4 observed; 3.35 expectedDay 500: 4 observed; 5.00 expected

End of window: 4 observed / 5.00 expected.

Worked example: 99% VaR over 500 days

Use a one-day VaR forecast of 2,000,000, confidence 99%, backtest window 500 days, seed 7, realised volatility multiplier 1, stress window 0 days and stress multiplier 2 (inactive with no stress days). These are the default inputs with the unedited Java source. The seed fixes this constructed simulation; these are not observed market losses.

The expected count is 500 × (1 − 0.99) = 5 exceptions. The default Java run produces 4, or 0.8% of observations.

Default simulation: likelihood-ratio statistics and p-values
TestStatisticp-value
Kupiec coverage0.2170.641
Christoffersen independence0.0650.799
Conditional coverage0.2820.869

How to interpret the tests

At a 5% significance threshold, a p-value below 0.05 rejects the corresponding null hypothesis. None of these three tests rejects in this example. Kupiec checks frequency, independence checks the adjacent-day pattern, and conditional coverage adds their statistics, using a chi-square reference distribution with two degrees of freedom.

A large p-value does not prove the model is correct. With only four exceptions, evidence about clustering is sparse and the asymptotic p-values need caution. These tests do not implement Basel traffic-light zones or replace validation against independent market data.

For the mechanics, read VaR backtesting with Kupiec and Christoffersen tests. For two sequences with the same exception count but different timing, see why Kupiec can pass while Christoffersen rejects. To explore the forecast itself, use the VaR and Expected Shortfall calculator.

Implementation guide

Testing whether VaR exceptions behave as expected

Backtesting assesses whether realised P&L breaches a VaR forecast at the expected frequency and without suspicious clustering.

Core mechanism

The Kupiec test focuses on unconditional coverage; the Christoffersen framework adds a test of independence. Together they distinguish too many exceptions from a pattern that suggests changing risk dynamics.

Practical workflow

Freeze the forecast and realised-P&L definitions, maintain a complete exception record, test at a sufficient sample size, and investigate events rather than treating p-values as an automatic verdict.

Key details

A VaR backtest compares a forecast loss threshold with realised P&L over a sequence of dates. An exception occurs when the realised loss is worse than the forecast at the stated confidence level. The sign convention must be fixed first: this project reports VaR as a positive loss, so realised P&L is converted consistently before comparison.

The Kupiec proportion-of-failures test asks whether the observed exception rate is consistent with the nominal tail probability. It does not ask whether exceptions arrive independently. A model can have roughly the right number of exceptions while still failing when breaches cluster during changing volatility or correlation regimes.

The Christoffersen independence test uses the transition pattern between exception and non-exception days. Combining it with the coverage test produces a conditional-coverage diagnostic: both the frequency and time pattern must be plausible under the forecast model.

The result should be read with sample size in mind. At high confidence levels, exceptions are deliberately rare, so a short history has low statistical power. A p-value is evidence about a precisely defined null hypothesis, not an automatic model-approval decision.

The implementation exposes the exception count and the one- and two-degree-of-freedom chi-square survival probabilities used by these tests. A useful review also examines the individual exception dates, P&L attribution, data revisions and whether the VaR and realised-P&L horizons actually match.

Scope and review point

Statistical tests have low power on short samples. A validation process also needs P&L attribution, data-quality controls and qualitative review.