Vai al contenuto
PAPER SIAT 2026Apri la pagina della ricerca
Cryptoverso

Lab del libro · L07

L07 — Correlazioni a finestre mobili e quanto salgono nei crolli

Lab 7 — Quando la diversificazione svanisce

Quaderno del capitolo «Quando la diversificazione svanisce» di La matematica di chi perde.

Tre cose. Prima: il modo intuitivo di misurare la correlazione nei crolli dà la risposta sbagliata, e qui lo vedi succedere. Seconda: la correlazione non sta ferma, e sale proprio quando servirebbe che non salisse. Terza: quanto vale davvero avere N asset, con il conto che quasi nessuno fa.

Le righe marcate PROVA sono quelle da cambiare: cambiale e riesegui per vedere l'effetto. Il resto — comprese le righe marcate NON TOCCARE — serve a mantenere il risultato confrontabile con quello stampato nel libro.

Output

3 serie allineate, 2128 giorni comuni, dal 2020-09-02 al 2026-06-30
Espandi lo script di questo passo
lab_07_correlazioni.py
python
import datetime as dt

import matplotlib.pyplot as plt
import numpy as np
import polars as pl

from cvbook.dati import carica
from cvbook.metriche import drawdown, rendimenti

NOMI = ["btcusdt", "ethusdt", "solusdt"]  # ← PROVA / TRY: aggiungi "ftsemib" o "eni"
                                          # (aggiungili anche a avvio.prepara([...]))
DA = dt.date(2020, 9, 1)  # PROVA / TRY: sposta la data d'inizio

serie = [
    carica(n).filter(pl.col("data") >= DA).select(["data", "chiusura"]).rename({"chiusura": n})
    for n in NOMI
]
tabella = serie[0]
for s in serie[1:]:
    tabella = tabella.join(s, on="data")
tabella = tabella.sort("data")

date = tabella["data"].to_list()[1:]
M = np.column_stack([rendimenti(tabella[n].to_numpy()) for n in NOMI])
print(f"{len(NOMI)} serie allineate, {len(M)} giorni comuni, dal {date[0]} al {date[-1]}")

1. Il metodo intuitivo, e perché è sbagliato

L'idea naturale: prendo i giorni peggiori e calcolo la correlazione lì. Sembra ovvia, ed è un artefatto — selezionando in base a un valore estremo di una variabile se ne restringe la variabilità, e la correlazione risulta distorta verso il basso.

Output

correlazione media su tutto il periodo:        0.675
correlazione media sul 5% dei giorni peggiori: 0.582

Sembra che nei crolli la correlazione SCENDA. E' falso, ed e' il risultato del modo in cui abbiamo selezionato i giorni.
Espandi lo script di questo passo
lab_07_correlazioni.py
python
coppie = np.triu_indices(len(NOMI), 1)


def correlazione_media(blocco: np.ndarray) -> float:
    return float(np.corrcoef(blocco.T)[coppie].mean())


primo = M[:, 0]
peggiori = np.argsort(primo)[: len(primo) // 20]  # il 5% dei giorni peggiori
                                                   # PROVA / TRY: // 5 per il 20% (vedi esercizio 3)

print(f"correlazione media su tutto il periodo:        {correlazione_media(M):.3f}")
print(f"correlazione media sul 5% dei giorni peggiori: {correlazione_media(M[peggiori]):.3f}")
print("\nSembra che nei crolli la correlazione SCENDA. E' falso, ed e' il "
      "risultato del modo in cui abbiamo selezionato i giorni.")

2. Il metodo corretto: finestre temporali

Si misura la correlazione su finestre mobili e poi si guarda in quali periodi è più alta. Nessuna selezione basata sul valore delle variabili.

La correlazione media fra tre serie misurata su finestre mobili di 60 giorni, dal 2021 al 2026, con l'asse verticale da 0 a 1 e una riga punteggiata sulla media del periodo. Le bande ombreggiate segnano i periodi in cui il mercato sta oltre il 30% sotto il massimo: la correlazione oscilla fra 0,17 e 0,94, vale 0,79 di media dentro quelle bande e 0,68 fuori.

La correlazione fra tre serie, e quanto sale proprio nei periodi difficili.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Correlazione media a coppie delle variazioni giornaliere su finestre mobili di 60 giorni; le bande sono i giorni in cui la serie di riferimento sta oltre il 30% sotto il proprio massimo.

Output

minimo 0.17   massimo 0.94   media 0.73
media nei periodi difficili: 0.79
media nel resto del tempo:   0.68
Espandi lo script di questo passo
lab_07_correlazioni.py
python
FINESTRA = 60  # PROVA / TRY: 20 · 60 · 200 (vedi esercizio 2)

corr = np.array([correlazione_media(M[i - FINESTRA:i]) for i in range(FINESTRA, len(M))])
date_c = date[FINESTRA:]
dd = drawdown(np.concatenate([[1.0], np.cumprod(1 + primo)]))[FINESTRA + 1:]
brutti = dd < -0.30

with avvio.figura("schermo"):
    fig, ax = plt.subplots(figsize=(10, 4))
    ax.fill_between(date_c, 0, 1, where=brutti, step="mid", alpha=0.25,
                    label="mercato oltre il 30% sotto il massimo")
    ax.plot(date_c, corr, linewidth=1.3, label="correlazione media a 60 giorni")
    ax.axhline(corr.mean(), linestyle=":", linewidth=1.2)
    ax.set_ylim(0, 1)
    ax.set_ylabel("Correlazione media")
    ax.legend(loc="lower left")
    fig.autofmt_xdate()
    plt.show()

print(f"minimo {corr.min():.2f}   massimo {corr.max():.2f}   media {corr.mean():.2f}")
print(f"media nei periodi difficili: {corr[brutti].mean():.2f}")
print(f"media nel resto del tempo:   {corr[~brutti].mean():.2f}")

3. Quanto vale davvero avere N asset

La quota di oscillazione che resta, rispetto a possederne uno solo: uno diviso il numero degli asset, più la correlazione moltiplicata per tutto il resto. E poi la radice quadrata.

Output

correlazione media delle finestre mobili (quella del capitolo): 0.73
correlazione statica sull'intero periodo:                       0.67

 asset   indipendenti    a corr. 0,3   a corr, 0,73    a corr. 0,9
     2          70.7%         80.6%         93.1%         97.5%
     3          57.7%         73.0%         90.7%         96.6%
     4          50.0%         68.9%         89.5%         96.2%
     5          44.7%         66.3%         88.7%         95.9%
    10          31.6%         60.8%         87.2%         95.4%
    15          25.8%         58.9%         86.7%         95.2%
    30          18.3%         56.9%         86.2%         95.0%

numero di asset DAVVERO indipendenti equivalenti ai tuoi 3: 1.22
Espandi lo script di questo passo
lab_07_correlazioni.py
python
def oscillazione_residua(n: int, rho: float) -> float:
    return float(np.sqrt(1 / n + (1 - 1 / n) * rho))


# Due stimatori della stessa parola. `correlazione_media(M)` è la correlazione
# **statica** su tutto il periodo: 0,675. La media delle finestre mobili della
# cella precedente è 0,73, ed è quella che il capitolo usa in ogni suo conto.
# Non sono la stessa cosa e non vanno confuse — è l'errore che il capitolo
# stesso ha dovuto correggere. Qui si usa quella del capitolo.
#
# Two estimators of the same word: the static correlation over the whole period
# (0.675) and the average of the rolling windows (0.73). The chapter's numbers
# all use the second one, so this table uses it too.
rho_misurata = float(corr.mean())
rho_statica = correlazione_media(M)
print(f"correlazione media delle finestre mobili (quella del capitolo): {rho_misurata:.2f}")
print(f"correlazione statica sull'intero periodo:                       {rho_statica:.2f}\n")
etichetta_misurata = f"a corr. {rho_misurata:.2f}".replace(".", ",")
print(f"{'asset':>6s} {'indipendenti':>14s} {'a corr. 0,3':>14s} "
      f"{etichetta_misurata:>14s} {'a corr. 0,9':>14s}")
for n in (2, 3, 4, 5, 10, 15, 30):
    valori = "".join(f"{oscillazione_residua(n, rho) * 100:13.1f}%"
                     for rho in (0.0, 0.3, rho_misurata, 0.9))
    print(f"{n:6d} {valori}")

# `len(NOMI)` e non un 3 scritto a mano: la cella sopra invita ad aggiungere
# serie, e con un 3 fisso il numero effettivo restava quello di tre asset
# qualunque cosa il lettore ci mettesse dentro.
quanti = len(NOMI)
n_eff = 1 / (1 / quanti + (1 - 1 / quanti) * rho_misurata)
print(f"\nnumero di asset DAVVERO indipendenti equivalenti ai tuoi {quanti}: {n_eff:.2f}")

Esercizi

  1. Nella terza cella guarda la colonna della correlazione misurata: fra 4 e 15 asset la riduzione cambia di pochissimo. Dal quinto in poi paghi costi e complessità senza comprare protezione.
  2. Cambia FINESTRA da 60 a 20 e poi a 200. Con finestre corte la correlazione balla molto di più: quanto della sua instabilità è del mercato e quanto è della misura?
  3. Riesegui la prima cella prendendo il 20% dei giorni peggiori invece del 5%. L'artefatto si attenua. È la dimostrazione che era un effetto della selezione e non un fatto del mercato.

Riproducibilità e download

Eseguito il 2026-08-27 dal quaderno del repository

Il quaderno

I dati

  • btcusdt.parquet93,2 KB

    sha256 ea75ad84e6e981507054df5c622c6b0ec3c8849c1f4dd007721878d4e4c8a329

    Fonte: Binance Data Vision · Periodo: 2017-08-17 → 2026-06-30 · 3240 righe · estratto 2026-08-16

  • ethusdt.parquet87,0 KB

    sha256 c2bd0259da905e0fec87235d7a62295532433fb89657726dd2d19558db7c072a

    Fonte: Binance Data Vision · Periodo: 2017-08-17 → 2026-06-30 · 3240 righe · estratto 2026-08-16

  • solusdt.parquet57,5 KB

    sha256 c7ba2368a3e419b898fb31ec6d5345b7212b74784b69079d3d43571c2ac63657

    Fonte: Binance Data Vision · Periodo: 2020-08-11 → 2026-06-30 · 2150 righe · estratto 2026-08-16

Torna all’indice dei lab