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

Lab del libro · L17

L17 — Le due coordinate di un movimento, e il volume messo alla prova

Lab 17 — Le due coordinate di un movimento

Quaderno del capitolo «Prezzo e tempo» di La matematica di chi perde.

Un movimento di mercato si descrive con due numeri: quanto si è spostato il prezzo e quanto a lungo ci ha messo. Il volume è la terza colonna che ogni piattaforma mostra. La domanda del capitolo è se sia una terza coordinata o la conseguenza delle prime due e del calendario.

Qui rifai la misura sull'asset che scegli, e soprattutto puoi provare a farla cadere: cambia la soglia, cambia la finestra, cambia il mercato.

Nulla di quello che c'è qui dentro è un'indicazione operativa. La segmentazione riconosce un estremo dopo l'inversione: descrive movimenti finiti, non ne annuncia uno che comincia.

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

from cvbook.ciclica import (
    decomposizione,
    effetto_scadenza,
    movimenti,
    r_quadro,
    tavolo,
)
from cvbook.dati import carica

SERIE = "btcusdt"   # ← PROVA / TRY: una delle 8 preparate nel setup qui sopra
                    # (btcusdt · ethusdt · solusdt · ftsemib · eni · enel · intesa · generali)
SOGLIA = 0.05       # ← quanto deve rientrare il prezzo perché un estremo sia definitivo
                    # PROVA / TRY: 0,02 · 0,05 · 0,15 (vedi esercizio 1)
CRIPTO = SERIE.endswith("usdt")

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

1. Dove sono gli estremi

Una regola sola, dichiarata prima: un estremo diventa definitivo quando il prezzo si è allontanato di SOGLIA nella direzione opposta.

Output

342 movimenti su 3240 barre

Il prezzo di btcusdt da gennaio a luglio 2026, fra 60.000 e 95.000, con sopra la spezzata dei quattordici movimenti piu' recenti riconosciuti a soglia 5%: ogni vertice e' un estremo diventato definitivo solo dopo che il prezzo si e' allontanato del 5% nella direzione opposta. Sull'intera serie i movimenti riconosciuti sono 342 su 3.240 barre.

Gli estremi di un movimento riconosciuti da una regola dichiarata prima di guardare.Fonte: Binance Data Vision · Yahoo Finance · Periodo: 2000-01-03 … 2026-06-30 · Metodo: Un estremo diventa definitivo quando il prezzo si e' allontanato del 5% nella direzione opposta; la spezzata unisce gli estremi cosi' riconosciuti.
Espandi lo script di questo passo
lab_17_prezzo_e_tempo.py
python
tratti = movimenti(prezzi, SOGLIA)
estremi = sorted({i for coppia in tratti for i in coppia})
print(f"{len(tratti)} movimenti su {len(prezzi)} barre")

with avvio.figura():
    fig, ax = plt.subplots(figsize=(9, 4))
    fetta = slice(estremi[-14], len(prezzi))
    ax.plot(df["data"].to_numpy()[fetta], prezzi[fetta], linewidth=0.9)
    dentro = [i for i in estremi if i >= estremi[-14]]
    ax.plot(df["data"].to_numpy()[dentro], prezzi[dentro], marker="o", linewidth=1.4)
    ax.set_title(f"{SERIE}: gli ultimi movimenti riconosciuti a soglia {SOGLIA:.0%}")
    plt.show()

2. Quanto spiegano velocità, tempo e volume

Il bersaglio è l'ampiezza del movimento. I tre blocchi sono misurati sulla stessa finestra e con lo stesso trattamento: è ciò che rende leale il confronto. La ripartizione è quella di Shapley, cioè la media del contributo su tutti gli ordini di inserimento possibili — con variabili correlate, «quanto spiega questa» dipende dall'ordine, e la media è l'unica risposta che non lo sceglie a piacere.

Output

movimenti misurati       256
velocità (quota Shapley) 21.8%
tempo  (quota Shapley)   40.1%
volume (quota Shapley)   4.6%
tutte e tre insieme      66.5%
solo velocità e tempo    63.7%
il volume aggiunge       +2.8% di R quadro
Espandi lo script di questo passo
lab_17_prezzo_e_tempo.py
python
t = tavolo(prezzi, volumi, SOGLIA)
d = decomposizione(t)

print(f"movimenti misurati       {d['movimenti']:.0f}")
print(f"velocità (quota Shapley) {d['velocita']:.1%}")
print(f"tempo  (quota Shapley)   {d['tempo']:.1%}")
print(f"volume (quota Shapley)   {d['volume']:.1%}")
print(f"tutte e tre insieme      {d['totale']:.1%}")
print(f"solo velocità e tempo    {d['velocita_e_tempo']:.1%}")
print(f"il volume aggiunge       {d['guadagno_volume']:+.1%} di R quadro")

3. Il primo esercizio: prova a far cadere il risultato

La soglia dello zigzag è un parametro, e un parametro è sempre sospetto — vedi il capitolo sull'ottimizzare. Fallo variare e guarda se la conclusione si muove. Se si muovesse, il capitolo sarebbe da riscrivere.

Output

 soglia  movimenti velocità    tempo   volume
     2%        423    55.7%    32.8%    11.5%
     3%        353    44.4%    46.1%     9.5%
     5%        256    32.8%    60.2%     7.0%
     8%        171    22.4%    73.8%     3.8%
    10%        129    17.1%    78.8%     4.1%
    15%         68    10.8%    87.0%     2.2%
Espandi lo script di questo passo
lab_17_prezzo_e_tempo.py
python
print(f"{'soglia':>7s} {'movimenti':>10s} {'velocità':>8s} {'tempo':>8s} {'volume':>8s}")
for s in (0.02, 0.03, 0.05, 0.08, 0.10, 0.15):
    ts = tavolo(prezzi, volumi, s)
    if len(ts) < 40:
        print(f"{s:7.0%} {len(ts):10d}  (troppo pochi movimenti)")
        continue
    ds = decomposizione(ts)
    quota = ds["velocita"] + ds["tempo"] + ds["volume"]
    print(f"{s:7.0%} {ds['movimenti']:10.0f} {ds['velocita'] / quota:8.1%} "
          f"{ds['tempo'] / quota:8.1%} {ds['volume'] / quota:8.1%}")

4. Il secondo esercizio: togli il legame e guardalo sparire

Rimescola la colonna del volume fra i movimenti. Il volume resta lo stesso insieme di numeri, ma non appartiene più al movimento accanto a cui sta. Se la sua quota fosse rumore, non cambierebbe quasi nulla. Vedere una struttura sparire quando la si distrugge di proposito è il modo più diretto di convincersi che c'era.

Output

il volume vero aggiunge      +2.77%
un volume rimescolato        +0.14% in media, +0.57% nel 5% dei casi migliori
Espandi lo script di questo passo
lab_17_prezzo_e_tempo.py
python
rng = np.random.default_rng(0)
y = np.log(t.ampiezza)
velocita, tempo = np.log(t.velocita), np.log(t.durata)
volume = np.log(t.volume)

vero = r_quadro(y, [velocita, tempo, volume]) - r_quadro(y, [velocita, tempo])
finti = [
    r_quadro(y, [velocita, tempo, rng.permutation(volume)]) - r_quadro(y, [velocita, tempo])
    for _ in range(500)
]
print(f"il volume vero aggiunge      {vero:+.2%}")
print(f"un volume rimescolato        {np.mean(finti):+.2%} in media, "
      f"{np.percentile(finti, 95):+.2%} nel 5% dei casi migliori")

5. Da dove viene il volume: il calendario

I derivati non scadono quando capita. Sull'IDEM di Borsa Italiana indici e azioni scadono il terzo venerdì del mese; sui future e sulle opzioni in criptovaluta la scadenza mensile è l'ultimo venerdì. Sono date pubbliche, note con anni di anticipo, che non dicono niente su dove andrà il prezzo.

Output

btcusdt: 105 giorni di scadenza (ultimo venerdì)
volume mediano in scadenza   1.143
volume mediano negli altri   0.994
eccesso                      +4.9%
Espandi lo script di questo passo
lab_17_prezzo_e_tempo.py
python
e = effetto_scadenza(df["data"].to_list(), volumi, cripto=CRIPTO)
quale = "ultimo venerdì" if CRIPTO else "terzo venerdì"
print(f"{SERIE}: {e['scadenze']} giorni di scadenza ({quale})")
print(f"volume mediano in scadenza   {e['mediana_scadenza']:.3f}")
print(f"volume mediano negli altri   {e['mediana_normale']:.3f}")
print(f"eccesso                      {e['eccesso']:+.1%}")

6. Il terzo esercizio: il test placebo sulla scadenza

Sposta la data di scadenza di una o due settimane. Restando di venerdì il confronto non cambia natura: cambia solo il fatto che quel venerdì non era una scadenza. Se l'eccesso di volume viene davvero dalla scadenza, sulle date finte deve sparire. Se restasse, staremmo misurando qualcos'altro.

Su ENI il salto è netto: la data vera sta a +39%, le finte fra il −15% e il +3%. Su Bitcoin la data vera sta a +5% e le finte oscillano fra −5% e +3%: cioè il +5% è dentro il rumore delle date sbagliate, e la conclusione onesta è che lì la scadenza non si vede.

Output

scadenza -14 giorni: eccesso   -0.1%
scadenza  -7 giorni: eccesso   +3.0%
scadenza       vera: eccesso   +4.9%
scadenza  +7 giorni: eccesso   -5.0%
scadenza +14 giorni: eccesso   +0.7%
Espandi lo script di questo passo
lab_17_prezzo_e_tempo.py
python
import datetime as dt

date = df["data"].to_list()
for spostamento in (-14, -7, 0, 7, 14):  # PROVA / TRY: aggiungi altri spostamenti (esercizio 3)
    finte = [g + dt.timedelta(days=spostamento) for g in date]
    e2 = effetto_scadenza(finte, volumi, cripto=CRIPTO)
    etichetta = "vera" if spostamento == 0 else f"{spostamento:+d} giorni"
    print(f"scadenza {etichetta:>10s}: eccesso {e2['eccesso']:+7.1%}")

Cosa portarti via

  1. Un movimento ha due coordinate, e la seconda — il tempo — di solito pesa più della prima.
  2. Il volume ha avuto le stesse identiche condizioni delle altre due colonne e non è entrato. Non perché sia inutile: perché quello che dice, prezzo e tempo lo dicevano già.
  3. Buona parte di ciò che resta del volume è calendario. Il calendario è una forma del tempo, non una terza dimensione.

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

  • ftsemib.parquet107,0 KB

    sha256 ad37e8ac1ea979dc9ecf9f2dff58151166559945549ddb3f692066b1e3b85a78

    Fonte: Yahoo Finance · Periodo: 2000-01-03 → 2026-06-30 · 6757 righe · estratto 2026-08-17

  • eni.parquet212,1 KB

    sha256 6db634e458915b7007412eb67ca098218fab6d7214b9f07b4ee4a6f46f060fca

    Fonte: Yahoo Finance · Periodo: 2000-01-03 → 2026-06-30 · 6767 righe · estratto 2026-08-17

  • enel.parquet211,8 KB

    sha256 f96245c6b593edccaf8c26dfa325ca45efebe9e5b9a69c6cd691a936f01fde93

    Fonte: Yahoo Finance · Periodo: 2000-01-03 → 2026-06-30 · 6767 righe · estratto 2026-08-17

  • intesa.parquet213,8 KB

    sha256 25b40b095ee7f72878b7fab98a709d6c1aca178b5b7351e8ac87292fb8da76f0

    Fonte: Yahoo Finance · Periodo: 2000-01-03 → 2026-06-30 · 6767 righe · estratto 2026-08-17

  • generali.parquet210,7 KB

    sha256 7e14bf4937ce324e370ac9d216bfacb30604d4e0e87f79c4978ec7376daeea6a

    Fonte: Yahoo Finance · Periodo: 2000-01-03 → 2026-06-30 · 6767 righe · estratto 2026-08-17

Torna all’indice dei lab