Skip to content
SIAT PAPER 2026Open the research page
Cryptoverso

Lab from the book · L15

L15 — Optimising a meaningless rule, in sample and out of sample

Lab 15 — Optimizing is fooling yourself

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 "Optimizing is fooling yourself". Here you redo the experiment that takes apart Lab 12's result: the same rule across its one parameter, in and out of sample. Then you try to find a value that wins in both halves. It's frustrating in the right way. And finally the piece that convinced me more than any other: take a rule deliberately without sense, optimize it, and look at what a nice in-sample result you get. Then look out of sample.

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

from cvbook.dati import carica
from cvbook.metriche import drawdown_massimo
from cvbook.regole import compra_e_tieni, esegui, sopra_media

SERIE = "btcusdt"     # ← PROVA / TRY: "ethusdt" · "solusdt" (esercizio 1)
FINESTRE = np.arange(5, 121, 5)  # PROVA / TRY: allarga o restringi il passo
COSTO = 0.0012                   # PROVA / TRY: 0,0006 · 0,0012 · 0,0025

df = carica(SERIE).sort("data")
prezzi = df["chiusura"].to_numpy()
meta = len(prezzi) // 2
prima, seconda = prezzi[:meta], prezzi[meta:]

1. The map, in and out of sample

Two lines over the length of the moving average, from 5 to 120 days, with the final capital on a logarithmic scale from ten to the zero to ten to the one. The solid one, measured on the first half of the history — the same half used to choose — has an isolated peak at 50 days worth 30.8 times; the dashed one, measured on the second half, is worth 2.7 at that point and is higher at 120 days, where it reaches 3.3.

The same parameter seen on the half that chose it and on the half that never saw it.Source: Binance Data Vision · Period: 2017-08-17 … 2026-06-30 · Method: The same rule computed for every length of the moving average on both halves of the series; the best value is picked on the first half alone.

Output

il valore migliore sulla PRIMA meta':  50 giorni → 30.81x
lo stesso valore sulla SECONDA meta':  2.69x
restringimento: un fattore 11.5

il valore migliore sulla SECONDA meta': 120 giorni → 3.29x

Il numero che sembrava una scoperta non e' nemmeno quello giusto a posteriori: era una proprieta' di quel campione.
Show the script for this step
lab_15_ottimizzazione.py
python
dentro = np.array([esegui(prima, sopra_media(prima, int(f)), costo=COSTO)["finale"]
                   for f in FINESTRE])
fuori = np.array([esegui(seconda, sopra_media(seconda, int(f)), costo=COSTO)["finale"]
                  for f in FINESTRE])

migliore_dentro = int(FINESTRE[np.argmax(dentro)])
migliore_fuori = int(FINESTRE[np.argmax(fuori)])

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.plot(FINESTRE, dentro, marker="o", linewidth=2, label="prima metà (usata per scegliere)")
    ax.plot(FINESTRE, fuori, marker="s", linewidth=2, linestyle="--",
            label="seconda metà (mai vista)")
    ax.axvline(migliore_dentro, linestyle=":", linewidth=1.2)
    ax.set_yscale("log")
    ax.set_xlabel("Lunghezza della media (giorni)")
    ax.set_ylabel("Capitale finale (volte, scala log)")
    ax.legend()
    plt.show()

scelto = int(np.argmax(dentro))
print(f"il valore migliore sulla PRIMA meta':  {migliore_dentro} giorni "
      f"→ {dentro[scelto]:.2f}x")
print(f"lo stesso valore sulla SECONDA meta':  {fuori[scelto]:.2f}x")
print(f"restringimento: un fattore {dentro[scelto] / fuori[scelto]:.1f}\n")
print(f"il valore migliore sulla SECONDA meta': {migliore_fuori} giorni "
      f"→ {fuori[np.argmax(fuori)]:.2f}x")
print("\nIl numero che sembrava una scoperta non e' nemmeno quello giusto a "
      "posteriori: era una proprieta' di quel campione.")

2. Compared to what? The honest comparison on the second half

The right comparison for a result on one window is doing nothing on that same window, not over the entire period.

Output

seconda meta': 1620 giorni (4.4 anni)

                     finale   calo massimo   tempo dentro
       la regola      2.69x         -44.5%            50%
  compra e tieni      1.62x         -66.7%           100%
Show the script for this step
lab_15_ottimizzazione.py
python
riferimento = esegui(seconda, compra_e_tieni(seconda), costo=COSTO)
regola = esegui(seconda, sopra_media(seconda, migliore_dentro), costo=COSTO)

print(f"seconda meta': {len(seconda)} giorni ({len(seconda) / 365:.1f} anni)\n")
print(f"{'':>16s} {'finale':>10s} {'calo massimo':>14s} {'tempo dentro':>14s}")
print(f"{'la regola':>16s} {regola['finale']:9.2f}x {drawdown_massimo(regola['curva']):14.1%} "
      f"{regola['esposizione']:14.0%}")
print(f"{'compra e tieni':>16s} {riferimento['finale']:9.2f}x "
      f"{drawdown_massimo(riferimento['curva']):14.1%} {1.0:14.0%}")

3. Find a value that wins in both halves

The exercise that's frustrating in the right way.

Output

 finestra  rango dentro  rango fuori
       35             5           11
       40             3            8
       45             2            5
       50             1            6
       55             4            7
      105            22            4
      110            19            2
      115            23            3
      120            24            1

correlazione fra i ranghi delle due meta': -0.02
Se fosse vicina a +1, il parametro migliore sul passato sarebbe anche quello migliore sul futuro. Non lo e'.
Show the script for this step
lab_15_ottimizzazione.py
python
ranghi_dentro = np.argsort(np.argsort(-dentro)) + 1
ranghi_fuori = np.argsort(np.argsort(-fuori)) + 1

print(f"{'finestra':>9s} {'rango dentro':>13s} {'rango fuori':>12s}")
for k, f in enumerate(FINESTRE):
    if ranghi_dentro[k] <= 5 or ranghi_fuori[k] <= 5:
        print(f"{f:9d} {ranghi_dentro[k]:13d} {ranghi_fuori[k]:12d}")

correlazione = float(np.corrcoef(ranghi_dentro, ranghi_fuori)[0, 1])
print(f"\ncorrelazione fra i ranghi delle due meta': {correlazione:+.2f}")
print("Se fosse vicina a +1, il parametro migliore sul passato sarebbe anche "
      "quello migliore sul futuro. Non lo e'.")

4. A rule deliberately without sense

We buy based on the day of the month. There is no reason it should work, and indeed there isn't one: let's optimize it anyway.

Output

combinazioni provate: 6,496
la migliore: investire FUORI dal giorno 20 al giorno 23 del mese, saltando il mercoledi
  sulla prima meta':      36.58x   (compra e tieni: 8.17x)
  sulla seconda meta':     2.19x   (compra e tieni: 1.62x)

restringimento dentro→fuori: un fattore 16.7

Dentro campione e' una curva che si mostrerebbe volentieri, e sotto non c'e' alcun meccanismo: l'abbiamo scelta apposta senza senso.
Fuori campione il risultato si restringe di piu' di un ordine di grandezza. Quel poco che avanza NON e' un vantaggio: e' l'effetto di stare fuori dal mercato una parte del tempo, che su un periodo agitato basta a evitare qualche giorno brutto. Il metro del caso del Lab 12 e' li' apposta per separare le due cose — provaci.
Show the script for this step
lab_15_ottimizzazione.py
python
date = df["data"].to_list()
giorno_mese = np.array([d.day for d in date])
giorno_settimana = np.array([d.weekday() for d in date])

GIORNI_SETTIMANA = ["lunedi", "martedi", "mercoledi", "giovedi", "venerdi",
                    "sabato", "domenica", "nessuno"]


def regola_assurda(mese: np.ndarray, settimana: np.ndarray,
                   dal: int, al: int, escluso: int, invertita: bool) -> np.ndarray:
    """Investito nei giorni del mese fra `dal` e `al`, saltando un giorno della
    settimana. Nessun meccanismo la sostiene: e' scelta apposta perche' non ne ha.
    """
    dentro_finestra = (mese >= dal) & (mese <= al)
    if invertita:
        dentro_finestra = ~dentro_finestra
    segnale = (dentro_finestra & (settimana != escluso)).astype(float)
    posizione = np.zeros(len(segnale))
    posizione[1:] = segnale[:-1]
    return posizione


migliore, tentativi = (None, -np.inf), 0
for dal in range(1, 29):
    for al in range(dal, 29):
        for escluso in range(8):          # 0-6 = un giorno saltato, 7 = nessuno
            for invertita in (False, True):
                tentativi += 1
                valore = esegui(
                    prima,
                    regola_assurda(giorno_mese[:meta], giorno_settimana[:meta],
                                   dal, al, escluso, invertita),
                    costo=COSTO,
                )["finale"]
                if valore > migliore[1]:
                    migliore = ((dal, al, escluso, invertita), valore)

(dal, al, escluso, invertita), valore_dentro = migliore
valore_fuori = esegui(
    seconda,
    regola_assurda(giorno_mese[meta:], giorno_settimana[meta:], dal, al, escluso, invertita),
    costo=COSTO,
)["finale"]

print(f"combinazioni provate: {tentativi:,}")
print(f"la migliore: investire {'FUORI dal' if invertita else 'dal'} giorno {dal} "
      f"al giorno {al} del mese, saltando il {GIORNI_SETTIMANA[escluso]}")
# Il compra-e-tieni fra parentesi passa dallo stesso motore delle regole, con
# gli stessi costi: la cella 2 lo faceva gia', questa lo prendeva grezzo.
print(f"  sulla prima meta':   {valore_dentro:8.2f}x   (compra e tieni: "
      f"{esegui(prima, compra_e_tieni(prima), costo=COSTO)['finale']:.2f}x)")
print(f"  sulla seconda meta': {valore_fuori:8.2f}x   (compra e tieni: "
      f"{esegui(seconda, compra_e_tieni(seconda), costo=COSTO)['finale']:.2f}x)")
print(f"\nrestringimento dentro→fuori: un fattore "
      f"{valore_dentro / max(valore_fuori, 1e-9):.1f}")
print("\nDentro campione e' una curva che si mostrerebbe volentieri, e sotto non "
      "c'e' alcun meccanismo: l'abbiamo scelta apposta senza senso.")
print("Fuori campione il risultato si restringe di piu' di un ordine di grandezza. "
      "Quel poco che avanza NON e' un vantaggio: e' l'effetto di stare fuori dal "
      "mercato una parte del tempo, che su un periodo agitato basta a evitare "
      "qualche giorno brutto. Il metro del caso del Lab 12 e' li' apposta per "
      "separare le due cose — provaci.")

Exercises

  1. Change SERIE. The best value on the first half changes from one market to another. If it were a property of the world, it shouldn't.
  2. In the first cell, instead of the maximum take the center of the widest plateau — the region where the result stays acceptable. Compare the two out-of-sample values: the second one usually holds up better.
  3. In the fourth cell try explaining to yourself why "from day X to day Y of the month" should work. You'll manage it: the brain produces explanations for anything, and that's exactly the point.

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

  • ethusdt.parquet87.0 KB

    sha256 c2bd0259da905e0fec87235d7a62295532433fb89657726dd2d19558db7c072a

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

  • solusdt.parquet57.5 KB

    sha256 c7ba2368a3e419b898fb31ec6d5345b7212b74784b69079d3d43571c2ac63657

    Source: Binance Data Vision · Period: 2020-08-11 → 2026-06-30 · 2,150 rows · extracted 2026-08-16

Back to the lab index