← Torna agli articoli
September 25, 2025
5 min di lettura

Teoria del Portafoglio di Markowitz per le Crypto: Da Zero a Esperto

Teoria del Portafoglio di Markowitz per le Crypto: Da Zero a Esperto
#ottimizzazione del portafoglio
#Markowitz
#crypto
#Python
#finanza quantitativa
#gestione del rischio
#diversificazione
#frontiera efficiente
#indice di Sharpe
#trading algoritmico

Costruire portafogli crypto ottimali con Python - perché il YOLO non è una strategia

Teoria del Portafoglio di Markowitz 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 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()

Frontiera Efficiente 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}")

Ottimizzazione del Portafoglio Multi-Asset 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()

![Backtest delle Strategie di Portafoglio](/images/blog/markowitz-backtest.webp)
*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:

  1. Calcoli di base per rischio e rendimento
  2. Visualizzazione della frontiera efficiente
  3. Ottimizzazione matematica per diversi obiettivi
  4. Strategie avanzate come il risk parity
  5. Framework di backtesting per validare le tue strategie
  6. 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! 📈

Disclaimer: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.