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

Calcolatore del libro · C03

C03 — Tempo sotto il massimo e dimensione compatibile con la tua tolleranza

Calcolatore 3 — Il rischio, misurato in tempo

Quaderno del capitolo «Il rischio non è un numero» di La matematica di chi perde.

Il rischio sono quattro domande diverse, e la volatilità risponde solo alla prima. Qui le calcoli tutte e quattro, e poi fai il conto che consiglio prima di aprire qualunque posizione: quale dimensione sarebbe stata compatibile con la tua tolleranza, nel periodo storico peggiore.

Di solito è molto più piccola di quella che si aveva in mente.

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

from cvbook.dati import carica
from cvbook.lingua import t
from cvbook.metriche import drawdown, drawdown_massimo, equity, rendimenti, sharpe, volatilita

SERIE = "btcusdt"  # ← PROVA / TRY: "ethusdt" · "solusdt" (le tre preparate nel setup)
                   # per un'altra delle 11 serie in codice/dati/registro.json
                   # aggiungila anche a avvio.prepara([...]) qui sopra

df = carica(SERIE).sort("data")
prezzi = df["chiusura"].to_numpy()
date = df["data"].to_list()
r = rendimenti(prezzi)
curva = equity(r)
dd = drawdown(curva)

1. Le quattro domande

Quattro numeri diversi, tutti chiamati «rischio». Il primo è quello che compare ovunque; il quarto è quello che decide se molli.

Output

btcusdt  ·  3240 giorni  ·  2017-08-17 → 2026-06-30

1. Quanto oscilla?           volatilita' annualizzata     67.5%
2. Quanto perdo in un colpo? giorno peggiore             -39.5%
3. Quanto scendo in totale?  calo massimo dal picco      -83.2%
4. Per quanto resto sotto?   vedi la tabella qui sotto

   indicatore rendimento/rischio piu' usato al mondo: 0.78
Espandi lo script di questo passo
calc_03_stop_sizing.py
python
print(f"{SERIE}  ·  {len(prezzi)}" + t(" giorni  ·  ", " days  ·  ") + f"{date[0]}{date[-1]}\n")
print(t("1. Quanto oscilla?           volatilita' annualizzata  ",
        "1. How much does it swing?  annualized volatility     ") + f"{volatilita(r):>8.1%}")
print(t("2. Quanto perdo in un colpo? giorno peggiore           ",
        "2. How much in one hit?     worst single day           ") + f"{r.min():>8.1%}")
print(t("3. Quanto scendo in totale?  calo massimo dal picco    ",
        "3. How far down in total?   max drawdown from peak     ") + f"{drawdown_massimo(curva):>8.1%}")
print(t("4. Per quanto resto sotto?   vedi la tabella qui sotto",
        "4. For how long am I down?  see the table below"))
print(t("\n   indicatore rendimento/rischio piu' usato al mondo: ",
        "\n   most widely used return/risk indicator in the world: ") + f"{sharpe(r):.2f}")

2. Il rischio misurato in tempo

La domanda che nessuno fa, ed è quella che determina se una persona reale esegue il piano fino in fondo.

Due pannelli sovrapposti su 3.240 giorni di btcusdt, dal 17 agosto 2017 al 30 giugno 2026. Sopra, l'area della distanza dal massimo precedente: scende oltre 80 sotto lo zero nel 2018 e nel 2022, e tocca lo zero poche volte. Sotto, la quota di tempo passata ad almeno una certa distanza dal massimo, dal 100% a sinistra fino a sfiorare lo zero a destra: 81% dei giorni ad almeno 10 di distanza, 71% ad almeno 20, 40% ad almeno 50, 10% ad almeno 70.

Quanto tempo si passa lontano dal proprio massimo, e non quanto si scende.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Distanza percentuale di ogni chiusura giornaliera dal massimo raggiunto fino a quel giorno, e quota di giorni che stanno ad almeno una certa distanza.

Output

almeno 10% sotto il massimo:  81.3% dei giorni  (~7.2 anni su 8.9)
almeno 20% sotto il massimo:  71.5% dei giorni  (~6.3 anni su 8.9)
almeno 50% sotto il massimo:  40.1% dei giorni  (~3.6 anni su 8.9)
almeno 70% sotto il massimo:   9.6% dei giorni  (~0.9 anni su 8.9)
almeno 80% sotto il massimo:   2.2% dei giorni  (~0.2 anni su 8.9)

giorni al proprio massimo storico: 111 su 3240 (3.4% del tempo)
Espandi lo script di questo passo
calc_03_stop_sizing.py
python
with avvio.figura("schermo"):
    fig, (a, b) = plt.subplots(2, 1, figsize=(10, 6), height_ratios=[2, 1.4])
    a.fill_between(date, dd * 100, 0, step="mid")
    a.set_ylabel(t("Distanza dal massimo precedente (%)", "Distance from previous peak (%)"))

    soglie = np.arange(0, 0.91, 0.05)
    quote = [float((dd <= -s).mean()) * 100 for s in soglie]
    b.plot(soglie * 100, quote, marker="o")
    b.set_xlabel(t("Almeno questa distanza dal massimo (%)", "At least this far from the peak (%)"))
    b.set_ylabel(t("Quota del tempo (%)", "Share of time (%)"))
    plt.show()

for s in (0.10, 0.20, 0.50, 0.70, 0.80):
    quota = float((dd <= -s).mean())
    print(t(f"almeno {s:.0%} sotto il massimo: {quota:6.1%} dei giorni  "
            f"(~{quota * len(dd) / 365:.1f} anni su {len(dd) / 365:.1f})",
            f"at least {s:.0%} below the peak: {quota:6.1%} of the days  "
            f"(~{quota * len(dd) / 365:.1f} years out of {len(dd) / 365:.1f})"))

sotto_zero = int(np.sum(dd < -0.001))
print(t(f"\ngiorni al proprio massimo storico: {len(dd) - sotto_zero} su {len(dd)} "
        f"({1 - sotto_zero / len(dd):.1%} del tempo)",
        f"\ndays at their all-time high: {len(dd) - sotto_zero} out of {len(dd)} "
        f"({1 - sotto_zero / len(dd):.1%} of the time)"))

3. Quanto ci si mette a tornare a galla

Non solo quanto si scende: quanto dura. È la statistica che manca in ogni scheda prodotto.

Output

episodi sotto il massimo: 50
durata mediana:       4 giorni
durata media:        63 giorni
il piu' lungo:     1073 giorni  (2.9 anni)
Espandi lo script di questo passo
calc_03_stop_sizing.py
python
picchi = np.maximum.accumulate(curva)
in_calo = curva < picchi - 1e-12

durate, corrente = [], 0
for x in in_calo:
    if x:
        corrente += 1
    elif corrente:
        durate.append(corrente)
        corrente = 0
if corrente:
    durate.append(corrente)  # ancora in corso alla fine della serie / still ongoing at the end of the series

durate = np.array(durate)
print(t(f"episodi sotto il massimo: {len(durate)}", f"episodes below the peak: {len(durate)}"))
print(t(f"durata mediana:  {np.median(durate):6.0f} giorni", f"median duration: {np.median(durate):6.0f} days"))
print(t(f"durata media:    {durate.mean():6.0f} giorni", f"mean duration:   {durate.mean():6.0f} days"))
print(t(f"il piu' lungo:   {durate.max():6.0f} giorni  ({durate.max() / 365:.1f} anni)",
        f"the longest:     {durate.max():6.0f} days  ({durate.max() / 365:.1f} years)"))

4. Il conto da fare prima di aprire una posizione

Metti il tuo capitale e la perdita che davvero non vuoi superare. Il calcolo risponde: quanto potevi metterci, se il periodo peggiore già accaduto si ripetesse identico.

Output

calo massimo gia' accaduto su btcusdt: 83.2%
perdita accettabile: 3,000 su 20,000 euro (15.0% del capitale)

posizione compatibile: 18.0% del capitale, cioe' 3,606 euro

E ricordati che il peggio gia' visto NON e' il peggio possibile: e' il peggio di una sola realizzazione. Con un margine del 20% la posizione scende a 3,005 euro.
Espandi lo script di questo passo
calc_03_stop_sizing.py
python
CAPITALE = 20_000.0        # ← il tuo capitale totale, in euro
                           # PROVA / TRY: il tuo capitale reale
PERDITA_ACCETTABILE = 3_000.0  # ← quanto sei disposto a vedere sparire, in euro
                                # PROVA / TRY: la tua soglia VERA, non quella
                                # che diresti a un amico (vedi esercizio 2)

peggiore = abs(drawdown_massimo(curva))
quota_massima = PERDITA_ACCETTABILE / (CAPITALE * peggiore)

print(t(f"calo massimo gia' accaduto su {SERIE}: {peggiore:.1%}",
        f"max drawdown already seen on {SERIE}: {peggiore:.1%}"))
print(t(f"perdita accettabile: {PERDITA_ACCETTABILE:,.0f} su {CAPITALE:,.0f} euro "
        f"({PERDITA_ACCETTABILE / CAPITALE:.1%} del capitale)\n",
        f"acceptable loss: {PERDITA_ACCETTABILE:,.0f} out of {CAPITALE:,.0f} euros "
        f"({PERDITA_ACCETTABILE / CAPITALE:.1%} of capital)\n"))
print(t(f"posizione compatibile: {min(quota_massima, 1.0):.1%} del capitale, "
        f"cioe' {min(quota_massima, 1.0) * CAPITALE:,.0f} euro",
        f"compatible position: {min(quota_massima, 1.0):.1%} of capital, "
        f"i.e. {min(quota_massima, 1.0) * CAPITALE:,.0f} euros"))
print(t(f"\nE ricordati che il peggio gia' visto NON e' il peggio possibile: e' il "
        f"peggio di una sola realizzazione. Con un margine del 20% la posizione "
        f"scende a {min(quota_massima / 1.2, 1.0) * CAPITALE:,.0f} euro.",
        f"\nAnd remember that the worst seen so far is NOT the worst possible: it's "
        f"the worst of a single realization. With a 20% margin the position "
        f"drops to {min(quota_massima / 1.2, 1.0) * CAPITALE:,.0f} euros."))

Esercizi

  1. Cambia SERIE. Il calo massimo cambia, e con esso la posizione compatibile: la stessa tolleranza produce dimensioni molto diverse su asset diversi. È così che si confrontano gli asset, non guardando quanto sono saliti.
  2. Nella quarta cella metti la tua perdita accettabile vera — quella dopo la quale cambieresti comportamento, non quella che dichiareresti a un amico.
  3. Guarda l'episodio più lungo della terza cella e chiediti la domanda del capitolo: quanto tempo posso restare sotto senza cambiare comportamento? Se la risposta è più corta di quel numero, il problema non è l'asset: è l'accoppiamento fra lui e te.

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

  • 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