Dispersione Trasversale dei Rendimenti


Sapere quanto sono diversi tra loro i rendimenti \(r_{1,t},r_{2,t},\dots,r_{n,t}\) di \(n\) diversi asset osservati in uno stesso istante \(t\) dà una misura di quanto i titoli si muovono in modo omogeneo o eterogeneo.
La deviazione standard di questi rendimenti, o dispersione cross-section, è \[σ_{cs} = \sqrt{\frac{1}{n}\sum_{i=1}^n(r_{i,t}-\overline{r_{i,t}})^2}.\]

import yfinance as yf import pandas as pd import numpy as np def gini_index(series): values = series.dropna().values # Rimuovi NaN # Se ci sono valori negativi traslo per rendere non negativi if np.any(values < 0): values = values - np.min(values) + 1e-10 sorted_values = np.sort(values) # Ordina in modo crescente n = len(sorted_values) sum_values = np.sum(sorted_values) # Somma totale if sum_values == 0: return 0.0 # Gini = 0 se tutti valori sono zero #formula con indici 1-based (più stabile numericamente) indices = np.arange(1, n + 1) # ← CHIAVE: parte da 1, non da 0 weighted_sum = np.sum(indices * sorted_values) return (2 * weighted_sum) / (n * sum_values) - (n + 1) / n def cross_section_dispersion(tickers, date): """ Calcola la dispersione dei rendimenti per la lista dei tickers nella data di interesse date """ target_date = pd.to_datetime(date) data = yf.download(tickers, start=target_date - pd.Timedelta(days=10), end=target_date) valid_returns_df = data['Close'].pct_change().dropna() returns = valid_returns_df.iloc[-1] # Calcola indici di dispersione using the obtained returns stats = { 'data': valid_returns_df.index[-1].strftime('%Y-%m-%d'), 'num_titoli': len(returns), 'media_rendimenti': returns.mean(), 'mediana_rendimenti': returns.median(), 'deviazione_standard': returns.std(), 'range': returns.max() - returns.min(), 'IQR': returns.quantile(0.75) - returns.quantile(0.25), 'skew': returns.skew(), 'kurtosis': returns.kurtosis(), 'gini': gini_index(returns) } return stats # Esempio con un paniere di titoli tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA', 'NVDA', 'JPM', 'V', 'JNJ'] stats = cross_section_dispersion(tickers, '2026-05-17') print() for key, value in stats.items(): if isinstance(value, (float, np.number)): print(f"{key}: {value:.4f}") else: print(f"{key}: {value}")
/tmp/ipykernel_16836/2676860743.py:37: FutureWarning: YF.download() has changed argument auto_adjust default to True data = yf.download(tickers, start=target_date - pd.Timedelta(days=10), end=target_date) [*********************100%***********************] 10 of 10 completed data: 2026-05-15 num_titoli: 10 media_rendimenti: -0.0098 mediana_rendimenti: -0.0088 deviazione_standard: 0.0235 range: 0.0780 IQR: 0.0196 skew: -0.1786 kurtosis: 0.1463 gini: 0.3242
Quando la dispersione cross-section è: Nella costruzione dei portafogli occorre considerare che la sua varianza dev'essere minore della media delle varianze individuali se le correlazioni sono < 1. Inoltre la varianza del portafoglio equipesato \(\displaystyle σ^2_p = Var \left(\frac{1}{n}\sum_{i=1}^nr_{i,t}\right) \) e la dispersione cross-section \(σ^2_{cs}\) devono essere legati dalla relazione \[σ^2_p ≈ \frac{\overline\rho}{1-\overline\rho} × σ^2_{cs}\] dove \(\displaystyle\overline\rho =\frac{2}{n(n-1)}\sum_{i=1}^n\sum_{j=i+1}^nCorr(r_i,r_j)\) è la correlazione media tra tutti gli asset del paniere.

Potremmo considerare anche una dispersione ponderata che come pesi utilizzi ad esempio la capitalizzazione di mercato normalizzata \(w_i:\) \[\sqrt{\sum_{i=1}^nw_i(r_{i,t}-\overline{w_ir_{i,t}})^2}.\] È considerata lo standard nelle analisi della dispersione di mercato (es. Goldman Sachs, Morgan Stanley), nei modelli di cross‑sectional risk, nelle strategie long/short e statistical arbitrage, nelle misure di diversificazione effettiva, nella decomposizione del rischio di portafoglio.

Nel seguito mettiamo a confronto la dispersione cross-section non ponderata e quella ponderata di un portafoglio titoli.

def cross_section_dispersion(returns, weights=None): returns = np.asarray(returns) returns = returns[~np.isnan(returns)] if weights is not None: # Dispersione ponderata (es. per capitalizzazione) mean_return = np.sum(weights * returns) dispersion = np.sqrt(np.sum(weights * (returns - mean_return)**2)) else: # Dispersione semplice (tutti gli asset hanno lo stesso peso) mean_return = np.mean(returns) dispersion = np.std(returns, ddof=0) # deviazione standard della popolazione return dispersion def daily_dispersion(tickers, start, end, w=False): """ dispersione giornaliera. """ data = yf.download(tickers, start=start, end=end) returns = data['Close'].pct_change().dropna() if len(returns) == 0: return np.nan if w: # Dispersione ponderata per capitalizzazione market_caps = [] for ticker in tickers: try: azione = yf.Ticker(ticker) # Il metodo .info restituisce un dizionario con molti dati info_azione = azione.info # La capitalizzazione di mercato è nella chiave 'marketCap' market_cap = info_azione.get('marketCap') if market_cap: # yfinance restituisce il valore come intero (numero di dollari) market_caps.append(market_cap) else: market_caps.append(None) except Exception as e: print(f"Si è verificato un errore per {ticker}: {e}") return None weights = np.asarray(market_cap) weights = weights / np.sum( weights) # normalizza else: weights = None return returns.apply(cross_section_dispersion, axis=1, args=(weights,)) # Esempio con un paniere di titoli tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA', 'NVDA', 'JPM', 'V', 'JNJ'] start, end = '2025-01-01', '2026-05-27' dd = pd.DataFrame() dd["non pesata"] = daily_dispersion(tickers, start, end) dd["pesata"] = daily_dispersion(tickers, start, end, True) dd.plot()

Gli index provider (S&P, MSCI, FTSE) non pubblicano ufficialmente una misura di dispersione cross‑section dei rendimenti dei componenti ma esistono fonti professionali che la calcolano e la pubblicano regolarmente come strumenti professionali, non gratuiti:

Il rapporto dispersione/volatilità è un indicatore utile: se è maggiore di 1, le differenze tra titoli sono più ampie della volatilità del mercato; se è minore di 1, il mercato si muove come un blocco unico.

La dispersione cross-section di un portafoglio ha correlazione

Gli indicatori di market breadth (ampiezza di mercato) valutano la salute complessiva di un indice misurando il numero di azioni che partecipano a un trend, anziché basarsi solo sulla performance dei titoli a maggiore capitalizzazione; evidenziano se un un rialzo marcato, rapido e talvolta inatteso dei prezzi (rally) o un ribasso è generalizzato o guidato da pochi titoli. Ad esempio, se l'indice principale (come S&P 500) continua a registrare nuovi massimi ma gli indicatori di ampiezza iniziano a scendere, significa che il rialzo è sostenuto solo da un numero ristretto di titoli "pesanti" e che l'intero mercato potrebbe essere vulnerabile a un'inversione di tendenza.
Gli indicatori di market breadth forniscono: Hanno una correlazione complessa con la dispersione cross-section ma in genere c'è una proporzionalità inversa: bassa dispersione e alta ampiezza favorisce la gestione passiva (ETF indicizzati), poiché quasi tutti i titoli salgono in modo uniforme; alta dispersione e bassa ampiezza favorisce la gestione attiva (stock picking), poiché la selezione del singolo titolo corretto genera un enorme extra-rendimento (alpha) rispetto alla media del mercato.
I principali indicatori di market breadth sono: Buona pratica richiede:
  1. conferma incrociata, di non usare mai un singolo indicatore da solo ma aspettare che 3 indicatori si allineino o che i segnali si confermino su 2-3 sessioni di trading;
  2. analisi settoriale, di applicare questi indicatori a singoli settori (es. Tecnologia) per identificare forze e debolezze specifiche all'interno del mercato;
  3. limitazioni, poiché il breadth può divergere dai prezzi per lunghi periodi durante trend forti guidati da poche azioni (es. mega-cap); non fornisce segnali di trade diretti, ma di contesto e rischio.

Riferimenti sito/bibliografici