Skip to content

Backtesting VaR and ES: Kupiec, Christoffersen, and the Traffic Light

Every VaR backtest compresses the hit sequence into one number, and what it throws away decides what it cannot catch. From Kupiec to Christoffersen to DQ, recovering information step by step — and why ES still has no consensus test.

1 of 12 ~14 min

A risk model is a story you tell about tomorrow based on today. Like any story, it can be wrong. The only way to know whether your VaR/ES model is calibrated to reality is to wait for tomorrow and check whether the loss exceeded the model’s prediction.

Backtesting is the formal statistical machinery for that check. Given a series of VaR forecasts VtV_t, t=1,…,Tt = 1, \ldots, T, and realized losses LtL_t, count how often Lt>VtL_t > V_t. A model claiming 99% VaR should breach about 1% of the time. Consistently 3% means the model underestimates risk; consistently 0.3% means you are over-reserving capital (or getting lucky).

Counting breaches is only the starting point, though. The through-line of this article is an information problem: the hit sequence (I1,…,IT)(I_1, \ldots, I_T) contains all the evidence of miscalibration there is, and every test must compress it into a single statistic — whatever the compression discards is exactly the failure mode that test cannot catch. Kupiec keeps only the total count and throws away timing. Christoffersen recovers adjacent transitions and catches clustering. The DQ test pulls in the entire conditioning information set. And the difficulty with ES is more fundamental still: it has no binary “hit” event at all, so the raw material to compress is itself hard to define. We walk through the tests in that order, then look at what regulators actually run.

Notation for Backtesting

Let LtL_t be the realized loss over the holding period at time tt, and Vt=VaR^α(Lt)V_t = \widehat{\text{VaR}}^\alpha(L_t) the model’s VaR forecast made at the start of the period, using only information up to t−1t-1. Define the hit sequence:

It=1{Lt>Vt}I_t = \mathbb{1}\{L_t > V_t\}

A hit (It=1I_t = 1) means the realized loss exceeded the predicted VaR. Under the null hypothesis of a correctly calibrated model,

P(It=1)=1−α\mathbb{P}(I_t = 1) = 1 - \alpha

For α=0.99\alpha = 0.99 the breach probability is 1%. Note that the null is really two claims: the breach probability is right (unconditional coverage), and the ItI_t are independent of each other (a breach today does not make a breach tomorrow more likely). A calibrated model should emit an iid Bernoulli(1−α)(1-\alpha) sequence. A test’s job is to use the observed hit sequence to refute one or both claims.

The Kupiec Proportion-of-Failures (POF) Test: Count Only

The simplest compression squeezes the whole sequence into one number: the hit count T1=∑tItT_1 = \sum_t I_t. Under the null, T1∼Binomial(T,1−α)T_1 \sim \text{Binomial}(T, 1-\alpha), and the log-likelihood-ratio test (Kupiec, 1995; also called the unconditional coverage test) is

LRPOF=−2log⁡(1−α)T1 αT−T1p^ T1(1−p^)T−T1\text{LR}_{\text{POF}} = -2 \log \frac{(1 - \alpha)^{T_1}\, \alpha^{T - T_1}}{\hat p^{\,T_1} (1 - \hat p)^{T - T_1}}

where p^=T1/T\hat p = T_1 / T is the empirical breach rate — the numerator is the likelihood if the breach probability is exactly 1−α1-\alpha, the denominator the likelihood when the data speak for themselves. Under the null, LRPOF∼χ12\text{LR}_{\text{POF}} \sim \chi^2_1. Expanded:

LR=2[T1log⁡(T1T(1−α))+(T−T1)log⁡(T−T1Tα)]LR = 2 \left[ T_1 \log\left(\frac{T_1}{T(1-\alpha)}\right) + (T - T_1) \log\left(\frac{T - T_1}{T\alpha}\right) \right]

The 95% critical value of χ12\chi^2_1 is 3.84; reject at 5% when LR>3.84LR > 3.84.

Try it with numbers. Take α=0.99\alpha = 0.99, T=250T = 250 days, T1=5T_1 = 5 hits: the empirical breach rate is 5/250=2%5/250 = 2\%, double the expected 1%. The statistic is

LR=2[5log⁡(52.5)+245log⁡(245247.5)]≈2[5⋅0.693+245⋅(−0.0102)]≈2[3.47−2.49]=1.96LR = 2 \left[ 5 \log\left(\frac{5}{2.5}\right) + 245 \log\left(\frac{245}{247.5}\right) \right] \approx 2 [5 \cdot 0.693 + 245 \cdot (-0.0102)] \approx 2[3.47 - 2.49] = 1.96

Below 3.84 — the test does not reject, even at twice the expected breach rate. Now take T1=10T_1 = 10 (a 4% rate):

LR=2[10log⁡(10/2.5)+240log⁡(240/247.5)]=2[10⋅1.386+240⋅(−0.0304)]≈2[13.86−7.30]=13.12LR = 2 \left[ 10 \log(10/2.5) + 240 \log(240/247.5) \right] = 2 [10 \cdot 1.386 + 240 \cdot (-0.0304)] \approx 2[13.86 - 7.30] = 13.12

Well past 3.84: strong rejection.

Those two worked examples already reveal the test’s temperament. With T=250T = 250 and α=0.99\alpha = 0.99, the expected hit count is 2.5 — there is simply not much information in the sample — and the power against small deviations is very low: even at a true breach rate of twice the expected, the rejection rate is only around 30%. That is partly deliberate: a 250-day window is short, and an aggressive test would generate constant regulatory noise. But the cost is on the table — a model can be substantially miscalibrated without ever triggering the alarm.

The Basel Traffic Light: Replacing the Test With a Lookup Table

Regulators did not adopt Kupiec’s test directly. The framework accompanying the 1996 Amendment to the Capital Accord gave a blunter instrument instead:

ColorTierHits in 250 daysInterpretation
Green10–4Model passes.
Yellow25–9Intermediate zone; extra scrutiny, capital multiplier stepped up.
Red310+Model fails. Must improve; capital multiplier maxed.

The traffic light looks like a test but is not one in any formal statistical sense — it is a regulatory rule of thumb, and its virtues are zero ambiguity and comparability across banks. Setting it next to Kupiec is instructive. The expected count is 2.5 and the yellow zone begins at 5, yet Kupiec at the 5% level does not reject at 5 or 6 hits — rejection starts at 7. So the bottom of the yellow zone is stricter than the statistical test, while its top still leaves the model room to argue; the red zone (10+) is unambiguous under either standard. Landing in yellow or red is not free: the market-risk capital multiplier has a base of 3, the yellow zone adds 0.4 to 0.85 depending on the count, and red adds the full 1, pushing the multiplier to 4. A persistent stay in red (six months or more) typically triggers a supervisory directive to fix the model.

FRTB did not retire this machinery. The exception-count traffic light survives — now counting exceptions at both the 99% and 97.5% VaR levels — with a P&L attribution test added to reconcile front-office and risk-model views. Notice what is being backtested throughout: VaR exceptions, never ES itself. That thread gets picked up below.

Christoffersen’s Independence Test: Recovering Time

When Kupiec compresses the sequence to a count, it discards when the hits happened. A model with zero hits for 200 days and then 5 hits inside 50 days still totals 5 — likely green zone, invisible to Kupiec. Yet clustering is precisely the failure mode most worth detecting: a model that fails systematically in a particular regime is far more dangerous than one that misses iid, because its bad days arrive bunched together, on exactly the days you need the model most.

Christoffersen’s (1998) independence test recovers the cheapest piece of the discarded timing information: adjacent-day transitions. It compares the likelihood of two models — an independence model with a single parameter π\pi (hit probability the same everywhere) against a first-order Markov model with two parameters π0,π1\pi_0, \pi_1 (today’s hit probability after a no-hit day versus after a hit day):

LRIND=−2log⁡L0L1=−2[log⁡L0−log⁡L1]LR_{\text{IND}} = -2 \log \frac{L_0}{L_1} = -2 \left[\log L_0 - \log L_1\right]

Written out:

LRIND=−2log⁡πT01+T11(1−π)T00+T10π0T01(1−π0)T00⋅π1T11(1−π1)T10LR_{\text{IND}} = -2 \log \frac{\pi^{T_{01} + T_{11}} (1-\pi)^{T_{00} + T_{10}}}{\pi_0^{T_{01}} (1-\pi_0)^{T_{00}} \cdot \pi_1^{T_{11}} (1-\pi_1)^{T_{10}}}

where TijT_{ij} counts transitions with It−1=iI_{t-1} = i and It=jI_t = j, π=(T01+T11)/(T00+T01+T10+T11)\pi = (T_{01} + T_{11}) / (T_{00} + T_{01} + T_{10} + T_{11}) is the overall empirical hit rate, and πi=Ti1/(Ti0+Ti1)\pi_i = T_{i1} / (T_{i0} + T_{i1}) is the empirical probability of a hit today given yesterday’s state ii. Under independence, π0=π1=π\pi_0 = \pi_1 = \pi, the two likelihoods coincide, and the statistic is zero; under clustering, π1≫π0\pi_1 \gg \pi_0 and the statistic blows up. The asymptotic distribution is χ12\chi^2_1.

Let’s actually compute the scenario Kupiec could not see. Take 250 days with hits on days {201,202,203,209,215}\{201, 202, 203, 209, 215\} — five hits, all crammed into the last 50 days. Counting the 249 transitions: T01=3T_{01} = 3 (days 201, 209, 215, each preceded by a no-hit day), T11=2T_{11} = 2 (days 202, 203), T10=3T_{10} = 3 (days 204, 210, 216), T00=241T_{00} = 241. So π=5/249≈0.0201\pi = 5/249 \approx 0.0201, π0=3/244≈0.0123\pi_0 = 3/244 \approx 0.0123, and π1=2/5=0.4\pi_1 = 2/5 = 0.4 — after a hit day, the empirical probability of another hit is over thirty times the baseline. Plugging in:

LRIND=−2[(5log⁡0.0201+244log⁡0.9799)−(3log⁡0.0123+241log⁡0.9877+2log⁡0.4+3log⁡0.6)]≈9.89LR_{\text{IND}} = -2\left[\big(5 \log 0.0201 + 244 \log 0.9799\big) - \big(3 \log 0.0123 + 241 \log 0.9877 + 2 \log 0.4 + 3 \log 0.6\big)\right] \approx 9.89

At 9.89, independence is decisively rejected — while the Kupiec statistic on the same data is still 1.96, sailing through. That is what “compressing away time” costs, in concrete numbers.

Christoffersen also proposed combining the two tests to examine coverage and independence jointly:

LRCC=LRPOF+LRINDLR_{\text{CC}} = LR_{\text{POF}} + LR_{\text{IND}}

which is χ22\chi^2_2 under the null (5% critical value 5.99). In the example above, LRCC=1.96+9.89=11.85LR_{\text{CC}} = 1.96 + 9.89 = 11.85 — the combined test rejects too. As an aside, Christoffersen & Pelletier (2004) later built a duration-based test on the distribution of times between hits — same idea, since under independence the gaps should be memoryless — with better power against some alternatives.

The DQ Test: Recovering the Whole Information Set

Adjacent transitions are still only a sliver of the timing information. Hits may not depend on yesterday’s hit yet depend on yesterday’s VaR level, on volatility, or on anything else visible at t−1t-1 — and if today’s breach can be predicted from known information at all, the model has not used its information cleanly. The Dynamic Quantile (DQ) test of Engle & Manganelli (2004) turns that observation into a regression: regress the centered hit indicator on its own lag and any other candidate predictors,

Hitt−(1−α)=β0+β1 Hitt−1+β2 VaRt+ϵt\text{Hit}_t - (1 - \alpha) = \beta_0 + \beta_1\, \text{Hit}_{t-1} + \beta_2\, \text{VaR}_t + \epsilon_t

where Hitt=1{Lt>VaR^t}\text{Hit}_t = \mathbb{1}\{L_t > \widehat{\text{VaR}}_t\}. Under calibration, the left-hand variable should be uncorrelated with everything known at t−1t-1, so test all coefficients jointly zero:

DQ=β^⊤X⊤X β^α(1−α)DQ = \frac{\hat{\boldsymbol\beta}^\top \mathbf{X}^\top \mathbf{X}\, \hat{\boldsymbol\beta}}{\alpha(1-\alpha)}

with X\mathbf{X} the T×pT \times p regressor matrix (here p=3p = 3: intercept, lagged hit, VaR level) and β^\hat{\boldsymbol\beta} the OLS estimates; asymptotically χp2\chi^2_p under the null. The regressor set can be extended at will, which is the point of DQ: whatever form of predictability you want to test for, put the corresponding variable in. Its power against dependence and misspecification is generally higher than Kupiec’s or Christoffersen’s.

Backtesting Expected Shortfall: No More “Hit”

Everything so far stood on one foundation: a clean binary event — did the loss exceed VaR — whose sequence you compress and test. ES removes that foundation. ES is a quantity (the average loss in the worst 1−α1-\alpha fraction), not a threshold; what needs testing is a conditional expectation, and the conditioning event itself depends on the VaR forecast — the object under test is entangled with its own conditioning. That is the structural reason ES backtesting is harder than VaR backtesting, and the reason there is still no consensus regulatory test. The existing approaches run along three lines.

The first is McNeil & Frey (2000): look only at breach days and check the mean of the excess loss. On days with Lt>V^tL_t > \hat{V}_t, define the exceedance residual

Zt=Lt−ES^tZ_t = L_t - \widehat{\text{ES}}_t

Under calibration, E[Zt∣Lt>V^t]=0\mathbb{E}[Z_t \mid L_t > \hat{V}_t] = 0 — the average loss on breach days should equal the predicted ES. The test is essentially a tt-test of ZtZ_t restricted to hit days:

tES=Zˉ(1)S(1)/N(1)t_{\text{ES}} = \frac{\bar Z_{(1)}}{S_{(1)} / \sqrt{N_{(1)}}}

with Zˉ(1)\bar Z_{(1)}, S(1)S_{(1)}, N(1)N_{(1)} the mean, standard deviation, and count over the hit-day subset (the original paper bootstraps the p-value, since tail residuals are far from normal). This is the most widely used approach in industry, and its weakness is plain: N(1)N_{(1)} is the hit count — about 2.5 in 250 days — so the test rests on a handful of observations.

The second line, Du & Escanciano (2017), works around the “information only on breach days” bind. Use the probability integral transform ut=Ft(Lt)u_t = F_t(L_t) to turn every day’s loss into a uniform variable on [0,1][0,1], then define the cumulative violation

Ht=(ut−α)+1−αH_t = \frac{(u_t - \alpha)^+}{1 - \alpha}

which can be read as integrating the hit indicators over all VaR levels in (α,1](\alpha, 1] — a non-breach day contributes 0, and a deeper breach contributes closer to 1. Under calibration, the HtH_t are iid with E[Ht]=(1−α)/2\mathbb{E}[H_t] = (1-\alpha)/2, so the unconditional test is a tt-test on the mean of HtH_t and the conditional test checks its autocorrelation. Unlike a breach count, HtH_t retains the depth of each breach — exactly the part ES cares about.

The third line is the direct test of Acerbi & Szekely (2014). Their second statistic, in loss notation, is

Z2=1T(1−α)∑t=1TLt ItES^t−1Z_2 = \frac{1}{T(1-\alpha)} \sum_{t=1}^T \frac{L_t\, I_t}{\widehat{\text{ES}}_t} - 1

Under calibration, E[Z2]=0\mathbb{E}[Z_2] = 0; Z2>0Z_2 > 0 says realized tail losses exceed the ES forecasts (ES understated), Z2<0Z_2 < 0 says ES was overstated. Critical values come from simulating under the model’s own forecast distributions — which is both the practical cost of the method and its honesty: the test’s power depends on being willing to generate the null distribution from your own model.

The regulatory picture closes the thread left open earlier: FRTB computes capital from ES but still backtests by counting VaR exceptions. None of the three lines has been enshrined as a standard; most banks run several ES backtests in parallel internally and look for agreement in direction.

Run It Yourself

import numpy as np
from scipy import stats

# Toy VaR backtest
np.random.seed(3)
T = 250
alpha = 0.99

# True model is correctly calibrated
losses = -np.random.normal(0.001, 0.015, T)
var_pred = 2.326 * 0.015 - 0.001     # 99% VaR assuming correct model
hits = (losses > var_pred).astype(int)

# Kupiec POF
T1 = hits.sum()
LR_pof = 2 * (T1 * np.log(T1 / (T * (1-alpha))) +
              (T - T1) * np.log((T - T1) / (T * alpha)))
print(f"Kupiec LR: {LR_pof:.3f}  chi2_1, 0.95 = 3.84")

# Christoffersen independence: count transitions
T00 = ((hits[:-1] == 0) & (hits[1:] == 0)).sum()
T01 = ((hits[:-1] == 0) & (hits[1:] == 1)).sum()
T10 = ((hits[:-1] == 1) & (hits[1:] == 0)).sum()
T11 = ((hits[:-1] == 1) & (hits[1:] == 1)).sum()
pi0 = T01 / (T00 + T01 + 1e-12) if (T00 + T01) > 0 else 0
pi1 = T11 / (T11 + T10 + 1e-12) if (T11 + T10) > 0 else 0
pi  = (T1 - hits[0]) / (T - 1)   # leave-one-out style

LR_ind = 0
if (T00 + T01) > 0 and (T11 + T10) > 0 and pi not in (0, 1) and pi0 not in (0, 1) and pi1 not in (0, 1):
    LR_ind = -2 * (np.log((1 - pi)**(T00+T10) * pi**(T01+T11)) -
                   np.log((1-pi0)**T00 * pi0**T01 * (1-pi1)**T10 * pi1**T11))
print(f"Christoffersen LR_ind: {LR_ind:.3f}")
print(f"Christoffersen LR_cc = LR_pof + LR_ind: {LR_pof + LR_ind:.3f}")

# Traffic light
print(f"Total hits: {T1}  ({T1/T*100:.2f}%)  zone: " + 
      ("green" if T1 <= 4 else "yellow" if T1 <= 9 else "red"))

Run it under a few different seeds and watch how much the statistics swing — that is the sample size of 2.5 expected hits talking. With a correctly calibrated model, LRPOF\text{LR}_{\text{POF}} stays below 3.84 about 95% of the time, and the red light only comes on under serious miscalibration.

The flip side of that variability is that false positives are unavoidable. A backtest is a binary rejection: with a genuinely correct model, a 5% significance level still fails roughly 50 out of every 1000 models by pure chance. Banks run dozens of VaR models, so on any given day one of them may be failing a test coincidentally. The right response to a single failure is therefore investigation, not a capital add-on: recompute the VaR by hand for the failing days and check the inputs, confirm the realized losses themselves were computed correctly, and only then diagnose — regime change, model misspecification, or a bad price that crept into the historical series. Isolated failures are usually flukes; persistent failure (two consecutive months or more) is what points to genuine model error.

What Backtesting Can and Cannot Tell You

Teams that run backtests on real data tend to see three recurring patterns. Well-built models breach close to expectation — 2 to 4 hits per year on a 99% VaR is normal, while 5 to 10 hits year after year is miscalibration. Hits carry a little clustering almost everywhere — volatility clustering in returns propagates into VaR hit clustering, and Christoffersen’s test sees it. And the pattern that matters most: even good models fail collectively at regime shifts — 2008 and 2020 are the standing examples — with hits both clustered and oversized, the model believing it is operating in a normal world when the world has already changed.

That third pattern marks the boundary of what backtesting can do. A backtest can only reconcile a model with data that has already happened; an event that resembles nothing in the past is undetectable in principle. No refinement of the test fixes this — it is what stress testing, scenario analysis, and reverse stress testing exist for, and that is another article’s subject.

For anyone reading a risk report, backtests have one more homely use: they are the test of the risk team itself. If someone quotes a 99% VaR of USD 5M while the 250-day backtest shows a 10% breach rate, the number is wrong in the direction that hurts you — the thing to interrogate is the model, not the market. One expectation is worth memorizing: 99% VaR, 250 trading days, hits should land around 2.5. Five hits: yellow zone, investigate. Ten: red zone, the model is wrong. Zero or one: the model is too conservative and is tying up capital — conservatism is not free either.

References

Acerbi, C., & Szekely, B. (2014). Backtesting expected shortfall. Risk Magazine, December 2014.

Basel Committee on Banking Supervision. Supervisory Framework for the Use of Backtesting in Conjunction with the Internal Models Approach to Market Risk Capital Requirements. 1996.

Christoffersen, P. F. (1998). Evaluating interval forecasts. International Economic Review, 39(4), 841-862.

Christoffersen, P. F., & Pelletier, D. (2004). Backtesting value-at-risk: a duration-based approach. Journal of Financial Econometrics, 2(1), 84-108.

Du, Z., & Escanciano, J. C. (2017). Backtesting expected shortfall: accounting for tail risk. Management Science, 63(4), 940-958.

Engle, R. F., & Manganelli, S. (2004). CAViaR: conditional autoregressive value at risk by regression quantiles. Journal of Business & Economic Statistics, 22(4), 367-381.

Kupiec, P. H. (1995). Techniques for verifying the accuracy of risk measurement models. Journal of Derivatives, 3(2), 73-84.

McNeil, A. J., & Frey, R. (2000). Estimation of tail-related risk measures for heteroscedastic financial time series: an extreme value approach. Journal of Empirical Finance, 7(2-3), 271-300.

McNeil, A. J., Frey, R., & Embrechts, P. (2015). Quantitative Risk Management: Concepts, Techniques and Tools (rev. ed.). Princeton University Press. Ch. 13.