켈리 기준(Kelly Criterion)이란?
켈리 기준은 1956년 벨 연구소의 존 래리 켈리 주니어가 제안한 최적 베팅 비율 공식입니다. 원래 정보 이론에서 출발했지만, 현재는 퀀트 트레이딩과 자동매매에서 자금 배분(Money Management)의 핵심 도구로 활용됩니다.
핵심 아이디어는 간단합니다. 승률과 손익비를 알면, 장기 자산 성장률을 극대화하는 최적 투자 비율을 수학적으로 계산할 수 있다는 것입니다.
켈리 공식 유도
기본 켈리 공식은 다음과 같습니다:
f* = (p × b - q) / b
여기서:
f* = 최적 투자 비율 (전체 자본 대비)
p = 승률 (winning probability)
q = 패률 (1 - p)
b = 손익비 (평균 이익 / 평균 손실)
예를 들어 승률 55%, 손익비 1.5인 전략이라면:
f* = (0.55 × 1.5 - 0.45) / 1.5
= (0.825 - 0.45) / 1.5
= 0.375 / 1.5
= 0.25 (25%)
즉, 매 트레이드마다 전체 자본의 25%를 투입하는 것이 장기 복리 성장률을 극대화하는 비율입니다.
파이썬으로 켈리 비율 계산하기
실제 트레이딩 데이터에서 켈리 비율을 계산하는 파이썬 코드입니다:
import numpy as np
import pandas as pd
def kelly_criterion(win_rate: float, avg_win: float, avg_loss: float) -> float:
"""기본 켈리 비율 계산"""
b = avg_win / abs(avg_loss) # 손익비
q = 1 - win_rate
kelly = (win_rate * b - q) / b
return max(kelly, 0) # 음수면 투자하지 않음
def kelly_from_returns(returns: pd.Series) -> dict:
"""과거 수익률 시리즈에서 켈리 비율 추출"""
wins = returns[returns > 0]
losses = returns[returns < 0]
win_rate = len(wins) / len(returns)
avg_win = wins.mean() if len(wins) > 0 else 0
avg_loss = losses.mean() if len(losses) > 0 else -1
full_kelly = kelly_criterion(win_rate, avg_win, avg_loss)
return {
"win_rate": round(win_rate, 4),
"avg_win": round(avg_win, 4),
"avg_loss": round(avg_loss, 4),
"payoff_ratio": round(avg_win / abs(avg_loss), 4),
"full_kelly": round(full_kelly, 4),
"half_kelly": round(full_kelly / 2, 4),
"quarter_kelly": round(full_kelly / 4, 4),
}
# 사용 예시
np.random.seed(42)
sample_returns = pd.Series(np.random.normal(0.002, 0.03, 200))
result = kelly_from_returns(sample_returns)
for key, val in result.items():
print(f"{key:>16}: {val}")
풀 켈리 vs 프랙셔널 켈리
실전에서 풀 켈리(Full Kelly)를 그대로 사용하면 위험합니다. 그 이유는 다음과 같습니다:
- 파라미터 추정 오류: 실제 승률과 손익비는 변동하며, 과거 데이터에서 추정한 값이 미래에도 유효하다는 보장이 없습니다.
- 극단적 변동성: 풀 켈리는 수학적 최적이지만, 중간 과정에서 50~80% 드로다운이 발생할 수 있습니다.
- 비정규 분포: 켈리 공식은 이항 분포를 가정하지만, 실제 시장 수익률은 팻테일(fat-tail) 분포를 따릅니다.
이 때문에 실무에서는 하프 켈리(Half Kelly) 또는 쿼터 켈리(Quarter Kelly)를 주로 사용합니다:
| 방식 | 투자 비율 | 기대 성장률 | 최대 드로다운 | 실전 적합도 |
|---|---|---|---|---|
| 풀 켈리 | f* | 최대 | 매우 큼 (50%+) | ❌ 비추천 |
| 하프 켈리 | f*/2 | 최대의 75% | 적당 | ✅ 가장 많이 사용 |
| 쿼터 켈리 | f*/4 | 최대의 44% | 작음 | ✅ 보수적 운용 |
다중 자산 켈리: 포트폴리오 적용
여러 전략이나 자산에 동시 투자할 때는 다중 자산 켈리(Multi-Asset Kelly)를 사용합니다. 공분산 행렬을 활용한 확장 공식입니다:
import numpy as np
def multi_asset_kelly(expected_returns: np.ndarray,
cov_matrix: np.ndarray,
fraction: float = 0.5) -> np.ndarray:
"""
다중 자산 켈리 비율 계산
f* = Σ^(-1) × μ (공분산 역행렬 × 기대수익률 벡터)
"""
cov_inv = np.linalg.inv(cov_matrix)
full_kelly = cov_inv @ expected_returns
return full_kelly * fraction # 프랙셔널 켈리 적용
# 3개 전략의 일간 기대수익률과 공분산
mu = np.array([0.001, 0.0015, 0.0008])
cov = np.array([
[0.0004, 0.0001, 0.00005],
[0.0001, 0.0006, 0.0002],
[0.00005, 0.0002, 0.0003]
])
weights = multi_asset_kelly(mu, cov, fraction=0.5)
print("하프 켈리 포트폴리오 비중:")
for i, w in enumerate(weights):
print(f" 전략 {i+1}: {w:.2%}")
다중 자산 켈리는 몬테카를로 포트폴리오 최적화와 결합하면 더욱 강력한 자금 배분 도구가 됩니다.
자동매매 시스템에 켈리 기준 통합하기
켈리 기준을 실제 파이썬 자동매매 봇에 통합하는 방법입니다:
class KellyPositionSizer:
"""롤링 윈도우 기반 켈리 포지션 사이저"""
def __init__(self, lookback: int = 100, fraction: float = 0.5,
max_risk: float = 0.15):
self.lookback = lookback
self.fraction = fraction
self.max_risk = max_risk # 최대 투자 비율 제한
self.trade_results = []
def add_trade(self, pnl_pct: float):
"""트레이드 결과 기록"""
self.trade_results.append(pnl_pct)
if len(self.trade_results) > self.lookback:
self.trade_results.pop(0)
def get_position_size(self, capital: float) -> float:
"""현재 자본 대비 최적 포지션 크기 반환"""
if len(self.trade_results) < 20:
return capital * 0.02 # 데이터 부족 시 보수적 고정 비율
returns = np.array(self.trade_results)
wins = returns[returns > 0]
losses = returns[returns < 0]
if len(losses) == 0 or len(wins) == 0:
return capital * 0.02
win_rate = len(wins) / len(returns)
payoff = wins.mean() / abs(losses.mean())
kelly = (win_rate * payoff - (1 - win_rate)) / payoff
kelly = max(kelly, 0) * self.fraction
kelly = min(kelly, self.max_risk) # 상한 제한
return capital * kelly
# 실전 사용
sizer = KellyPositionSizer(lookback=100, fraction=0.5, max_risk=0.15)
# 과거 트레이드 결과 입력
for pnl in [0.02, -0.01, 0.03, -0.015, 0.01, 0.025, -0.008] * 15:
sizer.add_trade(pnl)
capital = 10_000_000 # 1000만원
position = sizer.get_position_size(capital)
print(f"추천 포지션 크기: {position:,.0f}원 ({position/capital:.1%})")
켈리 기준의 실전 주의사항
- 롤링 윈도우 사용: 고정된 승률이 아니라 최근 N회 트레이드 기준으로 켈리 비율을 동적 업데이트해야 합니다.
- 레버리지 제한: 켈리 비율이 100%를 넘으면 레버리지를 의미하는데, 개인 투자자는 절대 레버리지를 켈리 기준으로 정당화하면 안 됩니다.
- 거래 비용 반영: 슬리피지와 수수료를 슬리피지 최적화를 통해 반영한 후 켈리를 계산해야 합니다.
- 드로다운 한도: 켈리 비율과 별도로, 전체 포트폴리오의 최대 드로다운 한도(예: 20%)를 설정하세요.
- 비정상 시장: 급등락 장세에서는 켈리를 일시적으로 축소하는 레짐 필터를 적용하세요.
켈리 vs 고정 비율: 성과 비교
import numpy as np
def simulate_growth(returns, fraction_func, initial=1_000_000, n_sim=1000):
"""고정 비율 vs 켈리 비율 시뮬레이션"""
np.random.seed(0)
results = []
for _ in range(n_sim):
capital = initial
sampled = np.random.choice(returns, size=252, replace=True)
for r in sampled:
f = fraction_func(capital)
capital += capital * f * r
capital = max(capital, 0)
results.append(capital)
return np.array(results)
# 가상 일간 수익률
daily_returns = np.concatenate([
np.random.normal(0.003, 0.02, 150), # 이기는 날
np.random.normal(-0.002, 0.015, 100) # 지는 날
])
fixed_results = simulate_growth(daily_returns, lambda c: 0.05)
kelly_results = simulate_growth(daily_returns, lambda c: 0.12)
print(f"고정 5% — 중앙값: {np.median(fixed_results):,.0f}원")
print(f"켈리 12% — 중앙값: {np.median(kelly_results):,.0f}원")
print(f"고정 5% — 파산율: {(fixed_results < 100_000).mean():.1%}")
print(f"켈리 12% — 파산율: {(kelly_results < 100_000).mean():.1%}")
핵심 정리
켈리 기준은 수학적으로 최적의 자금 배분 비율을 제시하는 강력한 도구입니다. 하지만 실전에서는 반드시 프랙셔널 켈리(하프 또는 쿼터)를 사용하고, 롤링 윈도우와 최대 리스크 상한을 결합해야 합니다. 자동매매 시스템에 켈리를 통합하면, 전략 성과에 따라 포지션 크기가 자동으로 조절되어 장기 복리 수익률을 극대화할 수 있습니다.