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

Calcolatore del libro · C05

C05 — Probabilità di un evento di custodia e costo della concentrazione

Calcolatore 5 — Il rischio di stare in un posto solo

Quaderno del capitolo «Dove stanno davvero i tuoi soldi» di La matematica di chi perde.

Tre numeri: la probabilità di subire almeno un evento di custodia nel tuo orizzonte, quanto servirebbe guadagnare per tornare in pari, e come cambia la distribuzione del capitale finale al variare della quota che tieni nel posto più pieno.

La probabilità annua che metti qui dentro è un'ipotesi tua, non una misura. Non esiste una statistica affidabile dei fallimenti di piattaforma, per la stessa ragione per cui non esiste un elenco completo dei token morti: chi sparisce smette anche di comparire nei conteggi. Questo quaderno serve a capire la forma del problema, non a stimarne il livello.

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

from cvbook import seed_for
from cvbook.dati import carica
from cvbook.lingua import t
from cvbook.metriche import recupero_necessario, rendimenti
from cvbook.simulazioni import bootstrap_traiettorie

1. I tuoi tre numeri

Output

ipotesi di rischio annuo: 2.0%
orizzonte:                10 anni
quota nel posto piu' pieno: 60%

probabilita' di almeno un evento in 10 anni: 18.3%
se accade, ti resta:                     40% del capitale
per tornare in pari devi guadagnare:     +150%
Espandi lo script di questo passo
calc_05_custodia.py
python
RISCHIO_ANNUO = 0.02   # ← la TUA ipotesi: probabilita' che la sede sparisca in un anno
                       # PROVA / TRY: 0,005 · 0,01 · 0,02 · 0,05 — è un'ipotesi
                       # tua, non una misura (vedi la nota qui sopra)
ORIZZONTE = 10         # ← per quanti anni ci tieni i soldi
                       # PROVA / TRY: 5 · 10 · 20 (anni)
QUOTA = 0.60           # ← quanta parte del capitale sta nel posto piu' pieno
                       # PROVA / TRY: 0,20 · 0,60 · 1,00

almeno_uno = 1 - (1 - RISCHIO_ANNUO) ** ORIZZONTE

print(t(f"ipotesi di rischio annuo: {RISCHIO_ANNUO:.1%}", f"assumed annual risk: {RISCHIO_ANNUO:.1%}"))
print(t(f"orizzonte:                {ORIZZONTE} anni", f"horizon:                  {ORIZZONTE} years"))
print(t(f"quota nel posto piu' pieno: {QUOTA:.0%}\n", f"share at the fullest venue: {QUOTA:.0%}\n"))
print(t(f"probabilita' di almeno un evento in {ORIZZONTE} anni: {almeno_uno:.1%}",
        f"probability of at least one event in {ORIZZONTE} years: {almeno_uno:.1%}"))
print(t(f"se accade, ti resta:                     {1 - QUOTA:.0%} del capitale",
        f"if it happens, you're left with:         {1 - QUOTA:.0%} of capital"))
print(t(f"per tornare in pari devi guadagnare:     +{recupero_necessario(QUOTA):.0%}",
        f"to break even you need to gain:          +{recupero_necessario(QUOTA):.0%}"))

2. Una piccola probabilità, ripetuta, non resta piccola

Due pannelli affiancati. A sinistra quattro curve crescenti che danno la probabilita' di almeno un evento entro 25 anni di permanenza, una per rischio annuo: con 0,5% l'anno si arriva a circa 12%, con 5% l'anno si supera 70%, e una riga punteggiata segna l'orizzonte scelto nel quaderno. A destra il guadagno necessario per tornare in pari al crescere della quota di capitale tenuta in una sede sola, con tre punti segnati: 25% chiede 33%, 50% chiede 100%, 75% chiede 300%.

Una probabilita' piccola ripetuta per anni, e il costo di tenere tutto in un posto solo.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Probabilita' complementare di nessun evento in N anni per quattro rischi annui, e guadagno che riporta in pari una perdita pari alla quota concentrata.

Output

 rischio annuo          5 anni        10 anni        20 anni
          0.5%           2.5%          4.9%          9.5%
          1.0%           4.9%          9.6%         18.2%
          2.0%           9.6%         18.3%         33.2%
          5.0%          22.6%         40.1%         64.2%
Espandi lo script di questo passo
calc_05_custodia.py
python
anni = np.arange(0, 26)

with avvio.figura("schermo"):
    fig, (sx, dx) = plt.subplots(1, 2, figsize=(12, 4.5))
    for p in (0.005, 0.01, 0.02, 0.05):
        sx.plot(anni, (1 - (1 - p) ** anni) * 100, linewidth=2,
                label=t(f"{p:.1%} l'anno", f"{p:.1%} a year"))
    sx.axvline(ORIZZONTE, linestyle=":", linewidth=1.2)
    sx.set_xlabel(t("Anni di permanenza", "Years held"))
    sx.set_ylabel(t("Probabilita' di almeno un evento (%)", "Probability of at least one event (%)"))
    sx.legend()

    quote = np.linspace(0.02, 0.95, 300)
    dx.plot(quote * 100, [recupero_necessario(q) * 100 for q in quote], linewidth=2)
    for q in (0.25, 0.50, 0.75):
        dx.plot([q * 100], [recupero_necessario(q) * 100], marker="o")
        dx.annotate(f"{q:.0%} → +{recupero_necessario(q):.0%}",
                    xy=(q * 100, recupero_necessario(q) * 100),
                    xytext=(-6, 8), textcoords="offset points", ha="right")
    dx.set_ylim(0, 500)
    dx.set_xlabel(t("Quota del capitale nella sede (%)", "Share of capital at the venue (%)"))
    dx.set_ylabel(t("Guadagno necessario per tornare in pari (%)", "Gain needed to break even (%)"))
    plt.show()

print(f"{t('rischio annuo', 'annual risk'):>14s} "
      + "".join(f"{a:>10d}" + t(" anni", " years") for a in (5, 10, 20)))
for p in (0.005, 0.01, 0.02, 0.05):
    print(f"{p:14.1%} " + "".join(f"{1 - (1 - p) ** a:14.1%}" for a in (5, 10, 20)))

3. Nove anni di mercato vero, con e senza il rischio di sede

I rendimenti sono quelli realmente accaduti, ricampionati a blocchi. L'unica cosa aggiunta è l'evento raro. Le tre curve differiscono solo per quanta parte del capitale sta in una sede sola.

Qui l'orizzonte non lo scegli tu: lo dettano i dati. bootstrap_traiettorie restituisce percorsi lunghi quanto i rendimenti da cui pesca, e di Bitcoin ce ne sono 3.239 — nove anni scarsi. Sorteggiare l'evento di custodia su dieci anni, come faceva questa cella, significava caricare un anno di rischio in più su percorsi che non c'erano: la probabilità saliva dal 16,6% al 18,3% e i percorsi sotto il capitale dal 24,8% al 27,7%, cioè il quaderno rispondeva a una domanda diversa da quella della figura del capitolo.

Tre curve cumulate sul capitale dopo dieci anni, in scala logaritmica da 10 alla meno 4 a 10 alla 4 volte quello iniziale, contro la quota di percorsi con esito peggiore o uguale, da 0 a 100%. La curva con tutto il capitale in una sede sola ha una coda che arriva fino a 10 alla meno 4; quelle con meta' e con un quinto in una sede restano progressivamente piu' a destra nella parte bassa del grafico.

Dieci anni di mercato vero, ricampionati, con e senza il rischio di tenere tutto in una sede.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Cinquemila percorsi ricampionati a blocchi dalle variazioni giornaliere realmente accadute, con l'evento raro applicato alla sola quota concentrata; le curve sono le distribuzioni cumulate degli esiti.

Output

orizzonte dei percorsi, dettato dai dati: 9 anni (3239 rendimenti)
probabilita' di almeno un evento nei 9 anni: 16.2%

 quota in una sede    mediana   5% peggiore   sotto il capitale
              100%      8.99x         0.00x               24.8%
               50%     13.09x         0.42x               11.0%
               20%     14.32x         0.48x               10.4%

La mediana si sposta poco: nel caso tipico non succede niente, ed e' per questo che il problema non si vede. Cambia la coda sinistra, cioe' esattamente la parte che decide se sei ancora nel gioco.
Espandi lo script di questo passo
calc_05_custodia.py
python
PERCORSI = 5000  # PROVA / TRY: 500 (veloce) · 5000 · 20000 (code più nette)
QUOTE = [1.00, 0.50, 0.20]  # PROVA / TRY: le tue quote di concentrazione

r = rendimenti(carica("btcusdt").sort("data")["chiusura"].to_numpy())
# PROVA / TRY: per un'altra serie aggiungila a avvio.prepara([...]) qui sopra
# — le 11 disponibili sono in codice/dati/registro.json
rng = np.random.default_rng(seed_for("custodia"))
# NON TOCCARE / DO NOT CHANGE: è lo stesso seme della figura del capitolo. Con
# questo, gli stessi 5.000 percorsi e gli stessi anni, la tabella qui sotto è
# quella stampata nel libro — che è la promessa del libro, non un dettaglio.
# Cambiarlo dopo aver visto il risultato è il p-hacking che il libro smonta.
# This is the same seed as the chapter's figure. With it — same 5,000 paths,
# same number of years — the table below is the one printed in the book.
anni_percorso = max(1, round(len(r) / 365))
mercato = bootstrap_traiettorie(r, n_traiettorie=PERCORSI, rng=rng,
                                a_blocchi=20)[:, -1]
colpito = (rng.random((PERCORSI, anni_percorso)) < RISCHIO_ANNUO).any(axis=1)

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    for quota in QUOTE:
        finali = np.maximum(np.where(colpito, mercato * (1 - quota), mercato), 1e-4)
        ordinati = np.sort(finali)
        probabilita = np.arange(1, len(ordinati) + 1) / len(ordinati) * 100
        ax.plot(ordinati, probabilita, linewidth=2,
                label=t(f"{quota:.0%} in una sede", f"{quota:.0%} at one venue"))
    ax.axvline(1.0, linewidth=1.2, linestyle=":")
    ax.set_xscale("log")
    ax.set_xlabel(t(f"Capitale dopo {anni_percorso} anni (volte quello iniziale, scala log)",
                     f"Capital after {anni_percorso} years (× starting, log scale)"))
    ax.set_ylabel(t("Percorsi con esito peggiore o uguale (%)", "Paths with equal or worse outcome (%)"))
    ax.legend()
    plt.show()

print(t(f"orizzonte dei percorsi, dettato dai dati: {anni_percorso} anni "
        f"({len(r)} rendimenti)",
        f"path horizon, set by the data: {anni_percorso} years ({len(r)} returns)"))
print(t(f"probabilita' di almeno un evento nei {anni_percorso} anni: {colpito.mean():.1%}\n",
        f"probability of at least one event in {anni_percorso} years: {colpito.mean():.1%}\n"))
print(f"{t('quota in una sede', 'share at one venue'):>18s} {t('mediana', 'median'):>10s} "
      f"{t('5% peggiore', 'worst 5%'):>13s} {t('sotto il capitale', 'below capital'):>19s}")
for quota in QUOTE:
    finali = np.where(colpito, mercato * (1 - quota), mercato)
    print(f"{quota:18.0%} {np.median(finali):9.2f}x {np.percentile(finali, 5):12.2f}x "
          f"{float((finali < 1).mean()):19.1%}")

print(t("\nLa mediana si sposta poco: nel caso tipico non succede niente, ed e' per "
        "questo che il problema non si vede. Cambia la coda sinistra, cioe' "
        "esattamente la parte che decide se sei ancora nel gioco.",
        "\nThe median barely moves: in the typical case nothing happens, and "
        "that's exactly why the problem is invisible. What changes is the left "
        "tail — exactly the part that decides whether you're still in the "
        "game."))

4. L'esercizio che consiglio

Fallo due volte: la prima con la tua situazione attuale, così com'è; la seconda con la quota che avresti deciso di avere se ci avessi pensato. La differenza fra i due numeri è il costo di non aver mai preso quella decisione.

Output

   cosi' com'e' oggi (quota 85%): 5% peggiore   0.32x   percorsi sotto il capitale 14.0%
       con il limite (quota 20%): 5% peggiore   0.48x   percorsi sotto il capitale 10.4%

Distribuire su piu' sedi NON riduce a zero il rischio: lo trasforma da un interruttore in una perdita parziale, e in cambio aggiunge piu' cose da gestire, piu' credenziali, piu' punti in cui sbagliare. E' un compromesso, non una soluzione.
Espandi lo script di questo passo
calc_05_custodia.py
python
QUOTA_ATTUALE = 0.85    # ← quanto hai davvero nel posto piu' pieno, oggi
                        # PROVA / TRY: la tua quota vera
QUOTA_DECISA = 0.20     # ← il limite che vorresti darti
                        # PROVA / TRY: il limite che decideresti tu


def coda(quota: float) -> tuple[float, float]:
    finali = np.where(colpito, mercato * (1 - quota), mercato)
    return float(np.percentile(finali, 5)), float((finali < 1).mean())


for nome, quota in (
    (t("cosi' com'e' oggi", "as it is today"), QUOTA_ATTUALE),
    (t("con il limite", "with the limit"), QUOTA_DECISA),
):
    peggiore, sotto = coda(quota)
    print(t(f"{nome:>20s} (quota {quota:.0%}): 5% peggiore {peggiore:6.2f}x   "
            f"percorsi sotto il capitale {sotto:.1%}",
            f"{nome:>20s} (share {quota:.0%}): worst 5% {peggiore:6.2f}x   "
            f"paths below capital {sotto:.1%}"))

print(t("\nDistribuire su piu' sedi NON riduce a zero il rischio: lo trasforma da un "
        "interruttore in una perdita parziale, e in cambio aggiunge piu' cose da "
        "gestire, piu' credenziali, piu' punti in cui sbagliare. E' un compromesso, "
        "non una soluzione.",
        "\nSpreading across several venues does NOT reduce risk to zero: it turns "
        "it from an on/off switch into a partial loss, and in exchange adds more "
        "things to manage, more credentials, more points of failure. It's a "
        "trade-off, not a solution."))

Le quattro domande, che nessun calcolo sostituisce

  1. Di chi è la chiave? Se le credenziali che muovono i fondi sono solo tue, il rischio è tuo e riducibile con procedure che dipendono da te. Se sono di qualcun altro, stai correndo il rischio di quel qualcuno.
  2. Cosa succede se quella sede chiude domani mattina? Non cosa dice che succederebbe: cosa succede materialmente.
  3. Quanto ne ho lì, in percentuale? In percentuale, non in valore assoluto: il valore assoluto cresce da solo con il mercato, ed è così che quasi tutti finiscono concentrati senza averlo deciso.
  4. Chi altro può muovere queste cose? Autorizzazioni concesse anni fa e mai revocate, dispositivi dismessi, copie delle credenziali in posti che al momento sembravano comodi.

Riproducibilità e download

Eseguito il 2026-08-27 dal quaderno del repository

Il quaderno

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

Torna all’indice dei lab