안녕하세요, 흔한트레이더입니다.
어떤 전략의 하루 95% VaR가 1.38%라고 가정하겠습니다. 이 숫자를 하루 최대 손실이 1.38%라는 뜻으로 해석하면 안 됩니다. 실제 같은 표본에서 VaR 경계 밖 49일의 평균 손실은 2.43%였고, 최악의 하루는 -5.66%였습니다.
VaR는 손실 분포의 경계가 어디인지 보여주지만 그 경계를 넘은 뒤 손실이 얼마나 깊어지는지는 알려주지 않습니다. CVaR는 VaR를 초과한 손실의 평균을 계산해 이 빈틈을 보완합니다.
이번 글에서는 같은 평균과 95% VaR를 가진 두 예제를 먼저 비교하고, 실제 6개 LAB 전략의 974개 일간 수익률로 역사적 VaR와 CVaR를 계산합니다.
이번 글의 핵심 결론
- 95% VaR는 약 95%의 거래일에서 넘지 않았던 손실 경계이지 최대 손실이 아닙니다.
- EMA 20·100의 95% VaR는 1.38%였지만 경계 밖 평균 손실인 CVaR는 2.43%, 최악의 하루는 -5.66%였습니다.
- 신뢰수준을 99%로 높이면 관측값은 10일로 줄고 CVaR는 4.11%로 커져 추정 불확실성도 함께 증가합니다.
1. VaR는 어떤 손실을 측정할까
VaR(Value at Risk)는 정한 기간과 신뢰수준에서 손실이 어느 경계까지 발생했는지 보여줍니다.
이 글은 하루 수익률과 95% 신뢰수준을 사용합니다. 역사적 수익률을 작은 값부터 정렬하고 하위 5% 지점의 수익률을 찾은 뒤, 손실 크기를 읽기 쉽도록 양수로 바꿔 표시합니다.
95% 역사적 VaR = -(일간 수익률의 하위 5% 분위수)
95% VaR가 1.38%라는 말은 과거 표본의 약 95%에서 하루 손실이 1.38%를 넘지 않았다는 뜻입니다. 나머지 약 5%에서는 더 큰 손실이 발생했습니다.
다음 문장으로 바꾸면 의미가 달라지므로 주의해야 합니다.
- 하루 최대 손실이 1.38%라는 뜻이 아닙니다.
- 앞으로 손실이 반드시 1.38% 안에 머문다는 뜻이 아닙니다.
- 1.38% 손실이 정확히 20일마다 한 번 발생한다는 뜻도 아닙니다.
2. CVaR는 경계 밖 손실의 평균이다
CVaR(Conditional Value at Risk)는 VaR 경계와 같거나 더 나쁜 손실들의 평균입니다. Expected Shortfall이라고도 부릅니다.
95% CVaR
= -(수익률이 5% 분위수 이하인 관측값의 평균)
VaR가 어디부터 꼬리인가를 알려준다면 CVaR는 꼬리에 들어갔을 때 평균적으로 얼마나 잃었는가에 답합니다.
손실 꼬리가 깊을수록 CVaR는 VaR보다 크게 벌어집니다. 이 글은 두 지표를 모두 손실 크기의 양수로 표시하므로 숫자가 클수록 위험이 큽니다. 원래 수익률 분위수와 꼬리 평균은 음수라는 점을 구분해야 합니다.
3. VaR가 같아도 CVaR는 달라질 수 있다
100일 수익률 두 묶음의 평균을 모두 하루 +0.10%로 맞췄습니다. 첫 번째는 손실 6일이 모두 -2%이고, 두 번째는 -10%가 4일, -2%가 2일입니다.
| 예제 | 일평균 수익률 | 95% VaR | 95% CVaR | 최악의 하루 |
|---|---|---|---|---|
| 고른 꼬리 | +0.10% | 2.00% | 2.00% | -2.00% |
| 극단 꼬리 | +0.10% | 2.00% | 7.33% | -10.00% |
두 예제의 평균과 VaR는 같습니다. 그러나 극단 꼬리는 경계 밖에서 -10% 손실이 반복되어 CVaR가 7.33%로 커졌습니다. VaR 하나만 보면 이 차이가 사라집니다.

분위수 계산 방식과 같은 값이 반복되는 정도에 따라 경계에 포함되는 관측 수는 조금 달라질 수 있습니다. 여기서는 NumPy 기본 선형 분위수를 사용하고 경계값 이하의 모든 관측값을 CVaR 계산에 포함했습니다.
4. 실제 LAB 전략의 95% VaR와 CVaR
실제 비교에는 SYSTEM LAB 14에서 사용한 6개 전략의 동일 기간 포트폴리오 수익률을 사용했습니다.
| 항목 | 조건 |
|---|---|
| 검증 기간 | 2022-01-04 ~ 2025-12-30 |
| 관측값 | 전략별 974개 일간 수익률 |
| 포트폴리오 | 30개 종목 동일 비중 |
| 손익 기준 | 원본 LAB의 거래비용 반영 수익률 |
| 계산 방식 | 비모수 역사적 VaR·CVaR |
| 기본 신뢰수준 | 95% |
| 전략 | 95% VaR | 95% CVaR | 최악의 하루 | 꼬리 관측값 |
|---|---|---|---|---|
| EMA 20·100 | 1.38% | 2.43% | -5.66% | 49일 |
| EMA 35·120 | 1.41% | 2.42% | -5.86% | 49일 |
| ROC 추세 | 1.27% | 1.98% | -5.03% | 49일 |
| 돈치안 돌파 | 1.01% | 1.65% | -3.85% | 49일 |
| ATR 돌파 | 0.89% | 1.58% | -4.80% | 49일 |
| CCI 역추세 | 0.24% | 0.45% | -1.34% | 49일 |
EMA 20·100은 95% VaR 기준으로 두 번째로 위험했지만 CVaR는 2.43%로 가장 컸습니다. EMA 35·120은 VaR가 1.41%로 조금 더 높았지만 경계 밖 평균은 2.42%로 조금 낮아 두 지표의 순서가 바뀌었습니다.
차이는 0.01%포인트 수준이므로 실질적인 우열로 과장할 수는 없습니다. 중요한 점은 VaR 순위가 CVaR 순위를 자동으로 결정하지 않는다는 사실입니다.
ATR 돌파도 95% CVaR는 1.58%로 돈치안 돌파의 1.65%보다 낮았지만 최악의 하루는 -4.80%로 돈치안의 -3.85%보다 컸습니다. CVaR가 꼬리 평균을 보여줘도 단 한 번의 최악 손실까지 대신하지는 못합니다.
5. 신뢰수준을 99%로 바꾸면
신뢰수준을 95%에서 99%로 높이면 하위 5%가 아니라 하위 1%의 더 깊은 손실을 봅니다.
| 전략 | 95% VaR | 95% CVaR | 99% VaR | 99% CVaR |
|---|---|---|---|---|
| EMA 20·100 | 1.38% | 2.43% | 3.26% | 4.11% |
| EMA 35·120 | 1.41% | 2.42% | 3.09% | 3.97% |
| ROC 추세 | 1.27% | 1.98% | 2.52% | 3.10% |
| 돈치안 돌파 | 1.01% | 1.65% | 2.22% | 2.73% |
| ATR 돌파 | 0.89% | 1.58% | 2.11% | 2.82% |
| CCI 역추세 | 0.24% | 0.45% | 0.58% | 0.80% |
EMA 20·100의 CVaR는 2.43%에서 4.11%로 커졌습니다. 더 극단적인 손실만 평균했기 때문입니다.
대신 974개 거래일에서 99% 꼬리에 포함된 관측값은 10일뿐입니다. 신뢰수준을 높이면 더 깊은 위험을 보지만 계산에 사용되는 표본이 줄어 추정값이 몇 번의 사건에 민감해집니다. 높은 신뢰수준이 언제나 더 정확하다는 뜻은 아닙니다.
6. 파이썬으로 역사적 VaR와 CVaR 계산하기
역사적 방법은 수익률 분포가 정규분포라고 가정하지 않고 실제 관측값의 분위수를 사용합니다.
import numpy as np
def calculate_historical_var_cvar(returns, confidence=0.95):
values = np.asarray(returns, dtype=float)
return_quantile = np.quantile(
values,
1.0 - confidence,
)
tail_returns = values[values <= return_quantile]
return {
"var": -return_quantile,
"cvar": -tail_returns.mean(),
"tail_count": len(tail_returns),
}
target_mean = 0.001
stable_losses = [-0.02] * 6
extreme_losses = [-0.10] * 4 + [-0.02] * 2
stable_gain = (
target_mean * 100 - sum(stable_losses)
) / 94
extreme_gain = (
target_mean * 100 - sum(extreme_losses)
) / 94
examples = {
"고른 꼬리": stable_losses + [stable_gain] * 94,
"극단 꼬리": extreme_losses + [extreme_gain] * 94,
}
for name, returns in examples.items():
metrics = calculate_historical_var_cvar(returns)
print(
f'{name}: VaR {metrics["var"]:.2%}, '
f'CVaR {metrics["cvar"]:.2%}, '
f'꼬리 {metrics["tail_count"]}일'
)
실행 결과는 다음과 같습니다.
고른 꼬리: VaR 2.00%, CVaR 2.00%, 꼬리 6일
극단 꼬리: VaR 2.00%, CVaR 7.33%, 꼬리 6일
손실을 음수로 출력하려면 마지막에 붙인 음수 부호를 제거하면 됩니다. 서로 다른 보고서의 값을 비교할 때는 손실을 양수와 음수 중 어떤 방식으로 표시했는지 먼저 확인해야 합니다.
7. 역사적 VaR·CVaR의 한계
과거에 없던 손실은 계산할 수 없습니다
역사적 방법은 실제 분포를 그대로 사용한다는 장점이 있지만 표본에 없는 위기는 반영하지 못합니다. 구조적 변화나 과거보다 큰 급락이 발생하면 추정값을 넘어설 수 있습니다.
기간 단위를 바꾸면 값도 달라집니다
하루 VaR와 한 달 VaR는 같은 지표가 아닙니다. 일간 VaR에 단순히 거래일 수의 제곱근을 곱하는 방식은 수익률이 독립적이고 분포가 안정적이라는 가정이 필요합니다. 손실 꼬리와 변동성 군집이 있는 전략에는 그대로 적용하기 어렵습니다.
표본 수와 신뢰수준을 함께 봐야 합니다
974일의 95% 꼬리는 49일이지만 99% 꼬리는 10일입니다. 99.9%를 계산하면 관측값이 거의 남지 않습니다. 신뢰수준뿐 아니라 실제 꼬리 표본 수도 공개해야 합니다.
VaR와 CVaR는 손실의 발생 순서를 모릅니다
같은 손실이 연속으로 발생했는지 멀리 떨어져 발생했는지는 두 지표에 나타나지 않습니다. 연속 손실, MDD와 회복 기간은 별도로 확인해야 합니다.
계산 방법을 섞으면 비교할 수 없습니다
역사적 방법, 정규분포를 가정한 모수적 방법과 몬테카를로 방법은 서로 다른 결과를 만들 수 있습니다. 보유 기간, 신뢰수준, 데이터 빈도와 분위수 계산법을 통일해야 합니다.
8. 함께 확인할 성과지표
- 변동성은 전체 수익률의 흔들림을 측정하지만 손실 꼬리의 모양은 구분하지 못합니다. 변동성과 하방편차에서는 상승 변동성과 하락 변동성을 분리했습니다.
- MDD와 낙폭 기간은 일별 손실이 누적되어 계좌 고점에서 얼마나 무너졌고 회복에 얼마나 걸렸는지 보여줍니다.
- 소르티노 비율은 목표수익률 아래의 전체 하방편차로 위험조정 성과를 계산합니다. 소르티노 비율 계산법은 하방 위험 정의에 따른 전략 순위 변화를 다룹니다.
- 스트레스 테스트는 과거 표본에 없던 급락과 유동성 악화를 별도 시나리오로 적용해 역사적 VaR의 빈틈을 보완합니다.
9. 정리
VaR는 정한 기간과 신뢰수준에서 손실 꼬리가 시작되는 경계를 보여줍니다. CVaR는 그 경계를 넘었을 때 평균적으로 얼마나 더 깊이 손실이 발생했는지 보여줍니다.
실제 결과를 확인할 때는 VaR만 보고 최대 손실로 오해하지 않습니다. 같은 조건의 CVaR와 최악 손실을 함께 보고, 신뢰수준을 바꿨을 때 꼬리 표본이 몇 개 남는지도 확인해야 합니다.
다음 글에서는 벤치마크와의 관계를 측정하는 알파, 베타와 정보비율을 살펴보겠습니다. 시장을 따라 얻은 수익과 벤치마크를 넘어선 수익을 분리해 확인합니다.