Workspace IndexMath › Day 40

VaR and Tail Risk TODO

Math · Day 40 / 52 · November — Probability, Statistics & Financial Math (Day 35-43)

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/.


한국어

VaR·꼬리리스크 TODO

Math · Day 40 / 52 · 11월 — 확률·통계·금융수학 (Day 35–43)

개념

VaR는 주어진 기간과 신뢰수준에서 손실이 넘지 않을 것으로 보는 임계값으로, 손실분포의 분위수로 정의된다. 정의상 VaR는 그 임계값을 넘었을 때 손실이 얼마나 커지는지는 아무것도 말해주지 않으며, 이것이 꼬리리스크를 과소평가하는 근본 한계다. 또한 VaR는 일반적으로 열등가법성(subadditivity)을 만족하지 않아 포트폴리오를 합쳤을 때 위험이 부분의 합보다 커 보이는 경우가 생기고, 이 때문에 정합적 위험척도(coherent risk measure)로 인정되지 않는다. 기대손실(Expected Shortfall, CVaR)은 VaR를 초과하는 손실의 조건부 기댓값으로 정의되어 꼬리의 크기를 반영하고 열등가법성을 만족한다. 정규분포 가정으로 VaR를 계산하면 실제 금융 수익률의 두꺼운 꼬리를 놓치므로, 과거 시뮬레이션이나 극단값 이론 같은 대안이 쓰인다.

청산 임계값이나 담보 요구치를 정규분포 가정 VaR로 잡으면 평상시엔 멀쩡하다가 극단 구간에서 정확히 무너지는 설계가 된다.

코드 · 수식

# 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만으로는 꼬리위험을 과소평가한다.")

연습

실제 자산 일간 수익률로 정규분포 가정 VaR, 과거 시뮬레이션 VaR, 기대손실을 각각 계산하고 세 값이 신뢰수준 99%에서 얼마나 벌어지는지 비교하라.

실무 · Verex 연결

Verex에서 마켓메이커의 최대 손실 한도, 담보 비율, 극단적 가격 이동 시의 정산 안전마진을 정할 때 VaR 대신 기대손실로 보는 편이 안전한 이유가 여기에 있다.

공부한 날 원본 커리큘럼(docs/knowledge/math-50-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 39. 랜덤워크·열확산 방정식41. LMSR/마켓 스코어링(Verex 연결) →