Calcolatore del libro · C01
C01 — Quanto serve guadagnare per recuperare una perdita
Calcolatore 1 — Quanto serve per recuperare una perdita
Quaderno del capitolo «L'aritmetica che nessuno ti mostra» di La matematica di chi perde.
La perdita e il recupero non sono simmetrici, e la differenza cresce in fretta. Questo calcolatore mette i tuoi numeri dentro quell'asimmetria: quanto serve per tornare in pari, quanto costa la volatilità al capitale composto, e cosa succede aggiungendo la leva.
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 matplotlib.pyplot as plt
import numpy as np
from cvbook.dati import carica
from cvbook.lingua import t
from cvbook.metriche import drawdown_massimo, equity, recupero_necessario, rendimenti1. Il tuo numero
Cambia PERDITA e riesegui. È l'unica cella che devi toccare.
Output
perdita subita: 50.0% ti resta: 50.0% del capitale serve un guadagno del: 100.0% per tornare al punto di partenza
Espandi lo script di questo passo
PERDITA = 0.50 # ← la perdita subita, in frazione: 0,50 vuol dire meno 50%
# PROVA / TRY: qualunque valore fra 0,01 e 0,99 · guarda cosa
# succede vicino a 0,90 (dove la curva diventa quasi verticale)
recupero = recupero_necessario(PERDITA)
print(t("perdita subita: ", "loss taken: ") + f"{PERDITA:6.1%}")
print(t("ti resta: ", "you have left: ") + f"{1 - PERDITA:6.1%}"
+ t(" del capitale", " of capital"))
print(t("serve un guadagno del: ", "you need a gain of: ") + f"{recupero:6.1%}"
+ t(" per tornare al punto di partenza", " to get back to break even"))2. La curva intera
La forma è la cosa da guardare: fino al 30% è una salita, dal 70% in poi è un muro. Non c'è nessuna soglia dichiarata da qualche parte — è aritmetica.
Una curva che sale sempre piu' ripida: in orizzontale la perdita subita, dall'1 al 95%, in verticale il guadagno che serve per tornare al capitale di partenza, con l'asse graduato fino a 1.000%. Quattro punti segnati la leggono: a una perdita del 20% serve un guadagno del 25%, al 50% ne serve uno del 100%, all'80% uno del 400%, al 90% uno del 900%.
Espandi lo script di questo passo
perdite = np.linspace(0.01, 0.95, 200)
recuperi = np.array([recupero_necessario(p) for p in perdite])
with avvio.figura("schermo"):
fig, ax = plt.subplots()
ax.plot(perdite * 100, recuperi * 100, linewidth=2)
for p in (0.2, 0.5, 0.8, 0.9):
r = recupero_necessario(p)
ax.plot([p * 100], [r * 100], marker="o")
ax.annotate(
f"-{p:.0%} → +{r:.0%}",
xy=(p * 100, r * 100),
xytext=(-6, 8),
textcoords="offset points",
ha="right",
)
ax.set_xlabel(t("Perdita subita (%)", "Loss taken (%)"))
ax.set_ylabel(t("Guadagno necessario per tornare in pari (%)", "Gain needed to break even (%)"))
ax.set_ylim(0, 1000)
plt.show()3. Il freno della volatilità, sui tuoi parametri
Due strategie con lo stesso rendimento medio non lasciano gli stessi soldi: la più volatile ne lascia meno. Il freno vale circa metà della varianza, e il calcolo qui sotto lo verifica invece di affermarlo.
Output
volatilita/giorno media aritm. composto capitale finale
0.500% 0.0793% 0.0781% 2.18x
1.000% 0.1433% 0.1380% 3.97x
2.000% 0.1931% 0.1725% 5.60x
3.500% -0.0079% -0.0708% 0.49x
5.000% 0.1105% -0.0175% 0.84xEspandi lo script di questo passo
MEDIA_GIORNALIERA = 0.0010 # ← rendimento medio per giorno
# PROVA / TRY: 0,0005 · 0,0010 · 0,0020 — il freno
# dipende dalla volatilità, non da questo valore
GIORNI = 1000 # PROVA / TRY: 250 (un anno) · 1000 · 3000
rng = np.random.default_rng(20260816)
# NON TOCCARE / DO NOT CHANGE: il seme è fisso perché la tabella qui sotto è
# commentata nel testo con questi numeri esatti; cambiarlo dopo aver visto il
# risultato è il p-hacking che il libro smonta altrove.
# The seed is fixed because the table below is discussed in the text with
# these exact numbers; changing it after seeing the result is the p-hacking
# the book takes apart elsewhere.
print(f"{t('volatilita/giorno', 'volatility/day'):>18s} "
f"{t('media aritm.', 'arith. mean'):>14s} "
f"{t('composto', 'compounded'):>12s} "
f"{t('capitale finale', 'final capital'):>16s}")
for vol in (0.005, 0.01, 0.02, 0.035, 0.05):
r = rng.normal(MEDIA_GIORNALIERA, vol, GIORNI)
curva = equity(r)
composto = curva[-1] ** (1 / GIORNI) - 1
print(f"{vol:18.3%} {r.mean():14.4%} {composto:12.4%} {curva[-1]:16.2f}x")La colonna della media aritmetica resta ferma; quella del composto scende. È lo stesso rendimento medio che produce risultati diversi, e la differenza è solo la volatilità.
4. La leva, che moltiplica il freno per il suo quadrato
Con leva k il rendimento atteso si moltiplica per k, ma il freno per k². Ecco perché esiste un punto oltre il quale aumentare la leva peggiora tutto — e su un asset già volatile quel punto arriva prestissimo.
Output
leva capitale finale calo massimo azzerato il
1 13.6811x -83.2% —
2 2.1427x -99.1% —
3 0.0000x -100.0% 2020-03-12
5 0.0000x -100.0% 2020-03-12
10 0.0000x -100.0% 2017-09-14Espandi lo script di questo passo
df = carica("btcusdt").sort("data")
# PROVA / TRY: per usare un'altra serie aggiungila anche alla cella di setup
# (avvio.prepara([...])) — le 11 disponibili sono in codice/dati/registro.json:
# btcusdt · ethusdt · solusdt · lunausdt · fttusdt · ftsemib · eni · enel ·
# intesa · generali · eurusd
r_reali = rendimenti(df["chiusura"].to_numpy())
def curva_con_leva(rend: np.ndarray, k: float) -> np.ndarray:
"""Capitale con leva `k`, con l'azzeramento che e' definitivo.
Un giorno in cui la posizione perde piu' del capitale non produce un numero
negativo: produce la fine. Senza questo taglio il calcolo restituirebbe cali
superiori al 100%, che non significano niente.
Capital with `k`× leverage, where wipeout is permanent: a day in which the
position loses more than the capital does not produce a negative number —
it produces the end. Without this floor the calculation would return
drawdowns above 100%, which mean nothing.
"""
passi = np.maximum(1.0 + k * rend, 0.0)
return np.concatenate([[1.0], np.cumprod(passi)])
print(f"{t('leva', 'leverage'):>5s} {t('capitale finale', 'final capital'):>16s} "
f"{t('calo massimo', 'max drawdown'):>14s} {t('azzerato il', 'wiped out on'):>14s}")
for k in (1, 2, 3, 5, 10): # PROVA / TRY: aggiungi 4 o 6, come suggerito negli esercizi
curva = curva_con_leva(r_reali, k)
azzerata = np.argmax(curva <= 0.0) if np.any(curva <= 0.0) else None
quando = str(df["data"][int(azzerata)]) if azzerata is not None else "—"
print(f"{k:5d} {curva[-1]:16.4f}x {drawdown_massimo(curva):14.1%} {quando:>14s}")Esercizi
- Metti
PERDITA = 0.83, che è il calo massimo realmente attraversato da questo asset nel periodo. Il numero che esce è il motivo per cui il capitolo sul rischio parla di sopravvivenza e non di rendimento. - Nella cella della leva, prova
k = 4ek = 6. Trova il punto in cui il capitale finale smette di crescere. Nessuna previsione è cambiata: solo la dimensione. - Nella cella del freno, tieni fissa
MEDIA_GIORNALIERAe chiediti quale volatilità azzera il composto. La risposta è la radice quadrata del doppio della media — provala.
Riproducibilità e download
Eseguito il 2026-08-27 dal quaderno del repository
Il quaderno
calc_01_recupero_perdite.ipynb12,6 KB
sha256 e659bf07777822fa838a7382876c3f9e0bbc16030554db9847c4a3fcdaf41166
calc_01_recupero_perdite.py9,6 KB
sha256 8473c432aa6dc5f4e53d156045b7f1bc0c8a4d93c67b10c57e8fce96b119cc99
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