Lab del libro · L11
L11 — Quante osservazioni servono per distinguere un vantaggio dal caso
Lab 11 — Quante osservazioni servono davvero
Quaderno del capitolo «Quanto serve per sapere se sei bravo» di La matematica di chi perde.
Il conto con i tuoi numeri: quante operazioni servirebbero per stabilire che il vantaggio che pensi di avere non è rumore. E poi la simulazione che consiglio a tutti: venti strategie prive di qualunque vantaggio, testate tutte. In media una supera il test standard. Vederla passare, sapendo che dentro non c'è nulla, vale più di dieci pagine di spiegazioni.
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
from math import ceil, erf, sqrt
import matplotlib.pyplot as plt
import numpy as np
from cvbook import seed_for
from cvbook.dati import carica
from cvbook.metriche import rendimenti1. Il conto, con i tuoi numeri
Due soli ingredienti: quanto è grande il vantaggio che vuoi dimostrare e quanto oscillano i risultati attorno a esso. Il rapporto fra i due decide tutto. È lo stesso motivo per cui in una stanza silenziosa senti un sussurro e in discoteca devi urlare.
Output
vantaggio da dimostrare: 0.100% per operazione oscillazione per operazione: 3.5% operazioni necessarie: 7,574 a 250 operazioni l'anno: 30.3 anni
Espandi lo script di questo passo
VANTAGGIO = 0.001 # ← guadagno medio per operazione, al netto dei costi
# PROVA / TRY: il TUO vantaggio stimato (esercizio 1)
OSCILLAZIONE = 0.035 # ← deviazione standard del risultato per operazione
# PROVA / TRY: la TUA oscillazione (esercizio 1)
OPERAZIONI_ANNO = 250 # ← quante ne fai in un anno
POTENZA = 0.80 # ← probabilita' di accorgersene, se il vantaggio esiste
ALFA = 0.05 # ← rischio accettato di scambiare rumore per segnale
def quantile_normale(p: float) -> float:
"""Inversa della normale standard, per bisezione: nessuna dipendenza esterna."""
basso, alto = -10.0, 10.0
for _ in range(200):
mezzo = (basso + alto) / 2
if 0.5 * (1 + erf(mezzo / sqrt(2))) < p:
basso = mezzo
else:
alto = mezzo
return (basso + alto) / 2
def quante_servono(vantaggio: float, oscillazione: float,
potenza: float = POTENZA, alfa: float = ALFA) -> int:
if vantaggio <= 0:
raise ValueError("il vantaggio deve essere positivo")
z_alfa = quantile_normale(1 - alfa)
z_potenza = quantile_normale(potenza)
return int(ceil(((z_alfa + z_potenza) * oscillazione / vantaggio) ** 2))
n = quante_servono(VANTAGGIO, OSCILLAZIONE)
print(f"vantaggio da dimostrare: {VANTAGGIO:.3%} per operazione")
print(f"oscillazione per operazione: {OSCILLAZIONE:.1%}\n")
print(f"operazioni necessarie: {n:,}")
print(f"a {OPERAZIONI_ANNO} operazioni l'anno: {n / OPERAZIONI_ANNO:,.1f} anni")2. La tabella, e la riga che fa male
Output
oscillazione giornaliera misurata su Bitcoin: 3.5%
vantaggio operazioni a 250/anno
0.05% 30,836 123.3 anni
0.10% 7,709 30.8 anni
0.20% 1,928 7.7 anni
0.50% 309 1.2 anni
1.00% 78 0.3 anniUna curva ripidamente discendente su scala logaritmica: in orizzontale il vantaggio medio per operazione, da 0 a 1,2%, in verticale quante operazioni servono per distinguerlo dal caso, da 100 a 100.000. Cinque punti la leggono: con un vantaggio dello 0,05% servono 30.836 operazioni, con lo 0,1% ne servono 7.709, con lo 0,5% 309, con l'1% 78. L'oscillazione giornaliera usata e' 3,5%.
Un vantaggio dello 0,1% per operazione sarebbe un risultato eccellente — e i costi si mangiano gia' lo 0,12% a giro. Per dimostrarlo servono decenni di operativita' quotidiana.
Espandi lo script di questo passo
oscillazione_btc = float(np.std(rendimenti(
carica("btcusdt").sort("data")["chiusura"].to_numpy()), ddof=1))
print(f"oscillazione giornaliera misurata su Bitcoin: {oscillazione_btc:.1%}\n")
vantaggi = [0.0005, 0.001, 0.002, 0.005, 0.010]
print(f"{'vantaggio':>10s} {'operazioni':>12s} {'a 250/anno':>14s}")
for v in vantaggi:
q = quante_servono(v, oscillazione_btc)
print(f"{v:10.2%} {q:12,d} {q / 250:13.1f} anni")
with avvio.figura("schermo"):
fig, ax = plt.subplots()
griglia = np.linspace(0.0003, 0.012, 200)
ax.plot(griglia * 100, [quante_servono(v, oscillazione_btc) for v in griglia], linewidth=2)
for v in vantaggi:
ax.plot([v * 100], [quante_servono(v, oscillazione_btc)], marker="o")
ax.set_yscale("log")
ax.set_xlabel("Vantaggio medio per operazione (%)")
ax.set_ylabel("Operazioni necessarie (scala log)")
plt.show()
print("\nUn vantaggio dello 0,1% per operazione sarebbe un risultato eccellente — "
"e i costi si mangiano gia' lo 0,12% a giro. Per dimostrarlo servono "
"decenni di operativita' quotidiana.")3. Quaranta operazioni non distinguono nulla da nulla
Con una moneta perfettamente equa, quante volte capita di ottenere 26 teste o più su 40 lanci? Il capitolo dice il 4%. Verifichiamolo invece di crederci.
Output
su 200,000 sequenze di 40 lanci di una moneta EQUA: 26 vittorie o piu': 4.04% delle volte E se hai provato piu' di una manciata di strategie prima di trovare questa, quel 4% te lo sei praticamente garantito.
Espandi lo script di questo passo
LANCI = 40
VITTORIE = 26
PROVE = 200_000 # PROVA / TRY: 20000 (veloce) · 200000 (percentuale più precisa)
rng = np.random.default_rng(seed_for("lab-potere-moneta"))
esiti = rng.binomial(LANCI, 0.5, PROVE)
quota = float((esiti >= VITTORIE).mean())
print(f"su {PROVE:,} sequenze di {LANCI} lanci di una moneta EQUA:")
print(f" {VITTORIE} vittorie o piu': {quota:.2%} delle volte")
print(f"\nE se hai provato piu' di una manciata di strategie prima di trovare "
f"questa, quel {quota:.0%} te lo sei praticamente garantito.")4. Venti strategie senza alcun vantaggio, testate tutte
Output
strategia risultato medio statistica t supera il test?
1 0.3061% 1.93 SI
2 0.0795% 0.51 no
3 -0.0285% -0.18 no
4 0.0263% 0.17 no
5 -0.1789% -1.17 no
6 0.3351% 2.13 SI
7 0.2047% 1.38 no
8 0.1193% 0.77 no
9 0.0500% 0.32 no
10 -0.0574% -0.39 no
11 0.0728% 0.49 no
12 -0.3965% -2.60 no
13 0.0611% 0.41 no
14 0.3027% 1.95 SI
15 0.0682% 0.44 no
16 -0.0143% -0.09 no
17 -0.1687% -1.04 no
18 -0.2347% -1.44 no
19 0.2045% 1.34 no
20 0.0957% 0.63 no
3 strategie su 20 hanno superato il test standard.
Dentro non c'era niente. Nessuna di esse aveva un vantaggio: era zero, messo li' da noi.Espandi lo script di questo passo
STRATEGIE = 20 # PROVA / TRY: 200 (vedi esercizio 2)
OSSERVAZIONI = 500 # PROVA / TRY: 5000 (vedi esercizio 3)
rng = np.random.default_rng(seed_for("lab-potere-multipli"))
soglia = quantile_normale(1 - ALFA)
print(f"{'strategia':>10s} {'risultato medio':>17s} {'statistica t':>14s} {'supera il test?':>17s}")
passate = 0
for k in range(STRATEGIE):
campione = rng.normal(0.0, OSCILLAZIONE, OSSERVAZIONI) # vantaggio ESATTAMENTE zero
t = campione.mean() / (campione.std(ddof=1) / np.sqrt(OSSERVAZIONI))
supera = t > soglia
passate += supera
print(f"{k + 1:10d} {campione.mean():17.4%} {t:14.2f} {'SI' if supera else 'no':>17s}")
print(f"\n{passate} strategie su {STRATEGIE} hanno superato il test standard.")
print("Dentro non c'era niente. Nessuna di esse aveva un vantaggio: era zero, "
"messo li' da noi.")5. Il correttore per test multipli
Gli dici quanti tentativi hai fatto e ti restituisce la soglia che avresti dovuto usare. Applicalo ai tuoi risultati passati — con una certa cautela emotiva.
Output
tentativi soglia sulla statistica t prob. che almeno uno passi
1 1.64 5.0%
5 2.33 22.6%
20 2.81 64.2%
50 3.09 92.3%
100 3.29 99.4%
500 3.72 100.0%
Con cento tentativi, trovare qualcosa che supera il test non corretto e' praticamente certo. Non e' un difetto del test: e' la sua definizione.Espandi lo script di questo passo
def soglia_corretta(tentativi: int, alfa: float = ALFA) -> float:
"""Correzione conservativa: si divide il rischio accettato per i tentativi."""
return quantile_normale(1 - alfa / tentativi)
print(f"{'tentativi':>10s} {'soglia sulla statistica t':>27s} "
f"{'prob. che almeno uno passi':>28s}")
for tentativi in (1, 5, 20, 50, 100, 500):
print(f"{tentativi:10d} {soglia_corretta(tentativi):27.2f} "
f"{1 - (1 - ALFA) ** tentativi:27.1%}")
print("\nCon cento tentativi, trovare qualcosa che supera il test non corretto e' "
"praticamente certo. Non e' un difetto del test: e' la sua definizione.")Esercizi
- Nella prima cella metti il tuo vantaggio stimato e la tua oscillazione, calcolati sul tuo registro. Il numero che esce è il tuo orizzonte di verifica reale.
- Nella quarta cella porta
STRATEGIEa 200. Quante passano? Circa il 5%, come previsto — e ognuna di esse, mostrata da sola, sembrerebbe una scoperta. - Cambia
OSSERVAZIONIda 500 a 5000 nella quarta cella. La quota di strategie che passa non cambia: più dati non proteggono dai test multipli. Solo il conteggio dei tentativi protegge.
Riproducibilità e download
Eseguito il 2026-08-27 dal quaderno del repository
Il quaderno
lab_11_potere.ipynb13,2 KB
sha256 3f6fd59cc9f40cedd146e27a8e7906631c769f3a7ad55f6118894416333af93e
lab_11_potere.py9,9 KB
sha256 be7a69c1c279dd60e131c06308a7a12e6e9718c4ba5185ed1c495bfb78735032
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