평균분산 포트폴리오 최적화 전략

평균-분산 포트폴리오 최적화란?

평균-분산 포트폴리오 최적화(Mean-Variance Optimization, MVO)는 1952년 해리 마코위츠(Harry Markowitz)가 제안한 현대 포트폴리오 이론(MPT)의 핵심 기법입니다. 기대 수익률을 극대화하면서 리스크(분산)를 최소화하는 자산 배분 비율을 수학적으로 도출하는 방법론으로, 퀀트 투자의 근간이 되는 전략입니다.

자동매매 시스템에서 포트폴리오 최적화는 단순히 “어떤 종목을 살까”를 넘어 “각 자산에 얼마나 배분할까”라는 핵심 질문에 답합니다. 이 글에서는 파이썬으로 효율적 프론티어를 구현하고, 실전 자동매매에 적용하는 방법을 단계별로 알아봅니다.

효율적 프론티어(Efficient Frontier) 이해하기

효율적 프론티어는 주어진 리스크 수준에서 달성 가능한 최대 기대 수익률을 연결한 곡선입니다. 이 곡선 위의 포트폴리오만이 합리적인 투자자가 선택할 수 있는 “효율적” 조합입니다.

구분 설명 특징
최소 분산 포트폴리오 리스크가 가장 낮은 조합 보수적 투자자에게 적합
최대 샤프 비율 포트폴리오 위험 대비 수익이 최대인 조합 가장 널리 사용되는 기준
최대 수익 포트폴리오 수익률이 가장 높은 조합 고위험·고수익 추구

파이썬으로 구현하는 평균-분산 최적화

실제 구현에서는 scipy.optimize를 활용하여 제약 조건 하에서 최적 비중을 산출합니다. 아래는 핵심 구현 코드입니다.

1단계: 수익률·공분산 행렬 계산

import numpy as np
import pandas as pd

# 일별 수익률 계산
returns = prices.pct_change().dropna()

# 연율화된 기대 수익률
mu = returns.mean() * 252

# 연율화된 공분산 행렬
cov_matrix = returns.cov() * 252

print(f"자산 수: {len(mu)}")
print(f"기대 수익률 범위: {mu.min():.2%} ~ {mu.max():.2%}")

공분산 행렬은 자산 간의 상관관계와 변동성을 동시에 반영합니다. 상관관계가 낮은 자산을 조합할수록 분산 효과가 극대화됩니다.

2단계: 최적화 함수 정의

from scipy.optimize import minimize

def portfolio_volatility(weights, cov_matrix):
    """포트폴리오 변동성 계산"""
    return np.sqrt(weights @ cov_matrix @ weights)

def neg_sharpe_ratio(weights, mu, cov_matrix, rf=0.04):
    """음의 샤프 비율 (최소화 → 최대 샤프)"""
    port_return = weights @ mu
    port_vol = portfolio_volatility(weights, cov_matrix)
    return -(port_return - rf) / port_vol

def optimize_max_sharpe(mu, cov_matrix, rf=0.04):
    n = len(mu)
    constraints = [
        {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}  # 비중 합 = 1
    ]
    bounds = [(0, 0.4) for _ in range(n)]  # 개별 자산 최대 40%
    
    result = minimize(
        neg_sharpe_ratio,
        x0=np.ones(n) / n,  # 동일 비중 초기값
        args=(mu, cov_matrix, rf),
        method='SLSQP',
        bounds=bounds,
        constraints=constraints
    )
    return result.x

bounds 매개변수로 개별 자산의 최대 비중을 제한하면 과도한 집중 투자를 방지할 수 있습니다. 실전에서는 보통 20~40%로 설정합니다.

3단계: 효율적 프론티어 생성

def efficient_frontier(mu, cov_matrix, n_points=100):
    """효율적 프론티어 포인트 생성"""
    results = []
    target_returns = np.linspace(mu.min(), mu.max(), n_points)
    
    for target in target_returns:
        n = len(mu)
        constraints = [
            {'type': 'eq', 'fun': lambda w: np.sum(w) - 1},
            {'type': 'eq', 'fun': lambda w: w @ mu - target}
        ]
        bounds = [(0, 1) for _ in range(n)]
        
        result = minimize(
            portfolio_volatility,
            x0=np.ones(n) / n,
            args=(cov_matrix,),
            method='SLSQP',
            bounds=bounds,
            constraints=constraints
        )
        
        if result.success:
            results.append({
                'return': target,
                'volatility': result.fun,
                'weights': result.x
            })
    
    return pd.DataFrame(results)

실전 자동매매 적용 시 주의사항

이론적으로 완벽해 보이는 MVO도 실전에서는 여러 함정이 존재합니다. 퀀트 백테스트 과적합 방지법에서 다룬 것처럼, 과거 데이터에 과도하게 의존하면 미래 성과가 크게 달라질 수 있습니다.

추정 오차 문제와 해결책

MVO의 가장 큰 약점은 입력값(기대 수익률, 공분산)의 추정 오차에 매우 민감하다는 점입니다. 작은 추정 오차가 최적 비중을 크게 변동시킵니다.

# 수축 추정량(Shrinkage Estimator) 적용
from sklearn.covariance import LedoitWolf

# Ledoit-Wolf 수축 공분산
lw = LedoitWolf().fit(returns)
shrunk_cov = pd.DataFrame(
    lw.covariance_ * 252,
    index=returns.columns,
    columns=returns.columns
)

# 수축 강도 확인
print(f"수축 계수(shrinkage): {lw.shrinkage_:.4f}")
# 1에 가까울수록 단위 행렬로 수축 → 상관관계를 보수적으로 추정

Ledoit-Wolf 수축 추정량은 표본 공분산 행렬과 구조화된 타겟(단위 행렬)의 가중 평균을 취하여 추정 오차를 줄입니다. 자산 수가 많거나 데이터가 부족할 때 특히 효과적입니다.

리밸런싱 주기와 거래 비용

최적 비중이 매일 바뀐다고 매일 리밸런싱하면 거래 비용이 수익을 초과할 수 있습니다. 자동 포트폴리오 리밸런싱 전략에서 자세히 다뤘듯이, 임계값 기반 리밸런싱이 실전에서 가장 효율적입니다.

def should_rebalance(current_weights, target_weights, threshold=0.05):
    """비중 편차가 임계값을 초과하면 리밸런싱"""
    deviation = np.abs(current_weights - target_weights)
    return np.any(deviation > threshold)

# 거래 비용 반영 최적화
def optimize_with_cost(mu, cov_matrix, current_weights, cost=0.001):
    n = len(mu)
    
    def objective(w):
        port_vol = portfolio_volatility(w, cov_matrix)
        turnover = np.sum(np.abs(w - current_weights))
        return port_vol + cost * turnover  # 거래 비용 페널티
    
    constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
    bounds = [(0, 0.4) for _ in range(n)]
    
    result = minimize(objective, x0=current_weights,
                      method='SLSQP', bounds=bounds,
                      constraints=constraints)
    return result.x

블랙-리터만 모델로 확장하기

순수 MVO의 한계를 보완하는 대표적인 방법이 블랙-리터만 자산배분 전략입니다. 시장 균형 수익률에 투자자의 주관적 전망(view)을 결합하여 보다 안정적인 기대 수익률을 산출합니다.

# 블랙-리터만 기대 수익률
def black_litterman_mu(cov, market_caps, risk_aversion=2.5):
    """시장 균형 내재 수익률(implied equilibrium returns)"""
    market_weights = market_caps / market_caps.sum()
    pi = risk_aversion * cov @ market_weights
    return pi

# 시장 내재 수익률 + 투자자 전망 결합
pi = black_litterman_mu(shrunk_cov, market_caps)
# 이후 전망 행렬(P, Q)과 불확실성(Omega)을 적용하여 조정

실전 워크플로우: 월간 자동 리밸런싱

아래는 평균-분산 최적화를 활용한 월간 자동 리밸런싱 파이프라인의 전체 흐름입니다.

import schedule

def monthly_rebalance():
    # 1. 최근 252일 가격 데이터 수집
    prices = fetch_prices(assets, lookback=252)
    returns = prices.pct_change().dropna()
    
    # 2. 수축 공분산 추정
    lw = LedoitWolf().fit(returns)
    cov = pd.DataFrame(lw.covariance_ * 252,
                       index=returns.columns,
                       columns=returns.columns)
    mu = returns.mean() * 252
    
    # 3. 최대 샤프 비율 비중 산출
    optimal_weights = optimize_max_sharpe(mu, cov)
    
    # 4. 현재 비중과 비교 → 임계값 초과 시만 실행
    current = get_current_weights()
    if should_rebalance(current, optimal_weights, threshold=0.05):
        execute_rebalance(current, optimal_weights)
        log_rebalance(optimal_weights)
    
    # 5. 성과 기록
    sharpe = (optimal_weights @ mu - 0.04) / portfolio_volatility(
        optimal_weights, cov)
    print(f"예상 샤프 비율: {sharpe:.2f}")

schedule.every().month.do(monthly_rebalance)

MVO 한계와 대안 전략 비교

전략 장점 단점 적합한 상황
평균-분산 최적화 이론적 최적해 추정 오차 민감 안정적 자산군
리스크 패리티 수익률 추정 불필요 기대 수익 무시 다자산 분산
블랙-리터만 전망 반영 가능 전망 설정 주관적 액티브 운용
최소 분산 구현 단순 수익 최적화 안 함 방어적 전략

핵심 정리

평균-분산 포트폴리오 최적화는 퀀트 투자의 출발점이자 가장 강력한 자산 배분 도구입니다. 실전 적용 시 기억해야 할 핵심 포인트를 정리합니다.

  • 수축 추정량 필수: 표본 공분산을 그대로 쓰면 극단적 비중이 산출됩니다. Ledoit-Wolf 등 수축 기법을 반드시 적용하세요.
  • 비중 제약 설정: 개별 자산 최대 비중(bounds)을 설정하여 집중 리스크를 제한합니다.
  • 거래 비용 반영: 턴오버 페널티를 목적함수에 포함하여 불필요한 매매를 억제합니다.
  • 임계값 리밸런싱: 비중 편차가 일정 수준(보통 5%)을 초과할 때만 리밸런싱하여 비용을 절감합니다.
  • 블랙-리터만 확장: 시장 전망을 체계적으로 반영하려면 블랙-리터만 모델로 확장하는 것을 권장합니다.

자동매매 시스템에 MVO를 통합하면 감정적 판단을 배제하고 수학적으로 최적화된 자산 배분을 실현할 수 있습니다. 다만 모든 모델은 가정에 의존하므로, 정기적인 검증과 파라미터 조정이 필수입니다.

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