VaR and Tail Risk TODO
Concept
Value at Risk (VaR) is the loss threshold that, at a given confidence level and horizon, losses are not expected to exceed — defined as a quantile of the loss distribution. By definition, VaR says nothing about how large losses get once that threshold is breached, which is the fundamental limitation that makes it understate tail risk. VaR also generally fails to satisfy subadditivity, so combining portfolios can make risk look larger than the sum of the parts, which is why it is not recognized as a coherent risk measure. Expected Shortfall (CVaR) is defined as the conditional expectation of losses beyond VaR, so it reflects the size of the tail and does satisfy subadditivity. Computing VaR under a normal-distribution assumption misses the fat tails of real financial returns, so alternatives such as historical simulation or extreme value theory are used instead.
Setting liquidation thresholds or collateral requirements using normal-assumption VaR produces a design that looks fine in ordinary times but fails exactly in extreme regimes.
Code & Formula
# VaR·꼬리리스크 — 손실분포의 분위수(VaR)와 조건부 꼬리손실(CVaR/ES)
# VaR는 "얼마나 자주 넘는가"만 말하고, CVaR는 "넘었을 때 얼마나 큰가"까지 말해준다.
import random
import statistics
random.seed(7)
# 일간 로그수익률을 정규분포로 근사 시뮬레이션 (평균 0, 변동성 2%)
N = 20_000
mu, sigma = 0.0, 0.02
returns = [random.gauss(mu, sigma) for _ in range(N)]
losses = sorted(-r for r in returns) # 손실 = -수익률, 오름차순
def var(losses_sorted, alpha):
"""신뢰수준 alpha(예: 0.99)에서의 Value at Risk = 손실분포의 alpha 분위수."""
idx = int(alpha * len(losses_sorted))
return losses_sorted[idx]
def cvar(losses_sorted, alpha):
"""VaR를 넘는 손실들의 평균 (Expected Shortfall)."""
idx = int(alpha * len(losses_sorted))
tail = losses_sorted[idx:]
return statistics.mean(tail)
for alpha in (0.95, 0.99):
v = var(losses, alpha)
c = cvar(losses, alpha)
print(f"alpha={alpha:.2f} VaR={v*100:6.3f}% CVaR={c*100:6.3f}% (CVaR >= VaR: {c >= v})")
# 극단 꼬리(팻테일) 샘플 몇 개를 강제로 섞어 VaR는 그대로인데 CVaR만 커지는 걸 보여준다
losses2 = sorted(losses + [0.30, 0.35, 0.40]) # 블랙스완급 손실 3건 추가
v99, c99 = var(losses2, 0.99), cvar(losses2, 0.99)
print(f"\n꼬리 이벤트 추가 후 alpha=0.99 VaR={v99*100:6.3f}% CVaR={c99*100:6.3f}%")
print("-> VaR는 거의 안 변해도 CVaR는 크게 뛴다: VaR만으로는 꼬리위험을 과소평가한다.")
Exercise
Using an actual asset's daily returns, compute normal-assumption VaR, historical-simulation VaR, and Expected Shortfall separately, and compare how far apart the three values are at the 99% confidence level.
Practical Connection
This is exactly why, when Verex sets a market maker's maximum loss limit, collateral ratio, and settlement safety margin under extreme price moves, using Expected Shortfall instead of VaR is the safer choice.
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/math-50-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.