← Torna agli articoli
August 7, 2026
5 min di lettura

Toda-Yamamoto vs Differenced Granger: Does the BTC Lead-Lag Survive?

Toda-Yamamoto vs Differenced Granger: Does the BTC Lead-Lag Survive?
#causal-inference
#Granger-causality
#lead-lag
#crypto
#VAR

Quasi tutti i risultati di causalità Granger pubblicati sulle criptovalute vengono calcolati sui rendimenti di registro. Questa scelta non è gratuita. La differenziazione rende la serie stazionaria, come richiesto dal test F standard, ma scarta anche la relazione di livello e, se due monete vengono cointegrate, il VAR differenziato viene specificato in modo errato e il test risponde a una domanda leggermente diversa da quella posta.

Toda e Yamamoto (1995) offrono una via d'uscita: adattare il VAR ai livelli con ritardi aggiuntivi, testare solo quelli originali e ottenere un valore valido χ2\chi^2 indipendentemente dalle radici unitarie o dalla cointegrazione. È ben noto in econometria e quasi mai applicato al lead-lag crittografico.

Quindi questo articolo fa una cosa: eseguire entrambi i test sulle stesse coppie, nella stessa finestra, e segnalare se non sono d'accordo. Quindi applicare il follow-up onesto: una correzione di test multipli su famiglie correlate sulla matrice risultante e un valore p mobile per vedere se il ritardo "significativo" è sufficientemente stabile per essere scambiato o semplicemente supera la soglia.

La teoria seguente è solo quanto necessario per rendere leggibile il confronto. Il blog tratta già radici unitarie, cointegrazione, caricamento dei dati, dimensionamento e costi; questi sono collegamenti, non sezioni.

Ciò che la causalità di Granger afferma effettivamente

Percorsi direzionali lead-lag negli eventi di mercato

La causalità di Granger è una precedenza predittiva, non un meccanismo: XX Cause Granger YY se passato XX riduce la varianza dell'errore di previsione di YY al di là di ciò che è passato YY già spiega.

XX Cause Granger YY Se σ2(YtYt1,,Ytp)>σ2(YtYt1,,Ytp,Xt1,,Xtp)\sigma^2(Y_t \mid Y_{t-1}, \ldots, Y_{t-p}) > \sigma^2(Y_t \mid Y_{t-1}, \ldots, Y_{t-p}, X_{t-1}, \ldots, X_{t-p})

Due serie guidate da un fattore nascosto comune possono mostrare la causalità di Granger senza alcun collegamento diretto tra loro. Nelle criptovalute questo avvertimento non è accademico: BTC è un singolo fattore dominante nell'intero complesso delle altcoin, quindi quasi ogni "causalità" da alt ad alt è un candidato artefatto con velocità di risposta diverse allo stesso shock di BTC.

Il quadro VAR

Il test vive all'interno di un'Autoregressione Vettoriale. Per due variabili con pp ritardi:

Yt=α0+i=1pαiYti+i=1pβiXti+ϵ1tY_t = \alpha_0 + \sum_{i=1}^{p} \alpha_i Y_{t-i} + \sum_{i=1}^{p} \beta_i X_{t-i} + \epsilon_{1t} Xt=γ0+i=1pγiXti+i=1pδiYti+ϵ2tX_t = \gamma_0 + \sum_{i=1}^{p} \gamma_i X_{t-i} + \sum_{i=1}^{p} \delta_i Y_{t-i} + \epsilon_{2t}

In forma matriciale, un VAR(p) con kk variabili:

yt=c+A1yt1++Apytp+ut\mathbf{y}_t = \mathbf{c} + \mathbf{A}_1 \mathbf{y}_{t-1} + \cdots + \mathbf{A}_p \mathbf{y}_{t-p} + \mathbf{u}_t

Testare se XX Cause Granger YY sta testando la restrizione congiunta H0:β1==βp=0H_0: \beta_1 = \cdots = \beta_p = 0 nella prima equazione. Il percorso standard è un test F sulle somme residue dei quadrati ristrette e illimitate,

F=(RSSrRSSu)/pRSSu/(T2p1)F = \frac{(RSS_r - RSS_u) / p}{RSS_u / (T - 2p - 1)}

con la forma di Wald asintoticamente equivalente W=T(RSSrRSSu)/RSSudχ2(p)W = T \cdot (RSS_r - RSS_u)/RSS_u \xrightarrow{d} \chi^2(p). Tutto ciò che segue è una domanda su a quali coefficienti viene applicata tale restrizione e su quali dati è stato adattato il VAR.

Selezione del ritardo

pp conta più di quanto ammetta la maggior parte degli articoli: troppo pochi ritardi non colgono la dinamica, troppi bruciano gradi di libertà e distruggono il potere.

AIC(p)=lnΣ^u(p)+2pk2T,BIC(p)=lnΣ^u(p)+pk2lnTT\text{AIC}(p) = \ln|\hat{\Sigma}_u(p)| + \frac{2pk^2}{T}, \qquad \text{BIC}(p) = \ln|\hat{\Sigma}_u(p)| + \frac{pk^2 \ln T}{T} HQIC(p)=lnΣ^u(p)+2pk2ln(lnT)T\text{HQIC}(p) = \ln|\hat{\Sigma}_u(p)| + \frac{2pk^2 \ln(\ln T)}{T}

Intervalli di ricerca ragionevoli: 1-60 sui dati dei secondi, 1-30 sui dati dei minuti, 1-48 sui dati orari. BIC è il valore predefinito corretto in questo caso: è il criterio più parsimonioso e il lead-lag nelle coppie di criptovalute liquide è un fenomeno a memoria corta. Ogni risultato riportato di seguito riporta il BIC selezionato pp per coppia anziché fissare un ritardo nell’universo, perché un ritardo fisso converte silenziosamente una scelta di selezione del ritardo in un’affermazione di significatività.

Perché il test differenziale è l'impostazione predefinita sbagliata

La differenziazione può frammentare una relazione lead-lag

Le serie dei prezzi delle criptovalute sono I(1). La soluzione abituale, ovvero i rendimenti logaritmici, acquista stazionarietà a scapito del livello di cointegrazione e, se la coppia condivide un equilibrio di lungo periodo, il VAR differenziale viene specificato in modo errato. Il meccanismo di radice unitaria e di cointegrazione dietro quella frase (ADF, Engle-Granger con i suoi valori critici Monte-Carlo, Johansen su un sistema VAR) è già trattato in arbitraggio statistico e trading di coppie; supponiamolo qui. Il punto è solo che il rimedio abituale – la differenza, quindi il test – è una decisione modellante con conseguenze, e Toda-Yamamoto è il modo per evitare di farlo.

La procedura Toda-Yamamoto

Montare un VAR con p+dmaxp + d_{max} ritardi, dove pp è l'ordine di ritardo ottimale e dmaxd_{max} è l'ordine di integrazione massimo della serie, quindi restrizioni al test solo sulla prima pp ritardi. L'extra dmaxd_{max} i ritardi assorbono la non stazionarietà; la statistica Wald sul primo pp i coefficienti seguono uno standard χ2(p)\chi^2(p) indipendentemente dal fatto che le serie siano I(0), I(1) o cointegrate.

  1. Determinare dmaxd_{max} — ADF e KPSS su ciascuna serie. Per i prezzi delle criptovalute dmax=1d_{max} = 1 quasi sempre.
  2. Seleziona pp — adattare un VAR ai livelli, scegliere tramite BIC.
  3. Stima il VAR aumentato(p+dmaxp + d_{max}) su livelli, nessuna differenziazione.
  4. Wald-test il primo pp solo ritardi, ignorando il dmaxd_{max} extra. Il risultato è χ2(p)\chi^2(p).

Il vantaggio: nessun pre-test di cointegrazione, nessuna differenziazione e dimensione del test corretta: il tasso di rifiuto sotto il valore nullo rimane vicino al nominale qualunque siano le proprietà di integrazione. Il costo è che il passaggio 4 non è quello test_causality lo fa per impostazione predefinita, ed è qui che la maggior parte delle implementazioni vanno tranquillamente storte.

Implementazione

Flusso di implementazione della matrice lag

Supponiamo che tu abbia già allineato le barre dei minuti in un DataFrame: il boilerplate di recupero e indice di ccxt è in rilevamento del regime con HMM e il wrapper ADF che utilizzeresti per il passaggio 1 è in arbitraggio statistico e scambio di coppie.

Una nota sulla versione prima dell'esecuzione di tutto ciò: grangercausalitytests(..., verbose=False) è stato deprecato e poi rimosso in statsmodels 0.15. Rilascia l'argomento (la funzione non viene più stampata per impostazione predefinita) o pin statsmodels<0.15. Il codice seguente presuppone la firma moderna.

Toda-Yamamoto, fatto correttamente

La restrizione deve essere costruita a mano. VARResults.test_causality testa tutti i ritardi della variabile causale nel modello adattato — su un VAR(p+1p+1) che include il dmaxd_{max} ritardo nella restrizione, che è esattamente ciò che Toda-Yamamoto dice di non fare. La correzione è esplicita RR matrice contro la covarianza completa dei coefficienti:

import numpy as np
from scipy.stats import chi2
from statsmodels.tsa.api import VAR


def toda_yamamoto_test(data, target, predictor, max_lag=15, d_max=1,
                       significance=0.05):
    """
    Toda-Yamamoto Granger causality on levels (no differencing).

    Fits VAR(p + d_max) and applies a Wald test to the predictor's
    coefficients at lags 1..p ONLY, leaving the d_max augmenting lags
    unrestricted. Statistic is chi2(p).
    """
    pair = data[[target, predictor]].dropna()
    n_vars = pair.shape[1]
    target_idx = list(pair.columns).index(target)
    pred_idx = list(pair.columns).index(predictor)

    model = VAR(pair)
    p = model.select_order(maxlags=max_lag).bic or 1
    res = model.fit(p + d_max)

    beta = res.params.values.ravel(order="F")
    cov = res.cov_params()
    cov = cov.values if hasattr(cov, "values") else np.asarray(cov)

    n_per_eq = res.params.shape[0]
    idx = [target_idx * n_per_eq + 1 + lag * n_vars + pred_idx
           for lag in range(p)]                      # first p lags only

    R = np.zeros((p, beta.size))
    R[np.arange(p), idx] = 1.0

    Rb = R @ beta
    V = R @ cov @ R.T
    wald = float(Rb @ np.linalg.solve(V, Rb))        # no pinv: V must be PD
    p_value = float(chi2.sf(wald, df=p))

    return {
        "target": target, "predictor": predictor,
        "p": p, "augmented_lag": p + d_max, "d_max": d_max,
        "wald_stat": wald, "p_value": p_value,
        "significant": p_value < significance,
    }

Due dettagli che è facile sbagliare e che, se lo si fa, invalidano il test. Innanzitutto, l'aritmetica dell'indice deve corrispondere al modo in cui i modelli statistici si appiattiscono params — verificare su un modello montato che beta[target_idx * n_per_eq + 1] è uguale res.params.iloc[1, target_idx] prima di fidarsi di qualsiasi valore p. In secondo luogo, utilizzare np.linalg.solve, non pinv: Se VV è singolare, la restrizione è degenerata e l'esecuzione dovrebbe fallire rumorosamente anziché restituire un numero dall'aspetto plausibile. Uno pseudo-inverso qui è il modo in cui i test Wald interrotti sopravvivono alla revisione del codice.

Granger differenziato standard, per confronto

La linea di base rispetto al confronto: rendimenti logaritmici, stesse coppie, stesso ritardo BIC:

from statsmodels.tsa.stattools import grangercausalitytests


def differenced_granger(data, target, predictor, p):
    """Standard Granger test on log-returns at a fixed lag p."""
    pair = data[[target, predictor]].dropna()   # returns, not levels
    out = grangercausalitytests(pair, maxlag=[p])   # statsmodels >= 0.15
    f_stat, p_value = out[p][0]["ssr_ftest"][:2]
    return {"target": target, "predictor": predictor, "lag": p,
            "f_stat": f_stat, "p_value": p_value}

Nota maxlag=[p] piuttosto che maxlag=p: passando un int viene eseguito ogni ritardo da 1 a pp e ti invita a segnalare quello migliore, ovvero un test multiplo non registrato in aggiunta al test multiplo che stai già eseguendo.

Il confronto

Per ciascuna coppia, eseguire il test differenziale sui rendimenti e Toda-Yamamoto sui livelli, allo stesso BIC selezionato ppe tabulare dove i due non sono d'accordo. Il disaccordo è la cellula interessante: una coppia significativa sulle differenze ma non sui livelli è un artefatto candidato per differenziare una relazione di cointegrazione; il contrario suggerisce che la relazione di livello trasporta informazioni che il test di ritorno non può vedere.

Confronti multipli su una matrice di causalità

Filtraggio di una matrice di causalità di grandi dimensioni

Il pieno n×nn \times n Matrix è dove questo diventa pericoloso. Uno sweep di 20 asset e 10 ritardi corrisponde a 3.800 test di ipotesi, e Bonferroni è la correzione sbagliata per una famiglia così correlata: i test condividono dati, condividono il fattore BTC e non sono neanche lontanamente indipendenti, quindi Bonferroni è allo stesso tempo troppo conservatore nel complesso e fuorviante su quali cellule sopravvivono. Utilizza il meccanismo N efficace dell'articolo deflated Sharpe, che è stato creato esattamente per questa situazione: raggruppa la famiglia di test correlata, conta le prove indipendenti anziché quelle grezze e stabilisci una soglia rispetto a ciò.

def granger_matrix(data, max_lag=15, d_max=1):
    """Toda-Yamamoto p-value matrix. Entry (i, j): does col_j cause col_i?"""
    cols = list(data.columns)
    out = pd.DataFrame(np.nan, index=cols, columns=cols, dtype=float)
    for target in cols:
        for predictor in cols:
            if target == predictor:
                continue
            r = toda_yamamoto_test(data, target, predictor,
                                   max_lag=max_lag, d_max=d_max)
            out.loc[target, predictor] = r["p_value"]
    return out

Il numero che conta non è quante celle sono inferiori a 0,05, ma quante sopravvivono alla soglia corretta per N effettivo.

Leggi la matrice sopravvissuta strutturalmente, non cella per cella: le righe in cui una risorsa ne causa molte altre confermano una gerarchia di informazioni; una colonna causata dal nulla suggerisce dinamiche idiosincratiche, specifiche del token piuttosto che una scoperta.

Il test onesto: stabilità al rotolamento

Segnale stabile attraverso finestre scorrevoli

Un singolo valore p nel campione è quasi inutile per il trading. La questione rilevante è se il significato persiste. Le relazioni tra asset nelle criptovalute dipendono dal regime — la struttura di correlazione differisce nettamente tra calma e panico, e il regime stesso è stimabile (rilevamento del regime HMM) — quindi un lead-lag stimato su una finestra è una dichiarazione su quella finestra.

def rolling_granger(data, target, predictor, window=500, lag=5, step=50):
    """Rolling-window p-value: when is the lead-lag active vs dormant?"""
    rows = []
    for start in range(0, len(data) - window, step):
        chunk = data.iloc[start:start + window][[target, predictor]].dropna()
        if len(chunk) < window * 0.8:
            continue
        try:
            out = grangercausalitytests(chunk, maxlag=[lag])
            f_stat, p_value = out[lag][0]["ssr_ftest"][:2]
        except Exception:
            f_stat, p_value = np.nan, np.nan
        rows.append({"timestamp": data.index[start + window - 1],
                     "f_stat": f_stat, "p_value": p_value})
    return pd.DataFrame(rows).set_index("timestamp")

Riporta due cose da questa serie: la frazione di finestre inferiori a 0,05 e il numero di superamenti della soglia. Una relazione significativa nell'80% delle finestre con tre incroci è un oggetto diverso da una relazione significativa nel 55% delle finestre con quaranta incroci, anche se il valore p aggregato è identico. Il secondo non è negoziabile: non è possibile dimensionare una posizione su un segnale la cui esistenza si inverte ogni poche centinaia di barre, e il ritardo di rivalutazione garantisce che si negozi sempre il regime precedente.

La trappola dei dati specifica di Granger

I tick crittografici irregolari interrompono i segnali causali

L'igiene generica dei dati crittografici è trattata altrove: lacune e candele mancanti nella parità backtest-live, disciplina del timestamp nella tassonomia dei bias look-ahead e dimostrazione dell'assenza di look-ahead attraverso i tempi. Una modalità di errore, tuttavia, è specifica di Granger e abbastanza grave da poter essere nominata:

Il riempimento in avanti produce il risultato. Una candela riempita in avanti ripete la chiusura precedente, che inietta autocorrelazione pura nella serie e autocorrelazione lag-1 in YY con cui è meccanicamente allineato XX è indistinguibile, per il test F, da una relazione causale con un solo ritardo. Lo stesso vale per tutte le sedi: un sfalsamento dell'orologio di 1 secondo tra due scambi sulle barre dei minuti può creare o distruggere completamente il significato del ritardo 1, perché sposta il lato del confine della barra su cui si ferma una mossa. Prima di eseguire qualsiasi operazione, verificare che gli spazi vuoti vengano eliminati anziché riempiti e che entrambe le serie siano contrassegnate dallo stesso orologio.

Risultati lead-lag riportati in letteratura

Costellazione di prove per la ricerca lead-lag

Queste sono citazioni, non misurazioni da questo articolo. Sono qui per dire ciò che afferma il documento pubblicato, in modo che il confronto di cui sopra abbia qualcosa con cui essere d'accordo o in disaccordo.

  • BTC in altcoin. Uno studio del 2026 in Mercati finanziari dell'Asia-Pacifico (Springer) riporta una trasmissione dei prezzi ad alta frequenza da Bitcoin agli altcoin, con monete a piccola capitalizzazione che mostrano risposte significativamente ritardate e una minore liquidità associata a reazioni più lente. L'entità del ritardo comunemente citata da questa linea di lavoro - circa 1-3 minuti da BTC a ETH, più lunga per le società a media e piccola capitalizzazione, sempre più unidirezionale man mano che la capitalizzazione di mercato scende - sono risultati citati, non riderivati ​​qui. Il livello di capitalizzazione di mercato su cui si basano è la stessa scala utilizzata nella convalida multi-simbolo.
  • CEX guida DEX. Ricerca sulla microstruttura crittografica (MDPI) segnala sedi centralizzate che dominano la scoperta dei prezzi, con un flusso di informazioni che va da CEX a DEX e nessuna causalità inversa significativa, coerente con motori di corrispondenza inferiori al millisecondo rispetto alla liquidazione in tempo di blocco.
  • ETH come moneta indipendente. Operazione VAR-SVAR (MDPI) trova regimi in cui Ethereum funge da fonte e Bitcoin come destinatario dello spillover, in particolare durante le fasi guidate da DeFi e NFT.

Cross-exchange, in breve

Il ritardo della stessa moneta in due sedi è una configurazione reale, ma i suoi aspetti economici - soglie tariffarie, scala di latenza, perché il ritardo esiste - sono già elaborati in arbitraggio statistico e trading di coppie e il kimchi premium. L'unica cosa che Granger aggiunge è una domanda sulla stabilità: la causalità tra Binance e Coinbase è persistente significativa o tremola come tutto il resto? Esegui il test a rotazione sulla coppia di sedi; la risposta è una serie temporale di valori p, non un sì.

Trasformare un ritardo sopravvissuto in una posizione

La soglia del segnale diventa una posizione controllata

Se una coppia supera la soglia corretta e il test a rotazione, il segnale in sé è banale: rendimento cumulativo del predittore sulla finestra di ritardo, con soglia.

def lead_lag_signal(btc_returns, alt_returns, lag=5, threshold=0.001):
    """+1 / -1 / 0 on ALT from BTC's cumulative return over `lag` bars."""
    btc_cum = btc_returns.rolling(lag).sum()
    signal = pd.Series(0, index=alt_returns.index)
    signal[btc_cum > threshold] = 1
    signal[btc_cum < -threshold] = -1
    return signal

Tre cose che non fanno parte di questo articolo perché sono già stabilite qui:

Limitazioni

Confini dell'incertezza causale

La causalità di Granger non è causalità. I confondenti (un macro evento che sposta entrambe le gambe con un leggero disallineamento del timestamp), i driver comuni (due alt che seguono entrambi BTC a velocità diverse) e le variabili omesse (testare BTC su DOGE senza ETH nel sistema) producono tutti statistiche significative senza flusso di informazioni diretto. Limitarsi ai test bivariati, come fa questo articolo, peggiora il problema delle variabili omesse, anziché migliorarlo.

Linearità. Il test vede solo la struttura predittiva lineare nella media condizionale. Le criptovalute presentano clustering di volatilità, effetti leva e cambi di regime: consulta Previsione della volatilità GARCH e Correlazione dinamica DCC-GARCH per la storia del secondo momento. Alternative non lineari che vale la pena conoscere: il test del kernel di Diks-Panchenko (2006), l'entropia di trasferimento come analogo della teoria dell'informazione e la causalità copula di Granger attraverso l'intera distribuzione congiunta.

Interruzioni strutturali. Qualsiasi relazione stimata su una finestra è condizionata al regime di quella finestra; il test a rotazione sopra riportato è la diagnostica minima ed è una diagnostica, non una correzione.

Riepilogo

Segnale lead-lag robusto distillato dal rumore

  1. Adatta il VAR sui livelli, seleziona pp di BIC per coppia, aumentare di dmaxd_{max}, e Wald-test il primo pp solo ritardi. Costruisci a mano la matrice di restrizione: quella della biblioteca test_causality limita anche il ritardo crescente, che non è Toda-Yamamoto.
  2. Esegui il test dei rendimenti differenziati come base di riferimento sulle stesse coppie e ritardo e segnala i disaccordi. Quella tabella è la scoperta; una matrice di asterischi non lo è.
  3. Correggere la matrice per una famiglia di test correlata tramite N efficace, non Bonferroni, e riportare quante cellule sopravvivono.
  4. Arrotolalo. Riportare la frazione di finestre significative e il conteggio degli incroci. Significativo che lo sfarfallio non è un ritardo negoziabile, non importa quanto piccolo sia il valore p aggregato.
  5. Elimina i buchi invece di riempirli in avanti e controlla l'allineamento dell'orologio tra le sedi, prima di credere a qualsiasi risultato di ritardo 1.

Un risultato perfettamente positivo di questa procedura è negativo: una relazione BTC-alt che è significativa nel campione, sopravvive alla correzione e attraversa ancora la linea 0,05 in entrambe le direzioni ogni poche centinaia di barre è un risultato reale sulla commerciabilità e vale la pena pubblicarlo come tale.

Riferimenti

Disclaimer: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.