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
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.
2 regole su 6 hanno battuto il non far niente.
Espandi lo script di questo passo
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
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.
Output
valori che battono il compra-e-tieni: 11 su 24 mediana della griglia: 12.21x
Espandi lo script di questo passo
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.
Output
la regola: 35.66x con 94 operazioni mediana delle casuali: 3.75x percentile: 98.4
Espandi lo script di questo passo
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
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
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
- Cambia
COSTOe riesegui la seconda cella. Guarda l'ordine della classifica cambiare: è la dimostrazione più rapida che «quale regola è migliore» non è una proprietà della regola. - Aggiungi una regola tua al catalogo, seguendo il modello di
cvbook.regole, e sottoponila agli stessi tre controlli. Ricordati di contarla fra i tentativi. - 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
lab_16_analisi_tecnica.ipynb16,0 KB
sha256 c5c04b6347235201a9d3604b3d10b308cce6b38a34b2820231300a52a3a24620
lab_16_analisi_tecnica.py12,0 KB
sha256 261e6881ae847ff1e37bb2a0b9178f4986fd34e071bd909be9c026e946ba9b61
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