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

Lab del libro · L04

L04 — Avversione alle perdite e disposizione, misurate sui propri numeri

Lab 4 — Il costo misurato dei tuoi automatismi

Quaderno del capitolo «Perché il tuo cervello non è fatto per questo» di La matematica di chi perde.

Il capitolo dice che prendere i piccoli utili e tenere le perdite grandi è misurabile e caro. Qui lo misuri sui tuoi parametri, e poi provi l'esperimento che il capitolo racconta: distinguere a occhio un processo con un vantaggio reale da uno senza. Quasi nessuno ci riesce.

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

from cvbook import seed_for
from cvbook.dati import carica
from cvbook.metriche import drawdown_massimo, equity, rendimenti
from cvbook.simulazioni import equity_casuali

prezzi = carica("btcusdt").sort("data")["chiusura"].to_numpy()
r = rendimenti(prezzi)

1. Il costo del prendere subito l'utile

Due comportamenti, stessa serie di prezzi, stesso capitale, stessi costi. Il primo compra e non tocca più niente. Il secondo fa quello che l'esperienza dei conti reali documenta: chiude appena è in utile di una certa percentuale, e resta dentro finché la perdita non raggiunge una soglia molto più larga.

Nessuna previsione distingue i due. Solo le due soglie.

Due curve di capitale su scala logaritmica lungo 3.240 giorni, con l'asse verticale da 10 alla 0 a 10 alla 1. Quella continua, di chi non tocca niente, arriva a 13,7 volte il capitale iniziale; quella tratteggiata, di chi chiude appena e' in utile del 10% e sopporta perdite fino al 50%, si ferma a 8,5. I cali massimi sono simili, 83% e 81%.

Stessa serie di prezzi, stessi costi: cambiano solo le due soglie di comportamento.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Due regole applicate alle stesse chiusure giornaliere con costo dello 0,12% per operazione: la prima non opera mai, la seconda chiude sull'utile e attende sulla perdita.

Output

chi non ha toccato niente:   13.66x   calo massimo -83.2%
chi ha preso i piccoli utili:   9.01x   calo massimo -81.2%
differenza: -34.1%
Espandi lo script di questo passo
lab_04_bias.py
python
PRENDI_UTILE = 0.10   # ← chiudi quando sei in utile di questa percentuale
                      # PROVA / TRY: 0,05 · 0,10 · 0,50 (vedi esercizi 1 e 2)
SOPPORTA_PERDITA = 0.50  # ← resti dentro finche' la perdita non arriva a questa
                         # PROVA / TRY: 0,10 · 0,50 · 0,70 (vedi esercizi 1 e 2)
COSTO = 0.0012           # ← costo di un GIRO COMPLETO — chiudere e riaprire
                         # PROVA / TRY: 0,0006 · 0,0012 · 0,0025
# NON TOCCARE / DO NOT CHANGE: qui lo 0,12% e' il costo di un giro completo, ed
# e' la convenzione che il libro dichiara fino al capitolo sull'analisi tecnica.
# Addebitarlo due volte per giro — una in uscita e una al rientro — e' la
# convenzione severa, che il libro introduce solo dal cap. 18b in avanti: con
# quella il quaderno chiudeva a 8,46 volte dove il capitolo stampa 9,0.
# Here 0.12% is the cost of a full round trip, the convention the book declares
# up to the technical-analysis chapter. Charging it twice per round trip is the
# stricter convention, introduced only from ch. 18b on.


def con_soglie(p: np.ndarray, su: float, giu: float, costo: float) -> np.ndarray:
    """Chiude quando la posizione tocca una soglia, e rientra il giorno dopo.

    Sono tre le cose che questo comportamento paga rispetto al non far nulla,
    e vale la pena tenerle distinte perche' pesano in modo molto diverso:

    1. il costo del giro completo — uscita piu' rientro — addebitato una volta
       sola, in uscita, secondo la convenzione dichiarata dal capitolo;
    2. il giorno passato fuori dal mercato ad ogni chiusura — ed e' questa la
       voce piu' cara, perche' il capitolo sulla media che mente ha mostrato
       che pochissimi giorni contengono quasi tutto il risultato;
    3. niente altro: nessuna previsione, nessuna scelta di direzione.
    """
    valore = np.empty(len(p))
    valore[0] = 1.0
    ingresso, quota, liquido, dentro = p[0], 1.0 / p[0], 0.0, True

    for i in range(1, len(p)):
        if dentro:
            corrente = quota * p[i]
            variazione = p[i] / ingresso - 1.0
            if variazione >= su or variazione <= -giu:
                liquido = corrente * (1.0 - costo)   # esce: paga il giro intero
                dentro, corrente = False, liquido
            valore[i] = corrente
        else:
            ingresso = p[i]          # rientra il giorno dopo, senza ripagare:
            quota = liquido / p[i]   # il giro e' gia' stato addebitato in uscita
            dentro = True
            valore[i] = quota * p[i]

    # Anche l'ingresso del primo giorno e' un'operazione, e va pagato: sotto
    # sta il compra-e-tieni che lo paga, e le due colonne devono essere
    # confrontabili fino all'ultimo centesimo.
    return valore * (1.0 - costo)


# Anche chi non tocca niente paga il proprio ingresso: e' un'operazione, e il
# metro di confronto non puo' viaggiare gratis mentre l'altro comportamento
# paga. Sono dodici centesimi per mille euro, tutti a favore della tesi.
# Whoever never touches anything still pays for the entry: it is one trade, and
# the benchmark cannot travel for free while the other behaviour pays.
fermo = equity(r) * (1 - COSTO)
nervoso = con_soglie(prezzi, PRENDI_UTILE, SOPPORTA_PERDITA, COSTO)

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.semilogy(fermo, linewidth=1.8, label="compra e non tocca niente")
    ax.semilogy(nervoso, linewidth=1.8, linestyle="--",
                label=f"chiude a +{PRENDI_UTILE:.0%}, sopporta -{SOPPORTA_PERDITA:.0%}")
    ax.set_ylabel("Capitale (scala log)")
    ax.set_xlabel("Giorni")
    ax.legend()
    plt.show()

print(f"chi non ha toccato niente:  {fermo[-1]:6.2f}x   calo massimo {drawdown_massimo(fermo):.1%}")
print(f"chi ha preso i piccoli utili: {nervoso[-1]:6.2f}x   calo massimo {drawdown_massimo(nervoso):.1%}")
print(f"differenza: {nervoso[-1] / fermo[-1] - 1:+.1%}")

Nota le due colonne del calo massimo. Il secondo comportamento ha rinunciato a una parte del risultato senza comprarsi in cambio nemmeno un po' di tranquillità. Ha pagato per l'illusione di controllo.

2. L'asimmetria del dolore, e perché appiattisce

I parametri sperimentali della teoria del prospetto: la perdita pesa circa due volte e mezzo il guadagno di pari entità, e la curva si appiattisce allontanandosi dallo zero.

Una curva a esse centrata sull'origine, con il guadagno o la perdita in euro da meno 10.000 a 10.000 in orizzontale e il valore percepito in unita' arbitrarie in verticale, da meno 6.000 a poco oltre 2.000. Il ramo delle perdite scende molto piu' ripido di quanto salga quello dei guadagni: a 2.000 euro il rapporto fra dolore e piacere e' 2,25, e il tratto da meno 8.000 a meno 10.000 aggiunge meno dolore del primo tratto sotto lo zero.

L'asimmetria fra il dolore di perdere e il piacere di guadagnare, e il suo appiattirsi.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Curva di valore della teoria del prospetto con i parametri sperimentali: curvatura 0,88 e avversione alle perdite 2,25.

Output

 2,000 euro:  piacere     803.4   dolore    1807.5   rapporto 2.25
 8,000 euro:  piacere    2720.9   dolore    6122.1   rapporto 2.25

dolore nel passare da 0 a -2.000:        1807.5
dolore nel passare da -8.000 a -10.000:   1328.3
È il motivo per cui, dopo una perdita gia' grande, rischiare ancora costa pochissimo in termini di sofferenza attesa.
Espandi lo script di questo passo
lab_04_bias.py
python
CURVATURA = 0.88
AVVERSIONE = 2.25
# NON TOCCARE / DO NOT CHANGE: sono i parametri stimati sperimentalmente dalla
# teoria del prospetto (Kahneman e Tversky), non un valore a piacere — cambiarli
# smetterebbe di rappresentare quella ricerca.
# These are the parameters experimentally estimated by prospect theory
# (Kahneman and Tversky), not an arbitrary value — changing them would stop
# representing that research.

importi = np.linspace(-10_000, 10_000, 400)
# np.where valuta entrambi i rami: si eleva a potenza il valore assoluto e si
# rimette il segno dopo, altrimenti numpy si lamenta delle radici di numeri
# negativi (e ha ragione).
grandezza = np.abs(importi) ** CURVATURA
valore = np.where(importi >= 0, grandezza, -AVVERSIONE * grandezza)

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.plot(importi, valore, linewidth=2)
    ax.axhline(0, linewidth=0.8, color="#8C8C8C")
    ax.axvline(0, linewidth=0.8, color="#8C8C8C")
    ax.set_xlabel("Guadagno o perdita (euro)")
    ax.set_ylabel("Valore percepito (unità arbitrarie)")
    plt.show()

for x in (2_000, 8_000):
    su = x**CURVATURA
    giu = AVVERSIONE * x**CURVATURA
    print(f"{x:6,d} euro:  piacere {su:9.1f}   dolore {giu:9.1f}   rapporto {giu / su:.2f}")

passo_vicino = AVVERSIONE * (2000**CURVATURA)
passo_lontano = AVVERSIONE * (10000**CURVATURA - 8000**CURVATURA)
print(f"\ndolore nel passare da 0 a -2.000:      {passo_vicino:8.1f}")
print(f"dolore nel passare da -8.000 a -10.000: {passo_lontano:8.1f}")
print("È il motivo per cui, dopo una perdita gia' grande, rischiare ancora "
      "costa pochissimo in termini di sofferenza attesa.")

3. Riesci a distinguere il vantaggio dal rumore?

Sei serie. Alcune hanno un vantaggio reale, altre no. Scrivi la tua risposta prima di eseguire la cella successiva.

Sei riquadri titolati da «serie 1» a «serie 6», ciascuno con una curva di capitale a base 100 e una riga punteggiata sul valore di partenza. Le scale verticali sono diverse fra loro: la prima va da 70 a 105, la seconda arriva a 140, la quarta scende sotto 60. Due delle sei hanno dentro un vantaggio reale, le altre quattro no, e l'ordine e' mescolato.

Sei serie: due hanno un vantaggio dentro, quattro no.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Sei sequenze generate con lo stesso rumore, due delle quali con un vantaggio dello 0,05% per operazione; l'ordine e' mescolato dal seme del quaderno.
Espandi lo script di questo passo
lab_04_bias.py
python
rng = np.random.default_rng(seed_for("lab-bias-indovina"))
# NON TOCCARE / DO NOT CHANGE: scrivi la tua risposta PRIMA di eseguire la
# cella successiva. Cambiare il seme dopo aver sbagliato per ottenere un
# disegno più facile vanificherebbe l'esercizio, non lo migliorerebbe.
# Write down your answer BEFORE running the next cell. Changing the seed
# after getting it wrong, to get an easier draw, would defeat the exercise,
# not improve it.
VANTAGGI = rng.permutation([0.0, 0.0, 0.0, 0.0005, 0.0005, 0.0])

with avvio.figura("schermo"):
    fig, assi = plt.subplots(2, 3, figsize=(11, 5))
    curve = []
    for k, ax in enumerate(assi.flat):
        c = equity_casuali(1, 400, rendimento_atteso=VANTAGGI[k],
                           volatilita_periodo=0.02, rng=rng)[0]
        curve.append(c)
        ax.plot(c * 100, linewidth=1.4)
        ax.axhline(100, linestyle=":", linewidth=0.8)
        ax.set_title(f"serie {k + 1}", fontsize=10)
        ax.set_xticks([])
    plt.show()

Output

vantaggio reale per operazione:
  serie 1: 0.0000%  →  capitale finale 0.88x
  serie 2: 0.0500%  →  capitale finale 1.08x
  serie 3: 0.0000%  →  capitale finale 0.95x
  serie 4: 0.0000%  →  capitale finale 0.48x
  serie 5: 0.0000%  →  capitale finale 1.19x
  serie 6: 0.0500%  →  capitale finale 1.43x

Se le due con vantaggio non sono quelle che avevi indicato, non e' un tuo limite: 400 osservazioni non bastano a distinguerle, e il capitolo sulla potenza statistica dice quante ne servirebbero.
Espandi lo script di questo passo
lab_04_bias.py
python
print("vantaggio reale per operazione:")
for k, v in enumerate(VANTAGGI):
    print(f"  serie {k + 1}: {v:.4%}  →  capitale finale {curve[k][-1]:.2f}x")
print("\nSe le due con vantaggio non sono quelle che avevi indicato, non e' un "
      "tuo limite: 400 osservazioni non bastano a distinguerle, e il capitolo "
      "sulla potenza statistica dice quante ne servirebbero.")

Esercizi

  1. Nella prima cella metti PRENDI_UTILE = 0.05 e SOPPORTA_PERDITA = 0.70: è il comportamento estremo, e il costo cresce di conseguenza.
  2. Prova PRENDI_UTILE = 0.50 e SOPPORTA_PERDITA = 0.10 — cioè il contrario di quello che fa quasi tutti. Guarda cosa succede al risultato e al calo massimo: non è gratis nemmeno quello.
  3. Rifai l'esperimento della terza cella cambiando 400 in 4000. Con dieci volte le osservazioni la distinzione diventa possibile. È esattamente il punto del capitolo sul potere statistico.

Riproducibilità e download

Eseguito il 2026-08-27 dal quaderno del repository

Il quaderno

  • lab_04_bias.ipynb15,8 KB

    sha256 ff5ff213ddd9e016fa2d3f454055be78f7adba9fdd3f8ef1914d89fe23d61c70

  • lab_04_bias.py12,3 KB

    sha256 a154812752bac28498310133090ce0cf5e70fc067904e06d2cf7b9984bf6b373

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