Lý thuyết danh mục đầu tư Markowitz cho Crypto: Từ Cơ Bản đến Nâng Cao
Xây dựng danh mục đầu tư crypto tối ưu bằng Python - vì YOLO không phải là chiến lược
Lý thuyết danh mục đầu tư Markowitz: Tối ưu hóa toán học ứng dụng vào tài sản kỹ thuật số nhằm tối đa hóa lợi nhuận với mức rủi ro cho trước.
Giới thiệu: Tại sao danh mục crypto của bạn cần toán học (chứ không chỉ cảm tính)
Chào các anh em crypto! 👋
Bạn còn nhớ lần bỏ cả đống tiền vào DOGE chỉ vì Elon đăng tweet? Hay lần hoảng loạn bán hết trong đợt crash gần nhất? Vâng, ai trong chúng ta cũng đã từng như vậy. Hôm nay chúng ta sẽ nói về một thứ có thể cứu danh mục của bạn (và cả sự tỉnh táo của bạn): Lý thuyết danh mục đầu tư Markowitz.
Harry Markowitz đã nhận giải Nobel vì công trình này vào năm 1990. Ý tưởng cốt lõi? Bạn có thể tối ưu hóa danh mục bằng toán học để đạt được lợi nhuận tốt nhất có thể với bất kỳ mức rủi ro nào. Giống như có GPS cho khoản đầu tư của bạn thay vì lái xe mà bịt mắt.
Khái niệm cốt lõi: Rủi ro vs Lợi nhuận (Cuộc khiêu vũ vĩnh cửu)
Trước khi đi vào mã lập trình, hãy hiểu những gì chúng ta đang xử lý:
- Lợi nhuận kỳ vọng: Bạn kỳ vọng kiếm được bao nhiêu tiền
- Rủi ro (Biến động): Giá trị danh mục của bạn dao động như thế nào
- Tương quan: Các tài sản khác nhau biến động tương tự nhau như thế nào
Điều kỳ diệu xảy ra khi bạn kết hợp các tài sản không biến động hoàn toàn đồng bộ. Khi Bitcoin sụp đổ, có thể một số token DeFi giữ được giá trị tốt hơn. Đó là đa dạng hóa đang hoạt động cho bạn.
Rủi ro vs. Lợi nhuận: Cân bằng giữa tài sản lợi suất cao biến động mạnh và nền tảng ổn định để đạt được lợi nhuận trung bình nhân tối ưu.
Thiết lập môi trường Python
Đầu tiên - hãy chuẩn bị các công cụ:
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")
Cấp độ 1: Bước khởi đầu - Toán học danh mục cơ bản
Hãy bắt đầu với những điều cơ bản. Chúng ta sẽ tính lợi nhuận và rủi ro cho một danh mục 2 tài sản đơn giản.
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())
Bây giờ hãy tính một số chỉ số danh mục cơ bản:
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}")
Cấp độ 2: Nghiêm túc hơn - Đường biên hiệu quả
Bây giờ mới thú vị! Đường biên hiệu quả cho chúng ta thấy tất cả các danh mục tối ưu có thể có. Mỗi điểm đại diện cho lợi nhuận tốt nhất có thể với một mức rủi ro nhất định.
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()
Đường biên hiệu quả: Đường cong biểu diễn lợi nhuận kỳ vọng tối đa có thể đạt được với một mức rủi ro nhất định.
Cấp độ 3: Bậc thầy tối ưu hóa - Tìm danh mục hoàn hảo
Lấy mẫu ngẫu nhiên thì vui, nhưng chúng ta muốn có giải pháp tối ưu về mặt toán học. Đã đến lúc dùng đến vũ khí hạng nặng - tối ưu hóa 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%}")
Cấp độ 4: Đa tài sản - Danh mục crypto thực tế
Hãy mở rộng lên danh mục crypto thực sự với nhiều tài sản:
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}")
Đa dạng hóa đa tài sản: Xây dựng danh mục vững chắc bằng cách kết hợp các tài sản crypto không tương quan vào một cấu trúc hình học ổn định.
Cấp độ 5: Kỹ thuật nâng cao - Black-Litterman và Cân bằng rủi ro
Dành cho những ninja tối ưu hóa danh mục thực sự, hãy triển khai một số kỹ thuật nâng cao:
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()

*Kiểm tra ngược thuật toán: Mô phỏng hiệu suất lịch sử để xác nhận các mô hình tối ưu hóa lý thuyết.*
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)
Kiểm tra thực tế: Những điều Markowitz không nói với bạn
Trước khi bạn đặt cược tất cả vào tối ưu hóa toán học, đây là một số sự thật khắc nghiệt về crypto:
1. Hiệu suất quá khứ ≠ Kết quả tương lai Thị trường crypto còn non trẻ và hỗn loạn. Những tương quan bạn đã tính toán? Chúng có thể đảo ngược qua đêm khi quy định thay đổi hoặc khi vụ hack lớn tiếp theo xảy ra.
2. Chi phí giao dịch quan trọng Tái cân bằng danh mục tốn tiền. Trong DeFi, phí gas có thể ăn hết lợi nhuận của bạn. Hãy tính yếu tố này vào chiến lược của bạn.
3. Vấn đề thanh khoản Không phải tất cả các crypto đều có thanh khoản như nhau. Altcoin vốn hóa nhỏ đó có thể trông rất tốt trong tối ưu hóa của bạn, nhưng hãy thử bán nó trong đợt crash.
4. Thay đổi chế độ thị trường Thị trường crypto có các "chế độ" khác nhau - thị trường tăng, thị trường giảm, thị trường đi ngang. Điều có hiệu quả trong chế độ này có thể không hiệu quả trong chế độ khác.
Mẹo triển khai thực tế
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)
Kết luận: Bộ công cụ tối ưu hóa danh mục của bạn
Bây giờ bạn đã có một bộ công cụ hoàn chỉnh để tối ưu hóa danh mục crypto:
- Tính toán cơ bản về rủi ro và lợi nhuận
- Trực quan hóa đường biên hiệu quả
- Tối ưu hóa toán học cho các mục tiêu khác nhau
- Chiến lược nâng cao như cân bằng rủi ro
- Khung kiểm tra ngược để xác nhận chiến lược của bạn
- Các cân nhắc thực tế cho triển khai thực tế
Những bài học quan trọng
- Đa dạng hóa là bữa ăn miễn phí - bữa ăn miễn phí duy nhất trong đầu tư
- Tối ưu hóa dựa trên khả năng chịu rủi ro của bạn - tỷ lệ Sharpe tối đa không phải lúc nào cũng tốt nhất cho bạn
- Tái cân bằng có hệ thống nhưng không giao dịch quá nhiều
- Khiêm tốn - các mô hình là công cụ, không phải quả cầu pha lê
- Bắt đầu đơn giản và tăng độ phức tạp khi bạn học hỏi thêm
Hãy nhớ: Trong crypto, ngay cả những mô hình toán học tốt nhất cũng không thể dự đoán khi nào Elon sẽ tweet về Dogecoin hay khi nào sàn giao dịch tiếp theo bị hack. Hãy dùng lý thuyết danh mục làm nền tảng, nhưng luôn giữ một phần dự phòng và không bao giờ đầu tư nhiều hơn những gì bạn có thể chấp nhận mất.
Bây giờ hãy tiến lên và tối ưu hóa một cách có trách nhiệm! 🚀
Đọc thêm
Kho lưu trữ mã nguồn
Tất cả mã từ hướng dẫn này có sẵn trên GitHub: https://github.com/suenot/markowitz
Chúc tối ưu hóa vui vẻ! 📈
Tác Giả
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.