Teoria del Portafoglio di Markowitz per le Crypto: Da Zero a Esperto
Costruire portafogli crypto ottimali con Python - perché il YOLO non è una strategia. Scopri come applicare la teoria del portafoglio vincitrice del Premio Nobel agli investimenti in criptovalute con esempi pratici in Python.

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:
# Librerie essenziali per l'ottimizzazione del portafoglio crypto
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')
# Imposta lo stile dei grafici
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
# Ottieni dati per Bitcoin ed Ethereum
crypto_symbols = ['BTC-USD', 'ETH-USD']
prices = get_crypto_data(crypto_symbols)
# Calcola i rendimenti giornalieri
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
"""
# Rendimento del portafoglio (annualizzato)
portfolio_return = np.sum(returns.mean() * weights) * 252
# Volatilità del portafoglio (annualizzata)
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
return portfolio_return, portfolio_vol
# Esempio: 50% BTC, 50% ETH
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):
# Genera pesi casuali
weights = np.random.random(num_assets)
weights /= np.sum(weights) # Normalizza in modo che la somma sia 1
# Calcola le metriche del portafoglio
portfolio_return, portfolio_vol = portfolio_performance(weights, returns)
sharpe_ratio = portfolio_return / portfolio_vol
# Memorizza i risultati
results[0,i] = portfolio_return
results[1,i] = portfolio_vol
results[2,i] = sharpe_ratio
results[3,i:] = weights
return results
# Genera 10.000 portafogli casuali
results = generate_random_portfolios(returns)
# Crea un dataframe per una gestione più semplice
portfolio_results = pd.DataFrame({
'Returns': results[0],
'Volatility': results[1],
'Sharpe_Ratio': results[2]
})
# Traccia la frontiera efficiente
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)
# Vincoli e limiti
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
# Ipotesi iniziale
initial_guess = num_assets * [1. / num_assets]
if objective == 'sharpe':
# Massimizza il rapporto di Sharpe
result = minimize(negative_sharpe_ratio, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'min_vol':
# Minimizza la volatilità
result = minimize(minimize_volatility, initial_guess,
args=(returns,), method='SLSQP',
bounds=bounds, constraints=constraints)
elif objective == 'target_return':
# Minimizza la volatilità per un rendimento target
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
# Trova i portafogli ottimali
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:
# Espandi a più criptovalute
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()
# Controlla le correlazioni
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
"""
# Calcola l'intervallo dei rendimenti target
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)
# Calcola la frontiera efficiente
efficient_port = efficient_frontier(returns_multi)
# Traccia tutto insieme
plt.figure(figsize=(14, 10))
# Portafogli casuali
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)
# Frontiera efficiente
if len(efficient_port) > 0:
plt.plot(efficient_port[:,1], efficient_port[:,0], 'r-', linewidth=3, label='Frontiera Efficiente')
# Portafogli ottimali
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()
# Visualizza le allocazioni ottimali
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
# Calcola il portafoglio Risk Parity
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)
# Calcola i rendimenti cumulativi
cumulative_returns = (1 + portfolio_returns).cumprod()
# Metriche di performance
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 delle nostre strategie
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.*
# Tabella riepilogativa delle performance
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
# Esempio di utilizzo
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! 📈