A data-agnostic backtesting toolbox for VaR and other risk measures
Project description
risk-backtest
A data-agnostic Python toolbox for Value-at-Risk (VaR) backtesting, VaR/ES estimation, calibration statistics, cluster-adjusted hypothesis tests, and Basel regulatory zone classification.
Designed for portfolio risk teams that need to validate internal models against UCITS / CSSF / BCBS regulations and produce graphical reports.
Installation
pip install risk-backtest # core
pip install risk-backtest[parallel] # + joblib
pip install risk-backtest[models] # + arch (for GARCH family)
pip install risk-backtest[plotting] # + matplotlib
pip install risk-backtest[dev] # everything + pytest, build, twine
Quick start
import numpy as np
from risk_backtest import run_backtest, BacktestConfig
returns = np.random.normal(0, 0.01, 500)
var = np.full(500, 0.02)
result = run_backtest(returns, var, window_sizes=[250])
print(result.summary)
print(result.pass_rates)
Public API at a glance
| Group | Function / Class | Purpose |
|---|---|---|
| High-level | run_backtest |
One-call backtest (single or batch, parallel-ready) |
BacktestResult |
Container with summary, pass_rates, config |
|
| Config | BacktestConfig |
Risk measure + horizon + scaling |
RiskMeasure, Horizon |
Enums | |
| Statistical tests | VaRBacktest |
Binomial / Z / Kupiec / Christoffersen / Joint / Martingale |
| Calibration | calculate_bias_and_q_statistics |
Rolling bias & Q-statistic (single series) |
calculate_bias_q_batch |
Same, applied across a fund-level DataFrame | |
| Cluster detection | detect_cluster |
Identify breach clusters by proximity |
count_clusters |
Count cluster starts and isolated breaches | |
| Sensitivity | cluster_threshold_sensitivity |
Sweep cluster thresholds and compare pass-rates |
| Expected Shortfall | historical_es |
Empirical CVaR |
normal_es |
Closed-form Gaussian ES | |
es_from_var_series |
Mean breach magnitude vs forecast VaR | |
| Regulatory | basel_traffic_light |
Green/Yellow/Red zone + capital multiplier add-on |
TrafficLightResult |
Dataclass result | |
| VaR estimators | historical_var |
Empirical quantile |
normal_var |
Parametric Gaussian | |
cornish_fisher_var |
Skew/kurtosis-adjusted quantile | |
evt_var (EVTResult) |
Peaks-Over-Threshold / GPD | |
garch_var (GARCHResult) |
GARCH / GJR / EGARCH / APARCH | |
recursive_garch_variance |
Dependency-free variance recursion | |
estimate_var |
Dispatcher — run several methods at once | |
VAR_METHODS, GARCH_MODELS |
Available method names | |
| Utilities | compute_overshoots |
Build the breach boolean series |
create_windows |
Trailing-window slices | |
validate_inputs |
Align/length-check arrays | |
| Plotting | plot_var_vs_returns |
Report-style returns + VaR bands chart |
High-level backtesting
Single fund
from risk_backtest import run_backtest
result = run_backtest(returns, var, window_sizes=[250, 500])
result.summary # MultiIndex (name, window_size, cluster_adj)
result.pass_rates # % of tests passing at 5% significance
Batch + parallel
ret_dict = {"Fund_A": ret_a, "Fund_B": ret_b}
var_dict = {"Fund_A": var_a, "Fund_B": var_b}
result = run_backtest(ret_dict, var_dict,
window_sizes=[250, 500, 750],
n_jobs=-1)
Custom risk measure (annual 1-σ volatility)
from risk_backtest import BacktestConfig, run_backtest
config = BacktestConfig(
risk_measure="volatility",
confidence_level=0.8413, # 1-sigma one-sided
horizon="annual", # sqrt-T scaled to daily internally
)
result = run_backtest(returns, annual_vol, config=config, window_sizes=[252])
Statistical tests (VaRBacktest)
Six tests, each run twice — once on raw breaches and once cluster-adjusted:
| Test | What it measures |
|---|---|
| Binomial | Exact probability of observed breach count |
| Z-test | Normal approximation of breach frequency |
| Kupiec (LR-UC) | Unconditional coverage |
| Christoffersen (LR-IND) | Independence of breaches (first-order Markov) |
| Joint (LR-CC) | Coverage + independence combined |
| Martingale | Ljung-Box style autocorrelation in breach series |
from risk_backtest import VaRBacktest
bt = VaRBacktest(P=0.01)
res = bt.run_tests(overshoots, T=250)
print(res["Kupiec_P"], res["Christoffersen_P"])
VaR estimation
from risk_backtest import (
historical_var, normal_var, cornish_fisher_var,
evt_var, garch_var, estimate_var,
)
historical_var(returns, confidence_level=0.99)
normal_var(returns, confidence_level=0.99)
cornish_fisher_var(returns, confidence_level=0.99)
evt = evt_var(returns, threshold_quantile=0.90)
print(evt.var, evt.es, evt.shape, evt.scale)
garch = garch_var(returns, model_type="gjr-garch", dist="t")
print(garch.var, garch.conditional_vol, garch.persistence)
# Compare several methods at once
estimate_var(returns, methods=["historical", "cornish_fisher", "evt"])
Dependency-free conditional-variance recursion using fitted parameters:
from risk_backtest import recursive_garch_variance
sigma2 = recursive_garch_variance(returns,
omega=garch.omega,
alpha=garch.alpha,
beta=garch.beta,
gamma=garch.extra_params.get("gamma", 0),
model_type="gjr-garch")
Expected Shortfall
from risk_backtest import historical_es, normal_es, es_from_var_series
historical_es(returns, confidence_level=0.975) # FRTB default CL
normal_es(returns, confidence_level=0.975) # closed-form Gaussian
es_from_var_series(returns, var_series) # mean breach magnitude
Calibration: bias and Q-statistics
from risk_backtest import calculate_bias_and_q_statistics, calculate_bias_q_batch
bias, q = calculate_bias_and_q_statistics(
returns=fund_returns,
var_forecasts=var_series,
window_length=60,
confidence_level=0.99,
)
# Or on a multi-fund DataFrame, adds BIAS_* and Q_STAT_* columns
df = calculate_bias_q_batch(daily_results, window_length=60)
Interpretation: bias ≈ 1.0 well-calibrated; Q ≈ 1.577 (1 + Euler-Mascheroni) under correct calibration.
Cluster detection & sensitivity
from risk_backtest import detect_cluster, count_clusters, cluster_threshold_sensitivity
starts, isolated, real_cluster, cluster_adj = detect_cluster(overshoots, threshold=5)
n_clusters, n_isolated = count_clusters(starts, isolated, window_size=250)
# How sensitive are pass-rates to the cluster threshold?
df = cluster_threshold_sensitivity(returns, var, thresholds=range(1, 11))
Regulatory: Basel traffic light
from risk_backtest import basel_traffic_light
res = basel_traffic_light(breaches=6, n_obs=250)
res.zone # 'yellow'
res.multiplier_addon # 0.50
res.multiplier # 3.50 (Basel k = 3 + add-on)
res.cumulative_probability
| Breaches (250d, 99% VaR) | Zone | Add-on |
|---|---|---|
| 0 – 4 | green | 0.00 |
| 5 | yellow | 0.40 |
| 6 | yellow | 0.50 |
| 7 | yellow | 0.65 |
| 8 | yellow | 0.75 |
| 9 | yellow | 0.85 |
| ≥10 | red | 1.00 |
For non-standard windows, the add-on is interpolated from the binomial CDF.
Utilities & plotting
from risk_backtest import compute_overshoots, create_windows, validate_inputs
compute_overshoots(returns, var) # boolean breach array
create_windows(series, window_sizes=[250, 500])
validate_inputs(returns, var) # raises on mismatch / empty
from risk_backtest import plot_var_vs_returns
fig = plot_var_vs_returns(returns, var, dates=dates,
title="Fund A — 1-day 99% VaR")
fig.savefig("fund_a.png", dpi=150)
Requires pip install risk-backtest[plotting].
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file risk_backtest-1.0.1.tar.gz.
File metadata
- Download URL: risk_backtest-1.0.1.tar.gz
- Upload date:
- Size: 37.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66ad6768dcf5604870c264ad6396c29ad7b2207fe7597dcce175226c19b88133
|
|
| MD5 |
229cb9f89cb2dea506389cd13991b2e3
|
|
| BLAKE2b-256 |
282833cfd543397afdaa4a17ede004a12e795fea0358bdee654a4e416380f155
|
Provenance
The following attestation bundles were made for risk_backtest-1.0.1.tar.gz:
Publisher:
publish.yml on william0424/risk-backtest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
risk_backtest-1.0.1.tar.gz -
Subject digest:
66ad6768dcf5604870c264ad6396c29ad7b2207fe7597dcce175226c19b88133 - Sigstore transparency entry: 1710527789
- Sigstore integration time:
-
Permalink:
william0424/risk-backtest@7981135ac5afcef1568b0fa66136a994f5930159 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/william0424
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7981135ac5afcef1568b0fa66136a994f5930159 -
Trigger Event:
release
-
Statement type:
File details
Details for the file risk_backtest-1.0.1-py3-none-any.whl.
File metadata
- Download URL: risk_backtest-1.0.1-py3-none-any.whl
- Upload date:
- Size: 35.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c863b7ff65147a61c8d7c10ef6f5b4746eeafb40496756517e2c7e86e553f1c5
|
|
| MD5 |
16becb46a25217564bf72e7ec3699c1c
|
|
| BLAKE2b-256 |
4eb1b113dd87e40ec1829a197cfd7f05dfe614eff728221ba73f0f41bcbe8f37
|
Provenance
The following attestation bundles were made for risk_backtest-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on william0424/risk-backtest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
risk_backtest-1.0.1-py3-none-any.whl -
Subject digest:
c863b7ff65147a61c8d7c10ef6f5b4746eeafb40496756517e2c7e86e553f1c5 - Sigstore transparency entry: 1710527829
- Sigstore integration time:
-
Permalink:
william0424/risk-backtest@7981135ac5afcef1568b0fa66136a994f5930159 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/william0424
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7981135ac5afcef1568b0fa66136a994f5930159 -
Trigger Event:
release
-
Statement type: