Calculator from the book · C04
C04 — Optimal fraction, risk of ruin and overall risk
Calculator 4 — How much to risk
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 "Sizing is the strategy". Three calculations on your own numbers: how much to risk per trade given your capital and the maximum loss you accept; what the theoretical optimal fraction is given the edge you believe you have; and what the overall risk of your open positions is once correlation is taken into account. Then the ruin curve — the calculation to make before increasing size, not after.
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
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 GIORNI_ANNO, rendimenti, rischio_di_rovina1. The curve that rises, peaks and crashes
A game with a real and known edge: you win a certain percentage of the time, earning as much as you risk — an edge you don't find in real markets. The question is: how much to risk, every time?
Two side-by-side panels over the fraction of capital risked per trade, from 1 to 50%. On the left the median capital after 500 trades on a logarithmic axis running from ten to the minus twenty up to ten: it rises to 12 times the starting capital around 10% and then collapses, with a dotted line on the peak. On the right the share of paths that end below a fifth of capital: near zero up to 10%, 37% when risking 20%, 83% at 30%, 99% at 40%.
Output
vantaggio: si vince il 55% delle volte, rapporto 1 frazione con la crescita mediana migliore (simulata): 10% frazione ottimale teorica: 10% capitale mediano rischiando il 10%: 12.2x rischiando il 2%: 2.46x probabilita' di rovina 0.0% rischiando il 20%: 0.93x probabilita' di rovina 37.1% rischiando il 30%: 0.00x probabilita' di rovina 82.5% rischiando il 40%: 0.00x probabilita' di rovina 98.9% Avere ragione non basta. Bisogna anche rischiare la quantita' giusta: oltre un certo punto, aumentare il rischio RIDUCE il risultato — non lo aumenta con piu' varianza, lo riduce e basta.
Show the script for this step
VINCITE = 0.55 # ← quota di operazioni vincenti
# PROVA / TRY: 0,52 (vedi esercizio 1) · 0,55 · 0,60
RAPPORTO = 1.0 # ← quanto si guadagna rispetto a quanto si rischia
# PROVA / TRY: 0,5 · 1,0 · 2,0
OPERAZIONI = 500 # PROVA / TRY: 100 (veloce) · 500 · 2000 (curva più liscia)
PERCORSI = 4000 # PROVA / TRY: 500 (veloce, mediana rumorosa) · 4000 · 20000
frazioni = np.arange(0.01, 0.51, 0.01)
rng = np.random.default_rng(seed_for("calc-dimensionamento"))
# NON TOCCARE / DO NOT CHANGE: il seme fissa i numeri citati nel testo qui
# sotto (la frazione ottimale simulata, le probabilità di rovina) — cambiarlo
# dopo aver visto il risultato è il p-hacking che il libro smonta altrove.
# The seed fixes the numbers quoted in the text below (the simulated optimal
# fraction, the ruin probabilities) — changing it after seeing the result is
# the p-hacking the book takes apart elsewhere.
esiti = rng.random((PERCORSI, OPERAZIONI)) < VINCITE
mediane, rovine = [], []
for f in frazioni:
passi = np.where(esiti, 1 + f * RAPPORTO, 1 - f)
curve = np.cumprod(passi, axis=1)
mediane.append(float(np.median(curve[:, -1])))
rovine.append(float((curve[:, -1] < 0.2).mean()))
mediane, rovine = np.array(mediane), np.array(rovine)
ottimale = float(frazioni[int(np.argmax(mediane))])
with avvio.figura("schermo"):
fig, (sx, dx) = plt.subplots(1, 2, figsize=(12, 4.5))
sx.plot(frazioni * 100, mediane, linewidth=2)
sx.axvline(ottimale * 100, linestyle=":", linewidth=1.5)
sx.set_yscale("log")
sx.set_xlabel(t("Frazione di capitale rischiata per operazione (%)",
"Fraction of capital risked per trade (%)"))
sx.set_ylabel(t(f"Capitale mediano dopo {OPERAZIONI} operazioni (scala log)",
f"Median capital after {OPERAZIONI} trades (log scale)"))
dx.plot(frazioni * 100, rovine * 100, linewidth=2)
dx.set_xlabel(t("Frazione rischiata per operazione (%)", "Fraction risked per trade (%)"))
dx.set_ylabel(t("Percorsi sotto un quinto del capitale (%)", "Paths below a fifth of capital (%)"))
plt.show()
kelly = VINCITE - (1 - VINCITE) / RAPPORTO
print(t(f"vantaggio: si vince il {VINCITE:.0%} delle volte, rapporto {RAPPORTO:g}\n",
f"edge: wins {VINCITE:.0%} of the time, ratio {RAPPORTO:g}\n"))
print(t(f"frazione con la crescita mediana migliore (simulata): {ottimale:.0%}",
f"fraction with the best median growth (simulated): {ottimale:.0%}"))
print(t(f"frazione ottimale teorica: {kelly:.0%}",
f"theoretical optimal fraction: {kelly:.0%}"))
print(t(f"\ncapitale mediano rischiando il {ottimale:.0%}: {mediane[int(np.argmax(mediane))]:,.1f}x",
f"\nmedian capital risking {ottimale:.0%}: {mediane[int(np.argmax(mediane))]:,.1f}x"))
for f in (0.02, 0.20, 0.30, 0.40):
k = int(round(f * 100)) - 1
if 0 <= k < len(frazioni):
print(t(f" rischiando il {f:>4.0%}: {mediane[k]:12,.2f}x "
f"probabilita' di rovina {rovine[k]:6.1%}",
f" risking {f:>4.0%}: {mediane[k]:12,.2f}x "
f"probability of ruin {rovine[k]:6.1%}"))
print(t("\nAvere ragione non basta. Bisogna anche rischiare la quantita' giusta: "
"oltre un certo punto, aumentare il rischio RIDUCE il risultato — non lo "
"aumenta con piu' varianza, lo riduce e basta.",
"\nBeing right isn't enough. You also have to risk the right amount: "
"past a certain point, increasing risk REDUCES the outcome — it doesn't "
"boost it with more variance, it just reduces it."))2. The curve is flat on the left and steep on the right
The reason why, when you're uncertain about your edge — and you always are — you must err on the low side.
Output
frazione capitale mediano perdita rispetto al massimo
2% 2.5x -79.9%
4% 5.0x -59.5%
6% 8.2x -33.1%
8% 11.1x -9.6%
10% 12.2x 0.0%
12% 11.1x -9.6%
14% 8.1x -33.4%
16% 4.9x -60.1%
18% 2.4x -80.6%
Stare sotto costa poco, stare sopra costa moltissimo. E' l'asimmetria che perdona il difetto e punisce l'eccesso.Show the script for this step
i_ott = int(np.argmax(mediane))
print(f"{t('frazione', 'fraction'):>10s} {t('capitale mediano', 'median capital'):>18s} "
f"{t('perdita rispetto al massimo', 'loss relative to peak'):>30s}")
for delta in (-8, -6, -4, -2, 0, 2, 4, 6, 8):
k = i_ott + delta
if 0 <= k < len(frazioni):
perdita = mediane[k] / mediane[i_ott] - 1
print(f"{frazioni[k]:10.0%} {mediane[k]:17,.1f}x {perdita:29.1%}")
print(t("\nStare sotto costa poco, stare sopra costa moltissimo. E' l'asimmetria "
"che perdona il difetto e punisce l'eccesso.",
"\nBeing under costs little, being over costs a lot. It's the asymmetry "
"that forgives shortfall and punishes excess."))3. Your risk per trade
Note the distinction almost everyone confuses: size is how much capital you commit, risk is how much you lose if it goes wrong. It's the second one that must be kept constant.
Output
capitale: 20,000 euro rischio per operazione: 200 euro (1.0%) uscita in perdita a: 8.0% dall'ingresso → dimensione della posizione: 2,500 euro (12.5% del capitale) Se l'uscita fosse a meta' distanza, la dimensione raddoppierebbe a parita' di rischio. E' esattamente il meccanismo per cui gli stop stretti spesso AUMENTANO il rischio complessivo invece di ridurlo.
Show the script for this step
CAPITALE = 20_000.0 # PROVA / TRY: il tuo capitale reale
RISCHIO_PER_OPERAZIONE = 0.01 # ← percentuale del capitale, fra 0,5% e 2%
# PROVA / TRY: 0,005 · 0,01 · 0,02
DISTANZA_USCITA = 0.08 # ← a che distanza esci in perdita
# PROVA / TRY: 0,04 (stop stretto) · 0,08 · 0,15
rischio_euro = CAPITALE * RISCHIO_PER_OPERAZIONE
dimensione = rischio_euro / DISTANZA_USCITA
print(t(f"capitale: {CAPITALE:12,.0f} euro", f"capital: {CAPITALE:12,.0f} euros"))
print(t(f"rischio per operazione: {rischio_euro:12,.0f} euro ({RISCHIO_PER_OPERAZIONE:.1%})",
f"risk per trade: {rischio_euro:12,.0f} euros ({RISCHIO_PER_OPERAZIONE:.1%})"))
print(t(f"uscita in perdita a: {DISTANZA_USCITA:12.1%} dall'ingresso",
f"stop loss at: {DISTANZA_USCITA:12.1%} from entry"))
print(t(f"→ dimensione della posizione: {dimensione:,.0f} euro "
f"({dimensione / CAPITALE:.1%} del capitale)",
f"→ position size: {dimensione:,.0f} euros "
f"({dimensione / CAPITALE:.1%} of capital)"))
print(t("\nSe l'uscita fosse a meta' distanza, la dimensione raddoppierebbe a "
"parita' di rischio. E' esattamente il meccanismo per cui gli stop stretti "
"spesso AUMENTANO il rischio complessivo invece di ridurlo.",
"\nIf the stop were at half the distance, size would double for the same "
"risk. That's exactly the mechanism by which tight stops often INCREASE "
"overall risk instead of reducing it."))4. The ruin calculation
Output
rischio per op. 10 perdite di fila serve per tornare prob. di rovina
0.5% 95.1% 5% 0.0%
1.0% 90.4% 11% 0.0%
2.0% 81.7% 22% 0.0%
5.0% 59.9% 67% 0.5%
10.0% 34.9% 187% 18.4%
20.0% 10.7% 831% 79.0%
Dieci perdite consecutive, con un metodo che vince il 55% delle volte, capitano circa una volta ogni tremila operazioni: quasi certamente almeno una volta nella tua vita operativa.Show the script for this step
print(f"{t('rischio per op.', 'risk per trade'):>16s} "
f"{t('10 perdite di fila', '10 losses in a row'):>20s} "
f"{t('serve per tornare', 'needed to recover'):>19s} "
f"{t('prob. di rovina', 'ruin prob.'):>17s}")
for rischio in (0.005, 0.01, 0.02, 0.05, 0.10, 0.20): # PROVA / TRY: aggiungi il tuo rischio per operazione
resta = (1 - rischio) ** 10
recupero = 1 / resta - 1
# NON TOCCARE / DO NOT CHANGE: seme fissato riga per riga (uno per
# rischio) perché la probabilità di rovina stampata sia sempre la stessa.
# Seed fixed row by row (one per risk level) so the printed ruin
# probability is always the same.
prob = rischio_di_rovina(VINCITE, RAPPORTO, rischio, operazioni=1000,
soglia=0.2, campioni=4000,
rng=np.random.default_rng(seed_for(f"rovina-{rischio}")))
print(f"{rischio:16.1%} {resta:19.1%} {recupero:18.0%} {prob:17.1%}")
print(t("\nDieci perdite consecutive, con un metodo che vince il 55% delle volte, "
"capitano circa una volta ogni tremila operazioni: quasi certamente almeno "
"una volta nella tua vita operativa.",
"\nTen consecutive losses, with a method that wins 55% of the time, happen "
"roughly once every three thousand trades: almost certainly at least once "
"in your trading lifetime."))5. Overall risk, which is not the sum
If you have five positions each risking 2%, you're not risking 2%. Nor 10%, unless they're perfectly correlated.
Output
5 posizioni al 2% ciascuna
correlazione rischio complessivo
0.0 4.5%
0.3 6.6%
0.7 8.7%
0.9 9.6%
1.0 10.0%
Nei momenti brutti la correlazione sale — il Lab 7 lo misura — quindi il numero da usare per il limite complessivo e' quello delle righe in basso, non quello delle righe in alto.Show the script for this step
POSIZIONI = 5 # PROVA / TRY: il numero VERO delle tue posizioni aperte
RISCHIO_CIASCUNA = 0.02 # PROVA / TRY: il rischio che assegni a ciascuna
print(t(f"{POSIZIONI} posizioni al {RISCHIO_CIASCUNA:.0%} ciascuna\n",
f"{POSIZIONI} positions at {RISCHIO_CIASCUNA:.0%} each\n"))
print(f"{t('correlazione', 'correlation'):>13s} {t('rischio complessivo', 'overall risk'):>21s}")
for rho in (0.0, 0.3, 0.7, 0.9, 1.0):
varianza = POSIZIONI * RISCHIO_CIASCUNA**2 * (1 + (POSIZIONI - 1) * rho)
# `sqrt(varianza)`, e basta: prima qui c'era `sqrt(varianza / POSIZIONI) *
# sqrt(POSIZIONI)`, che vale esattamente lo stesso e non e' lo stesso da
# leggere. In un libro il cui valore e' che il codice si possa seguire,
# una divisione e una moltiplicazione che si annullano sono un ostacolo.
print(f"{rho:13.1f} {np.sqrt(varianza):21.1%}")
print(t("\nNei momenti brutti la correlazione sale — il Lab 7 lo misura — quindi il "
"numero da usare per il limite complessivo e' quello delle righe in basso, "
"non quello delle righe in alto.",
"\nIn bad moments correlation rises — Lab 7 measures it — so the number to "
"use for the overall limit is the one from the bottom rows, not the top "
"ones."))Exercises
- In the first cell set
VINCITE = 0.52, already a very hard edge to actually have. The optimal fraction collapses, and with it the margin for error. - Halve the edge you believe you have and redo the calculation: if you were risking the optimal fraction of the overestimated edge, you're now on the declining branch of the curve without having done anything.
- In the fifth cell, enter the real number of your open positions and the correlation measured with Lab 7. Compare it with the limit you had in mind.
Reproducibility & downloads
Run on 2026-08-27 from the repository notebook
The notebook
calc_04_dimensionamento.ipynb17.6 KB
sha256 6fcf07ca79c7dac8988136a9c4d5478717343fe2bc3a16f79725c31162c4e932
calc_04_dimensionamento.py13.8 KB
sha256 fd1c1431e23912fd9bf3d6abb51f5fb8f91ba3f96ffac8da5f04fb9cf6860a3c
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