정보 엔트로피와 금융 시장
클로드 섀넌(Claude Shannon)이 1948년 제안한 정보 엔트로피는 시스템의 불확실성을 수치로 측정합니다. 금융 시장에 적용하면 “현재 시장이 얼마나 무질서한가”를 객관적으로 계량할 수 있습니다. 엔트로피가 높으면 시장 참여자 간 의견이 분산되어 방향성이 불명확하고, 낮으면 한 방향으로 합의가 형성되어 강한 추세가 나타날 가능성이 높습니다.
이 기법은 전통 기술적 지표와 달리 가격 자체가 아닌 가격 변동의 확률 분포를 분석하므로, RSI나 MACD가 놓치는 시장 구조 변화를 포착할 수 있습니다.
섀넌 엔트로피 계산법
섀넌 엔트로피 공식은 다음과 같습니다: H = -Σ p(x) · log₂(p(x)). 수익률을 구간별로 나누어 각 구간의 발생 빈도를 확률로 변환한 뒤, 이 공식에 대입합니다.
import numpy as np
import pandas as pd
import ccxt
from scipy.stats import entropy
# 바이낸스 1시간봉 데이터 수집
exchange = ccxt.binance()
ohlcv = exchange.fetch_ohlcv('BTC/USDT', '1h', limit=720)
df = pd.DataFrame(ohlcv, columns=['ts','open','high','low','close','volume'])
df['returns'] = df['close'].pct_change().dropna()
def shannon_entropy(returns, n_bins=20):
"""수익률 분포의 섀넌 엔트로피 계산"""
counts, _ = np.histogram(returns.dropna(), bins=n_bins)
probs = counts / counts.sum()
probs = probs[probs > 0] # log(0) 방지
return entropy(probs, base=2)
# 롤링 엔트로피 (48시간 윈도우)
window = 48
df['entropy'] = df['returns'].rolling(window).apply(
lambda x: shannon_entropy(x, n_bins=10), raw=False
)
max_entropy = np.log2(10) # 균등분포일 때 최대 엔트로피
df['norm_entropy'] = df['entropy'] / max_entropy # 0~1 정규화
print(f"현재 정규화 엔트로피: {df['norm_entropy'].iloc[-1]:.3f}")
print(f"높으면 무질서(레인지), 낮으면 질서(추세)")
시장 레짐 분류: 엔트로피 기반
엔트로피 수준에 따라 시장을 세 가지 레짐으로 분류하고, 각 레짐에 맞는 전략을 자동 전환합니다.
def classify_regime(norm_entropy, trend_strength):
"""엔트로피 + 추세 강도로 시장 레짐 분류"""
if norm_entropy < 0.6:
if abs(trend_strength) > 0.02:
return 'STRONG_TREND' # 낮은 엔트로피 + 방향성 → 강한 추세
return 'COMPRESSION' # 낮은 엔트로피 + 방향성 없음 → 압축 구간
elif norm_entropy < 0.8:
return 'NORMAL' # 중간 엔트로피 → 일반 시장
else:
return 'HIGH_DISORDER' # 높은 엔트로피 → 무질서/레인지
# 추세 강도: 롤링 평균 수익률
df['trend'] = df['returns'].rolling(window).mean()
df['regime'] = df.apply(
lambda r: classify_regime(r['norm_entropy'], r['trend'])
if pd.notna(r['norm_entropy']) else 'UNKNOWN', axis=1
)
print(df[['close','norm_entropy','regime']].tail(5))
전략 1: 엔트로피 브레이크아웃
엔트로피가 장기간 낮게 유지되다 급격히 상승하면, 시장 구조가 변화하고 있다는 신호입니다. 이는 큰 움직임의 시작점이 될 수 있습니다.
def entropy_breakout_signal(entropy_series, lookback=100, z_threshold=2.0):
"""엔트로피 급변 감지 — 시장 구조 변화 포착"""
mean_ent = entropy_series.rolling(lookback).mean()
std_ent = entropy_series.rolling(lookback).std()
z_score = (entropy_series - mean_ent) / std_ent
latest_z = z_score.iloc[-1]
if latest_z > z_threshold:
return 'ENTROPY_SPIKE', latest_z # 무질서 급증 → 변동성 확대 예고
elif latest_z < -z_threshold:
return 'ENTROPY_DROP', latest_z # 질서 급증 → 추세 강화 예고
return 'NEUTRAL', latest_z
signal, z = entropy_breakout_signal(df['norm_entropy'])
print(f"엔트로피 신호: {signal} (z-score: {z:.2f})")
전략 2: 엔트로피 가중 모멘텀
기존 모멘텀 전략에 엔트로피를 가중치로 적용합니다. 엔트로피가 낮을 때(시장이 질서 있을 때) 모멘텀 신호를 강하게, 높을 때 약하게 반영합니다.
def entropy_weighted_momentum(df, mom_period=20, ent_col='norm_entropy'):
"""엔트로피 가중 모멘텀 — 질서 있는 추세에 집중"""
# 기본 모멘텀
momentum = df['close'].pct_change(mom_period)
# 엔트로피 가중치: 낮은 엔트로피 → 높은 가중치
weight = 1 - df[ent_col] # 0~1 (엔트로피 역수)
weight = weight.clip(0.1, 1.0)
weighted_mom = momentum * weight
# 신호 생성
signals = pd.Series('HOLD', index=df.index)
signals[weighted_mom > 0.02] = 'BUY'
signals[weighted_mom < -0.02] = 'SELL'
return weighted_mom, signals
df['w_momentum'], df['mom_signal'] = entropy_weighted_momentum(df)
print(f"가중 모멘텀: {df['w_momentum'].iloc[-1]:.4f}")
print(f"신호: {df['mom_signal'].iloc[-1]}")
전략 3: 교차 엔트로피 다이버전스
KL 다이버전스(Kullback-Leibler Divergence)로 두 기간의 수익률 분포 차이를 측정합니다. 최근 분포가 과거와 크게 달라지면 시장의 성격이 바뀌고 있다는 뜻입니다.
from scipy.stats import entropy as sp_entropy
def kl_divergence_signal(returns, recent_window=48, base_window=200, n_bins=15):
"""KL 다이버전스 — 수익률 분포 변화 감지"""
recent = returns.iloc[-recent_window:].dropna()
baseline = returns.iloc[-base_window:-recent_window].dropna()
# 동일한 bin으로 히스토그램 생성
bins = np.linspace(returns.min(), returns.max(), n_bins + 1)
p_recent, _ = np.histogram(recent, bins=bins, density=True)
q_baseline, _ = np.histogram(baseline, bins=bins, density=True)
# 0 방지 (라플라스 스무딩)
eps = 1e-10
p_recent = p_recent + eps
q_baseline = q_baseline + eps
p_recent = p_recent / p_recent.sum()
q_baseline = q_baseline / q_baseline.sum()
kl_div = sp_entropy(p_recent, q_baseline)
if kl_div > 0.5:
return 'REGIME_CHANGE', kl_div # 분포 크게 변화
elif kl_div > 0.2:
return 'SHIFTING', kl_div # 점진적 변화
return 'STABLE', kl_div
signal, kl = kl_divergence_signal(df['returns'])
print(f"KL 다이버전스: {kl:.4f} → {signal}")
통합 엔트로피 매매 시스템
세 전략을 통합하여 레짐별 자동 전략 전환을 구현합니다.
class EntropyTradingSystem:
def __init__(self):
self.regime_strategies = {
'STRONG_TREND': self._trend_follow,
'COMPRESSION': self._breakout_wait,
'NORMAL': self._momentum,
'HIGH_DISORDER': self._mean_revert
}
def _trend_follow(self, df):
"""추세 추종 — 엔트로피 낮고 방향성 있을 때"""
return 'BUY' if df['trend'].iloc[-1] > 0 else 'SELL'
def _breakout_wait(self, df):
"""압축 구간 — 돌파 대기"""
return 'WAIT_BREAKOUT'
def _momentum(self, df):
"""일반 시장 — 가중 모멘텀"""
return df['mom_signal'].iloc[-1]
def _mean_revert(self, df):
"""고엔트로피 — 평균회귀"""
z = (df['close'].iloc[-1] - df['close'].rolling(20).mean().iloc[-1])
z /= df['close'].rolling(20).std().iloc[-1]
if z < -1.5: return 'BUY'
if z > 1.5: return 'SELL'
return 'HOLD'
def execute(self, df):
regime = df['regime'].iloc[-1]
strategy_fn = self.regime_strategies.get(regime, self._momentum)
action = strategy_fn(df)
return {'regime': regime, 'action': action,
'entropy': df['norm_entropy'].iloc[-1]}
system = EntropyTradingSystem()
# result = system.execute(df)
실전 적용 시 핵심 포인트
- 빈(bin) 개수 최적화: 너무 적으면 정보 손실, 너무 많으면 노이즈 증가. Sturges' rule(k = 1 + 3.322·log(n))이나 교차 검증으로 최적값을 찾으세요.
- 다중 타임프레임 분석: 1시간, 4시간, 일봉의 엔트로피를 동시에 모니터링하면 페어 트레이딩처럼 다양한 관점을 확보할 수 있습니다.
- 전이 엔트로피(Transfer Entropy): 단순 엔트로피를 넘어, 한 자산이 다른 자산에 정보를 전달하는 방향성을 분석하면 선행 지표를 발굴할 수 있습니다.
- 과적합 주의: 엔트로피 임계값을 과거 데이터에 맞춰 과도하게 최적화하면 백테스트 과적합에 빠질 수 있습니다. 워크포워드 테스트를 병행하세요.
마무리: 정보 이론이 퀀트에 주는 통찰
엔트로피 기반 분석의 가장 큰 장점은 "시장이 무엇을 할지"가 아니라 "시장이 얼마나 예측 가능한 상태인지"를 먼저 판단한다는 것입니다. 예측 불가능한 시장에서 무리하게 포지션을 잡는 것은 동전 던지기와 다름없습니다. 엔트로피가 높을 때는 관망하고, 낮을 때 집중 매매하는 것만으로도 승률을 크게 개선할 수 있습니다.
| 엔트로피 수준 | 시장 상태 | 최적 전략 |
|---|---|---|
| 낮음 (< 0.6) | 질서 / 추세 | 추세추종, 모멘텀 |
| 중간 (0.6~0.8) | 일반 | 가중 모멘텀 |
| 높음 (> 0.8) | 무질서 / 레인지 | 평균회귀, 관망 |
| 급변 | 구조 변화 | 브레이크아웃 대비 |