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

Lab del libro · L16

L16 — Sei regole da manuale misurate insieme, con i tre controlli

Lab 16 — L'analisi tecnica, misurata

Quaderno del capitolo «L'analisi tecnica, misurata» di La matematica di chi perde.

Le sei regole del capitolo, scritte tutte nella stessa forma, applicate all'asset e al periodo che scegli tu. Senza selezionare quali mostrare dopo aver visto i risultati.

Poi i tre controlli sulla regola che vince: la forma della superficie, il metro del caso, e la correzione per il numero di tentativi.

Nulla di quello che c'è qui dentro è un'indicazione operativa. È un modo di porre la domanda.

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_16_analisi_tecnica.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
from cvbook.regole import CATALOGO, compra_e_tieni, esegui, rottura

SERIE = "btcusdt"   # ← PROVA / TRY: "ethusdt" · "solusdt" (le tre preparate nel setup)
COSTO = 0.0012      # ← il tuo costo per operazione
                    # PROVA / TRY: raddoppialo (vedi esercizio 1)

prezzi = carica(SERIE).sort("data")["chiusura"].to_numpy()

1. Sei regole da manuale, tutte insieme

Il vincolo che distingue una misura da una vetrina: si mostrano tutte.

Output

btcusdt, costi 0.12% per operazione

                regola     netto     lordo   oper.   dentro   calo max
  Forza relativa 30/70     0.50x     0.54x      71      47%     -83.4%
       Incrocio 50/200     4.33x     4.42x      18      50%     -66.8%
    Sopra la media 200     6.40x     6.91x      64      49%     -64.2%
        Incrocio 20/50    12.87x    14.03x      72      52%     -74.9%
     Momento a 12 mesi    14.17x    14.62x      26      59%     -63.3%
   Rottura a 20 giorni    35.66x    39.90x      94      52%     -60.9%
        compra e tieni    13.66x    13.68x       1     100%     -83.2%

Sei barre orizzontali, una per regola da manuale, col capitale finale per ogni euro su scala logaritmica da 10 alla 0 a 10 alla 1. Dall'alto: forza relativa 30/70 a 0,50 volte, incrocio 50/200 a 4,33, sopra la media 200 a 6,40, incrocio 20/50 a 12,87, momento a 12 mesi a 14,17, rottura a 20 giorni a 35,66. Una riga tratteggiata nera a 13,7 volte segna chi resta sempre dentro al mercato: due regole su sei la superano.

Sei regole misurate insieme, e non solo quella che ha funzionato.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Ogni regola applicata alle stesse chiusure giornaliere con costo dello 0,12% per operazione; il riferimento tratteggiato e' la stessa serie tenuta senza mai operare.
2 regole su 6 hanno battuto il non far niente.
Espandi lo script di questo passo
lab_16_analisi_tecnica.py
python
riferimento = esegui(prezzi, compra_e_tieni(prezzi), costo=COSTO)

righe = []
for nome, regola in CATALOGO.items():
    e = esegui(prezzi, regola(prezzi), costo=COSTO)
    righe.append((nome, e["finale"], e["finale_lordo"], e["operazioni"],
                  e["esposizione"], drawdown_massimo(e["curva"])))
righe.sort(key=lambda x: x[1])

print(f"{SERIE}, costi {COSTO:.2%} per operazione\n")
print(f"{'regola':>22s} {'netto':>9s} {'lordo':>9s} {'oper.':>7s} "
      f"{'dentro':>8s} {'calo max':>10s}")
for nome, netto, lordo, op, esp, dd in righe:
    print(f"{nome:>22s} {netto:8.2f}x {lordo:8.2f}x {op:7.0f} {esp:8.0%} {dd:10.1%}")
print(f"{'compra e tieni':>22s} {riferimento['finale']:8.2f}x "
      f"{riferimento['finale_lordo']:8.2f}x {riferimento['operazioni']:7.0f} "
      f"{riferimento['esposizione']:8.0%} {drawdown_massimo(riferimento['curva']):10.1%}")

with avvio.figura("schermo"):
    fig, ax = plt.subplots(figsize=(9, 4.5))
    y = np.arange(len(righe))
    ax.barh(y, [r[1] for r in righe])
    ax.axvline(riferimento["finale"], linewidth=2, linestyle="--", color="black")
    ax.annotate(f"compra e tieni: {riferimento['finale']:.1f}x",
                xy=(riferimento["finale"], len(righe) - 0.4),
                xytext=(6, 0), textcoords="offset points", va="center")
    ax.set_yticks(y)
    ax.set_yticklabels([r[0] for r in righe])
    ax.set_xscale("log")
    ax.set_xlabel("Capitale finale, per ogni euro investito (scala log)")
    plt.show()

battono = sum(1 for r in righe if r[1] > riferimento["finale"])
print(f"\n{battono} regole su {len(righe)} hanno battuto il non far niente.")

2. I costi decidono la classifica

Guarda le colonne «netto» e «lordo» della tabella. Qualche regola cambia lato.

Output

                regola       0.00%      0.06%      0.12%      0.25%      0.50%
       Incrocio 50/200       4.42x      4.38x      4.33x      4.23x      4.04x
        Incrocio 20/50      14.03x     13.44x     12.87x     11.71x      9.78x
    Sopra la media 200       6.91x      6.65x      6.40x      5.88x      5.01x
   Rottura a 20 giorni      39.90x     37.72x     35.66x     31.57x     24.96x
  Forza relativa 30/70       0.54x      0.52x      0.50x      0.46x      0.38x
     Momento a 12 mesi      14.62x     14.39x     14.17x     13.70x     12.84x
        compra e tieni      13.68x     13.67x     13.66x     13.65x     13.61x

«Quale regola e' migliore» dipende da quanto paghi. Chi pubblica un backtest senza costi non ha mentito su nessun numero: ha omesso una voce, e l'omissione puo' invertire la conclusione.
Espandi lo script di questo passo
lab_16_analisi_tecnica.py
python
print(f"{'regola':>22s} " + "".join(f"{c:>11.2%}" for c in (0.0, 0.0006, 0.0012, 0.0025, 0.005)))
for nome, regola in CATALOGO.items():
    valori = "".join(f"{esegui(prezzi, regola(prezzi), costo=c)['finale']:10.2f}x"
                     for c in (0.0, 0.0006, 0.0012, 0.0025, 0.005))
    print(f"{nome:>22s} {valori}")
# Anche il compra-e-tieni ha un'operazione — il proprio ingresso — quindi la
# sua riga cambia con la colonna, di poco ma cambia. Stamparla costante era
# comodo e falso: diceva che il metro di confronto non paga i costi.
print(f"{'compra e tieni':>22s} " +
      "".join(f"{esegui(prezzi, compra_e_tieni(prezzi), costo=c)['finale']:10.2f}x"
              for c in (0.0, 0.0006, 0.0012, 0.0025, 0.005)))

print("\n«Quale regola e' migliore» dipende da quanto paghi. Chi pubblica un "
      "backtest senza costi non ha mentito su nessun numero: ha omesso una voce, "
      "e l'omissione puo' invertire la conclusione.")

3. Il primo controllo: la forma della superficie

Un altopiano largo è compatibile con un fenomeno reale. Un picco isolato quasi mai lo è.

Una linea puntinata sulla finestra della rottura, da 5 a 120 giorni con passo 5, e il capitale finale su scala logaritmica fra 2 e 40 volte. Il profilo e' frastagliato invece che a altopiano: la mediana della griglia vale 12,2 volte, e 11 valori su 24 superano la riga tratteggiata nera di chi resta sempre dentro.

La forma della superficie attorno al parametro scelto: altopiano o picco isolato.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: La stessa regola ricalcolata per ogni finestra da 5 a 120 giorni con passo 5, sulla stessa serie e con gli stessi costi.

Output

valori che battono il compra-e-tieni: 11 su 24
mediana della griglia: 12.21x
Espandi lo script di questo passo
lab_16_analisi_tecnica.py
python
FINESTRE = np.arange(5, 121, 5)  # PROVA / TRY: allarga o restringi il passo
griglia = np.array([esegui(prezzi, rottura(prezzi, int(f)), costo=COSTO)["finale"]
                    for f in FINESTRE])

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.plot(FINESTRE, griglia, marker="o", linewidth=2)
    ax.axhline(riferimento["finale"], linestyle="--", linewidth=1.5, color="black",
               label="compra e tieni")
    ax.set_yscale("log")
    ax.set_xlabel("Finestra della rottura (giorni)")
    ax.set_ylabel("Capitale finale (volte, scala log)")
    ax.legend()
    plt.show()

print(f"valori che battono il compra-e-tieni: {int((griglia > riferimento['finale']).sum())} "
      f"su {len(griglia)}")
print(f"mediana della griglia: {np.median(griglia):.2f}x")

4. Il secondo controllo: il metro del caso

Istogramma del capitale finale di mille posizioni casuali, con l'asse orizzontale logaritmico da 10 alla meno 1 a 10 alla 2 volte e il conteggio fino a 400. La massa e' addensata attorno a 3,4 volte, la mediana; una riga verticale nera segna la regola scelta a 35,7 volte, e il titolo dice che sta al novantanovesimo percentile.

Dove cade la regola scelta dentro mille alternative che stanno dentro al mercato quanto lei.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Mille posizioni casuali con lo stesso numero di giorni dentro al mercato e lo stesso numero di operazioni della regola, con gli stessi costi.

Output

la regola: 35.66x con 94 operazioni
mediana delle casuali: 3.75x
percentile: 98.4
Espandi lo script di questo passo
lab_16_analisi_tecnica.py
python
N_CASUALI = 1000  # PROVA / TRY: 200 (veloce) · 1000 · 10000 (percentile più preciso)
SCELTA = 20


def posizione_casuale(n: int, n_operazioni: int, rng) -> np.ndarray:
    pos = np.zeros(n)
    punti = np.sort(rng.choice(n - 1, size=n_operazioni, replace=False))
    stato, precedente = 0.0, 0
    for i in punti:
        pos[precedente:i] = stato
        stato, precedente = 1.0 - stato, i
    pos[precedente:] = stato
    return pos


scelta = esegui(prezzi, rottura(prezzi, SCELTA), costo=COSTO)
n_op = int(scelta["operazioni"])
rng = np.random.default_rng(seed_for("tecnica-verifica"))
# NON TOCCARE / DO NOT CHANGE: è il seme della figura stampata nel capitolo.
# Con quello — stesse mille posizioni casuali, stesse operazioni, stessi costi
# — questa cella ridisegna l'istogramma del libro e stampa il suo percentile.
# Con un seme qualunque il quaderno resta corretto ma risponde 98,9 dove la
# pagina dice 98: due numeri per la stessa domanda, ed è la cosa che questo
# libro promette di non fare.
# This is the seed of the figure printed in the chapter.
casuali = np.array([
    esegui(prezzi, posizione_casuale(len(prezzi), n_op, rng), costo=COSTO)["finale"]
    for _ in range(N_CASUALI)
])
percentile = float((casuali < scelta["finale"]).mean() * 100)

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.hist(casuali, bins=50)
    ax.axvline(scelta["finale"], linewidth=2.5, color="black")
    ax.set_xscale("log")
    ax.set_xlabel("Capitale finale (volte, scala log)")
    ax.set_ylabel(f"Su {N_CASUALI} posizioni casuali")
    ax.set_title(f"La regola sta al {percentile:.0f}esimo percentile")
    plt.show()

print(f"la regola: {scelta['finale']:.2f}x con {n_op} operazioni")
print(f"mediana delle casuali: {np.median(casuali):.2f}x")
print(f"percentile: {percentile:.1f}")

5. Il terzo controllo: quanti tentativi ci sono stati dietro

Il controllo che quasi nessuno applica ai propri numeri.

Output

regole provate: 6
valori del parametro provati: 24
tentativi complessivi (stima prudente): 30

probabilita' che UN tentativo raggiunga questo percentile per caso: 1.6%
probabilita' che almeno uno su 30 lo faccia: 38.4%

I tentativi non sono del tutto indipendenti — finestre vicine danno risultati simili — quindi questa stima e' pessimistica. Ma il punto resta: un percentile, dopo trenta tentativi, non e' piu' quel percentile.
Espandi lo script di questo passo
lab_16_analisi_tecnica.py
python
regole_provate = len(CATALOGO)
parametri_provati = len(FINESTRE)
tentativi = regole_provate + parametri_provati

p_singolo = 1 - percentile / 100
print(f"regole provate: {regole_provate}")
print(f"valori del parametro provati: {parametri_provati}")
print(f"tentativi complessivi (stima prudente): {tentativi}\n")
print(f"probabilita' che UN tentativo raggiunga questo percentile per caso: {p_singolo:.1%}")
print(f"probabilita' che almeno uno su {tentativi} lo faccia: "
      f"{1 - (1 - p_singolo) ** tentativi:.1%}")
print("\nI tentativi non sono del tutto indipendenti — finestre vicine danno "
      "risultati simili — quindi questa stima e' pessimistica. Ma il punto resta: "
      "un percentile, dopo trenta tentativi, non e' piu' quel percentile.")

6. Altri mercati non sono altre prove

Output

   btcusdt: regola    35.66x   compra e tieni    13.66x   rapporto  2.61
   ethusdt: regola    48.88x   compra e tieni     5.20x   rapporto  9.40
   solusdt: regola   187.25x   compra e tieni    22.31x   rapporto  8.39

Tre conferme? No. Il Lab 8 ha mostrato che questi tre mercati contengono poco piu' di UNA scommessa: una sola componente ne spiega quasi l'ottanta per cento dei movimenti. Un metodo che funziona sul fattore comune funziona su tutti e tre — non perche' sia stato confermato tre volte, ma perche' e' stato confermato una volta e conteggiato tre.
Espandi lo script di questo passo
lab_16_analisi_tecnica.py
python
for altro in ("btcusdt", "ethusdt", "solusdt"):
    p = carica(altro).sort("data")["chiusura"].to_numpy()
    r = esegui(p, rottura(p, SCELTA), costo=COSTO)
    b = esegui(p, compra_e_tieni(p), costo=COSTO)
    print(f"{altro:>10s}: regola {r['finale']:8.2f}x   compra e tieni {b['finale']:8.2f}x   "
          f"rapporto {r['finale'] / b['finale']:5.2f}")

print("\nTre conferme? No. Il Lab 8 ha mostrato che questi tre mercati contengono "
      "poco piu' di UNA scommessa: una sola componente ne spiega quasi l'ottanta "
      "per cento dei movimenti. Un metodo che funziona sul fattore comune "
      "funziona su tutti e tre — non perche' sia stato confermato tre volte, ma "
      "perche' e' stato confermato una volta e conteggiato tre.")

Esercizi

  1. Cambia COSTO e riesegui la seconda cella. Guarda l'ordine della classifica cambiare: è la dimostrazione più rapida che «quale regola è migliore» non è una proprietà della regola.
  2. Aggiungi una regola tua al catalogo, seguendo il modello di cvbook.regole, e sottoponila agli stessi tre controlli. Ricordati di contarla fra i tentativi.
  3. Il più istruttivo: esegui tutto su un mercato non digitale. Attenzione ai giorni di borsa chiusa, che cambiano il conto dei periodi. Se il risultato si ripete lì, hai una conferma vera; se non si ripete, hai imparato qualcosa di più utile di una conferma.

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