Value at Risk: How Much Can You Lose on a Bad Day?

Every major bank and hedge fund must answer one question: how much could we lose in a really bad day? Value at Risk (VaR) is the industry standard. We derive three calculation methods, expose VaR biggest limitation, and build a complete risk reporting system.

10 February 20261 min readFree
#risk-management#value-at-risk#var#expected-shortfall#cvar#backtesting#fat-tails

How Much Can You Lose on a Bad Day?

Every major bank, hedge fund, and now DeFi protocol needs to answer one question: how much could we lose in a really bad day?

Value at Risk (VaR) is the industry's standard answer. It's a single number that regulators require, risk managers report, and traders live by. Today we derive three calculation methods, expose VaR's biggest limitation, and build a complete risk reporting system in Python.



1. What Is VaR?

Value at Risk is defined as:

The maximum loss not exceeded with probability Ξ±\alpha over a given time horizon.

At 95% confidence over one day (5% VaR), you're saying: on 95 out of 100 trading days, losses will not exceed this number.

Formal definition

For a portfolio with return distribution F(r)F(r), the Ξ±\alpha-VaR is:

VaRΞ±=βˆ’inf⁑{r:F(r)≀α}=βˆ’qΞ±\text{VaR}_\alpha = -\inf\{r : F(r) \leq \alpha\} = -q_\alpha

Where qΞ±q_\alpha is the Ξ±\alpha-quantile of the return distribution. For Ξ±=0.05\alpha = 0.05 (5% VaR), we're looking at the 5th percentile of returns.

Scaled to portfolio value MM:

VaRΞ±$=MΓ—βˆ£VaRα∣\text{VaR}_\alpha^{\$} = M \times |\text{VaR}_\alpha|

2. Three Methods to Calculate VaR

Method 1: Historical Simulation (Non-Parametric)

The simplest approach. Take your portfolio's actual historical P&L series β€” say 500 days. Sort losses from worst to best. The 5% VaR is the loss at the 25th worst observation (500 Γ— 0.05 = 25).

VaRΞ±hist=βˆ’q^Ξ±(r)\text{VaR}_\alpha^{\text{hist}} = -\hat{q}_\alpha(r)

Where q^Ξ±\hat{q}_\alpha is the empirical Ξ±\alpha-quantile of historical returns.

Pros:

  • Fully non-parametric β€” no distributional assumptions
  • Captures fat tails and actual market behavior
  • Easy to implement

Cons:

  • Completely dependent on the historical window
  • If your window doesn't include a crisis, VaR underestimates crisis risk severely
  • Cannot extrapolate beyond observed history

Method 2: Parametric (Normal Distribution)

Assume returns are normally distributed: r∼N(ΞΌ,Οƒ2)r \sim \mathcal{N}(\mu, \sigma^2). Then:

VaRΞ±norm=βˆ’(ΞΌβˆ’zΞ±β‹…Οƒ)\text{VaR}_\alpha^{\text{norm}} = -(\mu - z_\alpha \cdot \sigma)

Where zΞ±z_\alpha is the standard normal quantile:

  • z0.05=1.645z_{0.05} = 1.645 (95% VaR)
  • z0.01=2.326z_{0.01} = 2.326 (99% VaR)

For a portfolio with value MM:

VaRΞ±$=Mβ‹…(zΞ±β‹…Οƒβˆ’ΞΌ)\text{VaR}_\alpha^{\$} = M \cdot (z_\alpha \cdot \sigma - \mu)

Pros:

  • Fast, analytically tractable
  • Easy to decompose by asset (component VaR)
  • Minimal data requirements

Cons:

  • Financial returns have fat tails β€” the normal distribution massively underestimates tail losses
  • A 5-sigma event that should happen once every 3.5 million days happens every few years in markets

Method 3: Parametric (Student-t Distribution)

Replace the normal with a t-distribution with estimated degrees-of-freedom Ξ½\nu:

VaRΞ±t=βˆ’(ΞΌβˆ’tΞ½,Ξ±β‹…Οƒ)\text{VaR}_\alpha^{t} = -(\mu - t_{\nu,\alpha} \cdot \sigma)

Where tΞ½,Ξ±t_{\nu,\alpha} is the t-distribution quantile with Ξ½\nu degrees of freedom.

As Ξ½β†’βˆž\nu \to \infty, you recover the normal. For Ξ½β‰ˆ4βˆ’6\nu \approx 4-6, you get realistic fat tails matching equity return data.

Estimating Ξ½\nu:

from scipy import stats
 
def estimate_t_dof(returns):
    """Estimate degrees of freedom for t-distribution fit."""
    params = stats.t.fit(returns)
    return params[0]  # df parameter
 
# Example
returns = pd.read_csv('portfolio_returns.csv')['returns']
nu = estimate_t_dof(returns)
print(f"Estimated degrees of freedom: {nu:.2f}")
# Typical output: 4.5 to 6.5 for equity returns

This is strictly better than the normal method for financial returns with excess kurtosis above 3.


3. Interactive: VaR Methods Comparison

Upload a return series or use simulated data. Compare Historical, Normal, and Student-t VaR estimates across confidence levels.

β—ˆ InteractiveVaR Method Comparison

Compare Historical, Normal, and Student-t VaR. Notice Normal VaR underestimates tail risk.

Confidence:
Portfolio:
Loading chart…
Historical VaR
$25,235
(2.52%)
Normal VaR
$26,398
(2.64%)
Student-t VaR
$32,350
(3.23%)
Expected Shortfall
$33,321
(3.33%)
ℹ️ Note: Normal VaR underestimates tail risk by -4% compared to Historical VaR. This is due to fat tails in the return distribution. Use Student-t or Historical VaR for more accurate risk estimates.

Typical pattern:

  • Normal VaR: lowest estimate (dangerously optimistic)
  • Student-t VaR: 20-40% higher than Normal (more realistic)
  • Historical VaR: depends on window β€” may be higher or lower

4. Python Implementation

import numpy as np
import pandas as pd
from scipy import stats
from scipy.optimize import brentq
 
def var_historical(returns, alpha=0.05):
    """
    Historical VaR: empirical quantile.
    
    Args:
        returns: Array of historical returns
        alpha: Confidence level (0.05 for 95% VaR)
    
    Returns:
        VaR as a positive number (loss amount)
    """
    return -np.percentile(returns, alpha * 100)
 
 
def var_normal(returns, alpha=0.05):
    """
    Parametric VaR assuming normal distribution.
    """
    mu = np.mean(returns)
    sigma = np.std(returns)
    z_alpha = stats.norm.ppf(alpha)
    return -(mu + z_alpha * sigma)
 
 
def var_student_t(returns, alpha=0.05):
    """
    Parametric VaR assuming Student-t distribution.
    """
    mu = np.mean(returns)
    sigma = np.std(returns)
    
    # Fit t-distribution to get degrees of freedom
    df, loc, scale = stats.t.fit(returns)
    
    # VaR from fitted t-distribution
    t_quantile = stats.t.ppf(alpha, df, loc, scale)
    return -t_quantile
 
 
def expected_shortfall(returns, alpha=0.05):
    """
    Expected Shortfall (CVaR): expected loss given VaR is breached.
    
    Also called: Conditional VaR, Average VaR, Tail VaR
    """
    var = var_historical(returns, alpha)
    tail_losses = returns[returns <= -var]
    return -tail_losses.mean() if len(tail_losses) > 0 else var
 
 
def complete_var_report(returns, portfolio_value=1_000_000, alpha=0.05):
    """
    Complete VaR reporting suite.
    """
    report = {
        'Portfolio Value': f'${portfolio_value:,.0f}',
        'Confidence Level': f'{(1-alpha)*100:.0f}%',
        'Observations': len(returns),
        
        # VaR estimates (daily)
        'Historical VaR (daily)': f'${var_historical(returns, alpha) * portfolio_value:,.0f}',
        'Normal VaR (daily)': f'${var_normal(returns, alpha) * portfolio_value:,.0f}',
        'Student-t VaR (daily)': f'${var_student_t(returns, alpha) * portfolio_value:,.0f}',
        
        # Expected Shortfall
        'Expected Shortfall (CVaR)': f'${expected_shortfall(returns, alpha) * portfolio_value:,.0f}',
        
        # Distribution diagnostics
        'Mean Return (daily)': f'{np.mean(returns):.4%}',
        'Volatility (daily)': f'{np.std(returns):.4%}',
        'Skewness': f'{stats.skew(returns):.3f}',
        'Excess Kurtosis': f'{stats.kurtosis(returns):.3f}',
        
        # t-distribution fit
        't-distribution df': f'{stats.t.fit(returns)[0]:.2f}',
    }
    
    # 10-day VaR (square-root-of-time scaling)
    report['Historical VaR (10-day)'] = f'${var_historical(returns, alpha) * np.sqrt(10) * portfolio_value:,.0f}'
    report['Normal VaR (10-day)'] = f'${var_normal(returns, alpha) * np.sqrt(10) * portfolio_value:,.0f}'
    
    return pd.Series(report)
 
 
# ─── Example: Compare VaR methods on real-like data ───────────────────────
np.random.seed(42)
n_days = 500
 
# Simulate fat-tailed returns (t-distribution with df=5)
returns_t = stats.t.rvs(df=5, loc=0.0003, scale=0.015, size=n_days)
 
# Also generate normal returns for comparison
returns_norm = np.random.normal(0.0003, 0.015, n_days)
 
print("=" * 60)
print("VaR Report: Fat-Tailed Returns (t-distribution, df=5)")
print("=" * 60)
report = complete_var_report(returns_t, portfolio_value=1_000_000, alpha=0.05)
print(report)
 
print("\n" + "=" * 60)
print("Comparison: Normal Returns")
print("=" * 60)
report_norm = complete_var_report(returns_norm, portfolio_value=1_000_000, alpha=0.05)
print(report_norm)

Typical output:

============================================================
VaR Report: Fat-Tailed Returns (t-distribution, df=5)
============================================================
Portfolio Value:          $1,000,000
Confidence Level:         95%
Observations:             500
Historical VaR (daily):   $28,450
Normal VaR (daily):       $24,120    ← Underestimates by ~15%
Student-t VaR (daily):    $29,800    ← Closest to historical
Expected Shortfall:       $41,200    ← 45% higher than VaR!
...
Excess Kurtosis:          4.82       ← Fat tails detected
t-distribution df:        5.1        ← Correctly identified

============================================================
Comparison: Normal Returns
============================================================
...
Normal VaR (daily):       $24,680    ← Similar to historical
Student-t VaR (daily):    $25,100    ← Converges to normal
Excess Kurtosis:          0.12       ← Near-normal

5. VaR Scaling: The Square-Root-of-Time Rule

To convert 1-day VaR to T-day VaR:

VaRT=VaR1β‹…T\text{VaR}_T = \text{VaR}_1 \cdot \sqrt{T}

This holds only if returns are iid (independent, identically distributed).

Why it works (under iid)

If daily returns are independent with variance Οƒ2\sigma^2:

Var(r1+r2+...+rT)=Tβ‹…Οƒ2\text{Var}(r_1 + r_2 + ... + r_T) = T \cdot \sigma^2 ΟƒT=Tβ‹…Οƒ1\sigma_T = \sqrt{T} \cdot \sigma_1

Since VaR is proportional to Οƒ\sigma in the normal case:

VaRT=VaR1β‹…T\text{VaR}_T = \text{VaR}_1 \cdot \sqrt{T}

When it fails

Volatility clustering: Bad days follow bad days. During crisis periods, returns are positively autocorrelated in variance. The square-root rule understates multi-day VaR.

Example: March 2020. Daily VaR might suggest 10-day VaR = 1-day Γ— √10 β‰ˆ 3.16Γ—. But with volatility clustering, actual 10-day losses were 4-5Γ— the 1-day VaR.

Better scaling: GARCH adjustment

Fit a GARCH(1,1) model to capture volatility clustering:

Οƒt2=Ο‰+Ξ±rtβˆ’12+Ξ²Οƒtβˆ’12\sigma_t^2 = \omega + \alpha r_{t-1}^2 + \beta \sigma_{t-1}^2

Then scale VaR using forecasted multi-day volatility from the GARCH model rather than √T.

from arch import arch_model
 
def var_with_garch(returns, alpha=0.05, horizon=10):
    """
    VaR with GARCH volatility scaling.
    """
    # Fit GARCH(1,1)
    model = arch_model(returns, vol='GARCH', p=1, q=1)
    fit = model.fit(disp='off')
    
    # Forecast T-day volatility
    forecasts = fit.forecast(horizon=horizon)
    sigma_T = np.sqrt(np.sum(forecasts.variance.values[-1, :horizon]))
    
    # VaR using forecasted volatility
    mu = np.mean(returns) * horizon
    z_alpha = stats.norm.ppf(alpha)
    var_T = -(mu + z_alpha * sigma_T)
    
    return var_T

6. The Biggest VaR Limitation: It Ignores the Tail

VaR tells you the threshold. It says nothing about how bad losses can get beyond that threshold.

Consider two strategies:

Strategy A:

  • 95% of the time: lose exactly $10,000 (the VaR amount)
  • 5% of the time: lose exactly $10,000

Strategy B:

  • 95% of the time: lose exactly $10,000 (the VaR amount)
  • 5% of the time: lose $500,000

Both have identical 95% VaR = $10,000. But Strategy B is catastrophically more dangerous.

This is why Expected Shortfall (CVaR) was developed.


7. Expected Shortfall (CVaR): The Tail-Aware Metric

Expected Shortfall answers: given that VaR is breached, how bad is the loss on average?

CVaRΞ±=βˆ’E[r∣r<βˆ’VaRΞ±]\text{CVaR}_\alpha = -\mathbb{E}[r \mid r < -\text{VaR}_\alpha]

Also called:

  • Conditional VaR
  • Average VaR
  • Tail VaR

Properties

VaR
Threshold metricYes
Tail severityNo
Coherent risk measureNo
SubadditivityNo (can fail)
CVaR
Threshold metricNo
Tail severityYes
Coherent risk measureYes
SubadditivityYes (always)

Coherence: CVaR satisfies all four axioms of a coherent risk measure (monotonicity, subadditivity, positive homogeneity, translation invariance). VaR fails subadditivity β€” the VaR of a combined portfolio can exceed the sum of individual VaRs, violating diversification logic.

Basel III and Modern Risk Frameworks

Basel III now requires both VaR and Expected Shortfall for market risk capital calculations. Most sophisticated funds report CVaR alongside VaR as standard practice.


8. Component VaR: Decomposing Risk by Asset

For a portfolio, Component VaR tells you how much each position contributes to total VaR:

CVaRi=wiβ‹…βˆ‚VaRpβˆ‚wi\text{CVaR}_i = w_i \cdot \frac{\partial \text{VaR}_p}{\partial w_i}

In the normal case:

CVaRi=wi⋅(Σw)iσp⋅zα\text{CVaR}_i = w_i \cdot \frac{(\boldsymbol{\Sigma} \mathbf{w})_i}{\sigma_p} \cdot z_\alpha

Where (Ξ£w)i(\boldsymbol{\Sigma} \mathbf{w})_i is the covariance of asset ii with the portfolio.

def component_var_normal(weights, cov_matrix, portfolio_value, alpha=0.05):
    """
    Component VaR decomposition (normal assumption).
    """
    sigma_p = np.sqrt(weights @ cov_matrix @ weights)
    z_alpha = stats.norm.ppf(alpha)
    
    # Marginal contribution to risk
    marginal_risk = (cov_matrix @ weights) / sigma_p
    
    # Component VaR
    component_var = weights * marginal_risk * z_alpha * portfolio_value
    
    return {
        'Total VaR': abs(np.sum(component_var)),
        'Components': component_var,
        'Percent Contribution': abs(component_var) / abs(np.sum(component_var)) * 100
    }
 
# Example
assets = ['SPY', 'TLT', 'GLD', 'VNQ', 'EEM']
weights = np.array([0.35, 0.25, 0.15, 0.15, 0.10])
 
# Covariance matrix (annual, convert to daily)
cov_annual = ...  # From Episode 1 or 2
cov_daily = cov_annual / 252
 
result = component_var_normal(weights, cov_daily, portfolio_value=1_000_000, alpha=0.05)
 
print(f"Total 95% VaR: ${result['Total VaR']:,.0f}")
print("\nComponent breakdown:")
for asset, cv, pct in zip(assets, result['Components'], result['Percent Contribution']):
    print(f"  {asset}: ${abs(cv):>8,.0f}  ({pct:>5.1f}%)")

Use case: Identify concentration risk. If one asset contributes >40% of total VaR, you have a hidden concentration despite diversified weights.


9. VaR Backtesting: Did Your Model Work?

After computing VaR, you must backtest it. Count the number of "exceptions" β€” days when actual loss exceeded VaR.

For 95% VaR over 252 days, expect ~13 exceptions (5% Γ— 252). Significantly more β†’ model underestimates risk. Significantly fewer β†’ model is conservative (acceptable but capital-inefficient).

Kupiec Test (Proportion of Failures)

LRuc=βˆ’2ln⁑((1βˆ’p)Tβˆ’NpN)+2ln⁑((1βˆ’p^)Tβˆ’Np^N)LR_{uc} = -2 \ln\left( (1-p)^{T-N} p^N \right) + 2 \ln\left( (1-\hat{p})^{T-N} \hat{p}^N \right)

Where:

  • pp = expected exception rate (e.g., 0.05)
  • p^=N/T\hat{p} = N/T = observed exception rate
  • NN = number of exceptions
  • TT = number of observations

Under the null hypothesis (correct model), LRucβˆΌΟ‡2(1)LR_{uc} \sim \chi^2(1).

def var_backtest_kupiec(actual_returns, var_estimates, alpha=0.05):
    """
    Kupiec test for VaR model validation.
    """
    T = len(actual_returns)
    exceptions = actual_returns < -var_estimates
    N = np.sum(exceptions)
    
    p = alpha  # Expected exception rate
    p_hat = N / T  # Observed exception rate
    
    if N == 0 or N == T:
        return {'status': 'INVALID', 'reason': 'Zero or all exceptions'}
    
    # Likelihood ratio
    lr_uc = -2 * (
        np.log((1-p)**(T-N) * p**N) -
        np.log((1-p_hat)**(T-N) * p_hat**N)
    )
    
    # p-value from chi-squared distribution
    p_value = 1 - stats.chi2.cdf(lr_uc, df=1)
    
    # Decision
    if p_value < 0.05:
        status = 'REJECT'  # Model is invalid
    elif p_hat > p * 1.5:
        status = 'AMBER'   # Too many exceptions
    else:
        status = 'PASS'
    
    return {
        'Status': status,
        'Expected Exceptions': p * T,
        'Actual Exceptions': N,
        'Exception Rate': f'{p_hat:.2%}',
        'LR Statistic': f'{lr_uc:.3f}',
        'p-value': f'{p_value:.4f}',
    }

10. DeFi-Specific VaR Considerations

Standard VaR models underestimate DeFi portfolio risk. Two additional terms must be considered:

1. Liquidation Cascade Risk

During market stress, DeFi protocols experience correlated liquidations. A 20% ETH drop can trigger cascading liquidations across Aave, Compound, MakerDAO simultaneously.

Adjustment: Stress-test with crisis-period correlations (as in Episode 1). Multiply normal-market VaR by 1.5-2.0Γ— for DeFi portfolios with leveraged positions.

2. Smart Contract Exploit Risk

Rare but catastrophic. A standard VaR model that ignores this will chronically underestimate tail risk.

Approximation: Model as a Bernoulli process with small probability pp and large loss LL:

VaRexploitβ‰ˆpβ‹…L\text{VaR}_{\text{exploit}} \approx p \cdot L

For a $1M position in a protocol with estimated 0.5% annual exploit risk:

VaRexploitβ‰ˆ0.005Γ—1,000,000=$5,000Β perΒ yearβ‰ˆ$20Β perΒ day\text{VaR}_{\text{exploit}} \approx 0.005 \times 1{,}000{,}000 = \$5{,}000 \text{ per year} \approx \$20 \text{ per day}

Small in normal times, but the CVaR is $1M β€” the full loss given an exploit occurs.


Key Takeaways

  1. Three VaR methods: Historical (non-parametric), Normal (fast but optimistic), Student-t (fat-tail aware, recommended).

  2. Always report CVaR alongside VaR. VaR tells you the threshold; CVaR tells you the tail severity.

  3. Square-root-of-time scaling is approximate. It fails during volatility clustering β€” use GARCH for multi-day VaR in stressed markets.

  4. Component VaR reveals concentration risk. A portfolio can be diversified by weights but concentrated by risk contribution.

  5. Backtest your VaR model. Use the Kupiec test to validate exception rates.

  6. DeFi requires VaR adjustments. Liquidation cascade risk and smart contract exploit risk are not captured by standard models.


What's Next

Episode 5: Factor Models β€” how quants decompose every return into systematic exposures. We derive the Fama-French 5-factor model, show you how to run factor regressions in Python, and expose the humbling truth: most "alpha" is just hidden beta.


References

Foundational VaR Theory

  1. Jorion, P. (2007). Value at Risk: The New Benchmark for Managing Financial Risk (3rd ed.). McGraw-Hill. ISBN: 978-0-07-146495-6

  2. Duffie, D. & Pan, J. (1997). "An Overview of Value at Risk." Journal of Derivatives, 4(3), 7–49. https://doi.org/10.3905/jod.1997.407971

  3. RiskMetrics Group. (1996). RiskMetrics Technical Document (4th ed.). JP Morgan. https://www.msci.com/documents/10199/5915b101-4206-4ba0-aee2-3449d5c7e95a

Expected Shortfall & Coherent Risk Measures

  1. Artzner, P., Delbaen, F., Eber, J.-M. & Heath, D. (1999). "Coherent Measures of Risk." Mathematical Finance, 9(3), 203–228. https://doi.org/10.1111/1467-9965.00068

  2. Acerbi, C. & Tasche, D. (2002). "Expected Shortfall: A Natural Coherent Alternative to Value at Risk." Economic Notes, 31(2), 379–388. https://doi.org/10.1111/1468-0300.00091

  3. Rockafellar, R.T. & Uryasev, S. (2000). "Optimization of Conditional Value-at-Risk." Journal of Risk, 2(3), 21–41. https://doi.org/10.21314/JOR.2000.038

VaR Backtesting

  1. Kupiec, P.H. (1995). "Techniques for Verifying the Accuracy of Risk Measurement Models." Journal of Derivatives, 3(2), 73–84. https://doi.org/10.3905/jod.1995.407942

  2. Christoffersen, P.F. (1998). "Evaluating Interval Forecasts." International Economic Review, 39(4), 841–862. https://doi.org/10.2307/2527341

Fat Tails & Student-t VaR

  1. Blattberg, R.C. & Gonedes, N.J. (1974). "A Comparison of the Stable and Student Distributions as Statistical Models for Stock Prices." Journal of Business, 47(2), 244–280. https://doi.org/10.1086/295634

  2. 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(3-4), 271–300. https://doi.org/10.1016/S0927-5398(00)00012-8

GARCH & Volatility Clustering

  1. Engle, R.F. (1982). "Autoregressive Conditional Heteroscedasticity with Estimates of the Variance of United Kingdom Inflation." Econometrica, 50(4), 987–1007. https://doi.org/10.2307/1912773

  2. Bollerslev, T. (1986). "Generalized Autoregressive Conditional Heteroskedasticity." Journal of Econometrics, 31(3), 307–327. https://doi.org/10.1016/0304-4076(86)90063-1

Component VaR & Risk Decomposition

  1. Litterman, R. (1996). "Hot Spots and Hedges." In Risk Management: Approaches for Fixed Income Markets. Wiley.

  2. Meucci, A. (2007). "Risk Contributions from Generic User-Defined Factors." SSRN Working Paper. https://ssrn.com/abstract=1031800

DeFi Risk & VaR

  1. Harvey, C.R., Ramachandran, A. & Santoro, J. (2021). DeFi and the Future of Finance. Wiley. ISBN: 978-1-119-83601-8. (Chapter IX: Risk Management in DeFi)

  2. Klages-Mundt, A., Harz, D., Gudgeon, L., Liu, J.-Y. & Minca, A. (2020). "While Mining Was Sleeping: Systemic Risk in Decentralized Finance." SSRN Working Paper. https://ssrn.com/abstract=3700747

Textbooks

  1. McNeil, A.J., Frey, R. & Embrechts, P. (2015). Quantitative Risk Management: Concepts, Techniques and Tools (Revised ed.). Princeton University Press. ISBN: 978-0-691-16627-8

  2. Chan, E.P. (2009). Quantitative Trading: How to Build Your Own Algorithmic Trading Business. Wiley. ISBN: 978-0-470-28488-4

πŸ““ Jupyter Notebooks

EP04 β€” Value at Risk & Expected Shortfall

Full implementation: Historical/Normal/Student-t VaR, Expected Shortfall (CVaR), component VaR decomposition, Kupiec backtesting, GARCH volatility scaling.

free

All notebooks are written in Python 3 and run in Google Colab or local Jupyter. Enter your email to get permanent download links sent to your inbox.

Get the Full Model Code

Subscribe for weekly quant insights, free Jupyter notebooks, and early access to new episodes.