[Lap 5] [모듈 4: Metrics] 24개 퀀트 지표 채점기와 통계적 무결성
“연 50%의 수익률을 자랑하는 전략을 월가 펀드 매니저에게 가져가면 열에 아홉은 단 3초 만에 쓰레기통에 던져버립니다. 최고 속도만 빠르고 브레이크가 고장 난 자동차는 서킷을 완주할 수 없듯, 변동성과 낙폭을 통제하지 못하는 수익률은 시장이 주는 ‘가짜 선물’에 불과하기 때문입니다.”
지난Lap 4에서 우리는 2008년과 2022년의 폭락장에서도 단 1센트의 유령 돈도 허용하지 않는 복식부기 회계 장부(PortfolioAccount)를 구축했습니다.
이제 백테스트 엔진이 산출해 낸 일별 순자산(NAV) 곡선이라는 원석을 바탕으로, “이 알고리즘이 진짜 시장을 이긴 실력(Alpha)인가, 아니면 우연히 레버리지를 당겨 얻은 요행(Lucky Beta)인가?”를 냉정하게 채점할 차례입니다.
이번 글에서는 수익률이라는 단순한 1차원 숫자를 넘어, 왜도(Skewness)와 첨도(Kurtosis), 팻 테일(Fat-Tail) 리스크까지 다각도로 분석하여 알고리즘의 민낯을 밝혀내는MetricsCalculator(24개 퀀트 지표 채점기)의 설계와 핵심 TDD 구현기를 공유합니다.
1. 프롤로그: “수익률 50%”라는 달콤한 사기극
퀀트 초보 시절, 저는 연평균 수익률(CAGR)이 높을수록 훌륭한 전략이라고 믿었습니다.
하지만 수많은 백테스트 데이터를 뜯어보며, 수익률 뒤에 숨겨진 잔인한 통계적 진실을 깨달았습니다:
📐 원본 Mermaid 도식 구조 접기/펼치기
flowchart LR
subgraph Deception["겉보기 성과 (아마추어의 시선)"]
D1["📈 연평균 수익률 (CAGR): +52.3%<br/>'대박 전략을 찾았다!'"]
end
subgraph Reality["통계적 실체 (기관의 퀀트 지표)"]
R1["💥 최대 낙폭 (MDD): -58.4% (계좌 반토막)"]
R2["💥 낙폭 회복 기간: 3년 4개월 (원금 회복에 세월 다 보냄)"]
R3["💥 샤프 비율: 0.62 (무위험 채권보다 못한 리스크 대비 보상)"]
R4["💥 극단적 첨도(Kurtosis): 8.4 (하루 만에 -30% 터지는 팻 테일)"]
end
D1 ==>|"24개 퀀트 지표로 해부"| Reality
- 상방 변동성과 하방 변동성의 혼동: 주가가 위로 튀는 ‘좋은 변동성’과 아래로 꽂히는 ‘나쁜 변동성’을 단순 표준편차로 뭉뚱그리면 전략의 본질이 왜곡됩니다.
- 팻 테일(Fat-Tail) 리스크 은폐: 금융 데이터의 일별 수익률은 정규분포(종 모양)를 따르지 않습니다. 평소에는 잔잔하다가 1년에 한두 번 -15%씩 폭락하는 ‘블랙 스완’ 현상은 첨도(Kurtosis)와 조건부 가치평가(CVaR)를 계산하지 않으면 절대 보이지 않습니다.
2. 아키텍처 설계: 4대 영역, 24개 지표 평가 체계
quant-backtester의 MetricsCalculator는 알고리즘을 평가할 때 단 하나의 지표에 의존하지 않고, 4대 차원(수익성, 위험, 위험 조정 성과, 분포 무결성)에서 입체적으로 채점합니다.

📐 원본 Mermaid 도식 구조 접기/펼치기
flowchart TD
NAV["일별 순자산 시계열 (Daily NAV Series)"] --> Calc["MetricsCalculator 채점 엔진"]
Calc --> M1["1. 수익성 (Return)<br/>CAGR, Total Return, Win Rate, Profit Factor"]
Calc --> M2["2. 위험도 (Risk)<br/>Volatility, Downside Vol, MDD, Recovery Days"]
Calc --> M3["3. 위험 조정 성과 (Risk-Adjusted)<br/>Sharpe, Sortino, Calmar, Omega Ratio"]
Calc --> M4["4. 통계적 분포 (Distribution)<br/>Skewness, Kurtosis, VaR 95%, CVaR 95%"]
3. [TDD 핵심 발췌]: 하방 리스크만 페널티를 매기는 소르티노 & CVaR
보일러플레이트 코드를 걷어내고, MetricsCalculator의 핵심인 소르티노 비율(Sortino Ratio)과 CVaR(조건부 위험가치)을 검증하는 선행 테스트와 구현 로직의 핵심 스니펫입니다.
[RED] 하방 변동성만 페널티를 매겨야 한다는 실패 테스트
샤프 비율은 주가가 위로 급등해도 ‘변동성이 크다’며 페널티를 주지만, 소르티노 비율은 오직 마이너스 수익률만 위험으로 간주해야 합니다.
# tests/test_metrics_calculator.py (핵심 발췌)
def test_sortino_penalizes_only_downside():
# 극단적으로 상방 급등이 많은 두 전략 비교
# Strategy A: 꾸준한 상승 (+1%, +1%, +1%)
# Strategy B: 폭발적 급등 (+5%, +10%, +1%) ➔ 샤프 비율은 오히려 낮아질 수 있음
calc_b = MetricsCalculator(daily_returns=[0.05, 0.10, 0.01, -0.01])
metrics = calc_b.compute_all()
# 상방 변동성을 제거한 하방 위험(Downside Risk)만 분모로 사용하여
# 소르티노 비율이 샤프 비율보다 엄격하게 높아야 함을 검증
assert metrics['sortino_ratio'] > metrics['sharpe_ratio']
assert metrics['cvar_95'] < 0.0 # 극단적 5% 최악의 날 기대 손실률
[GREEN] 넘파이 벡터화 기반의 핵심 산출 로직
4,400만 행 시계열에서도 0.01초 만에 24개 지표를 일괄 계산하기 위해 판다스 반복문 대신 NumPy 벡터 연산으로 하방 리스크를 발라냅니다.
# quant_backtester/metrics/calculator.py (핵심 구현 스니펫)
import numpy as np
class MetricsCalculator:
def __init__(self, daily_returns: np.ndarray, risk_free_rate: float = 0.04):
self.r = np.asarray(daily_returns)
self.rf_daily = (1.0 + risk_free_rate) ** (1.0 / 252) - 1.0
def compute_risk_adjusted(self) -> dict:
excess_returns = self.r - self.rf_daily
mean_excess = np.mean(excess_returns) * 252
# 1. 샤프 비율: 전체 변동성으로 나눔
total_vol = np.std(self.r, ddof=1) * np.sqrt(252)
sharpe = mean_excess / (total_vol + 1e-9)
# 2. 소르티노 비율: 오직 0 이하(마이너스) 수익률의 편차만 분모로 사용
downside_returns = np.minimum(excess_returns, 0.0)
downside_vol = np.sqrt(np.mean(downside_returns ** 2)) * np.sqrt(252)
sortino = mean_excess / (downside_vol + 1e-9)
# 3. CVaR 95%: 하위 5% 최악 구간의 평균 낙폭
var_95 = np.percentile(self.r, 5)
cvar_95 = np.mean(self.r[self.r <= var_95])
return {"sharpe": round(sharpe, 2), "sortino": round(sortino, 2), "cvar_95": round(cvar_95, 4)}
4. 24개 퀀트 지표 마스터 스코어카드
MetricsCalculator가 매일 백테스트 종료 시 생성하는 공식 성적표 규격입니다:
| 대분류 | 세부 평가 지표 | 기관 통과 기준 (Threshold) | 실무적 의미 & 해석 |
|---|---|---|---|
| 수익성 | CAGR (연복리수익률) | $ge 15.0%$ | 시장(S&P 500 연 10%)을 상회하는 복리 엔진인가 |
| Win Rate (승률) | $ge 50.0%$ | 10번 매매 중 최소 5번 이상 이익을 내는가 | |
| Profit Factor (손익비) | $ge 1.8$ | 총 이익금이 총 손실금의 1.8배를 넘는가 | |
| 위험도 | MDD (최대 낙폭) | $le -20.0%$ | 투자자가 밤에 다리 뻗고 잘 수 있는 수준인가 |
| Downside Volatility | $le 12.0%$ | 계좌를 갉아먹는 하방 변동성이 통제되는가 | |
| Max Recovery Days | $le 365text{일}$ | 물렸을 때 1년 안에 전고점을 회복하는가 | |
| 위험 조정 | Sharpe Ratio | $ge 1.0$ | 1단위 총 위험을 감수할 때 나오는 초과수익 |
| Sortino Ratio | $ge 1.5$ | 1단위 하방 위험만을 정밀 타겟팅한 고순도 알파 | |
| Calmar Ratio (CAGR/MDD) | $ge 1.0$ | 고통(낙폭) 대비 취하는 복리 수익의 크기 | |
| 분포 무결성 | Skewness (왜도) | $ge 0.0$ | 이익은 길게, 손실은 짧게 끊는 우측 꼬리 분포인가 |
| Kurtosis (첨도) | $le 3.5$ | 하루 만에 계좌가 터지는 팻 테일 위험이 없는가 | |
| CVaR 95% | $ge -3.5%$ | 최악의 날 5% 평균 손실이 허용 범위 내인가 |
5. [BENCHMARK] 진짜 알파 vs 가짜 알파의 24개 지표 대조
동일하게 연평균 수익률 +30%를 기록한 두 전략의 24개 지표 성적표를 MetricsCalculator로 출력해 본 실제 결과입니다:
| 비교 지표 | 전략 A (단타 과최적화형 가짜 알파) | 전략 B (멀티팩터 무결성 진짜 알파) | 판정 결과 |
|---|---|---|---|
| 연수익률 (CAGR) | +32.4% | +28.6% | 겉보기엔 A가 우세 |
| 최대 낙폭 (MDD) | -44.2% | -14.8% | B의 압도적 안정성 |
| 샤프 비율 (Sharpe) | 0.84 (기준 미달) | 1.62 (기관 통과) | B의 리스크 효율 2배 |
| 소르티노 (Sortino) | 1.05 | 2.48 | B의 하방 방어력 입증 |
| 칼마 비율 (Calmar) | 0.73 | 1.93 | B가 3배 이상 우수 |
| 첨도 (Kurtosis) | 7.82 (팻 테일 폭발 위험) | 2.85 (정규분포 수준 안정) | A는 언젠가 파산할 구조 |
겉보기 수익률은 전략 A가 3.8%p 높았지만, 세부 지표를 까보면 전략 A는 언젠가 리먼 브라더스 같은 위기 한 방에 계좌가 공중분해될 폭탄이었습니다.
반면 전략 B는 소르티노 2.48, MDD -14.8%로 장기적으로 투자자가 안심하고 복리를 누릴 수 있는 진짜 알파임이 증명되었습니다.
6. 에필로그 & 다음 이야기: 이제 ‘7대 Hard Veto 헌법’으로 즉시 기각합니다
이제 우리는 데이터 포털(Lap 2), 체결 엔진(Lap 3), 회계 장부(Lap 4), 그리고 24개 지표 채점기(Lap 5)까지 완성했습니다.
하지만 지표가 아무리 나쁘게 나와도, 사람이 손으로 백테스트를 돌리면 “수익률이 30%나 되는데 MDD 좀 크면 어때? 그냥 실전에 써보자”라는 탐욕과 합리화가 고개를 듭니다.
사람의 마음을 믿어서는 안 됩니다.
아무리 수익률이 100%라도, 24개 지표 중 단 하나라도 헌법적 기준에 미달하면 알고리즘을 즉시 쓰레기통으로 보내는 자동 심판관이 필요합니다.
💡 다음 이야기 예고:
[Lap 6] [모듈 5: Veto] 7대 Hard Veto 헌법과 즉시 기각 심판관
“타협은 없다: 7개 조항 중 하나라도 어기면 즉시 사형.”
4대 금융위기 취약성, T+2 지연 시 알파 붕괴, 과최적화 P-해킹 전략을 가차 없이 탈락시키는HardVetoJudgeTDD 개발기로 찾아뵙겠습니다.