켈리 기준이란?
켈리 기준(Kelly Criterion)은 1956년 벨 연구소의 존 켈리(John L. Kelly Jr.)가 제안한 최적 베팅 비율 공식입니다. 원래 정보 이론에서 출발했지만, 현재는 퀀트 투자와 자동매매에서 자금 관리(Money Management)의 핵심 도구로 널리 사용됩니다.
핵심 아이디어는 간단합니다. 승률과 손익비를 기반으로, 장기적으로 자산을 가장 빠르게 성장시키는 최적의 투자 비율을 수학적으로 계산합니다.
켈리 공식의 수학적 유도
가장 기본적인 켈리 공식은 다음과 같습니다:
f* = (p × b - q) / b
여기서:
f* = 최적 투자 비율 (전체 자본 대비)
p = 승률 (이길 확률)
q = 패률 (1 - p)
b = 손익비 (평균 이익 / 평균 손실)
예를 들어 승률이 55%, 손익비가 2:1인 전략이라면:
f* = (0.55 × 2 - 0.45) / 2
f* = (1.10 - 0.45) / 2
f* = 0.325 → 전체 자본의 32.5%
이 비율로 매 거래에 투자하면, 이론적으로 장기 자산 성장률이 극대화됩니다.
왜 퀀트 투자에서 중요한가
자동매매 시스템에서 포지션 사이징은 진입·청산 신호만큼 중요합니다. 아무리 좋은 전략도 베팅 비율이 잘못되면 파산할 수 있고, 반대로 너무 보수적이면 수익 기회를 놓칩니다.
- 과대 투자: 켈리 비율보다 크게 투자하면 변동성이 극대화되어 변동성 타겟팅 없이는 큰 낙폭을 겪게 됩니다.
- 과소 투자: 너무 적게 투자하면 안전하지만 복리 효과를 제대로 활용하지 못합니다.
- 켈리 최적: 장기 기하 성장률(geometric growth rate)을 최대화하는 유일한 비율입니다.
파이썬으로 구현하는 켈리 기준
실제 자동매매 시스템에 켈리 기준을 적용하는 파이썬 코드를 살펴봅시다.
기본 켈리 계산기
import numpy as np
def kelly_criterion(win_rate: float, win_loss_ratio: float) -> float:
"""기본 켈리 기준 계산"""
q = 1 - win_rate
kelly = (win_rate * win_loss_ratio - q) / win_loss_ratio
return max(kelly, 0) # 음수면 투자하지 않음
def fractional_kelly(win_rate, win_loss_ratio, fraction=0.5):
"""프랙셔널 켈리 - 실전에서 가장 많이 사용"""
full_kelly = kelly_criterion(win_rate, win_loss_ratio)
return full_kelly * fraction
# 예시: 승률 55%, 손익비 2.0
full = kelly_criterion(0.55, 2.0)
half = fractional_kelly(0.55, 2.0, 0.5)
quarter = fractional_kelly(0.55, 2.0, 0.25)
print(f"풀 켈리: {full:.1%}") # 32.5%
print(f"하프 켈리: {half:.1%}") # 16.25%
print(f"쿼터 켈리: {quarter:.1%}") # 8.125%
과거 거래 데이터 기반 켈리 계산
import pandas as pd
def kelly_from_trades(trades: pd.Series) -> dict:
"""과거 거래 수익률 시리즈로 켈리 비율 계산"""
wins = trades[trades > 0]
losses = trades[trades < 0]
if len(losses) == 0 or len(wins) == 0:
return {"kelly": 0, "win_rate": 0, "wl_ratio": 0}
win_rate = len(wins) / len(trades)
avg_win = wins.mean()
avg_loss = abs(losses.mean())
wl_ratio = avg_win / avg_loss
kelly = kelly_criterion(win_rate, wl_ratio)
return {
"kelly": kelly,
"half_kelly": kelly * 0.5,
"win_rate": win_rate,
"wl_ratio": wl_ratio,
"avg_win": avg_win,
"avg_loss": avg_loss,
"total_trades": len(trades)
}
# 예시 사용
np.random.seed(42)
# 가상 거래 데이터: 승률 약 55%, 손익비 약 1.8
mock_trades = pd.Series(np.where(
np.random.random(200) < 0.55,
np.random.uniform(0.5, 3.0, 200), # 이익
-np.random.uniform(0.5, 2.0, 200) # 손실
))
result = kelly_from_trades(mock_trades)
for k, v in result.items():
print(f"{k}: {v:.4f}" if isinstance(v, float) else f"{k}: {v}")
프랙셔널 켈리: 실전 필수 전략
풀 켈리(Full Kelly)를 그대로 적용하면 이론적으로는 최적이지만, 실전에서는 매우 위험합니다. 그 이유는:
- 추정 오차: 승률과 손익비는 과거 데이터에서 추정한 값이며, 미래에도 동일하리란 보장이 없습니다.
- 극심한 변동성: 풀 켈리는 최대 낙폭(MDD)이 매우 클 수 있습니다.
- 비정규 분포: 실제 시장 수익률은 정규분포가 아니므로 몬테카를로 시뮬레이션으로 리스크를 추가 검증해야 합니다.
실전에서는 하프 켈리(Half Kelly) 또는 쿼터 켈리(Quarter Kelly)를 사용합니다. 하프 켈리는 풀 켈리 대비 성장률이 75% 수준이지만, 변동성은 절반으로 줄어듭니다.
몬테카를로 시뮬레이션으로 검증
def simulate_kelly(win_rate, wl_ratio, kelly_fractions,
n_trades=500, n_simulations=1000, initial=10000):
"""다양한 켈리 비율의 시뮬레이션 결과 비교"""
results = {}
for frac_name, frac in kelly_fractions.items():
full_kelly = kelly_criterion(win_rate, wl_ratio)
bet_size = full_kelly * frac
final_values = []
max_drawdowns = []
for _ in range(n_simulations):
capital = initial
peak = initial
max_dd = 0
for _ in range(n_trades):
if np.random.random() < win_rate:
capital *= (1 + bet_size * wl_ratio /
(wl_ratio + 1))
else:
capital *= (1 - bet_size / (wl_ratio + 1))
peak = max(peak, capital)
dd = (peak - capital) / peak
max_dd = max(max_dd, dd)
final_values.append(capital)
max_drawdowns.append(max_dd)
results[frac_name] = {
"median_final": np.median(final_values),
"mean_mdd": np.mean(max_drawdowns),
"ruin_prob": np.mean([1 for v in final_values
if v < initial * 0.1]),
"bet_pct": bet_size
}
return results
fractions = {
"풀 켈리": 1.0,
"하프 켈리": 0.5,
"쿼터 켈리": 0.25,
"1/8 켈리": 0.125
}
sim = simulate_kelly(0.55, 2.0, fractions)
for name, data in sim.items():
print(f"n{name} (투자비율: {data['bet_pct']:.1%}):")
print(f" 중앙값 최종자산: ${data['median_final']:,.0f}")
print(f" 평균 MDD: {data['mean_mdd']:.1%}")
print(f" 파산 확률: {data['ruin_prob']:.1%}")
다중 전략 켈리 배분
여러 자동매매 전략을 동시에 운용할 때, 각 전략에 켈리 기준을 개별 적용하면 전체 포지션이 과도해질 수 있습니다. 이때는 다중 자산 켈리(Multi-Asset Kelly)를 사용합니다.
def multi_strategy_kelly(returns_matrix: np.ndarray,
risk_free=0.0, fraction=0.5):
"""
다중 전략 켈리 최적 배분
returns_matrix: 각 열이 전략별 수익률 시리즈
"""
mean_returns = np.mean(returns_matrix, axis=0) - risk_free
cov_matrix = np.cov(returns_matrix.T)
# 켈리 최적 배분 = Σ^(-1) × μ
cov_inv = np.linalg.inv(cov_matrix)
kelly_weights = cov_inv @ mean_returns
# 프랙셔널 켈리 적용
kelly_weights *= fraction
# 레버리지 제한 (전체 합 ≤ 1)
total = np.sum(np.abs(kelly_weights))
if total > 1:
kelly_weights /= total
return kelly_weights
# 3개 전략의 수익률 데이터 예시
np.random.seed(0)
strat_returns = np.column_stack([
np.random.normal(0.002, 0.01, 252), # 전략 A
np.random.normal(0.001, 0.008, 252), # 전략 B
np.random.normal(0.003, 0.015, 252) # 전략 C
])
weights = multi_strategy_kelly(strat_returns)
labels = ["모멘텀 전략", "평균회귀 전략", "변동성 돌파"]
for label, w in zip(labels, weights):
print(f"{label}: {w:.1%}")
켈리 기준 적용 시 주의사항
켈리 기준은 강력한 도구이지만, 잘못 적용하면 오히려 독이 됩니다. 실전 적용 시 반드시 지켜야 할 원칙들입니다.
1. 반드시 프랙셔널 켈리를 사용하라
풀 켈리는 이론적 최적값일 뿐, 추정 오차가 조금만 있어도 성과가 급격히 악화됩니다. 하프 켈리(50%)를 기본으로 시작하고, 전략에 대한 확신이 높아지면 점진적으로 비율을 조정하세요.
2. 충분한 샘플 데이터 확보
최소 100회 이상의 거래 데이터로 승률과 손익비를 추정해야 합니다. 50회 미만의 데이터로 계산한 켈리 비율은 신뢰할 수 없습니다. 워크포워드 분석을 병행하면 더욱 견고한 추정이 가능합니다.
3. 롤링 윈도우로 동적 조정
def rolling_kelly(trades: pd.Series, window=100, fraction=0.5):
"""롤링 윈도우 기반 동적 켈리 비율"""
kelly_series = []
for i in range(window, len(trades)):
recent = trades.iloc[i-window:i]
result = kelly_from_trades(recent)
kelly_series.append(result["kelly"] * fraction)
return pd.Series(kelly_series,
index=trades.index[window:])
시장 환경은 변합니다. 고정된 켈리 비율 대신, 최근 N회 거래의 성과로 동적 조정하면 시장 레짐 변화에 적응할 수 있습니다.
4. 최대 투자 비율 상한선 설정
켈리 비율이 아무리 높게 나와도, 단일 거래에 전체 자본의 최대 20~25% 이상 투입하지 마세요. 블랙스완 이벤트에 대한 안전장치입니다.
5. 켈리 음수 = 투자하지 말라
켈리 공식 결과가 음수라면, 해당 전략은 기대값이 음수라는 뜻입니다. 절대 투자하지 마세요. 전략을 재검토하거나 폐기해야 합니다.
실전 자동매매 시스템 통합 예시
class KellyPositionSizer:
"""자동매매 시스템용 켈리 포지션 사이저"""
def __init__(self, fraction=0.5, max_position=0.25,
min_trades=50, lookback=100):
self.fraction = fraction
self.max_position = max_position
self.min_trades = min_trades
self.lookback = lookback
self.trade_history = []
def add_trade(self, pnl_pct: float):
"""거래 결과 기록"""
self.trade_history.append(pnl_pct)
def get_position_size(self, capital: float) -> float:
"""현재 최적 포지션 크기 계산"""
if len(self.trade_history) < self.min_trades:
return capital * 0.02 # 최소 거래로 기본 2%
recent = pd.Series(
self.trade_history[-self.lookback:]
)
result = kelly_from_trades(recent)
kelly_pct = min(
result["half_kelly"],
self.max_position
)
kelly_pct = max(kelly_pct, 0)
return capital * kelly_pct
def get_stats(self) -> dict:
"""현재 켈리 통계"""
if len(self.trade_history) < self.min_trades:
return {"status": "데이터 수집 중",
"trades": len(self.trade_history)}
recent = pd.Series(
self.trade_history[-self.lookback:]
)
return kelly_from_trades(recent)
# 사용 예시
sizer = KellyPositionSizer(fraction=0.5, max_position=0.20)
# 과거 거래 데이터 입력
for pnl in mock_trades:
sizer.add_trade(pnl)
capital = 100_000
position = sizer.get_position_size(capital)
print(f"추천 포지션 크기: ${position:,.0f}")
print(f"자본 대비: {position/capital:.1%}")
print(f"n현재 통계:")
for k, v in sizer.get_stats().items():
print(f" {k}: {v:.4f}" if isinstance(v, float) else f" {k}: {v}")
켈리 기준 vs 다른 자금 관리 방법
| 방법 | 장점 | 단점 | 적합한 경우 |
|---|---|---|---|
| 켈리 기준 | 수학적 최적, 복리 극대화 | 추정 오차에 민감 | 충분한 거래 데이터 보유 시 |
| 고정 비율 | 단순, 예측 가능 | 전략 성과 미반영 | 초보자, 테스트 단계 |
| 변동성 기반 | 시장 상황 반영 | 변동성 추정 필요 | 다양한 자산 동시 운용 |
| 리스크 패리티 | 리스크 균등 배분 | 수익률 최적화 아님 | 포트폴리오 분산 중시 |
마무리
켈리 기준은 "얼마나 투자할 것인가"라는 질문에 수학적으로 답하는 가장 검증된 방법입니다. 에드워드 소프(Edward Thorp)가 카지노 블랙잭에서 시작해 월스트리트 헤지펀드까지 활용한 이 공식은, 자동매매 시스템에서도 핵심적인 역할을 합니다.
실전에서는 반드시 프랙셔널 켈리를 사용하고, 롤링 윈도우로 동적 조정하며, 최대 투자 비율 상한선을 설정하세요. 이 세 가지 원칙만 지키면, 켈리 기준은 여러분의 자동매매 시스템을 한 단계 끌어올릴 것입니다.