Backtest a Value at Risk model against realized returns.

A Value at Risk estimate is only useful if it is actually well-calibrated against reality. This method builds a rolling, out-of-sample VaR path (each VaR estimate uses only the window_size returns preceding it, never the return it is compared against) and tests it with Kupiec’s Proportion of Failures test (is the overall breach rate consistent with alpha?) and/or Christoffersen’s independence test (are breaches spread out over time, or do they cluster together?).

Regulators backtest VaR on the number of exceptions over the last 250 trading days (the Basel traffic-light approach at 99% confidence), and Kupiec’s test formalises the same question. With few exceptions the test has little power, so a model that passes is not necessarily well calibrated. Clustered exceptions, which Christoffersen’s test detects, are the usual failure of historical VaR when volatility rises.

The formula is as follows (with the default distribution=”historic”, alpha=0.05 and window_size=252):

\[\text{VaR}_{t} = \operatorname{Quantile}(\text{Return}_{t-252},\; ...,\; \text{Return}_{t-1} \text{;} \alpha)\] \[\text{Breach}_{t} = 1 \;\text{if Return}_{t} < \text{VaR}_{t},\;\; \text{else 0}\] \[\text{Kupiec Statistic} = - 2 \cdot \text{ln} \left[(1 - \alpha) ^{n - x} \cdot \alpha ^{x} / ((1 - x / n) ^{n - x} \cdot (x / n) ^{x})\right]\] \[\text{Christoffersen Statistic} = - 2 \cdot \text{ln} \left[L(\pi) / L(\pi_{01},\; \pi_{11})\right]\]

Where n is the number of out-of-sample VaR estimates and x the number of breaches. L(pi) is the likelihood of the breach sequence with a single breach probability pi, and L(pi_01, pi_11) that of a first-order Markov chain in which the breach probability depends on whether the previous period was a breach. Both statistics are compared with a χ² distribution with one degree of freedom for the p-value.

For more information about the methods, see the following papers:

  • Kupiec, P.H. (1995). “Techniques for Verifying the Accuracy of Risk Measurement Models.” The Journal of Derivatives, 3(2), 73-84.
  • Christoffersen, P.F. (1998). “Evaluating Interval Forecasts.” International Economic Review, 39(4), 841-862.

Also known as: VaR validation, VaR backtest, POF test.

Value at Risk Backtest in Python or via the MCP

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_var_backtest as shown below.

from financetoolkit import Toolkit

toolkit = Toolkit(
    ["EURUSD", "USDJPY", "GBPUSD"],
    api_key="FINANCIAL_MODELING_PREP_KEY",
    start_date="2021-01-01",
    end_date="2025-12-31",
)

toolkit.risk.get_var_backtest(test="kupiec")

Or, after setting up the Finance Toolkit MCP, ask:

Does a rolling one-year 95% historical Value at Risk pass Kupiec's backtest for the euro against the dollar, the dollar against the yen, the pound against the dollar and the S&P 500 from 2021 to 2025?

Which returns:

  EURUSD USDJPY GBPUSD Benchmark
Kupiec Statistic 8.3686 1.8323 2.2039 0.0836
P-Value 0.0038 0.1759 0.1377 0.7725

Parameters

get_var_backtest accepts the following parameters.

ParameterTypeDescriptionExample
periodstrThe data frequency for returns (daily, weekly, monthly, quarterly or yearly). Defaults to “daily”, since window_size is expressed in return observations of this frequency (252 only means “about one year” when period is daily)."weekly"
distributionstrThe distribution to use for the rolling VaR estimates, one of “historic”, “gaussian”, “studentt” or “evt”. Defaults to “historic”. “studentt” and “evt” fit their distribution to every window, which takes minutes for years of daily data, where “historic” and “gaussian” take well under a second."studentt"
alphafloatThe tail probability for the VaR estimates (e.g., 0.05 for the 95% confidence level). Defaults to 0.05. With “evt” it can be at most 0.05, since the Generalized Pareto Distribution is fitted above the 95th percentile of losses; a larger alpha gives an empty result (with a logged error).0.01
window_sizeintThe rolling window size (in number of return observations) used to estimate each VaR value. Defaults to 252 (approximately one trading year of daily returns).52
teststrWhich test(s) to run, one of “kupiec”, “christoffersen” or “both”. Defaults to “both”."kupiec"
roundingint | NoneThe number of decimals to round the results to. Defaults to None.2
Share