Lab del libro · L03
L03 — Mortalità dei token e distorsione da sopravvivenza
Lab 3 — Il cimitero dei token
Quaderno del capitolo «Il cimitero dei token» di La matematica di chi perde.
Questo quaderno contiene i prezzi veri dei due asset morti di cui parla il capitolo — uno andato a zero in nove giorni, uno il cui mercato è stato chiuso. Conservare quei dati è più difficile di quanto sembri: la maggior parte delle fonti comode espone solo ciò che è ancora attivo.
Il valore del quaderno non è la cronologia del crollo. È l'ultimo esercizio: ricostruire lo stesso paniere guardando solo i sopravvissuti, e vedere che risposta tranquillizzante si ottiene.
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
import datetime as dt
import matplotlib.pyplot as plt
import numpy as np
import polars as pl
from cvbook.dati import DISCONTINUITA, carica, carica_strumento
from cvbook.metriche import recupero_necessario1. Nove giorni
Scala logaritmica: ogni tacca vale un fattore dieci. Su scala lineare questo grafico sarebbe una riga verticale seguita da una riga piatta — ed è esattamente così che i crolli appaiono a chi li guarda mentre accadono.
La serie si ferma il 13 maggio 2022, ed è il punto più importante di questa cella. Dal 31 maggio lo stesso identificativo, sullo stesso mercato, quota un'altra cosa: LUNA 2.0, un progetto nuovo lanciato dopo il crollo. Chi disegna la serie per intero ottiene un grafico che scende a 0,00005 e poi risale di centomila volte — e chi lo guarda conclude l'esatto contrario di quello che è successo. La data del taglio non è scritta qui: sta nel registro dei dati (cvbook.dati.DISCONTINUITA), che è l'unico posto in cui vive.
Il prezzo di chiusura di lunausdt in USDT su scala logaritmica, dal 1° aprile al 13 maggio 2022, con l'asse verticale che copre sei ordini di grandezza da 10 alla meno 4 a 10 alla 2. La linea parte poco sopra 100, scende lentamente verso 80 durante aprile, poi cade quasi verticalmente in nove giorni fino a cinque centomillesimi e finisce lì.
Espandi lo script di questo passo
luna = carica_strumento("lunausdt").sort("data").filter(pl.col("data") >= dt.date(2022, 4, 1))
with avvio.figura("schermo"):
fig, ax = plt.subplots()
ax.semilogy(luna["data"].to_list(), luna["chiusura"].to_numpy(), linewidth=1.6)
ax.set_ylabel("Prezzo di chiusura (USDT, scala log)")
fig.autofmt_xdate()
plt.show()Provalo tu: il grafico che racconta il contrario
Esegui la cella qui sotto. È la stessa serie, letta grezza — con dentro il ticker riusato — fino al 30 giugno. È il grafico che troveresti su quasi qualunque archivio, ed è quello che il capitolo ti chiede di non credere.
La stessa serie letta grezza: il prezzo di chiusura di lunausdt in USDT su scala logaritmica, dal 1° aprile al 30 giugno 2022, tracciato a linea tratteggiata sullo stesso asse verticale da 10 alla meno 4 a 10 alla 2. Una linea verticale punteggiata segna il 13 maggio. A sinistra della verticale c'è il crollo fino a cinque centomillesimi; a destra la linea risale di circa centomila volte e oscilla fra 2 e 9 fino a fine giugno.
Output
2022-05-05 82.35000 USDT perdita 0.000% servirebbe +0% 2022-05-06 77.30000 USDT perdita 6.132% servirebbe +7% 2022-05-07 68.13000 USDT perdita 17.268% servirebbe +21% 2022-05-08 64.26000 USDT perdita 21.967% servirebbe +28% 2022-05-09 30.29000 USDT perdita 63.218% servirebbe +172% 2022-05-10 17.46000 USDT perdita 78.798% servirebbe +372% 2022-05-11 1.07690 USDT perdita 98.692% servirebbe +7,547% 2022-05-12 0.00032 USDT perdita 100.000% servirebbe +25,734,275% 2022-05-13 0.00005 USDT perdita 100.000% servirebbe +99,999,900%
Espandi lo script di questo passo
grezza = carica("lunausdt").sort("data").filter(
pl.col("data").is_between(dt.date(2022, 4, 1), dt.date(2022, 6, 30))
)
with avvio.figura("schermo"):
fig, ax = plt.subplots()
ax.semilogy(grezza["data"].to_list(), grezza["chiusura"].to_numpy(),
linewidth=1.6, linestyle="--")
ax.axvline(DISCONTINUITA["lunausdt"], linewidth=1.0, linestyle=":")
ax.set_ylabel("Prezzo di chiusura (USDT, scala log)")
ax.set_title("La stessa serie letta grezza: da qui in poi è un altro token")
fig.autofmt_xdate()
plt.show()
righe = luna.filter(pl.col("data").is_between(dt.date(2022, 5, 5), dt.date(2022, 5, 13)))
partenza = float(righe["chiusura"][0])
for d, p in zip(righe["data"].to_list(), righe["chiusura"].to_numpy()):
perdita = 1 - p / partenza
recupero = recupero_necessario(min(perdita, 0.999999))
print(f"{d} {p:12.5f} USDT perdita {perdita:7.3%} servirebbe +{recupero:,.0%}")Guarda la penultima riga: la perdita era già del 98,7%. Il giorno dopo il prezzo si è diviso ancora per tremila. Anche dopo aver perso il 98%, c'era ancora tutto da perdere.
2. Quando il prezzo smette di esistere
Il secondo modo di sparire è peggiore, perché non lascia nemmeno un prezzo. Nota dove finisce la linea: non arriva a zero, si interrompe.
Il prezzo di chiusura di fttusdt in USDT dal primo ottobre al 15 novembre 2022, con l'asse verticale da 5 a 25. La linea, segnata da un punto per ogni giorno, oscilla attorno a 25 fino ai primi di novembre, poi scende e si interrompe: l'ultimo punto è il 15 novembre a 1,43, e dopo quella data non c'è più alcun prezzo.
Output
ultimo giorno con un prezzo: 2022-11-15 (1.43 USDT) dopo quella data non esiste piu' un mercato: non c'e' un prezzo, non c'e' una quotazione, non c'e' modo di vendere.
Espandi lo script di questo passo
ftt = carica("fttusdt").sort("data").filter(pl.col("data") >= dt.date(2022, 10, 1))
with avvio.figura("schermo"):
fig, ax = plt.subplots()
ax.plot(ftt["data"].to_list(), ftt["chiusura"].to_numpy(), linewidth=1.6, marker="o",
markersize=2.5)
ax.set_ylabel("Prezzo di chiusura (USDT)")
fig.autofmt_xdate()
plt.show()
print(f"ultimo giorno con un prezzo: {ftt['data'][-1]} ({float(ftt['chiusura'][-1]):.2f} USDT)")
print("dopo quella data non esiste piu' un mercato: non c'e' un prezzo, non c'e' "
"una quotazione, non c'e' modo di vendere.")3. Il paniere, con e senza i morti
Un paniere a peso uguale costruito il 1° aprile 2021. Lo misuriamo in due modi: guardando solo ciò che oggi esiste ancora, e guardando tutto quello che si era comprato davvero.
Due curve di capitale a base 100 dal 1° aprile 2021 al 31 dicembre 2022, con l'asse verticale graduato fino a 500 e una linea orizzontale punteggiata a quota 100. Entrambe salgono fino al massimo del 6 novembre 2021 — 564 quella dei soli sopravvissuti, 424 quella tratteggiata che tiene dentro anche i token morti — e poi scendono per tutto il 2022 sotto il livello di partenza: la prima chiude a 47,1, la seconda a 29,0, il 38,5% più in basso.
Output
paniere dei soli sopravvissuti: 47.1 paniere reale, morti compresi: 29.0 differenza: -38.5%
Espandi lo script di questo passo
INIZIO = dt.date(2021, 4, 1) # ← PROVA / TRY: sposta a gennaio 2022 (vedi esercizio 2)
FINE = dt.date(2022, 12, 31) # ← PROVA / TRY: porta a oggi e guarda cosa cambia
VIVI = ["btcusdt", "ethusdt", "solusdt"] # ← PROVA / TRY: togli "solusdt" (esercizio 1)
MORTI = ["lunausdt", "fttusdt"]
# NON TOCCARE / DO NOT CHANGE: MORTI deve restare com'è — sono gli unici due
# token defunti congelati nel registro dati; la dimostrazione è proprio che
# le fonti comode non li avrebbero mai lasciati scegliere (vedi esercizio 3)
# MORTI must stay as it is — these are the only two dead tokens frozen in the
# data registry; the whole demonstration is that convenient sources would
# never have let you choose them (see exercise 3)
def curva_paniere(nomi: list[str]) -> tuple[list, np.ndarray]:
"""Paniere a peso uguale, ribilanciato una sola volta all'inizio.
Due scelte, e vanno dette perché sono le stesse della figura stampata nel
libro — altrimenti questo quaderno risponderebbe a una domanda diversa da
quella del capitolo, che è esattamente ciò che non deve succedere.
**Un asset delistato non sparisce: resta all'ultimo valore noto.** È quanto
vale davvero per chi ce l'ha in portafoglio, e non è zero: FTT il 15 novembre
2022 valeva ancora 1,43 dollari, semplicemente non si poteva più vendere.
**Le serie si leggono con `carica_strumento`**, che si ferma dove
l'identificativo cambia strumento. Con la serie grezza LUNA risaliva al 6,8%
del valore iniziale e il paniere dei morti chiudeva a 30,3 invece che a 29,0:
questo quaderno avrebbe sottostimato del 7% proprio l'errore che esiste per
misurare.
"""
calendario = [
d for d in carica("btcusdt").sort("data")["data"].to_list()
if INIZIO <= d <= FINE
]
quote = []
for n in nomi:
s = carica_strumento(n).sort("data").filter(
pl.col("data").is_between(INIZIO, FINE)
)
mappa = dict(zip(s["data"].to_list(), s["chiusura"].to_numpy()))
base = mappa[min(mappa)]
ultimo, riempita = base, []
for d in calendario:
ultimo = mappa.get(d, ultimo)
riempita.append(ultimo / base)
quote.append(riempita)
return calendario, np.mean(np.array(quote), axis=0)
date_v, solo_vivi = curva_paniere(VIVI)
date_t, tutti = curva_paniere(VIVI + MORTI)
with avvio.figura("schermo"):
fig, ax = plt.subplots()
ax.plot(date_v, solo_vivi * 100, linewidth=1.8, label="solo i sopravvissuti")
ax.plot(date_t, tutti * 100, linewidth=1.8, linestyle="--",
label="tutti, morti compresi")
ax.axhline(100, linestyle=":", linewidth=0.9)
ax.set_ylabel("Capitale (base 100)")
ax.legend()
fig.autofmt_xdate()
plt.show()
print(f"paniere dei soli sopravvissuti: {solo_vivi[-1] * 100:.1f}")
print(f"paniere reale, morti compresi: {tutti[-1] * 100:.1f}")
print(f"differenza: {tutti[-1] / solo_vivi[-1] - 1:+.1%}")Nessuno dei due numeri è sbagliato: sono risposte a due domande diverse. La prima è «come sono andati questi tre asset». La seconda è «come sarebbe andata a me». Quasi tutti fanno la prima e credono di aver risposto alla seconda.
Esercizi
- Togli
"solusdt"daVIVIe riesegui: la distanza fra le due curve cambia parecchio. Quanto la conclusione dipende da quali nomi hai incluso? - Sposta
INIZIOa gennaio 2022. Il paniere reale peggiora molto di più di quello dei sopravvissuti: la distorsione cresce quando il periodo contiene più morti. - L'esercizio che vale il capitolo: prova a ricostruire questa stessa figura usando una fonte gratuita qualunque di dati storici. Scoprirai che i due asset morti non ci sono, e otterrai la curva tranquillizzante senza nemmeno accorgerti di aver fatto un errore.
Riproducibilità e download
Eseguito il 2026-08-27 dal quaderno del repository
Il quaderno
lab_03_cimitero_token.ipynb15,5 KB
sha256 9f2859d2011e405f7bb96bc91d15f5420d55d4bbe861ccd27718c6ae411b1156
lab_03_cimitero_token.py12,0 KB
sha256 95fe5ef3c088d79b5d1380b6ed67e3aeabb43f52c91c286c41df17d5fc4b816c
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
lunausdt.parquet27,6 KB
sha256 10fe10357f76eb408550f4809ce2a87cb1129164f6f6d074ae7eac730ccb7f15
Fonte: Binance Data Vision · Periodo: 2020-08-21 → 2022-12-31 · 846 righe · estratto 2026-08-16
fttusdt.parquet27,5 KB
sha256 7b235709ebb5ae31df0a9c315bd134b95e5249196a6c84b54763d9e63795fc06
Fonte: Binance Data Vision · Periodo: 2019-12-20 → 2022-11-15 · 1062 righe · estratto 2026-08-16