변동성 타겟팅 포트폴리오 전략

변동성 타겟팅이란?

변동성 타겟팅(Volatility Targeting)은 포트폴리오의 실현 변동성을 목표 수준으로 일정하게 유지하는 동적 자산 배분 전략입니다. 시장이 안정적일 때는 레버리지를 높이고, 변동성이 급등하면 자동으로 포지션을 축소하여 일관된 리스크 수준을 유지합니다.

브리지워터, AQR 등 글로벌 헤지펀드가 핵심 리스크 관리 기법으로 활용하며, 개인 퀀트 투자자도 파이썬으로 쉽게 구현할 수 있습니다. 전통적인 고정 비중 포트폴리오 대비 위험 대비 수익률(샤프 비율)을 크게 개선할 수 있는 전략입니다.

핵심 원리: 변동성 스케일링

변동성 타겟팅의 핵심 공식은 단순합니다:

  • 목표 비중 = 목표 변동성 / 실현 변동성
  • 실현 변동성이 10%이고 목표가 15%이면 → 비중 150% (레버리지)
  • 실현 변동성이 30%이고 목표가 15%이면 → 비중 50% (디레버리지)

이 메커니즘 덕분에 2008년 금융위기나 2020년 코로나 폭락 같은 고변동성 구간에서 자동으로 포지션이 축소되어 꼬리 위험(Tail Risk)을 효과적으로 줄입니다.

파이썬으로 변동성 타겟팅 구현

import numpy as np
import pandas as pd

class VolatilityTargeting:
    def __init__(self, target_vol=0.15, lookback=20, 
                 max_leverage=2.0, min_weight=0.1):
        self.target_vol = target_vol      # 목표 연간 변동성
        self.lookback = lookback          # 변동성 추정 기간 (거래일)
        self.max_leverage = max_leverage  # 최대 레버리지 제한
        self.min_weight = min_weight      # 최소 비중
    
    def estimate_volatility(self, returns, method='ewm'):
        """실현 변동성 추정"""
        if method == 'simple':
            # 단순 이동 표준편차
            vol = returns.rolling(self.lookback).std() * np.sqrt(252)
        elif method == 'ewm':
            # 지수가중 이동평균 (최근 데이터에 더 큰 가중치)
            vol = returns.ewm(span=self.lookback).std() * np.sqrt(252)
        elif method == 'yang_zhang':
            # Yang-Zhang 추정량 (OHLC 데이터 필요 시)
            vol = returns.rolling(self.lookback).std() * np.sqrt(252)
        return vol
    
    def calculate_weights(self, returns):
        """목표 변동성 기반 동적 비중 산출"""
        realized_vol = self.estimate_volatility(returns)
        
        # 목표 비중 = 목표 변동성 / 실현 변동성
        raw_weights = self.target_vol / realized_vol
        
        # 레버리지 제한 적용
        weights = raw_weights.clip(
            lower=self.min_weight, 
            upper=self.max_leverage
        )
        
        return weights, realized_vol
    
    def backtest(self, prices):
        """백테스트 실행"""
        returns = prices.pct_change().dropna()
        weights, realized_vol = self.calculate_weights(returns)
        
        # 전략 수익률 = 비중 × 자산 수익률
        # 비중은 전일 기준 (look-ahead bias 방지)
        strategy_returns = weights.shift(1) * returns
        strategy_returns = strategy_returns.dropna()
        
        # 성과 지표 계산
        cumulative = (1 + strategy_returns).cumprod()
        total_return = cumulative.iloc[-1] - 1
        annual_return = (1 + total_return) ** (252 / len(strategy_returns)) - 1
        annual_vol = strategy_returns.std() * np.sqrt(252)
        sharpe = annual_return / annual_vol
        
        # 최대 낙폭
        peak = cumulative.cummax()
        drawdown = (cumulative - peak) / peak
        max_drawdown = drawdown.min()
        
        return {
            'total_return': round(total_return * 100, 2),
            'annual_return': round(annual_return * 100, 2),
            'annual_vol': round(annual_vol * 100, 2),
            'sharpe_ratio': round(sharpe, 3),
            'max_drawdown': round(max_drawdown * 100, 2),
            'target_vol': round(self.target_vol * 100, 1),
            'avg_weight': round(weights.mean(), 2),
            'cumulative': cumulative,
            'weights': weights,
            'realized_vol': realized_vol
        }

멀티 자산 변동성 타겟팅

단일 자산이 아닌 여러 자산으로 구성된 포트폴리오에 변동성 타겟팅을 적용하는 방법입니다.

class MultiAssetVolTarget:
    def __init__(self, target_vol=0.10, lookback=60):
        self.target_vol = target_vol
        self.lookback = lookback
    
    def portfolio_vol_targeting(self, returns_df, base_weights=None):
        """멀티 자산 포트폴리오 변동성 타겟팅"""
        n_assets = returns_df.shape[1]
        
        if base_weights is None:
            base_weights = np.ones(n_assets) / n_assets
        
        results = []
        
        for i in range(self.lookback, len(returns_df)):
            window = returns_df.iloc[i - self.lookback:i]
            
            # 포트폴리오 공분산 행렬 추정
            cov_matrix = window.cov() * 252
            
            # 기본 비중 기반 포트폴리오 변동성
            port_vol = np.sqrt(
                base_weights @ cov_matrix.values @ base_weights
            )
            
            # 스케일링 팩터
            scale = min(self.target_vol / port_vol, 2.0)
            adjusted_weights = base_weights * scale
            
            # 실제 수익률
            daily_return = returns_df.iloc[i].values @ adjusted_weights
            
            results.append({
                'date': returns_df.index[i],
                'portfolio_return': daily_return,
                'scale_factor': scale,
                'estimated_vol': port_vol,
                'weights': adjusted_weights.tolist()
            })
        
        result_df = pd.DataFrame(results).set_index('date')
        return result_df
    
    def compare_strategies(self, returns_df):
        """고정 비중 vs 변동성 타겟팅 비교"""
        n_assets = returns_df.shape[1]
        equal_weights = np.ones(n_assets) / n_assets
        
        # 고정 비중 포트폴리오
        fixed_returns = returns_df @ equal_weights
        
        # 변동성 타겟팅 포트폴리오
        vt_result = self.portfolio_vol_targeting(returns_df, equal_weights)
        
        comparison = {
            'fixed_weight': {
                'annual_return': fixed_returns.mean() * 252 * 100,
                'annual_vol': fixed_returns.std() * np.sqrt(252) * 100,
                'sharpe': (fixed_returns.mean() / fixed_returns.std() 
                          * np.sqrt(252)),
                'max_dd': ((1 + fixed_returns).cumprod().cummax() - 
                          (1 + fixed_returns).cumprod()).max() / 
                          (1 + fixed_returns).cumprod().cummax().max() * -100
            },
            'vol_targeting': {
                'annual_return': (vt_result['portfolio_return'].mean() 
                                 * 252 * 100),
                'annual_vol': (vt_result['portfolio_return'].std() 
                              * np.sqrt(252) * 100),
                'sharpe': (vt_result['portfolio_return'].mean() / 
                          vt_result['portfolio_return'].std() * np.sqrt(252)),
                'avg_scale': vt_result['scale_factor'].mean()
            }
        }
        return comparison

변동성 추정 방법 비교

변동성 타겟팅의 성과는 변동성 추정 정확도에 크게 좌우됩니다. 주요 방법별 특징을 정리합니다.

  • 단순 이동평균(SMA): 구현이 쉽고 직관적이지만, 과거 데이터에 동일한 가중치를 부여하여 급변 시 반응이 느림
  • 지수가중 이동평균(EWMA): 최근 데이터에 더 큰 비중을 두어 시장 변화에 빠르게 적응. RiskMetrics에서 채택한 표준 방법
  • GARCH 모델: 변동성 클러스터링(한번 높아지면 지속되는 경향)을 모델링. 학술적으로 가장 정교하지만 연산 비용 높음
  • Parkinson/Yang-Zhang: 고가·저가 정보를 활용하여 종가 기반 추정보다 5배 효율적
def parkinson_volatility(high, low, window=20):
    """Parkinson 변동성 추정 (고저 기반)"""
    log_hl = np.log(high / low) ** 2
    factor = 1.0 / (4.0 * np.log(2))
    vol = np.sqrt(factor * log_hl.rolling(window).mean()) * np.sqrt(252)
    return vol

def garman_klass_volatility(open_p, high, low, close, window=20):
    """Garman-Klass 변동성 추정 (OHLC 기반)"""
    log_hl = (np.log(high / low)) ** 2
    log_co = (np.log(close / open_p)) ** 2
    
    gk = 0.5 * log_hl - (2 * np.log(2) - 1) * log_co
    vol = np.sqrt(gk.rolling(window).mean()) * np.sqrt(252)
    return vol

실전 적용 시 주의사항

  • 거래 비용: 비중 조절이 빈번하면 거래 비용이 수익을 잠식 — 리밸런싱 임계값(예: 비중 변화 5% 이상만 조절) 설정 필수
  • 레버리지 위험: 저변동성 구간에서 과도한 레버리지 방지를 위해 최대 비중 제한(1.5~2배)
  • 변동성 점프: 하루 만에 변동성이 급등하는 “볼 쇼크” 시 대응 지연 발생 가능 — 일중 모니터링 병행 권장
  • 목표 변동성 설정: 자산별 장기 평균 변동성의 70~100% 수준이 일반적
  • 백테스트 함정: 과거 저변동성 구간에서의 레버리지 효과가 과대 평가될 수 있음

변동성 타겟팅과 함께 퀀트 투자 샤프 비율 완벽 가이드를 참고하면 전략 성과를 정확히 평가할 수 있습니다. 또한 워크포워드 분석 파이썬 구현으로 과적합 여부를 검증하는 것을 권장합니다.

결론

변동성 타겟팅은 “리스크를 일정하게 유지한다”는 단순한 원칙으로 포트폴리오 성과를 크게 개선할 수 있는 전략입니다. 위기 상황에서 자동으로 방어 모드에 진입하고, 안정 구간에서는 수익 기회를 극대화합니다. 파이썬으로 구현하여 자신의 투자 전략에 적용해 보세요.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux