Lab del libro · L01
L01 — Quanti conti al dettaglio chiudono in perdita, e in quanto tempo
Lab 1 — Chi perde davvero, e quanto
Quaderno del capitolo «Chi perde davvero, e quanto» di La matematica di chi perde.
Il capitolo mostra tre coordinate senza le quali un risultato non significa niente: su cosa, su quale periodo, con quali costi. Qui le vedi muoversi.
La figura del libro dice che, su questo asset e in questo periodo, il momento in cui si è cominciato conta più di qualunque altra cosa. Il quaderno serve a rifare quel conto e — soprattutto — a smontarlo, esattamente come fa il capitolo: il campione è un solo mercato in un periodo fortunato, e questo va guardato in faccia invece che nascosto.
Esegui le celle dall'alto verso il basso. La prima richiede una ventina di secondi, le altre sono immediate.
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
serie: Binance Data Vision, estratta il 2026-08-16 periodo: 2017-08-17 → 2026-06-30 (3240 giorni)
Espandi lo script di questo passo
import matplotlib.pyplot as plt
import numpy as np
from cvbook.dati import carica, citazione
from cvbook.metriche import drawdown_massimo
df = carica("btcusdt").sort("data") # ← PROVA / TRY: "ethusdt" · "solusdt" (vedi esercizio 3)
prezzi = df["chiusura"].to_numpy()
date = df["data"].to_list()
fonte, estratto = citazione("btcusdt")
print(f"serie: {fonte}, estratta il {estratto}")
print(f"periodo: {date[0]} → {date[-1]} ({len(prezzi)} giorni)")1. Quanti ingressi finiscono in perdita, per orizzonte
Per ogni giorno della storia ci chiediamo: chi fosse entrato quel giorno e uscito dopo N giorni, come sarebbe andata? Poi contiamo la quota di ingressi chiusi in perdita.
È il conto che quasi nessuno fa, perché richiede di guardare tutti i giorni d'ingresso e non solo quello comodo.
Output
1 mese: 46.4% degli ingressi in perdita (3210 ingressi) 3 mesi: 45.5% degli ingressi in perdita (3150 ingressi) 1 anno: 33.6% degli ingressi in perdita (2875 ingressi) 2 anni: 24.3% degli ingressi in perdita (2510 ingressi) 4 anni: 0.0% degli ingressi in perdita (1780 ingressi)
Espandi lo script di questo passo
ORIZZONTI = [(30, "1 mese"), (90, "3 mesi"), (365, "1 anno"), (730, "2 anni"), (1460, "4 anni")]
# PROVA / TRY: aggiungi o cambia un orizzonte, es. (180, "6 mesi")
def quota_in_perdita(p: np.ndarray, giorni: int) -> float:
"""Frazione di giorni d'ingresso che, dopo `giorni`, si trova sotto zero."""
if giorni >= len(p):
return float("nan")
esiti = p[giorni:] / p[:-giorni] - 1.0
return float((esiti < 0).mean())
for giorni, etichetta in ORIZZONTI:
q = quota_in_perdita(prezzi, giorni)
print(f"{etichetta:>8s}: {q:6.1%} degli ingressi in perdita ({len(prezzi) - giorni} ingressi)")2. Il primo limite: le osservazioni si sovrappongono
I numeri appena stampati sembrano basati su migliaia di casi. Non lo sono.
2.875 ingressi a dodici mesi su nove anni di storia non sono 2.875 esperimenti indipendenti: sono nove anni guardati da 2.875 angolazioni che si accavallano quasi tutte fra loro.
Il conto onesto delle occasioni indipendenti è più vicino a questo.
Output
1 mese: 3210 righe nel file, ma circa 108 periodi davvero distinti 3 mesi: 3150 righe nel file, ma circa 36 periodi davvero distinti 1 anno: 2875 righe nel file, ma circa 8 periodi davvero distinti 2 anni: 2510 righe nel file, ma circa 4 periodi davvero distinti 4 anni: 1780 righe nel file, ma circa 2 periodi davvero distinti
Espandi lo script di questo passo
for giorni, etichetta in ORIZZONTI:
sovrapposti = len(prezzi) - giorni
indipendenti = len(prezzi) // giorni
print(
f"{etichetta:>8s}: {sovrapposti:5d} righe nel file, "
f"ma circa {indipendenti:3d} periodi davvero distinti"
)Guarda la colonna di destra. A quattro anni le osservazioni indipendenti sono due. Un numero su cui non si costruisce nessuna conclusione.
3. Il secondo limite: il periodo è fortunato
Cambia la finestra e guarda cosa succede ai numeri. Il capitolo lo dice esplicitamente: la colonna dei quattro anni non dimostra che a quattro anni non si perde — dimostra che in questa finestra non è successo.
Output
finestra scelta: 2017-08-17 → 2026-06-30 (3240 giorni) risultato del compra-e-tieni: 13.68x calo massimo attraversato: -83.2% 1 mese: 46.4% in perdita 3 mesi: 45.5% in perdita 1 anno: 33.6% in perdita 2 anni: 24.3% in perdita 4 anni: 0.0% in perdita
Espandi lo script di questo passo
INIZIO, FINE = "2017-08-17", "2026-06-30"
# PROVA / TRY: FINE = "2022-12-31" · INIZIO = "2021-01-01" (vedi esercizi 1 e 2)
import datetime as dt
maschera = [
dt.date.fromisoformat(INIZIO) <= d <= dt.date.fromisoformat(FINE) for d in date
]
sotto = prezzi[np.array(maschera)]
print(f"finestra scelta: {INIZIO} → {FINE} ({len(sotto)} giorni)")
print(f"risultato del compra-e-tieni: {sotto[-1] / sotto[0]:.2f}x")
print(f"calo massimo attraversato: {drawdown_massimo(sotto):.1%}\n")
for giorni, etichetta in ORIZZONTI:
q = quota_in_perdita(sotto, giorni)
print(f"{etichetta:>8s}: {q:6.1%} in perdita")Esercizi
- Metti
FINE = "2022-12-31"ed esegui di nuovo la cella. La colonna dei quattro anni smette di essere zero. Non è cambiato nessun dato: è cambiata la finestra. - Metti
INIZIO = "2021-01-01". Il compra-e-tieni scende parecchio. È il capitolo che dice che dieci mesi di differenza sull'ingresso valevano 129 punti percentuali. - Cambia
"btcusdt"in"ethusdt"o"solusdt"nella cella del setup e in quella del caricamento. La conclusione regge? Su quali orizzonti?
Cosa portarsi via
Un risultato non è un numero: è un numero con il suo periodo, il suo campione e la sua dimensione effettiva. Se una di queste tre cose manca, la risposta corretta a «quindi funziona?» è: non lo so, e non lo sai nemmeno tu.
Riproducibilità e download
Eseguito il 2026-08-27 dal quaderno del repository
Il quaderno
lab_01_chi_perde.ipynb11,4 KB
sha256 81f4f7a05bee17ca23a90d288c4ea9725f5e7c9ce0d7cced0aa12699d5193129
lab_01_chi_perde.py8,7 KB
sha256 ab9ac092c4b51303cc9e8d3cef8720bec70b4fe09224d5cf7ccf4b2d5ce106e1
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