Teoria del Portafoglio di Markowitz per le Crypto: Da Zero a Esperto
Costruire portafogli crypto ottimali con Python - perché il YOLO non è una strategia
Teoria del Portafoglio di Markowitz: Ottimizzazione matematica applicata agli asset digitali per massimizzare i rendimenti a un dato livello di rischio.
Introduzione: Perché il Tuo Portafoglio Crypto Ha Bisogno di Matematica (Non Solo di Istinto)
Ciao crypto degen! 👋
Ricordi quella volta in cui hai investito tutto il tuo stack in DOGE perché Elon aveva twittato? O quando hai venduto tutto in preda al panico durante l'ultimo crollo? Sì, ci siamo passati tutti. Oggi parleremo di qualcosa che potrebbe salvare il tuo portafoglio (e la tua sanità mentale): la Teoria del Portafoglio di Markowitz.
Harry Markowitz ha letteralmente vinto un Premio Nobel per questo nel 1990. L'idea di base? Puoi ottimizzare matematicamente il tuo portafoglio per ottenere i migliori rendimenti possibili per qualsiasi dato livello di rischio. È come avere un GPS per i tuoi investimenti invece di guidare bendato.
Il Concetto Fondamentale: Rischio vs Rendimento (La Danza Eterna)
Prima di immergerci nel codice, capiamo con cosa abbiamo a che fare:
- Rendimento Atteso: Quanto denaro ti aspetti di guadagnare
- Rischio (Volatilità): Quanto oscilla il valore del tuo portafoglio
- Correlazione: Quanto similmente si muovono insieme diversi asset
La magia accade quando combini asset che non si muovono in perfetta sincronia. Quando Bitcoin crolla, forse alcuni token DeFi resistono meglio. Questa è la diversificazione che lavora per te.
Rischio vs. Rendimento: Bilanciare asset ad alto rendimento e volatili con una base stabile per ottenere il rendimento geometrico medio ottimale.
Configurare il Nostro Ambiente Python
Prima di tutto - prepariamo i nostri strumenti:
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")
Livello 1: Primi Passi - Matematica Base del Portafoglio
Iniziamo con le basi. Calcoleremo rendimenti e rischio per un semplice portafoglio a 2 asset.
def get_crypto_data(symbols, period="1y"):
"""
Recupera dati crypto da Yahoo Finance
symbols: lista di simboli crypto (es. ['BTC-USD', 'ETH-USD'])
period: periodo temporale per i dati
"""
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("Anteprima Rendimenti Giornalieri:")
print(returns.head())
Ora calcoliamo alcune metriche base del portafoglio:
def portfolio_performance(weights, returns):
"""
Calcola rendimento e volatilità del portafoglio
weights: array dei pesi del portafoglio
returns: dataframe dei rendimenti degli asset
"""
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"Portafoglio 50/50:")
print(f"Rendimento Annuo Atteso: {ret_5050:.2%}")
print(f"Volatilità Annua: {vol_5050:.2%}")
print(f"Indice di Sharpe: {ret_5050/vol_5050:.3f}")
Livello 2: Sul Serio - La Frontiera Efficiente
Ora cominciamo a lavorare sul serio! La frontiera efficiente mostra tutti i possibili portafogli ottimali. Ogni punto rappresenta il miglior rendimento possibile per un dato livello di rischio.
def generate_random_portfolios(returns, num_portfolios=10000):
"""
Genera combinazioni di portafoglio casuali
"""
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) # Normalizza in modo che la somma sia 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='Indice di Sharpe')
plt.xlabel('Volatilità (Rischio)')
plt.ylabel('Rendimento Atteso')
plt.title('Frontiera Efficiente - Portafogli Casuali')
plt.show()
La Frontiera Efficiente: La curva che rappresenta il massimo rendimento atteso possibile per un dato livello di rischio.
Livello 3: Maestro dell'Ottimizzazione - Trovare il Portafoglio Perfetto
Il campionamento casuale è divertente, ma vogliamo la soluzione matematicamente ottimale. È ora di tirare fuori l'artiglieria pesante - l'ottimizzazione scipy!
def negative_sharpe_ratio(weights, returns, risk_free_rate=0.02):
"""
Calcola il rapporto di Sharpe negativo (lo minimizziamo)
"""
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe = (portfolio_return - risk_free_rate) / portfolio_vol
return -sharpe
def minimize_volatility(weights, returns):
"""
Calcola la volatilità del portafoglio (la minimizziamo)
"""
_, portfolio_vol = portfolio_performance(weights, returns)
return portfolio_vol
def portfolio_return_objective(weights, returns):
"""
Calcola il rendimento del portafoglio (lo massimizziamo)
"""
portfolio_return, _ = portfolio_performance(weights, returns)
return -portfolio_return # Negativo perché minimizziamo
def optimize_portfolio(returns, objective='sharpe', target_return=None):
"""
Ottimizza il portafoglio in base a diversi obiettivi
"""
num_assets = len(returns.columns)
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) # I pesi sommano a 1
bounds = tuple((0, 1) for _ in range(num_assets)) # Nessuna vendita allo scoperto
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("🎯 Portafoglio con Massimo Indice di Sharpe:")
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"Rendimento: {ret_sharpe:.2%}, Volatilità: {vol_sharpe:.2%}")
print(f"Indice di Sharpe: {ret_sharpe/vol_sharpe:.3f}\n")
print("🛡️ Portafoglio a Minima Volatilità:")
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"Rendimento: {ret_minvol:.2%}, Volatilità: {vol_minvol:.2%}")
Livello 4: Follia Multi-Asset - Portafoglio Crypto Reale
Espandiamoci a un portafoglio crypto appropriato con più asset:
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('Matrice di Correlazione degli Asset Crypto')
plt.show()
def efficient_frontier(returns, num_portfolios=50):
"""
Calcola la frontiera efficiente
"""
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='Frontiera Efficiente')
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 Volatilità')
plt.colorbar(label='Indice di Sharpe')
plt.xlabel('Volatilità (Rischio)')
plt.ylabel('Rendimento Atteso')
plt.title('Ottimizzazione del Portafoglio Crypto Multi-Asset')
plt.legend()
plt.show()
print("🚀 Allocazioni Ottimali Multi-Asset:")
print("\nPortafoglio con Massimo Indice di Sharpe:")
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: # Mostra solo le allocazioni significative
print(f"{asset}: {weight:.1%}")
print(f"\nMetriche del Portafoglio:")
print(f"Rendimento Atteso: {ret_sharpe_multi:.1%}")
print(f"Volatilità: {vol_sharpe_multi:.1%}")
print(f"Indice di Sharpe: {ret_sharpe_multi/vol_sharpe_multi:.2f}")
Diversificazione Multi-Asset: Costruire un portafoglio robusto combinando asset crypto non correlati in una struttura geometrica stabile.
Livello 5: Tecniche Avanzate - Black-Litterman e Risk Parity
Per i veri ninja dell'ottimizzazione del portafoglio, implementiamo alcune tecniche avanzate:
def risk_parity_portfolio(returns):
"""
Portafoglio Risk Parity - ogni asset contribuisce ugualmente al rischio del portafoglio
"""
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("⚖️ Portafoglio Risk Parity:")
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"\nMetriche Risk Parity:")
print(f"Rendimento Atteso: {ret_rp:.1%}")
print(f"Volatilità: {vol_rp:.1%}")
print(f"Indice di Sharpe: {ret_rp/vol_rp:.2f}")
def backtest_portfolio(weights, prices):
"""
Backtest semplice della performance del portafoglio
"""
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('Backtest delle Strategie di Portafoglio')
plt.xlabel('Data')
plt.ylabel('Rendimenti Cumulativi')
plt.legend()
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.show()

*Backtesting Algoritmico: Simulare la performance storica per validare i modelli di ottimizzazione teorici.*
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 = ['Rendimento Annuo', 'Volatilità Annua', 'Indice di Sharpe', 'Max Drawdown']
print("\n📊 Riepilogo Performance delle Strategie:")
print(performance_summary)
Il Controllo della Realtà: Cosa Markowitz Non Ti Dice
Prima di puntare tutto sull'ottimizzazione matematica, ecco alcune dure verità sulle crypto:
1. La Performance Passata ≠ Risultati Futuri Il mercato crypto è giovane e caotico. Quelle correlazioni che hai calcolato? Potrebbero capovolgersi da un giorno all'altro quando cambiano le normative o quando avviene il prossimo grande hack.
2. I Costi di Transazione Contano Ribilanciare il portafoglio costa denaro. In DeFi, le gas fee possono mangiarti il pranzo. Tienilo in considerazione nella tua strategia.
3. Problemi di Liquidità Non tutte le crypto sono ugualmente liquide. Quella altcoin a bassa capitalizzazione potrebbe sembrare ottima nella tua ottimizzazione, ma prova a venderla durante un crollo.
4. Cambiamenti di Regime I mercati crypto hanno diversi "regimi" - mercati rialzisti, ribassisti, laterali. Quello che funziona in uno potrebbe non funzionare in un altro.
Consigli Pratici per l'Implementazione
def practical_portfolio_rebalancing(target_weights, current_weights, threshold=0.05):
"""
Ribilancia solo quando i pesi si discostano oltre la soglia
"""
weight_diff = np.abs(target_weights - current_weights)
needs_rebalancing = np.any(weight_diff > threshold)
if needs_rebalancing:
print("🔄 Ribilanciamento necessario!")
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("✅ Portafoglio entro la tolleranza, nessun ribilanciamento necessario")
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)
Conclusione: Il Tuo Toolkit di Ottimizzazione del Portafoglio
Ora hai un toolkit completo per l'ottimizzazione del portafoglio crypto:
- Calcoli di base per rischio e rendimento
- Visualizzazione della frontiera efficiente
- Ottimizzazione matematica per diversi obiettivi
- Strategie avanzate come il risk parity
- Framework di backtesting per validare le tue strategie
- Considerazioni pratiche per l'implementazione nel mondo reale
Punti Chiave
- La diversificazione è il pasto gratis - l'unico pasto gratis negli investimenti
- Ottimizza in base alla tua tolleranza al rischio - il max Sharpe non è sempre il migliore per te
- Ribilancia sistematicamente ma non fare trading eccessivo
- Rimani umile - i modelli sono strumenti, non sfere di cristallo
- Inizia semplice e aggiungi complessità man mano che impari
Ricorda: nel mondo crypto, anche i migliori modelli matematici non possono prevedere quando Elon tweeterà di Dogecoin o quando il prossimo exchange verrà hackerato. Usa la teoria del portafoglio come fondamento, ma tieni sempre un po' di liquidità da parte e non investire mai più di quanto puoi permetterti di perdere.
Ora vai avanti e ottimizza responsabilmente! 🚀
Ulteriori Letture
Repository del Codice
Tutto il codice di questo tutorial è disponibile su GitHub: https://github.com/suenot/markowitz
Buona ottimizzazione! 📈
Autori
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.