← 기사 목록으로
September 25, 2025
5분 소요

암호화폐를 위한 마코위츠 포트폴리오 이론: 제로에서 히어로까지

암호화폐를 위한 마코위츠 포트폴리오 이론: 제로에서 히어로까지
#portfolio optimization
#Markowitz
#crypto
#Python
#quantitative finance
#risk management
#diversification
#efficient frontier
#Sharpe ratio
#algorithmic trading

Python으로 최적의 암호화폐 포트폴리오 구축하기 — YOLO는 전략이 아니니까요

마코위츠 포트폴리오 이론 마코위츠 포트폴리오 이론: 주어진 위험 수준에서 수익을 극대화하기 위해 디지털 자산에 적용된 수학적 최적화.


서론: 암호화폐 포트폴리오에 수학이 필요한 이유 (감이 아니라)

암호화폐 매니아 여러분! 👋

일론이 트윗했다고 전 재산을 DOGE에 넣었던 때를 기억하시나요? 아니면 마지막 폭락 때 모든 걸 패닉 매도했던 때는요? 네, 우리 모두 그런 경험이 있죠. 오늘은 여러분의 포트폴리오(와 정신 건강)를 구할 수 있는 것에 대해 이야기하겠습니다: 마코위츠 포트폴리오 이론입니다.

해리 마코위츠는 1990년에 이 연구로 노벨상을 수상했습니다. 기본 아이디어는? 주어진 위험 수준에 대해 최상의 수익을 얻도록 포트폴리오를 수학적으로 최적화할 수 있다는 것입니다. 눈가리고 운전하는 대신 GPS를 갖는 것과 같습니다.

핵심 개념: 위험 대 수익 (영원한 춤)

코드에 들어가기 전에, 무엇을 다루는지 이해합시다:

  • 기대 수익률: 얼마나 벌 것으로 기대하는지
  • 위험 (변동성): 포트폴리오 가치가 얼마나 출렁이는지
  • 상관관계: 서로 다른 자산이 얼마나 비슷하게 움직이는지

마법은 완벽하게 동기화되어 움직이지 않는 자산을 결합할 때 일어납니다. 비트코인이 폭락해도 일부 DeFi 토큰은 더 잘 버틸 수 있습니다. 그것이 분산 투자의 힘입니다.

위험 대 수익 위험 대 수익: 변동성 높은 고수익 자산과 안정적인 기반의 균형을 맞춰 최적의 기하 평균 수익률을 달성.

Python 환경 설정

먼저 도구를 준비합시다:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.optimize import minimize
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')

plt.style.use('dark_background')
sns.set_palette("husl")

레벨 1: 첫걸음 — 간단한 포트폴리오 수학

기본부터 시작합시다. 간단한 2자산 포트폴리오의 수익과 위험을 계산합니다.

def get_crypto_data(symbols, period="1y"):
    """
    Fetch crypto data from Yahoo Finance
    symbols: list of crypto symbols (e.g., ['BTC-USD', 'ETH-USD'])
    period: time period for data
    """
    data = yf.download(symbols, period=period)['Adj Close']
    return data

crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)

returns = prices.pct_change().dropna()
print("Daily Returns Preview:")
print(returns.head())

기본적인 포트폴리오 지표를 계산해 봅시다:

def portfolio_performance(weights, returns):
    """
    Calculate portfolio return and volatility
    weights: array of portfolio weights
    returns: dataframe of asset returns
    """
    portfolio_return = np.sum(returns.mean() * weights) * 252

    portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))

    return portfolio_return, portfolio_vol

weights_5050 = np.array([0.5, 0.5])
ret_5050, vol_5050 = portfolio_performance(weights_5050, returns)

print(f"50/50 Portfolio:")
print(f"Expected Annual Return: {ret_5050:.2%}")
print(f"Annual Volatility: {vol_5050:.2%}")
print(f"Sharpe Ratio: {ret_5050/vol_5050:.3f}")

레벨 2: 본격적으로 — 효율적 프론티어

이제 본격적입니다! 효율적 프론티어는 모든 가능한 최적 포트폴리오를 보여줍니다. 각 점은 주어진 위험 수준에 대한 최상의 수익을 나타냅니다.

def generate_random_portfolios(returns, num_portfolios=10000):
    """
    Generate random portfolio combinations
    """
    num_assets = len(returns.columns)
    results = np.zeros((4, num_portfolios))

    for i in range(num_portfolios):
        weights = np.random.random(num_assets)
        weights /= np.sum(weights)  # Normalize to sum to 1

        portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
        sharpe_ratio = portfolio_return / portfolio_vol

        results[0,i] = portfolio_return
        results[1,i] = portfolio_vol
        results[2,i] = sharpe_ratio
        results[3,i:] = weights

    return results

results = generate_random_portfolios(returns)

portfolio_results = pd.DataFrame({
    'Returns': results[0],
    'Volatility': results[1],
    'Sharpe_Ratio': results[2]
})

plt.figure(figsize=(12, 8))
scatter = plt.scatter(portfolio_results['Volatility'],
                     portfolio_results['Returns'],
                     c=portfolio_results['Sharpe_Ratio'],
                     cmap='viridis', alpha=0.6)
plt.colorbar(scatter, label='Sharpe Ratio')
plt.xlabel('Volatility (Risk)')
plt.ylabel('Expected Return')
plt.title('Efficient Frontier - Random Portfolios')
plt.show()

효율적 프론티어 효율적 프론티어: 주어진 위험 수준에 대한 최대 기대 수익을 나타내는 곡선.

레벨 3: 최적화 마스터 — 완벽한 포트폴리오 찾기

무작위 샘플링은 재미있지만, 수학적으로 최적인 해를 원합니다. scipy 최적화의 출번입니다!

def negative_sharpe_ratio(weights, returns, risk_free_rate=0.02):
    """
    Calculate negative Sharpe ratio (we minimize this)
    """
    portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
    sharpe = (portfolio_return - risk_free_rate) / portfolio_vol
    return -sharpe

def minimize_volatility(weights, returns):
    """
    Calculate portfolio volatility (we minimize this)
    """
    _, portfolio_vol = portfolio_performance(weights, returns)
    return portfolio_vol

def portfolio_return_objective(weights, returns):
    """
    Calculate portfolio return (we maximize this)
    """
    portfolio_return, _ = portfolio_performance(weights, returns)
    return -portfolio_return  # Negative because we minimize

def optimize_portfolio(returns, objective='sharpe', target_return=None):
    """
    Optimize portfolio based on different objectives
    """
    num_assets = len(returns.columns)

    constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})  # Weights sum to 1
    bounds = tuple((0, 1) for _ in range(num_assets))  # No short selling

    initial_guess = num_assets * [1. / num_assets]

    if objective == 'sharpe':
        result = minimize(negative_sharpe_ratio, initial_guess,
                         args=(returns,), method='SLSQP',
                         bounds=bounds, constraints=constraints)

    elif objective == 'min_vol':
        result = minimize(minimize_volatility, initial_guess,
                         args=(returns,), method='SLSQP',
                         bounds=bounds, constraints=constraints)

    elif objective == 'target_return':
        constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1},
                      {'type': 'eq', 'fun': lambda x: portfolio_performance(x, returns)[0] - target_return})

        result = minimize(minimize_volatility, initial_guess,
                         args=(returns,), method='SLSQP',
                         bounds=bounds, constraints=constraints)

    return result

max_sharpe = optimize_portfolio(returns, 'sharpe')
min_vol = optimize_portfolio(returns, 'min_vol')

print("🎯 Maximum Sharpe Ratio Portfolio:")
for i, symbol in enumerate(crypto_symbols):
    print(f"{symbol}: {max_sharpe.x[i]:.3f}")

ret_sharpe, vol_sharpe = portfolio_performance(max_sharpe.x, returns)
print(f"Return: {ret_sharpe:.2%}, Volatility: {vol_sharpe:.2%}")
print(f"Sharpe Ratio: {ret_sharpe/vol_sharpe:.3f}\n")

print("🛡️ Minimum Volatility Portfolio:")
for i, symbol in enumerate(crypto_symbols):
    print(f"{symbol}: {min_vol.x[i]:.3f}")

ret_minvol, vol_minvol = portfolio_performance(min_vol.x, returns)
print(f"Return: {ret_minvol:.2%}, Volatility: {vol_minvol:.2%}")

레벨 4: 멀티 에셋의 세계 — 본격적인 암호화폐 포트폴리오

여러 자산을 포함하는 본격적인 암호화폐 포트폴리오로 확장합시다:

crypto_portfolio = ['BTC-USD', 'ETH-USD', 'BNB-USD', 'ADA-USD', 'SOL-USD', 'DOT-USD']
prices_multi = get_crypto_data(crypto_portfolio, period="2y")
returns_multi = prices_multi.pct_change().dropna()

correlation_matrix = returns_multi.corr()

plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='RdYlBu_r', center=0)
plt.title('Crypto Asset Correlation Matrix')
plt.show()

def efficient_frontier(returns, num_portfolios=50):
    """
    Calculate the efficient frontier
    """
    ret_range = np.linspace(returns.mean().min()*252, returns.mean().max()*252, num_portfolios)

    efficient_portfolios = []

    for target_ret in ret_range:
        try:
            result = optimize_portfolio(returns, 'target_return', target_ret)
            if result.success:
                ret, vol = portfolio_performance(result.x, returns)
                efficient_portfolios.append([ret, vol, result.x])
        except:
            continue

    return np.array(efficient_portfolios)

efficient_port = efficient_frontier(returns_multi)

plt.figure(figsize=(14, 10))

random_results = generate_random_portfolios(returns_multi, 5000)
plt.scatter(random_results[1], random_results[0],
           c=random_results[2], cmap='viridis', alpha=0.3, s=10)

if len(efficient_port) > 0:
    plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Efficient Frontier')

max_sharpe_multi = optimize_portfolio(returns_multi, 'sharpe')
min_vol_multi = optimize_portfolio(returns_multi, 'min_vol')

ret_sharpe_multi, vol_sharpe_multi = portfolio_performance(max_sharpe_multi.x, returns_multi)
ret_minvol_multi, vol_minvol_multi = portfolio_performance(min_vol_multi.x, returns_multi)

plt.scatter(vol_sharpe_multi, ret_sharpe_multi, marker='*', color='gold', s=500, label='Max Sharpe')
plt.scatter(vol_minvol_multi, ret_minvol_multi, marker='*', color='red', s=500, label='Min Volatility')

plt.colorbar(label='Sharpe Ratio')
plt.xlabel('Volatility (Risk)')
plt.ylabel('Expected Return')
plt.title('Multi-Asset Crypto Portfolio Optimization')
plt.legend()
plt.show()

print("🚀 Optimal Multi-Asset Allocations:")
print("\nMaximum Sharpe Ratio Portfolio:")
sharpe_weights = pd.Series(max_sharpe_multi.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in sharpe_weights.items():
    if weight > 0.01:  # Only show significant allocations
        print(f"{asset}: {weight:.1%}")

print(f"\nPortfolio Metrics:")
print(f"Expected Return: {ret_sharpe_multi:.1%}")
print(f"Volatility: {vol_sharpe_multi:.1%}")
print(f"Sharpe Ratio: {ret_sharpe_multi/vol_sharpe_multi:.2f}")

멀티 에셋 포트폴리오 최적화 멀티 에셋 분산 투자: 상관관계가 낮은 암호화폐 자산을 결합하여 안정적인 기하학적 구조의 견고한 포트폴리오 구축.

레벨 5: 고급 기법 — 블랙-리터만과 리스크 패리티

진정한 포트폴리오 최적화 달인을 위한 고급 기법을 구현합시다:

def risk_parity_portfolio(returns):
    """
    Risk Parity Portfolio - each asset contributes equally to portfolio risk
    """
    def risk_contribution(weights, cov_matrix):
        portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
        marginal_contrib = np.dot(cov_matrix, weights) / portfolio_vol
        contrib = weights * marginal_contrib
        return contrib

    def risk_parity_objective(weights, cov_matrix):
        contrib = risk_contribution(weights, cov_matrix)
        target_contrib = np.ones(len(weights)) / len(weights)
        return np.sum((contrib - target_contrib)**2)

    num_assets = len(returns.columns)
    cov_matrix = returns.cov() * 252

    constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})
    bounds = tuple((0.001, 1) for _ in range(num_assets))
    initial_guess = num_assets * [1. / num_assets]

    result = minimize(risk_parity_objective, initial_guess,
                     args=(cov_matrix,), method='SLSQP',
                     bounds=bounds, constraints=constraints)

    return result

risk_parity_result = risk_parity_portfolio(returns_multi)

print("⚖️ Risk Parity Portfolio:")
rp_weights = pd.Series(risk_parity_result.x, index=crypto_portfolio).sort_values(ascending=False)
for asset, weight in rp_weights.items():
    print(f"{asset}: {weight:.1%}")

ret_rp, vol_rp = portfolio_performance(risk_parity_result.x, returns_multi)
print(f"\nRisk Parity Metrics:")
print(f"Expected Return: {ret_rp:.1%}")
print(f"Volatility: {vol_rp:.1%}")
print(f"Sharpe Ratio: {ret_rp/vol_rp:.2f}")

def backtest_portfolio(weights, prices):
    """
    Simple backtest of portfolio performance
    """
    returns = prices.pct_change().dropna()
    portfolio_returns = (returns * weights).sum(axis=1)

    cumulative_returns = (1 + portfolio_returns).cumprod()

    total_return = cumulative_returns.iloc[-1] - 1
    annualized_return = (1 + total_return) ** (252 / len(portfolio_returns)) - 1
    annualized_vol = portfolio_returns.std() * np.sqrt(252)
    sharpe_ratio = annualized_return / annualized_vol

    max_dd = (cumulative_returns / cumulative_returns.expanding().max() - 1).min()

    return {
        'total_return': total_return,
        'annualized_return': annualized_return,
        'annualized_volatility': annualized_vol,
        'sharpe_ratio': sharpe_ratio,
        'max_drawdown': max_dd,
        'cumulative_returns': cumulative_returns
    }

strategies = {
    'Max Sharpe': max_sharpe_multi.x,
    'Min Volatility': min_vol_multi.x,
    'Risk Parity': risk_parity_result.x,
    'Equal Weight': np.ones(len(crypto_portfolio)) / len(crypto_portfolio)
}

plt.figure(figsize=(14, 8))

for name, weights in strategies.items():
    backtest_results = backtest_portfolio(weights, prices_multi)
    plt.plot(backtest_results['cumulative_returns'], label=f"{name} (Sharpe: {backtest_results['sharpe_ratio']:.2f})")

plt.title('Portfolio Strategy Backtests')
plt.xlabel('Date')
plt.ylabel('Cumulative Returns')
plt.legend()
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.show()

![Portfolio Strategy Backtests](/images/blog/markowitz-backtest.webp)
*알고리즘 백테스팅: 이론적 최적화 모델을 검증하기 위한 역사적 성과 시뮬레이션.*


performance_summary = pd.DataFrame()
for name, weights in strategies.items():
    results = backtest_portfolio(weights, prices_multi)
    performance_summary[name] = [
        f"{results['annualized_return']:.1%}",
        f"{results['annualized_volatility']:.1%}",
        f"{results['sharpe_ratio']:.2f}",
        f"{results['max_drawdown']:.1%}"
    ]

performance_summary.index = ['Annual Return', 'Annual Volatility', 'Sharpe Ratio', 'Max Drawdown']
print("\n📊 Strategy Performance Summary:")
print(performance_summary)

현실 점검: 마코위츠가 알려주지 않는 것

수학적 최적화에 올인하기 전에, 암호화폐에 대한 몇 가지 냉혹한 진실이 있습니다:

1. 과거 성과 ≠ 미래 결과 암호화폐 시장은 젊고 혼란스럽습니다. 계산한 상관관계는? 규제가 바뀌거나 다음 대형 해킹이 발생하면 하룻밤 사이에 뒤집힐 수 있습니다.

2. 거래 비용이 중요하다 포트폴리오 리밸런싱에는 비용이 듭니다. DeFi에서는 가스비가 수익을 잠식할 수 있습니다. 전략에 이를 반영하세요.

3. 유동성 문제 모든 암호화폐가 동일하게 유동적이지는 않습니다. 최적화에서 소형 알트코인이 훌륭해 보일 수 있지만, 폭락 시 매도해 보세요.

4. 레짐 변화 암호화폐 시장에는 다른 "레짐"이 있습니다 — 상승장, 하락장, 횡보장. 하나의 레짐에서 잘 작동하는 것이 다른 레짐에서는 작동하지 않을 수 있습니다.

실용적 구현 팁

def practical_portfolio_rebalancing(target_weights, current_weights, threshold=0.05):
    """
    Only rebalance when weights drift beyond threshold
    """
    weight_diff = np.abs(target_weights - current_weights)
    needs_rebalancing = np.any(weight_diff > threshold)

    if needs_rebalancing:
        print("🔄 Rebalancing needed!")
        for i, (target, current) in enumerate(zip(target_weights, current_weights)):
            if abs(target - current) > threshold:
                print(f"Asset {i}: {current:.1%}{target:.1%}")
    else:
        print("✅ Portfolio within tolerance, no rebalancing needed")

    return needs_rebalancing

current_allocation = np.array([0.35, 0.25, 0.15, 0.10, 0.10, 0.05])
target_allocation = max_sharpe_multi.x

practical_portfolio_rebalancing(target_allocation, current_allocation)

결론: 포트폴리오 최적화 툴킷

이제 암호화폐 포트폴리오 최적화를 위한 완전한 툴킷을 갖추었습니다:

  1. 위험과 수익을 위한 기본 계산
  2. 효율적 프론티어 시각화
  3. 다양한 목표를 위한 수학적 최적화
  4. 리스크 패리티 같은 고급 전략
  5. 전략 검증을 위한 백테스팅 프레임워크
  6. 실제 구현을 위한 실용적 고려사항

핵심 요점

  • 분산 투자는 공짜 점심 — 투자에서 유일한 공짜 점심
  • 위험 허용 범위에 맞게 최적화 — 최대 샤프가 항상 최선은 아님
  • 체계적으로 리밸런싱하되 과잉 거래는 피할 것
  • 겸손하게 — 모델은 도구이지 수정 구슬이 아님
  • 단순하게 시작하고 배우면서 복잡도를 높일 것

기억하세요: 암호화폐에서는 최고의 수학적 모델조차 일론이 언제 Dogecoin에 대해 트윗할지, 다음에 어떤 거래소가 해킹당할지 예측할 수 없습니다. 포트폴리오 이론을 기반으로 사용하되, 항상 여유 자금을 남겨두고 감당할 수 있는 것 이상은 투자하지 마세요.

자, 책임감 있게 최적화합시다! 🚀

더 읽을거리

코드 저장소

이 튜토리얼의 모든 코드는 GitHub에서 이용 가능합니다: https://github.com/suenot/markowitz

Happy optimizing! 📈

blog.disclaimer

MarketMaker.cc Team

퀀트 리서치 및 전략

Telegram에서 토론하기
Newsletter

시장에서 앞서 나가세요

뉴스레터를 구독하여 독점적인 AI 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.