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

Lab del libro · L05

L05 — Tre sguardi sugli stessi dati, e il test del vero contro il finto

Lab 5 — Guardare non è misurare

Quaderno del capitolo «Cosa vuol dire misurare» di La matematica di chi perde.

Tre sguardi sugli stessi identici dati: il prezzo, le variazioni, la distribuzione. Non sono tre livelli di dettaglio — sono tre domande diverse, e il primo passo del misurare è sapere quale stai facendo.

Poi c'è l'esercizio che conviene fare prima di continuare a leggere il libro: distinguere una serie vera da una generata a caso. Le persone ci riescono poco più della metà delle volte, cioè poco meglio del lancio di una moneta.

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_05_misurare.py
python
import matplotlib.pyplot as plt
import numpy as np

from cvbook import seed_for
from cvbook.dati import carica
from cvbook.metriche import rendimenti, volatilita

1. Tre sguardi

Cambia SERIE e riesegui: la forma del primo pannello cambia molto, quella del terzo molto meno. È il motivo per cui il terzo permette confronti che il primo non permette.

Tre pannelli affiancati sugli stessi 3 240 giorni di btcusdt, dal 17 agosto 2017 al 30 giugno 2026. A sinistra il prezzo su un asse logaritmico che corre fra 10⁴ e 10⁵; al centro la variazione giornaliera in percentuale, con l'asse graduato da −40 a 20; a destra l'istogramma delle stesse variazioni, con il conteggio su asse logaritmico da 10⁰ a 10² e la massa addensata attorno allo zero.

Gli stessi dati guardati come prezzo, come variazione giornaliera e come distribuzione.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Chiusure giornaliere della serie scelta nel quaderno, variazioni fra chiusure consecutive, scala logaritmica sul prezzo e sul conteggio dell'istogramma.

Output

btcusdt: 3240 giorni, dal 2017-08-17 al 2026-06-30
volatilita' annualizzata dell'intero periodo: 67.5%
Espandi lo script di questo passo
lab_05_misurare.py
python
SERIE = "btcusdt"  # ← PROVA / TRY: "ethusdt" · "solusdt" (le tre preparate nel setup)
                   # per un'altra delle 11 serie in codice/dati/registro.json
                   # aggiungila anche a avvio.prepara([...]) qui sopra

df = carica(SERIE).sort("data")
prezzi = df["chiusura"].to_numpy()
date = df["data"].to_list()
r = rendimenti(prezzi)

with avvio.figura("schermo"):
    fig, (a, b, c) = plt.subplots(1, 3, figsize=(13, 4))

    a.semilogy(date, prezzi, linewidth=1.2)
    a.set_title("1. Il prezzo\n«dove siamo arrivati»", fontsize=10)
    a.set_ylabel("Prezzo (scala log)")

    b.plot(date[1:], r * 100, linewidth=0.5)
    b.set_title("2. Le variazioni\n«quanto si muove ogni giorno»", fontsize=10)
    b.set_ylabel("Variazione giornaliera (%)")

    c.hist(r * 100, bins=120)
    c.set_title("3. La distribuzione\n«quanto spesso succede cosa»", fontsize=10)
    c.set_xlabel("Variazione giornaliera (%)")
    c.set_yscale("log")

    for ax in (a, b):
        ax.tick_params(axis="x", rotation=30)
    plt.show()

print(f"{SERIE}: {len(prezzi)} giorni, dal {date[0]} al {date[-1]}")
print(f"volatilita' annualizzata dell'intero periodo: {volatilita(r):.1%}")

Nel secondo pannello guarda una cosa che nel primo non si vede: le scosse grandi arrivano raggruppate. Ci sono periodi tranquilli e periodi agitati, e i giorni agitati stanno vicini fra loro. È il capitolo sui regimi.

2. Vera o finta?

Sei grafici. Alcuni sono prezzi reali, altri sono passeggiate casuali con la stessa volatilità. Scrivi la tua risposta prima di eseguire la cella dopo.

Sei riquadri disposti su due righe, titolati da «grafico 1» a «grafico 6», ciascuno con 400 giorni riportati a base 100 e senza asse dei tempi. Tre sono finestre reali di btcusdt e tre passeggiate casuali con la stessa deviazione standard giornaliera; le scale verticali vanno da 40 nel primo riquadro a 225 nel quinto, mentre il secondo e il sesto restano fra 60 e 130.

Sei finestre da 400 giorni: tre reali e tre generate a caso, mescolate.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Finestre estratte a caso dalla serie e passeggiate casuali con la deviazione standard delle variazioni osservate, tutte riportate a base 100; il seme e fissato dal quaderno, quindi i sei riquadri sono sempre gli stessi.
Espandi lo script di questo passo
lab_05_misurare.py
python
rng = np.random.default_rng(seed_for("lab-vero-o-finto"))
# NON TOCCARE / DO NOT CHANGE: scrivi la tua risposta PRIMA di eseguire la
# cella con la soluzione. Cambiare il seme per "azzeccarci di più" vanifica
# l'esercizio invece di misurarlo.
# Write down your answer BEFORE running the cell with the solution. Changing
# the seed to "get it more right" defeats the exercise instead of measuring it.
FINESTRA = 400  # PROVA / TRY: 400 · 1200 (vedi esercizio 2)

sigma = float(np.std(r, ddof=1))
partenze = rng.integers(0, len(prezzi) - FINESTRA, size=6)
etichette = rng.permutation(["vera", "finta", "vera", "finta", "finta", "vera"])

serie_mostrate = []
for k in range(6):
    if etichette[k] == "vera":
        s = prezzi[partenze[k]: partenze[k] + FINESTRA]
        s = s / s[0] * 100
    else:
        s = 100 * np.cumprod(1 + rng.normal(0.0, sigma, FINESTRA))
    serie_mostrate.append(s)

with avvio.figura("schermo"):
    fig, assi = plt.subplots(2, 3, figsize=(12, 5))
    for k, ax in enumerate(assi.flat):
        ax.plot(serie_mostrate[k], linewidth=1.2)
        ax.set_title(f"grafico {k + 1}", fontsize=10)
        ax.set_xticks([])
    plt.show()

Output

soluzione:
  grafico 1: vera
  grafico 2: finta
  grafico 3: vera
  grafico 4: finta
  grafico 5: vera
  grafico 6: finta

Quasi nessuno supera il caso in questo esercizio. Non e' un limite personale: e' che la percezione trova regolarita' anche nel rumore, ed e' il motivo per cui serve una misura che possa dire di no.
Espandi lo script di questo passo
lab_05_misurare.py
python
print("soluzione:")
for k, e in enumerate(etichette):
    print(f"  grafico {k + 1}: {e}")
print(
    "\nQuasi nessuno supera il caso in questo esercizio. Non e' un limite "
    "personale: e' che la percezione trova regolarita' anche nel rumore, ed e' "
    "il motivo per cui serve una misura che possa dire di no."
)

3. Le quattro domande, in codice

Prima di credere a qualunque numero servono quattro risposte. Qui le vedi cambiare il risultato una alla volta.

Output

Su cosa?
  btcusdt:    13.68x nel proprio periodo disponibile
  ethusdt:     5.21x nel proprio periodo disponibile
  solusdt:    22.33x nel proprio periodo disponibile

Su quale periodo? (stesso asset, finestre diverse)
     ultimo anno:     0.55x
   ultimi 3 anni:     1.91x
           tutto:    13.68x

Con quale rappresentazione? (stessi dati, due misure di 'rendimento medio')
  media aritmetica giornaliera: 0.1439%  → su un anno 69.0%
  composto giornaliero:         0.0808%  → su un anno 34.3%

Confrontato con cosa? (la domanda che quasi nessuno pone)
  mediana di 200 sotto-campioni casuali della stessa serie: 4.02x
Espandi lo script di questo passo
lab_05_misurare.py
python
print("Su cosa?")
for nome in ("btcusdt", "ethusdt", "solusdt"):
    p = carica(nome).sort("data")["chiusura"].to_numpy()
    print(f"  {nome}: {p[-1] / p[0]:8.2f}x nel proprio periodo disponibile")

print("\nSu quale periodo? (stesso asset, finestre diverse)")
p = carica("btcusdt").sort("data")["chiusura"].to_numpy()
for taglio, etichetta in [(365, "ultimo anno"), (1095, "ultimi 3 anni"), (len(p), "tutto")]:
    s = p[-taglio:]
    print(f"  {etichetta:>14s}: {s[-1] / s[0]:8.2f}x")

print("\nCon quale rappresentazione? (stessi dati, due misure di 'rendimento medio')")
r_btc = rendimenti(p)
media = float(np.mean(r_btc))
composto = float(p[-1] / p[0]) ** (1 / len(r_btc)) - 1
print(f"  media aritmetica giornaliera: {media:.4%}  → su un anno {((1 + media) ** 365 - 1):.1%}")
print(f"  composto giornaliero:         {composto:.4%}  → su un anno {((1 + composto) ** 365 - 1):.1%}")

print("\nConfrontato con cosa? (la domanda che quasi nessuno pone)")
casuali = np.array([
    np.prod(1 + rng.permutation(r_btc)[: len(r_btc) // 2]) for _ in range(200)
])
print(f"  mediana di 200 sotto-campioni casuali della stessa serie: {np.median(casuali):.2f}x")

Esercizi

  1. Nella prima cella cambia serie e guarda quale pannello cambia di più. Il terzo è quello che rende confrontabili asset ed epoche diverse: è per quello che tutta la Parte II lavora lì.
  2. Nella seconda cella porta FINESTRA a 1200. Con serie più lunghe l'esercizio diventa un po' più facile — ma molto meno di quanto ti aspetti.
  3. Nella terza cella, guarda la differenza fra i due «rendimenti medi annui». Sono lo stesso dato. Uno descrive un giorno tipico che non esiste, l'altro quello che è successo davvero a chi era dentro.

Riproducibilità e download

Eseguito il 2026-08-27 dal quaderno del repository

Il quaderno

  • lab_05_misurare.ipynb12,0 KB

    sha256 83cc84f9f45968dddcf6a6cb1a781c5e0731b7a80c1b2bb8ee3d404164bac73d

  • lab_05_misurare.py9,0 KB

    sha256 8338433949ead76426d5b059d75900ac82caa994d2776c7736adabb844255ffc

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