vartests is a Python library with statistical tests to backtest Value at Risk (VaR) models and other quantile-based risk measures (e.g. Drawdown at Risk). Coverage tests only need the series of violations and the probability of violation, so they work for any risk measure that is a quantile.
| Group | Test | Question |
|---|---|---|
| Coverage | kupiec_test (Kupiec, 1995) |
Is the failure rate the nominal one? (chi-square approximation, two-sided) |
| Coverage | binomial_test |
Same question, exact binomial distribution (one- or two-sided) |
| Coverage | mid_p_test (Lancaster, 1961) |
Exact binomial with mid-p correction, closer to the nominal size |
| Coverage | poisson_binomial_test |
Exact test when each observation has its own probability of violation |
| Coverage | binomial_power |
Size and power of the binomial test for a given sample |
| Independence | duration_test (Christoffersen and Pelletier, 2004) |
Do violations have no memory, i.e. no clusters? |
| Distribution | berkowitz_tail_test (Berkowitz, 2001) |
Is the tail of the forecast distribution right? |
| Distribution | zero_mean_test |
Is the mean of the PnL zero? (assumption of parametric VaR) |
Installation
pip install vartests
Or the latest version from GitHub:
pip install https://github.com/rafa-rod/vartests/archive/refs/heads/main.zip
Requires Python 3.10 or later.
Results
Every test returns a dictionary with the same main keys:
| Key | Content |
|---|---|
null hypothesis |
H0 being tested |
statistic |
test statistic (for exact tests, the number of violations) |
p-value |
p-value |
decision |
"Reject H0", "Fail to reject H0" or "Not enough data" |
Some tests add specific keys, such as critical value (chi-square tests), non-rejection region and size (exact tests) or the estimated parameters.
Example
The file media/Example.xlsx has the PnL of a portfolio, its VaR at 99% and the violations (1 when the loss exceeds the VaR). Reading Excel files requires openpyxl:
import pandas as pd
import vartests
data = pd.read_excel("media/Example.xlsx", index_col=0)
violations = data["Violations"]
pnl = data["PnL"]
All functions accept a list, NumPy array, Series or single-column DataFrame.
Coverage
vartests.failure_rate(violations)
vartests.kupiec_test(violations, var_conf_level=0.99, conf_level=0.95)
vartests.binomial_test(violations, var_conf_level=0.99, conf_level=0.95, alternative="greater")
vartests.mid_p_test(violations, var_conf_level=0.99, conf_level=0.95, alternative="greater")
alternative="greater" (default of the exact tests) tests underestimation of risk, i.e. too many violations. Use "two-sided" to also detect a conservative model, or "less" for that alone.
With zero violations, the Kupiec and binomial tests do not reject automatically: the decision depends on the number of observations and on the nominal rate. For example, at a 5% significance level, Kupiec does not reject 0 to 1 violations in 250 days for a 99.9% VaR, but requires 1 to 6 for a 99% VaR (Giambiagi, ch. 10, Table 10.1).
Because the binomial distribution is discrete, the exact test rejects a correct model less often than the nominal level. The size key reports the actual probability; mid_p_test brings it closer to the nominal level, with no guarantee of staying below it. A common practice is to decide with the exact test and report the mid-p as a complement.
A non-rejection is only informative if the test has power. binomial_power shows the chance of detecting a wrong model:
vartests.binomial_power(n=60, var_conf_level=0.975, true_failure_rate=[0.05, 0.075, 0.10])
# {'non-rejection region': (0, 4), 'size': 0.017, 'power': {0.05: 0.18, 0.075: 0.47, 0.1: 0.73}}
When the probability of violation differs across observations (a VaR with a varying level, or the expected coverage of an estimator at each episode of a Drawdown at Risk), use the Poisson-binomial test with one probability per observation:
vartests.poisson_binomial_test(violations, probabilities, conf_level=0.95, alternative="greater")
Independence
vartests.duration_test(violations, conf_level=0.95)
Under H0 the time between violations is exponential (Weibull shape b = 1); b < 1 indicates clusters. At least two durations, one of them uncensored, are required; otherwise the decision is "Not enough data".
Distribution
The Berkowitz test checks whether losses beyond the VaR have the size a normal VaR model predicts, not only how often they happen. It takes the realized PnL and the VaR forecast for each day, in the same unit:
vartests.berkowitz_tail_test(pnl, var, var_conf_level=0.99, conf_level=0.95)
The test assumes a normal VaR with zero mean, so the forecast standard deviation is |VaR_t| / z, where z is the normal quantile of var_conf_level. The VaR can be reported as a positive loss or a negative return, as long as all values have the same sign. In the example file, the test rejects H0: the largest loss is about 3.3 times the VaR, much more than a normal tail allows.
Parametric models such as EWMA and GARCH often assume zero mean:
vartests.zero_mean_test(pnl, conf_level=0.95)
Changes in 0.4.0
berkowitz_tail_test(pnl, var, ...)now takes the realized PnL and the VaR series, instead of the PIT, and assumes a normal VaR with zero mean.berkowtiz_tail_test(misspelled, deprecated in 0.3.0) was removed.
Changes in 0.3.0
Version 0.3.0 fixes errors and changes the output of some functions:
kupiec_testno longer rejects automatically with zero violations and returns the p-value.duration_testfixes the likelihood when the series does not end with a violation (the usual case); p-values change in most samples.zero_mean_testworks with pandas 2 and 3 and accepts Series.berkowitz_tail_testreceives the PIT of the model being tested. Results differ slightly from 0.2.x because the conditional mean is now forecast without look-ahead.- Result keys are standardized (
statistic,p-value,decision). - New tests:
binomial_test,mid_p_test,poisson_binomial_testandbinomial_power.
References
- Berkowitz, J. (2001). Testing density forecasts, with applications to risk management. Journal of Business & Economic Statistics, 19(4), 465-474.
- Christoffersen, P. and Pelletier, D. (2004). Backtesting Value-at-Risk: a duration-based approach. Journal of Financial Econometrics, 2(1), 84-108.
- Giambiagi, F. (Org.). Derivativos e risco de mercado. Elsevier. Ch. 10 (Backtesting).
- Kupiec, P. (1995). Techniques for verifying the accuracy of risk measurement models. Journal of Derivatives, 3(2), 73-84.
- Lancaster, H. O. (1961). Significance tests in discrete distributions. Journal of the American Statistical Association, 56(294), 223-234.
Metadata
Release files for vartests 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vartests-0.4.0.tar.gz | 15.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vartests-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.5 kB
Release files / vartests-0.4.0.tar.gz
| Download URL | vartests-0.4.0.tar.gz |
|---|---|
| Size | 15.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
32cc0f26753d5978b4b5abe8c04bdcfdcc2f5b2d95bbd08db895fbaaea97e243
|
|
BLAKE2b-256 checksum How to use checksums |
f1b5b0b09ef92b8f67d6ba8af5d795ece3ce26149934dbe377385b9d2843701c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.5.1 CPython/3.11.17 Linux/6.17.0-1022-azure
|
Release files / vartests-0.4.0-py3-none-any.whl
| Download URL | vartests-0.4.0-py3-none-any.whl |
|---|---|
| Size | 14.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5b4b12a37498afa7654d2b33bb94c50044a404ef3f4880c5688a0e7092c72061
|
|
BLAKE2b-256 checksum How to use checksums |
ff4c68af1cb00df888c705208a8e57c8a373822540362b41ff02502c3eee9338
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.5.1 CPython/3.11.17 Linux/6.17.0-1022-azure
|