암호화폐를 위한 마코위츠 포트폴리오 이론: 제로에서 히어로까지
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()

*알고리즘 백테스팅: 이론적 최적화 모델을 검증하기 위한 역사적 성과 시뮬레이션.*
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)
결론: 포트폴리오 최적화 툴킷
이제 암호화폐 포트폴리오 최적화를 위한 완전한 툴킷을 갖추었습니다:
- 위험과 수익을 위한 기본 계산
- 효율적 프론티어 시각화
- 다양한 목표를 위한 수학적 최적화
- 리스크 패리티 같은 고급 전략
- 전략 검증을 위한 백테스팅 프레임워크
- 실제 구현을 위한 실용적 고려사항
핵심 요점
- 분산 투자는 공짜 점심 — 투자에서 유일한 공짜 점심
- 위험 허용 범위에 맞게 최적화 — 최대 샤프가 항상 최선은 아님
- 체계적으로 리밸런싱하되 과잉 거래는 피할 것
- 겸손하게 — 모델은 도구이지 수정 구슬이 아님
- 단순하게 시작하고 배우면서 복잡도를 높일 것
기억하세요: 암호화폐에서는 최고의 수학적 모델조차 일론이 언제 Dogecoin에 대해 트윗할지, 다음에 어떤 거래소가 해킹당할지 예측할 수 없습니다. 포트폴리오 이론을 기반으로 사용하되, 항상 여유 자금을 남겨두고 감당할 수 있는 것 이상은 투자하지 마세요.
자, 책임감 있게 최적화합시다! 🚀
더 읽을거리
코드 저장소
이 튜토리얼의 모든 코드는 GitHub에서 이용 가능합니다: https://github.com/suenot/markowitz
Happy optimizing! 📈
MarketMaker.cc Team
퀀트 리서치 및 전략