파이썬 리스크 패리티 포트폴리오

리스크 패리티(Risk Parity)란?

리스크 패리티는 각 자산이 포트폴리오 전체 리스크에 동일한 비중으로 기여하도록 자금을 배분하는 전략입니다. 1996년 브리지워터의 레이 달리오가 ‘올웨더(All Weather)’ 포트폴리오로 대중화했으며, 현재 전 세계 기관 투자자들이 가장 많이 사용하는 자산 배분 프레임워크 중 하나입니다.

전통적인 60/40 포트폴리오(주식 60%, 채권 40%)는 금액 기준으로는 분산되어 보이지만, 리스크의 90% 이상이 주식에 집중됩니다. 리스크 패리티는 이 문제를 해결합니다.

리스크 기여도(Risk Contribution) 이해하기

리스크 패리티의 핵심 개념은 한계 리스크 기여도(Marginal Risk Contribution)입니다:

포트폴리오 변동성: σ_p = √(w^T × Σ × w)

자산 i의 한계 리스크 기여도(MRC_i):
  MRC_i = (Σ × w)_i / σ_p

자산 i의 총 리스크 기여도(TRC_i):
  TRC_i = w_i × MRC_i

리스크 패리티 조건:
  TRC_1 = TRC_2 = ... = TRC_n = σ_p / n

즉, 모든 자산의 총 리스크 기여도가 동일해야 합니다. 변동성이 높은 자산은 비중을 줄이고, 낮은 자산은 비중을 늘립니다.

파이썬으로 리스크 패리티 구현

scipy 최적화를 사용한 리스크 패리티 포트폴리오 구현입니다:

import numpy as np
import pandas as pd
from scipy.optimize import minimize

def risk_parity_weights(cov_matrix: np.ndarray) -> np.ndarray:
    """
    리스크 패리티 최적 비중 계산
    목적함수: 각 자산의 리스크 기여도 차이를 최소화
    """
    n = cov_matrix.shape[0]
    target_risk = 1.0 / n  # 각 자산의 목표 리스크 비중

    def objective(w):
        port_vol = np.sqrt(w @ cov_matrix @ w)
        mrc = (cov_matrix @ w) / port_vol
        trc = w * mrc
        risk_contrib = trc / port_vol
        # 각 자산의 리스크 기여도와 목표의 차이 제곱합
        return np.sum((risk_contrib - target_risk) ** 2)

    constraints = {"type": "eq", "fun": lambda w: np.sum(w) - 1}
    bounds = [(0.01, 1.0)] * n
    x0 = np.ones(n) / n

    result = minimize(objective, x0, method="SLSQP",
                      bounds=bounds, constraints=constraints)
    return result.x

def analyze_risk_contribution(weights, cov_matrix, asset_names):
    """리스크 기여도 분석"""
    port_vol = np.sqrt(weights @ cov_matrix @ weights)
    mrc = (cov_matrix @ weights) / port_vol
    trc = weights * mrc
    risk_pct = trc / port_vol

    df = pd.DataFrame({
        "자산": asset_names,
        "비중": [f"{w:.1%}" for w in weights],
        "한계리스크기여도": [f"{m:.4f}" for m in mrc],
        "리스크기여비중": [f"{r:.1%}" for r in risk_pct],
    })
    return df, port_vol

# 4자산 포트폴리오 예시 (주식, 채권, 원자재, 금)
asset_names = ["주식", "채권", "원자재", "금"]
annual_vol = np.array([0.18, 0.05, 0.22, 0.15])
correlation = np.array([
    [1.0,  -0.2,  0.3,  0.0],
    [-0.2,  1.0, -0.1,  0.3],
    [0.3,  -0.1,  1.0,  0.1],
    [0.0,   0.3,  0.1,  1.0],
])
cov_matrix = np.outer(annual_vol, annual_vol) * correlation

# 리스크 패리티 비중 계산
rp_weights = risk_parity_weights(cov_matrix)
df, port_vol = analyze_risk_contribution(rp_weights, cov_matrix, asset_names)

print("=== 리스크 패리티 포트폴리오 ===")
print(df.to_string(index=False))
print(f"n포트폴리오 연간 변동성: {port_vol:.2%}")

60/40 vs 리스크 패리티 비교

전통적 60/40 포트폴리오와 리스크 패리티의 차이를 비교합니다:

항목 60/40 포트폴리오 리스크 패리티
배분 기준 금액 비중 리스크 기여도
주식 리스크 비중 ~90% ~25%
분산 효과 낮음 (주식에 편중) 높음 (균등 분산)
하락장 방어 취약 상대적 우수
구현 복잡도 단순 최적화 필요
리밸런싱 분기/반기 월간 권장

롤링 리밸런싱 백테스트

리스크 패리티를 월간 리밸런싱으로 백테스트하는 코드입니다:

def backtest_risk_parity(returns_df: pd.DataFrame,
                         lookback: int = 252,
                         rebalance_freq: str = "M") -> pd.DataFrame:
    """
    롤링 리스크 패리티 백테스트
    매월 말 과거 252일 데이터로 공분산 추정 후 리밸런싱
    """
    rebalance_dates = returns_df.resample(rebalance_freq).last().index
    weights_history = {}
    portfolio_returns = []

    current_weights = np.ones(len(returns_df.columns)) / len(returns_df.columns)

    for date in returns_df.index:
        if date in rebalance_dates:
            loc = returns_df.index.get_loc(date)
            if loc >= lookback:
                window = returns_df.iloc[loc - lookback:loc]
                cov = window.cov().values * 252  # 연율화
                try:
                    current_weights = risk_parity_weights(cov)
                except Exception:
                    pass  # 최적화 실패 시 이전 비중 유지
                weights_history[date] = current_weights

        daily_ret = returns_df.loc[date].values
        port_ret = np.sum(current_weights * daily_ret)
        portfolio_returns.append({"date": date, "return": port_ret})

    result = pd.DataFrame(portfolio_returns).set_index("date")
    result["cumulative"] = (1 + result["return"]).cumprod()
    return result

# 사용 예시 (가상 데이터)
np.random.seed(42)
dates = pd.date_range("2020-01-01", "2025-12-31", freq="B")
returns = pd.DataFrame({
    "주식": np.random.normal(0.0004, 0.012, len(dates)),
    "채권": np.random.normal(0.0001, 0.003, len(dates)),
    "원자재": np.random.normal(0.0002, 0.015, len(dates)),
    "금": np.random.normal(0.0002, 0.010, len(dates)),
}, index=dates)

result = backtest_risk_parity(returns, lookback=252)
total_return = result["cumulative"].iloc[-1] - 1
annual_return = (1 + total_return) ** (252 / len(result)) - 1
max_dd = (result["cumulative"] / result["cumulative"].cummax() - 1).min()

print(f"누적 수익률: {total_return:.2%}")
print(f"연평균 수익률: {annual_return:.2%}")
print(f"최대 낙폭: {max_dd:.2%}")

실전 적용 시 고려사항

  • 공분산 추정 안정성: 짧은 윈도우는 노이즈가 많고, 긴 윈도우는 최신 시장 상황을 반영하지 못합니다. 지수가중 공분산(EWMA)을 사용하면 최근 데이터에 더 높은 가중치를 줄 수 있습니다.
  • 레버리지 활용: 순수 리스크 패리티는 채권 비중이 높아 기대 수익률이 낮을 수 있습니다. 기관에서는 레버리지를 통해 목표 수익률을 맞추지만, 개인 투자자는 켈리 기준을 참고해 적정 레버리지 수준을 판단하세요.
  • 거래 비용: 월간 리밸런싱은 거래 비용이 발생합니다. 슬리피지 최적화를 적용하고, 비중 변화가 임계값(예: 2%) 이상일 때만 리밸런싱하는 밴드 방식을 고려하세요.
  • 위기 상황: 금융 위기 시 상관관계가 급등하면 리스크 패리티의 분산 효과가 약해집니다. 레짐 필터를 추가해 고변동성 장세에서는 전체 노출을 줄이는 것이 좋습니다.
  • 자산 유니버스 선택: 최소 3~4개 자산 클래스를 포함해야 합니다. 상관관계가 낮은 자산일수록 리스크 패리티의 효과가 극대화됩니다.

리스크 패리티 변형 전략

def hierarchical_risk_parity(returns_df: pd.DataFrame) -> np.ndarray:
    """
    계층적 리스크 패리티(HRP) - 간소화 버전
    상관 행렬 기반 클러스터링 후 하향식 배분
    """
    from scipy.cluster.hierarchy import linkage, leaves_list
    from scipy.spatial.distance import squareform

    corr = returns_df.corr().values
    dist = np.sqrt(0.5 * (1 - corr))
    np.fill_diagonal(dist, 0)

    # 계층적 클러스터링
    condensed = squareform(dist)
    link = linkage(condensed, method="ward")
    order = leaves_list(link)

    # 역분산 가중 (정렬된 순서)
    n = len(order)
    vol = returns_df.std().values
    inv_vol = 1.0 / vol
    weights = np.zeros(n)

    for i, idx in enumerate(order):
        weights[idx] = inv_vol[idx]

    weights /= weights.sum()
    return weights

# HRP는 공분산 역행렬이 불안정한 경우의 대안
hrp_w = hierarchical_risk_parity(returns)
print("HRP 비중:")
for name, w in zip(returns.columns, hrp_w):
    print(f"  {name}: {w:.1%}")

핵심 정리

리스크 패리티는 금액이 아닌 리스크 기여도를 균등 배분하는 포트폴리오 전략입니다. 전통적 60/40 대비 진정한 분산 투자를 실현하며, 파이썬으로 공분산 추정부터 최적화, 백테스트까지 자동화할 수 있습니다. 몬테카를로 포트폴리오 최적화와 결합하면 더욱 정교한 자산 배분이 가능합니다.

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