Introduction

Every investment can lose money. The question is: how much, and how often? Before the 1990s banks answered that question mostly with intuition and spreadsheets. Then J.P. Morgan published its RiskMetrics framework in 1994 and gave the world Value at Risk — a single number designed to capture downside in a form that executives and regulators could read at a glance.

The idea is disarmingly simple. Fix a confidence level (say 95%) and a time horizon (say one trading day). VaR is the dollar loss that will not be exceeded on 95% of days — equivalently, the loss you should expect to see surpassed only once every twenty days on average.

Formally, if LL is the random daily loss and α∈(0,1)\alpha \in (0,1) is the confidence level, then

VaRα=inf⁡{ℓ∈R:P(L>ℓ)≤1−α}\text{VaR}_\alpha = \inf\{ \ell \in \mathbb{R} : P(L > \ell) \leq 1 - \alpha \}

which is simply the α\alpha-quantile of the loss distribution. At 95% confidence, VaR is the point on the loss axis to the left of which 95% of the probability mass sits.

That one number became the lingua franca of risk management. Basel II and Basel III made it a regulatory requirement for banks worldwide. But a number this compact inevitably hides something — and what VaR hides can be severe.

Read VaR Off a Distribution

The demo below shows a simulated daily return distribution for a hypothetical portfolio. Returns have been negated so losses appear on the positive side of the axis — positive numbers are bad days.

<!-- VaR demo — confidence slider + distribution selector + resample button -->
<div class="controls">
  <label for="conf">Confidence level: <strong id="conf-val">95%</strong></label>
  <input type="range" id="conf" min="80" max="99" value="95" step="1">
  <label for="dist-select">Distribution:</label>
  <select id="dist-select">
    <option value="normal">Normal returns</option>
    <option value="skewed">Right-skewed (rare large losses)</option>
    <option value="heavy">Heavy-tailed (extreme events)</option>
  </select>
  <button id="resample" type="button">Resample</button>
</div>
<canvas id="chart" width="560" height="280"></canvas>
<div class="stats" id="stats"></div>
<p class="note">Red bars = tail losses beyond VaR. VaR is blind to how large they get.</p>
/* Styles for the VaR distribution chart demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem .9rem; align-items: center; margin-bottom: .6rem; }
label { font-size: .88rem; font-weight: 600; }
input[type=range] { width: 160px; cursor: pointer; }
select { font: .88rem system-ui, sans-serif; padding: .2rem .4rem; border: 1px solid #b0bec5; border-radius: 6px; }
button { font: 600 .82rem system-ui, sans-serif; padding: .3rem .75rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
canvas { display: block; width: 100%; max-width: 560px; }
.stats { font-size: .9rem; margin: .4rem 0; min-height: 1.6em; }
.stats span { font-weight: 700; }
.note { font-size: .8rem; color: #555; margin: .3rem 0 0; line-height: 1.4; }
@media (prefers-color-scheme: dark) {
  body { color: #ddd; background: #1a1a2e; }
  select { background: #222; color: #ddd; border-color: #555; }
  button { background: #3a5a8a; border-color: #3a5a8a; }
  .note { color: #aaa; }
}
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// VaR demo — draws a histogram, marks the VaR quantile line, computes Expected Shortfall

const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const PAD = { top: 20, right: 20, bottom: 36, left: 46 };

// Deterministic pseudo-random number generator (xorshift) seeded for reproducibility
function rng(seed) {
  let s = seed >>> 0;
  return function() {
    s ^= s << 13; s ^= s >>> 17; s ^= s << 5;
    return (s >>> 0) / 4294967296;
  };
}

// Box-Muller transform to generate normally-distributed values
function boxMuller(r) {
  const u1 = r(), u2 = r();
  return Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);
}

// Generate synthetic daily losses (positive = loss) for the chosen distribution type
function generateLosses(dist, seed) {
  const r = rng(seed);
  const n = 500;
  const losses = [];
  for (let i = 0; i < n; i++) {
    let val;
    if (dist === 'normal') {
      // Gaussian loss centered slightly positive (small daily drift toward losses)
      val = boxMuller(r) * 1.5 + 0.3;
    } else if (dist === 'skewed') {
      // Normal base + occasional jump (models fat-right tail from rare large events)
      val = boxMuller(r) * 1.2 + 0.5 + (r() < 0.08 ? r() * 6 : 0);
    } else {
      // 2% chance of a severe tail event; otherwise near-normal
      const u = r();
      val = u < 0.02 ? (r() * 12 + 4) : (boxMuller(r) * 1.3 + 0.4);
    }
    losses.push(val);
  }
  return losses;
}

// VaR = alpha-quantile of the sorted loss distribution
function computeVaR(losses, conf) {
  const sorted = [...losses].sort((a, b) => a - b);
  const idx = Math.floor(conf * sorted.length);
  return sorted[Math.min(idx, sorted.length - 1)];
}

// Expected Shortfall = average loss in the worst (1-alpha) fraction of days
function computeES(losses, conf) {
  const sorted = [...losses].sort((a, b) => a - b);
  const idx = Math.floor(conf * sorted.length);
  const tail = sorted.slice(idx);
  if (!tail.length) return sorted[sorted.length - 1];
  return tail.reduce((a, b) => a + b, 0) / tail.length;
}

// Build a fixed-bin histogram from the loss sample
function buildHistogram(losses, bins) {
  const min = Math.min(...losses), max = Math.max(...losses);
  const bw = (max - min) / bins;
  const counts = new Array(bins).fill(0);
  for (const l of losses) {
    let b = Math.floor((l - min) / bw);
    if (b >= bins) b = bins - 1;
    counts[b]++;
  }
  return { counts, min, max, bw };
}

let currentSeed = 42;
let losses = generateLosses('normal', currentSeed);

function draw() {
  const conf = parseInt(document.getElementById('conf').value) / 100;
  const var95 = computeVaR(losses, conf);
  const es = computeES(losses, conf);
  const pct = Math.round(conf * 100);

  const { counts, min, max, bw } = buildHistogram(losses, 40);
  const maxCount = Math.max(...counts);

  ctx.clearRect(0, 0, W, H);

  const plotW = W - PAD.left - PAD.right;
  const plotH = H - PAD.top - PAD.bottom;

  // Shade the tail region beyond VaR in light red
  const varX = PAD.left + ((var95 - min) / (max - min)) * plotW;
  ctx.fillStyle = 'rgba(231,76,60,0.18)';
  ctx.fillRect(varX, PAD.top, W - PAD.right - varX, plotH);

  // Draw histogram bars, coloring tail bars red
  for (let i = 0; i < counts.length; i++) {
    const x = PAD.left + (i / counts.length) * plotW;
    const bRight = PAD.left + ((i + 1) / counts.length) * plotW;
    const barH = (counts[i] / maxCount) * plotH;
    const y = PAD.top + plotH - barH;
    const barCenter = min + (i + 0.5) * bw;
    ctx.fillStyle = barCenter > var95 ? '#e74c3c' : '#3a7abf';
    ctx.fillRect(x + 1, y, bRight - x - 2, barH);
  }

  // Draw the vertical VaR threshold line
  ctx.strokeStyle = '#c0392b';
  ctx.lineWidth = 2;
  ctx.setLineDash([5, 3]);
  ctx.beginPath();
  ctx.moveTo(varX, PAD.top);
  ctx.lineTo(varX, PAD.top + plotH);
  ctx.stroke();
  ctx.setLineDash([]);

  // Label the VaR line with the confidence percentage
  ctx.fillStyle = '#c0392b';
  ctx.font = 'bold 11px system-ui, sans-serif';
  const labelX = varX + 4;
  ctx.fillText('VaR ' + pct + '%', labelX > W - 70 ? varX - 56 : labelX, PAD.top + 14);

  // Draw chart axes
  ctx.strokeStyle = '#aaa';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(PAD.left, PAD.top);
  ctx.lineTo(PAD.left, PAD.top + plotH);
  ctx.lineTo(PAD.left + plotW, PAD.top + plotH);
  ctx.stroke();

  // Draw x-axis tick labels (loss percentage values)
  ctx.fillStyle = '#666';
  ctx.font = '10px system-ui, sans-serif';
  ctx.textAlign = 'center';
  for (let t = 0; t <= 4; t++) {
    const val = min + (t / 4) * (max - min);
    const tx = PAD.left + (t / 4) * plotW;
    ctx.fillText(val.toFixed(1) + '%', tx, PAD.top + plotH + 14);
  }
  ctx.textAlign = 'left';

  // Draw rotated y-axis label
  ctx.save();
  ctx.translate(12, PAD.top + plotH / 2);
  ctx.rotate(-Math.PI / 2);
  ctx.textAlign = 'center';
  ctx.fillStyle = '#888';
  ctx.font = '10px system-ui, sans-serif';
  ctx.fillText('Frequency', 0, 0);
  ctx.restore();

  // Update the stats text below the chart
  const statsEl = document.getElementById('stats');
  statsEl.innerHTML = i18nFmt('VaR<sub>%pct%</sub> = <span>%var%%</span> &nbsp;|&nbsp; Expected Shortfall = <span>%es%%</span>', {
    pct: pct,
    var: var95.toFixed(2),
    es: es.toFixed(2)
  });

  document.getElementById('conf-val').textContent = pct + '%';
}

document.getElementById('conf').addEventListener('input', draw);

document.getElementById('dist-select').addEventListener('change', function() {
  losses = generateLosses(this.value, currentSeed);
  draw();
});

document.getElementById('resample').addEventListener('click', function() {
  currentSeed = (currentSeed * 1664525 + 1013904223) >>> 0;
  const dist = document.getElementById('dist-select').value;
  losses = generateLosses(dist, currentSeed);
  draw();
});

draw();
Result
Console

Drag the confidence slider and watch the VaR line move. At 95% the shaded tail contains 5% of the probability mass — those are the days VaR does not cover. Notice how VaR says nothing about how large the losses in that tail are: two portfolios can share the same VaR yet face very different catastrophes.

The Real Complexity

VaR looks clean, but its simplicity is also its danger.

What VaR measures — and what it does not. VaR marks a threshold on the loss distribution. Everything to the right of that threshold — the tail — is invisible to VaR. Two portfolios can have identical 95% VaRs while one has losses of $1 M in the tail and the other has losses of $1 B. VaR is silent on the severity of the worst outcomes.

Non-subadditivity. A coherent risk measure should satisfy ρ(A+B)≤ρ(A)+ρ(B)\rho(A + B) \leq \rho(A) + \rho(B): diversification should not increase risk. VaR violates this property for non-elliptical distributions. Adding two positions can produce a VaR higher than the sum of their individual VaRs — the opposite of what diversification should do.

Expected Shortfall fixes the tail blindness. Also called Conditional VaR (CVaR), Expected Shortfall answers: given that we are in the worst (1−α)(1-\alpha) fraction of days, what is the average loss? Formally:

ESα=11−α∫α1VaRu du\text{ES}_\alpha = \frac{1}{1-\alpha} \int_\alpha^1 \text{VaR}_u \, du

ES is coherent, subadditive, and tells you about tail severity — at the cost of being harder to estimate and less intuitive to communicate. Basel III's Fundamental Review of the Trading Book (FRTB, 2016) moved banks from 99% VaR to 97.5% ES for exactly this reason.

Model risk. VaR is only as good as the model for the loss distribution. The historical simulation approach uses the last TT days of returns directly — simple, but it completely misses risks not present in the window. The parametric (variance-covariance) approach assumes normality, which famously underestimates tail probabilities. Monte Carlo is flexible but computationally expensive and sensitive to correlation assumptions.

Estimation difficulty. Estimating the 99th percentile from daily data requires years of history to get a reliable sample — but markets change, making old data misleading. This is the classic bias-variance tradeoff applied to tail estimation: a short window is noisy; a long window is stale.

Where It Matters

VaR is not just an academic concept — it is embedded in the architecture of global finance:

  • Regulatory capital: Basel II/III requires banks to hold capital proportional to their VaR. A trading desk's VaR directly determines how much capital is locked up to absorb potential losses, affecting every loan and investment the bank can make.
  • Trading limits: risk managers set position limits in VaR terms. A desk is allowed to carry a portfolio whose daily VaR does not exceed a budget — VaR becomes the currency of internal risk allocation.
  • Hedge fund risk budgeting: funds allocate VaR across strategies the same way a company allocates a budget across departments, seeking the highest return per unit of risk.
  • Margin and collateral: clearing houses use VaR-like calculations to set the initial margin required for derivatives positions, sizing the buffer against a one-day adverse move.
  • Stress testing and back-testing: regulators require banks to count how often actual losses exceed VaR (back-testing). Too many exceedances trigger capital add-ons, creating a feedback loop between the model and the regulatory environment.

VaR's grip on finance is so tight that its flaws caused real damage. During the 2008 financial crisis, many institutions held positions whose VaR looked comfortable at 99% while tail losses were catastrophic — a reminder that a number's popularity does not guarantee its safety.

Conclusion

Value at Risk earned its place in finance by doing something genuinely hard: turning a whole probability distribution into a single number that busy executives and regulators can act on. At 95% confidence, VaR says we expect to lose at most X on a typical bad day — a sentence that fits in a board presentation.

But that compression is also its trap. VaR is blind to what happens beyond its threshold, can fail the diversification intuition, and is only as trustworthy as the model behind it. Expected Shortfall addresses the tail blindness; stress testing addresses the model-risk gap; neither replaces genuine judgment about where a portfolio's fragility lies.

Risk is not a single number. VaR is a useful approximation of downside — a starting point, not an ending one. The next time you see a risk report, ask not just what the VaR is, but what happens after it.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/value-at-risk/Content licensed under CC BY-NC 4.0.