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

Lab del libro · L06

L06 — Code grasse, curtosi e i venti giorni che decidono tutto

Lab 6 — La media che mente

Quaderno del capitolo «La media che mente» di La matematica di chi perde.

Tre cose, sui dati veri: quanto la campana sbaglia sulle code, quanto pochi giorni decidono il risultato, e perché i due slogan opposti che si ricavano da quella figura sono la stessa affermazione — e nessuno dei due è un consiglio.

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.

Espandi lo script di questo passo
lab_06_code_grasse.py
python
import matplotlib.pyplot as plt
import numpy as np

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

SERIE = "btcusdt"  # ← PROVA / TRY: "ethusdt" · "solusdt" (le tre preparate nel setup)

df = carica(SERIE).sort("data")
prezzi = df["chiusura"].to_numpy()
date = df["data"].to_list()
r = rendimenti(prezzi)
mu, sigma = float(np.mean(r)), float(np.std(r, ddof=1))

1. Quanti giorni estremi prevede la campana, e quanti ce ne sono

Se i rendimenti seguissero la curva a campana, il numero di giorni oltre una certa distanza dalla media sarebbe calcolabile. Confrontiamolo con quelli che ci sono davvero.

Output

btcusdt: 3239 giorni  ·  media 0.1439%  ·  deviazione standard 3.531%

   oltre   previsti dalla campana    osservati   rapporto
     2σ                  147.375          196        1.3x
     3σ                    8.745           54        6.2x
     4σ                    0.205           18       87.7x
     5σ                    0.002            5     2692.6x
     6σ                    0.000            2   312934.5x

giorno peggiore: -39.5%  →  11.2 deviazioni standard dalla media
Secondo il modello a campana un evento del genere non dovrebbe accadere nemmeno una volta nella storia dell'universo. E' successo, in nove anni.

curtosi: 11.6   (per la curva a campana vale 3)
Espandi lo script di questo passo
lab_06_code_grasse.py
python
from math import erfc, sqrt

n = len(r)
print(f"{SERIE}: {n} giorni  ·  media {mu:.4%}  ·  deviazione standard {sigma:.3%}\n")
print(f"{'oltre':>8s} {'previsti dalla campana':>24s} {'osservati':>12s} {'rapporto':>10s}")
for k in (2, 3, 4, 5, 6):
    previsti = n * erfc(k / sqrt(2))
    osservati = int(np.sum(np.abs(r - mu) > k * sigma))
    rapporto = osservati / previsti if previsti > 0 else float("inf")
    print(f"{k:>6d}σ {previsti:24.3f} {osservati:12d} {rapporto:10.1f}x")

peggiore = float(np.min(r))
distanza = abs(peggiore - mu) / sigma
print(f"\ngiorno peggiore: {peggiore:.1%}{distanza:.1f} deviazioni standard dalla media")
print("Secondo il modello a campana un evento del genere non dovrebbe accadere "
      "nemmeno una volta nella storia dell'universo. E' successo, in nove anni.")

curtosi = float(np.mean(((r - mu) / sigma) ** 4))
print(f"\ncurtosi: {curtosi:.1f}   (per la curva a campana vale 3)")

2. La forma, disegnata

La scala verticale è logaritmica: senza, la differenza sulle code — cioè l'unica parte che conta — sarebbe invisibile.

Un istogramma delle variazioni giornaliere di btcusdt su 200 classi, con l'asse orizzontale da meno 0,4 a 0,2 e il conteggio dei giorni su scala logaritmica che copre quasi trenta ordini di grandezza, da 10 alla meno 27 a 10. Sopra l'istogramma corre la curva a campana con la stessa media e la stessa deviazione standard: al centro le due coincidono, mentre agli estremi la campana precipita di decine di ordini di grandezza dove i giorni realmente accaduti ci sono.

Quello che e' successo e quello che la campana prevedeva, sulla stessa scala.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Variazioni fra chiusure consecutive in 200 classi, con sopra la densita' normale di pari media e deviazione standard riscalata sull'ampiezza delle classi; conteggio su scala logaritmica.
Espandi lo script di questo passo
lab_06_code_grasse.py
python
from math import exp, pi

griglia = np.linspace(r.min(), r.max(), 400)
campana = n * np.exp(-((griglia - mu) ** 2) / (2 * sigma**2)) / (sigma * np.sqrt(2 * pi))
larghezza = griglia[1] - griglia[0]

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.hist(r, bins=200, label="quello che e' successo")
    ax.plot(griglia, campana * larghezza, linewidth=2, label="quello che prevede la campana")
    ax.set_yscale("log")
    ax.set_xlabel("Variazione giornaliera")
    ax.set_ylabel("Numero di giorni (scala log)")
    ax.legend()
    plt.show()

3. Venti giorni su 3.200

Togliamo dalla serie i giorni migliori, poi i peggiori, e guardiamo cosa resta.

Output

tutti i 3239 giorni:                13.68x

   quanti giorni tolti   togliendo i peggiori   togliendo i migliori
                     1                 22.62x                 11.17x
                     5                 48.43x                  5.87x
                    10                102.96x                  2.92x
                    20                381.21x                  0.90x
                    50               8024.89x                  0.04x
Espandi lo script di questo passo
lab_06_code_grasse.py
python
ordine = np.argsort(r)


def senza(indici_da_togliere: np.ndarray) -> float:
    maschera = np.ones(len(r), dtype=bool)
    maschera[indici_da_togliere] = False
    return float(np.prod(1 + r[maschera]))


base = float(np.prod(1 + r))
print(f"tutti i {len(r)} giorni:            {base:9.2f}x\n")
print(f"{'quanti giorni tolti':>22s} {'togliendo i peggiori':>22s} {'togliendo i migliori':>22s}")
for quanti in (1, 5, 10, 20, 50):  # PROVA / TRY: aggiungi 100 (vedi esercizio 2)
    peggiori = senza(ordine[:quanti])
    migliori = senza(ordine[-quanti:])
    print(f"{quanti:>22d} {peggiori:21.2f}x {migliori:21.2f}x")

4. Ma stanno vicini

Il pezzo che quasi tutti i libri divulgativi omettono: i giorni migliori e i peggiori si toccano. Guarda marzo 2020 nella tabella qui sotto — il 12 è il peggiore di tutta la serie, il 13 è fra i migliori, e il 19 pure: tre dei venti giorni che decidono nove anni, nella stessa settimana.

L'ultima riga misura quanto vale il fenomeno in generale, e va letta per quello che dice: la mediana della distanza fra un giorno migliore e il peggiore più vicino è di due settimane, non di una. Ci sono coppie a un giorno di distanza e coppie a un anno. Chi esce per evitare i giorni peggiori non ha comunque il tempo di accorgersene.

Output

i dieci giorni peggiori e i dieci migliori, in ordine di calendario:

  2017-09-14   -19.2%  PEGGIORE
  2017-09-15   +16.0%  migliore
  2017-12-06   +15.8%  migliore
  2017-12-07   +22.5%  migliore
  2017-12-22   -14.0%  PEGGIORE
  2017-12-26   +14.5%  migliore
  2017-12-30   -13.5%  PEGGIORE
  2018-01-16   -19.5%  PEGGIORE
  2018-02-05   -15.2%  PEGGIORE
  2019-04-02   +17.2%  migliore
  2019-10-25   +16.8%  migliore
  2020-03-12   -39.5%  PEGGIORE
  2020-03-13   +16.2%  migliore
  2020-03-19   +14.3%  migliore
  2021-02-08   +19.5%  migliore
  2021-05-19   -14.4%  PEGGIORE
  2022-02-28   +14.5%  migliore
  2022-06-13   -15.4%  PEGGIORE
  2022-11-09   -14.1%  PEGGIORE
  2026-02-05   -14.0%  PEGGIORE

distanza mediana fra un giorno migliore e il peggiore piu' vicino: 15 giorni
Espandi lo script di questo passo
lab_06_code_grasse.py
python
peggiori_10 = np.sort(ordine[:10])
migliori_10 = np.sort(ordine[-10:])

print("i dieci giorni peggiori e i dieci migliori, in ordine di calendario:\n")
righe = sorted(
    [(date[i + 1], r[i], "PEGGIORE") for i in peggiori_10]
    + [(date[i + 1], r[i], "migliore") for i in migliori_10]
)
for giorno, variazione, tipo in righe:
    print(f"  {giorno}  {variazione:+7.1%}  {tipo}")

distanze = [
    min(abs(int(m) - int(p)) for p in peggiori_10) for m in migliori_10
]
print(f"\ndistanza mediana fra un giorno migliore e il peggiore piu' vicino: "
      f"{int(np.median(distanze))} giorni")

Esercizi

  1. Cambia SERIE in "ethusdt" o "solusdt". La curtosi resta molto sopra 3 e la concentrazione del risultato in pochi giorni resta: non è una peculiarità di un asset, è una proprietà dei mercati.
  2. Nella terza cella prova a togliere 100 giorni. Quel che resta non assomiglia più a niente di reale: è il motivo per cui «evitare i giorni peggiori» non è un consiglio ma una descrizione di un mondo che non esiste.
  3. Guarda l'ultima tabella. Prova a immaginare una regola che esca prima di ogni giorno peggiore e rientri prima di ogni giorno migliore: la distanza mediana appena stampata ti dice quanto tempo avresti per accorgertene.

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