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

Lab del libro · L20

L20 — Seme del caso, vettorizzazione e riproducibilità

Lab 20 — Le basi che ti servono davvero

Quaderno del capitolo «Le basi che ti servono davvero» di La matematica di chi perde.

Cinque celle, una per ciascuna delle idee del capitolo. Non serve saper programmare per seguirle: serve leggere i commenti e cambiare i numeri.

L'esercizio finale è togliere il seme dalla simulazione ed eseguirla tre volte. Vedere tre risultati diversi dalla stessa identica cella è il modo più rapido per capire perché la riproducibilità non è un dettaglio.

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_20_basi.py
python
import time

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

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

Uno — La riproducibilità

Un lavoro è riproducibile se, eseguito di nuovo, dà lo stesso identico risultato. Sempre. Su un altro computer, fra un anno, eseguito da un'altra persona.

Qui i dati sono congelati: salvati una volta, con la data di estrazione e un'impronta che ne verifica l'integrità.

Output

serie:      btcusdt
fonte:      Binance Data Vision
estratta:   2026-08-16
periodo:    2017-08-17 → 2026-06-30  (3240 righe)
impronta:   ea75ad84e6e981507054df5c622c6b0ec3c8849c1f4dd007721878d4e4c8a329

Se qualcuno modificasse quel file, `carica()` si rifiuterebbe di eseguire. Non e' pignoleria: e' cio' che rende le figure del libro verificabili fra dieci anni.
Espandi lo script di questo passo
lab_20_basi.py
python
voce = leggi_registro()["btcusdt"]
print(f"serie:      {voce.nome}")
print(f"fonte:      {voce.fonte}")
print(f"estratta:   {voce.estratto}")
print(f"periodo:    {voce.dal}{voce.al}  ({voce.righe} righe)")
print(f"impronta:   {voce.sha256}")
print("\nSe qualcuno modificasse quel file, `carica()` si rifiuterebbe di "
      "eseguire. Non e' pignoleria: e' cio' che rende le figure del libro "
      "verificabili fra dieci anni.")

Due — Il seme del caso

I computer non producono numeri davvero casuali: producono sequenze che sembrano casuali, generate a partire da un numero iniziale detto seme. Stesso seme, stessa sequenza.

Output

con lo stesso seme, tre esecuzioni:
    [ 0.3047 -1.04    0.7505  0.9406 -1.951 ]
    [ 0.3047 -1.04    0.7505  0.9406 -1.951 ]
    [ 0.3047 -1.04    0.7505  0.9406 -1.951 ]

senza fissare il seme, tre esecuzioni:
    [1.1927 0.2206 1.5968 0.2193 1.2435]
    [-0.6375  0.4579  0.5312 -0.0172 -1.0695]
    [ 0.6577 -0.6453 -0.0103  0.7493 -1.5414]

Quando qualcuno mostra il risultato di una simulazione, CHIEDI se il seme e' fissato e qual e'. Se non lo e', quel risultato non si puo' ricontrollare — e se ha eseguito piu' volte scegliendo l'esecuzione che gli piaceva di piu', e' il capitolo sui test multipli applicato ai numeri casuali.
Espandi lo script di questo passo
lab_20_basi.py
python
print("con lo stesso seme, tre esecuzioni:")
for _ in range(3):
    rng = np.random.default_rng(42)
    print("   ", np.round(rng.normal(size=5), 4))

print("\nsenza fissare il seme, tre esecuzioni:")
for _ in range(3):
    rng = np.random.default_rng()   # ← nessun seme
    print("   ", np.round(rng.normal(size=5), 4))

print("\nQuando qualcuno mostra il risultato di una simulazione, CHIEDI se il "
      "seme e' fissato e qual e'. Se non lo e', quel risultato non si puo' "
      "ricontrollare — e se ha eseguito piu' volte scegliendo l'esecuzione che "
      "gli piaceva di piu', e' il capitolo sui test multipli applicato ai "
      "numeri casuali.")

Tre — La vettorizzazione

Il modo intuitivo di elaborare tremila giorni è: prendi il primo, fai il conto; prendi il secondo, fai il conto. Funziona ed è lentissimo.

Il modo giusto è pensare all'intera serie come a un oggetto solo.

Output

un giorno alla volta:     1.63 ms
tutta la serie insieme:   0.116 ms
rapporto: 14 volte
risultati identici? True

Ma la velocita' non e' il punto vero. Il punto e' che CAMBIA LE DOMANDE che ti vengono in mente: chi pensa per serie intere si chiede naturalmente «quante volte e' successo, su tutti gli asset, in tutti i periodi». Chi pensa un giorno alla volta si ferma prima.
Espandi lo script di questo passo
lab_20_basi.py
python
prezzi = carica("btcusdt").sort("data")["chiusura"].to_numpy()

# Modo intuitivo: un giorno alla volta.
inizio = time.perf_counter()
lento = []
for i in range(1, len(prezzi)):
    lento.append(prezzi[i] / prezzi[i - 1] - 1)
tempo_lento = time.perf_counter() - inizio

# Modo vettorizzato: un'istruzione sola, applicata a tutto.
inizio = time.perf_counter()
veloce = prezzi[1:] / prezzi[:-1] - 1
tempo_veloce = time.perf_counter() - inizio

print(f"un giorno alla volta: {tempo_lento * 1000:8.2f} ms")
print(f"tutta la serie insieme:{tempo_veloce * 1000:8.3f} ms")
print(f"rapporto: {tempo_lento / max(tempo_veloce, 1e-9):.0f} volte")
print(f"risultati identici? {np.allclose(lento, veloce)}")

print("\nMa la velocita' non e' il punto vero. Il punto e' che CAMBIA LE DOMANDE "
      "che ti vengono in mente: chi pensa per serie intere si chiede "
      "naturalmente «quante volte e' successo, su tutti gli asset, in tutti i "
      "periodi». Chi pensa un giorno alla volta si ferma prima.")

Quattro — Ripetere su molte serie

È il passaggio che apre le domande interessanti. Tre righe.

Output

   btcusdt: 40.1% del tempo con meno della meta' del proprio massimo
   ethusdt: 58.4% del tempo con meno della meta' del proprio massimo
   solusdt: 50.6% del tempo con meno della meta' del proprio massimo
Espandi lo script di questo passo
lab_20_basi.py
python
risposte = {}
for nome in ("btcusdt", "ethusdt", "solusdt"):  # PROVA / TRY: aggiungi un'altra serie preparata
    p = carica(nome).sort("data")["chiusura"].to_numpy()
    dd = drawdown(np.concatenate([[1.0], np.cumprod(1 + rendimenti(p))]))
    risposte[nome] = float((dd < -0.5).mean())

for nome, quota in risposte.items():
    print(f"{nome:>10s}: {quota:5.1%} del tempo con meno della meta' del proprio massimo")

Cinque — Confrontare con il caso

Generare percorsi casuali e posizionarci sopra il proprio risultato. È forse la singola capacità più utile di tutto il libro.

Output

il risultato vero: 13.68x

con il seme fissato, tre esecuzioni:
    0.492
    0.492
    0.492

senza seme, tre esecuzioni:
    0.545
    0.485
    0.485

E' l'esercizio finale del capitolo: la stessa identica cella, tre risultati diversi. Se un numero cambia a ogni esecuzione, non si puo' ricontrollare — e quindi non e' una prova.
Espandi lo script di questo passo
lab_20_basi.py
python
r = rendimenti(prezzi)
reale = float(np.prod(1 + r))


def esperimento(seme: int | None, percorsi: int = 400) -> float:  # PROVA / TRY: percorsi=4000
    """Quota di percorsi ricampionati che fanno meglio di quello vero.

    Attenzione a una sottigliezza che il capitolo sull'aritmetica ha gia'
    incontrato: **rimescolare** i rendimenti non cambia il capitale finale — la
    moltiplicazione e' commutativa. Per ottenere storie diverse bisogna
    ricampionare **con reinserimento**, cioe' costruire percorsi in cui alcuni
    periodi si ripetono e altri mancano.
    """
    generatore = np.random.default_rng(seme)
    indici = generatore.integers(0, len(r), size=(percorsi, len(r)))
    return float((np.prod(1 + r[indici], axis=1) > reale).mean())


print(f"il risultato vero: {reale:.2f}x\n")
print("con il seme fissato, tre esecuzioni:")
for _ in range(3):
    print(f"    {esperimento(2026):.3f}")

print("\nsenza seme, tre esecuzioni:")
for _ in range(3):
    print(f"    {esperimento(None):.3f}")

print("\nE' l'esercizio finale del capitolo: la stessa identica cella, tre "
      "risultati diversi. Se un numero cambia a ogni esecuzione, non si puo' "
      "ricontrollare — e quindi non e' una prova.")

Le quattro capacità, e basta

  1. Caricare dei dati e guardarli. È il novanta per cento del lavoro reale.
  2. Fare un conto su un'intera serie. Rendimenti, medie, distanze dal massimo: tre righe ciascuna.
  3. Ripetere il conto su molte serie. È il passaggio che apre le domande interessanti.
  4. Confrontare con il caso. La cella qui sopra.

Non serve altro. Non servono le classi, le strutture dati avanzate, i modelli di apprendimento automatico. Il modo più rapido che conosco per cominciare non è studiare il linguaggio: è modificare qualcosa che già funziona e guardare cosa cambia. Questo quaderno è fatto per quello.

Riproducibilità e download

Eseguito il 2026-08-27 dal quaderno del repository

Il quaderno

  • lab_20_basi.ipynb12,9 KB

    sha256 931eff3473c8fb6cc4553bbfc6fab4472ec2c70170fa63e0c204b8c3c238416c

  • lab_20_basi.py9,6 KB

    sha256 26f9a220b7598c43ae0d0f921a539bae21116912f8946af98bb9ff78259f413d

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