암호화폐를 위한 마코위츠 포트폴리오 이론: 제로에서 히어로까지
Python으로 최적의 암호화폐 포트폴리오 구축하기 - YOLO는 전략이 아니니까요. 노벨상 수상 포트폴리오 이론을 암호화폐 투자에 적용하는 방법을 실용적인 Python 코드 예제로 배워봅니다.

Python으로 최적의 암호화폐 포트폴리오 구축하기 — YOLO는 전략이 아니니까요
마코위츠 포트폴리오 이론: 주어진 위험 수준에서 수익을 극대화하기 위해 디지털 자산에 적용된 수학적 최적화.
서론: 암호화폐 포트폴리오에 수학이 필요한 이유 (감이 아니라)
암호화폐 매니아 여러분! 👋
일론이 트윗했다고 전 재산을 DOGE에 넣었던 때를 기억하시나요? 아니면 마지막 폭락 때 모든 걸 패닉 매도했던 때는요? 네, 우리 모두 그런 경험이 있죠. 오늘은 여러분의 포트폴리오(와 정신 건강)를 구할 수 있는 것에 대해 이야기하겠습니다: 마코위츠 포트폴리오 이론입니다.
해리 마코위츠는 1990년에 이 연구로 노벨상을 수상했습니다. 기본 아이디어는? 주어진 위험 수준에 대해 최상의 수익을 얻도록 포트폴리오를 수학적으로 최적화할 수 있다는 것입니다. 눈가리고 운전하는 대신 GPS를 갖는 것과 같습니다.
핵심 개념: 위험 대 수익 (영원한 춤)
코드에 들어가기 전에, 무엇을 다루는지 이해합시다:
- 기대 수익률: 얼마나 벌 것으로 기대하는지
- 위험 (변동성): 포트폴리오 가치가 얼마나 출렁이는지
- 상관관계: 서로 다른 자산이 얼마나 비슷하게 움직이는지
마법은 완벽하게 동기화되어 움직이지 않는 자산을 결합할 때 일어납니다. 비트코인이 폭락해도 일부 DeFi 토큰은 더 잘 버틸 수 있습니다. 그것이 분산 투자의 힘입니다.
위험 대 수익: 변동성 높은 고수익 자산과 안정적인 기반의 균형을 맞춰 최적의 기하 평균 수익률을 달성.
Python 환경 설정
먼저 도구를 준비합시다:
# Essential libraries for our crypto portfolio optimization
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')
# Set up plotting style
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
# Get data for Bitcoin and Ethereum
crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)
# Calculate daily returns
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 (annualized)
portfolio_return = np.sum(returns.mean() * weights) * 252
# Portfolio volatility (annualized)
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
return portfolio_return, portfolio_vol
# Example: 50% BTC, 50% ETH
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):
# Generate random weights
weights = np.random.random(num_assets)
weights /= np.sum(weights) # Normalize to sum to 1
# Calculate portfolio metrics
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe_ratio = portfolio_return / portfolio_vol
# Store results
results[0,i] = portfolio_return
results[1,i] = portfolio_vol
results[2,i] = sharpe_ratio
results[3,i:] = weights
return results
# Generate 10,000 random portfolios
results = generate_random_portfolios(returns)
# Create a dataframe for easier handling
portfolio_results = pd.DataFrame({
'Returns': results[0],
'Volatility': results[1],
'Sharpe_Ratio': results[2]
})
# Plot the efficient frontier
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 and bounds
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
initial_guess = num_assets * [1. / num_assets]
if objective == 'sharpe':
# Maximize Sharpe ratio
result = minimize(negative_sharpe_ratio, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'min_vol':
# Minimize volatility
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'target_return':
# Minimize volatility for 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
# Find optimal portfolios
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: 멀티 에셋의 세계 — 본격적인 암호화폐 포트폴리오
여러 자산을 포함하는 본격적인 암호화폐 포트폴리오로 확장합시다:
# Expand to more cryptocurrencies
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()
# Check correlations
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
"""
# Calculate range of target returns
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)
# Calculate efficient frontier
efficient_port = efficient_frontier(returns_multi)
# Plot everything together
plt.figure(figsize=(14, 10))
# Random portfolios
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)
# Efficient frontier
if len(efficient_port) > 0:
plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Efficient Frontier')
# Optimal portfolios
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()
# Display optimal allocations
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
# Calculate Risk Parity portfolio
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)
# Calculate cumulative returns
cumulative_returns = (1 + portfolio_returns).cumprod()
# Performance metrics
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
}
# Backtest our strategies
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 table
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
# Example usage
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! 📈