Skip to content
SIAT PAPER 2026Open the research page
Cryptoverso

Calculator from the book · C01

C01 — How much you need to gain to recover a loss

Calculator 1 — How much it takes to recover from a loss

Code language

The code, its comments and its outputs are in Italian: they are the book’s code, kept identical to what the reader runs.

Notebook for the chapter "The arithmetic nobody shows you" of The math of those who lose. Loss and recovery are not symmetric, and the gap grows fast. This calculator puts your own numbers inside that asymmetry: how much it takes to break even, what volatility costs compounded capital, and what happens once you add leverage.

The lines marked TRY are the ones to change: edit them and rerun to see the effect. Everything else — including lines marked DO NOT CHANGE — exists to keep the result comparable with the one printed in the book.

Show the script for this step
calc_01_recupero_perdite.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_massimo, equity, recupero_necessario, rendimenti

1. Your number

Change PERDITA (loss) and rerun. It's the only cell you need to touch.

Output

perdita subita:         50.0%
ti resta:               50.0% del capitale
serve un guadagno del: 100.0% per tornare al punto di partenza
Show the script for this step
calc_01_recupero_perdite.py
python
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. The full curve

The shape is what matters: up to 30% it's a climb, past 70% it's a wall. No threshold is declared anywhere — it's arithmetic.

A curve that steepens as it climbs: the loss taken runs along the horizontal axis, from 1 to 95%, and the gain needed to get back to the starting capital along the vertical one, graduated up to 1,000%. Four marked points read it off: a 20% loss asks for a 25% gain, a 50% loss for 100%, an 80% loss for 400%, a 90% loss for 900%.

How much you need to gain to break even, as the loss taken grows.Source: Binance Data Vision · Period: 2017-08-17 … 2026-06-30 · Method: For every loss between 1 and 95%, over 200 points, the gain that brings capital back to its starting value.
Show the script for this step
calc_01_recupero_perdite.py
python
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. The volatility drag, on your own parameters

Two strategies with the same average return don't leave you with the same money: the more volatile one leaves less. The drag is worth about half the variance, and the calculation below verifies it instead of just asserting it.

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.84x
Show the script for this step
calc_01_recupero_perdite.py
python
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")

The arithmetic-mean column stays put; the compounded one drops. It's the same average return producing different outcomes, and the difference is only volatility.

4. Leverage, which multiplies the drag by its square

With leverage k the expected return is multiplied by k, but the drag by . That's why there's a point past which more leverage makes everything worse — and on an already-volatile asset that point arrives very soon.

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-14
Show the script for this step
calc_01_recupero_perdite.py
python
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}")

Exercises

  1. Set PERDITA = 0.83, the actual maximum drawdown this asset went through in the period. The resulting number is why the risk chapter talks about survival, not return.
  2. In the leverage cell, try k = 4 and k = 6. Find the point where final capital stops growing. No forecast changed — only the size.
  3. In the drag cell, keep MEDIA_GIORNALIERA fixed and ask yourself which volatility wipes out the compounded return. The answer is the square root of twice the mean — try it.

Reproducibility & downloads

Run on 2026-08-27 from the repository notebook

The notebook

The data

  • btcusdt.parquet93.2 KB

    sha256 ea75ad84e6e981507054df5c622c6b0ec3c8849c1f4dd007721878d4e4c8a329

    Source: Binance Data Vision · Period: 2017-08-17 → 2026-06-30 · 3,240 rows · extracted 2026-08-16

Back to the lab index