← К списку статей
August 7, 2026
5 мин. чтения

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

Почти каждый опубликованный результат теста причинности по Грейнджеру для криптовалют рассчитывается по логарифмическим доходностям. Такой выбор не бесплатен. Дифференцирование делает ряд стационарным, чего требует стандартный F-тест, но одновременно отбрасывает связь между уровнями. Если две монеты коинтегрированы, VAR для разностей задана неверно, и тест отвечает уже на немного другой вопрос.

Тода и Ямамото (1995) предлагают выход: оценить VAR на уровнях с дополнительными лагами, проверять только исходные лаги и получить корректную статистику χ2\chi^2 независимо от наличия единичных корней или коинтеграции. Этот подход хорошо известен в эконометрике, но почти не применяется к анализу опережения и запаздывания в криптовалютах.

Итак, эта статья делает одну вещь: запускает оба теста для одних и тех же пар на одном и том же окне и показывает, расходятся ли их выводы. Затем применяется честное продолжение: поправка на множественное тестирование для коррелированного семейства к полученной матрице и скользящее p-значение, чтобы понять, достаточно ли устойчив значимый лаг для торговли или он лишь время от времени пересекает порог.

Ниже приведен только тот объем теории, который нужен для понятного сравнения. Единичные корни, коинтеграция, загрузка данных, определение размера позиции и издержки уже разобраны в блоге; здесь на них стоят ссылки, а не создаются отдельные разделы.

Что на самом деле утверждает причинно-следственная связь Грейнджер

Направленные пути опережения и отставания в рыночных событиях

Причинность по Грейнджеру — это опережение в прогнозировании, а не механизм: XX является причиной YY по Грейнджеру, если прошлые значения XX уменьшают дисперсию ошибки прогноза YY сверх того, что уже объясняется прошлыми значениями YY.

XX Грейнджер-причины YY если σ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})

Два ряда, движимые общим скрытым фактором, могут демонстрировать причинность по Грейнджеру без прямой связи друг с другом. Для криптовалют это не отвлеченная оговорка: BTC — доминирующий фактор для всего комплекса альткоинов, поэтому почти любая причинность между альткоинами может оказаться артефактом разной скорости реакции на один и тот же шок BTC.

Структура VAR

Тест выполняется внутри векторной авторегрессии. Для двух переменных с pp лагами:

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}

В матричной форме VAR(p) с kk переменными:

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

Проверка того, является ли XX причиной YY по Грейнджеру, сводится к совместному ограничению H0:β1==βp=0H_0: \beta_1 = \cdots = \beta_p = 0 в первом уравнении. Стандартный подход — F-тест для сравнения ограниченной и неограниченной моделей по суммам квадратов остатков.

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

с асимптотически эквивалентной формой Вальда W=T(RSSrRSSu)/RSSudχ2(p)W = T \cdot (RSS_r - RSS_u)/RSS_u \xrightarrow{d} \chi^2(p). Все дальнейшее сводится к вопросу о том, к каким коэффициентам применяется это ограничение и на каких данных оценивается VAR.

Выбор задержки

pp важнее, чем принято считать: слишком малое число лагов упускает динамику, а слишком большое расходует степени свободы и снижает мощность теста.

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}

Разумные диапазоны поиска: 1-60 для секундных данных, 1-30 для минутных и 1-48 для часовых. Здесь разумно использовать BIC по умолчанию: это более экономный критерий, а опережение и запаздывание в ликвидных криптовалютных парах обычно имеют короткую память. Ниже для каждой пары используется выбранный по BIC pp, а не один фиксированный лаг для всей совокупности, поскольку фиксированный лаг незаметно превращает решение о выборе лага в утверждение о значимости.

Почему тест на разностях не должен быть вариантом по умолчанию

Дифференцирование может разрушить связь опережения и запаздывания

Ценовые ряды криптовалют имеют порядок интегрирования I(1). Обычное решение — перейти к логарифмическим доходностям — обеспечивает стационарность ценой потери коинтеграционной связи уровней. Если у пары есть долгосрочное равновесие, VAR для разностей задана неверно. Математическая база этого утверждения — тесты единичного корня и коинтеграции (ADF, Энгла-Грейнджера с критическими значениями Монте-Карло и Йохансена для системы VAR) — уже разобрана в статье о статистическом арбитраже и парной торговле; здесь примем ее как данность. Важно лишь, что стандартная процедура "взять разности, затем тестировать" является модельным решением с последствиями, а Тода-Ямамото позволяет его избежать.

Процедура Тода-Ямамото

Оцените VAR с p+dmaxp + d_{max} лагами, где pp — оптимальный порядок лага, а dmaxd_{max} — максимальный порядок интегрирования рядов, и проверяйте ограничения только для первых pp лагов. Дополнительные dmaxd_{max} лагов поглощают нестационарность; статистика Вальда для первых pp коэффициентов имеет стандартное распределение χ2(p)\chi^2(p) независимо от того, имеют ли ряды порядок I(0), I(1) или являются коинтегрированными.

  1. Определите dmaxd_{max} — примените ADF и KPSS к каждому ряду. Для цен криптовалют dmax=1d_{max} = 1 почти всегда.
  2. Выберите pp — оцените VAR на уровнях и выберите порядок по BIC.
  3. Оцените расширенную VAR(p+dmaxp + d_{max}) на уровнях, без дифференцирования.
  4. Проведите тест Вальда только для первых pp лагов, игнорируя дополнительные dmaxd_{max}. Результат имеет распределение χ2(p)\chi^2(p).

Преимущество: не нужен предварительный тест на коинтеграцию, не нужно дифференцировать ряд, а уровень значимости теста остается корректным — частота отклонения нулевой гипотезы при ее истинности близка к номинальной независимо от свойств интегрирования. Цена в том, что шаг 4 не выполняется функцией test_causality по умолчанию; именно здесь большинство реализаций незаметно ошибается.

Реализация

Последовательность реализации матрицы лагов

Предположим, что у вас уже есть синхронизированные минутные бары в DataFrame. Шаблон получения и индексации данных через ccxt приведен в статье об определении режима с помощью HMM, а оболочка ADF для шага 1 — в статье о статистическом арбитраже и парной торговле.

Перед запуском есть важное замечание о версии: grangercausalitytests(..., verbose=False) сначала объявили устаревшей конструкцией, а затем удалили в statsmodels 0.15. Уберите этот аргумент (теперь функция по умолчанию ничего не печатает) или зафиксируйте statsmodels<0.15. Ниже используется современная сигнатура.

Тода-Ямамото, сделано правильно

Ограничение нужно построить вручную. VARResults.test_causality проверяет все лаги объясняющей переменной в оцененной модели — в том числе дополнительный лаг расширенной VAR(p+1p+1), который включает dmaxd_{max}-й лаг в ограничение. Именно этого метод Тода-Ямамото делать не разрешает. Исправление — явно задать матрицу RR относительно полной ковариационной матрицы коэффициентов:

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,
    }

Здесь легко допустить две ошибки, каждая из которых делает тест недействительным. Во-первых, арифметика индексов должна соответствовать способу, которым statsmodels раскладывает params. На подогнанной модели проверьте, что beta[target_idx * n_per_eq + 1] равно res.params.iloc[1, target_idx], и только после этого доверяйте p-значениям. Во-вторых, используйте np.linalg.solve, а не pinv: если VV вырождена, ограничение является вырожденным, и выполнение должно завершиться явной ошибкой, а не вернуть правдоподобное число. Псевдообратная матрица позволяет сломанным тестам Вальда переживать проверку кода.

Стандартный тест Грейнджера на разностях

Базовая точка сравнения — логарифмические доходности, те же пары и тот же выбранный по 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}

Обратите внимание на maxlag=[p], а не maxlag=p: передача целого числа запускает тест для каждого лага от 1 до pp и подталкивает выбрать лучший результат. Это незарегистрированный тест на множественные гипотезы поверх уже выполняемого множественного тестирования.

Сравнение

Для каждой пары запустите тест на разностях доходностей и тест Тода-Ямамото на уровнях с одним и тем же выбранным по BIC значением pp, затем составьте таблицу расхождений. Именно расхождения представляют интерес: значимость на разностях при отсутствии значимости на уровнях указывает на возможный артефакт, возникший из-за удаления коинтеграционной связи; обратная картина говорит о том, что связь уровней содержит информацию, недоступную тесту доходностей.

Множественные сравнения в матрице причинности

Фильтрация большой матрицы причинности

Именно полная матрица n×nn \times n создает опасность. Перебор 20 активов и 10 лагов дает 3800 проверок гипотез, а поправка Бонферрони плохо подходит для настолько коррелированного семейства: тесты используют одни и те же данные, общий фактор BTC и далеко не независимы. Поэтому Бонферрони одновременно чрезмерно консервативна в целом и вводит в заблуждение относительно того, какие ячейки действительно проходят порог. Используйте механику эффективного N из статьи о дефлированном коэффициенте Шарпа о дефлированном коэффициенте Шарпа, созданную именно для такого случая: кластеризуйте коррелированное семейство тестов, считайте независимые испытания, а не исходное их число, и задавайте порог относительно этого количества.

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

Важно не то, сколько ячеек имеют p-значение ниже 0,05, а то, сколько из них переживают порог, скорректированный по эффективному N.

Читайте выжившую матрицу структурно, а не по одной ячейке: строки, в которых один актив является причиной многих других, подтверждают информационную иерархию; столбец, для которого нет причин со стороны других активов, скорее указывает на специфичную для токена динамику, чем на новое открытие.

Честный тест: устойчивость во времени

Устойчивый сигнал на скользящих окнах

Одно внутривыборочное p-значение почти бесполезно для торговли. Важен другой вопрос: сохраняется ли значимость. Межактивные связи в криптовалютах зависят от режима — структура корреляций резко меняется между спокойным рынком и паникой, а сам режим можно оценить с помощью HMM. Поэтому оценка опережения и запаздывания на одном окне является утверждением только об этом окне.

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")

Для этого ряда нужно сообщить две величины: долю окон с p-значением ниже 0,05 и число пересечений порога. Связь, значимая в 80% окон и имеющая три пересечения, — это не то же самое, что связь, значимая в 55% окон и имеющая сорок пересечений, даже при одинаковом объединенном p-значении. Второй сигнал нельзя торговать: невозможно определить размер позиции по сигналу, существование которого меняется каждые несколько сотен баров, а задержка переоценки гарантирует, что вы всегда торгуете уже прошедший режим.

Ловушка данных, характерная для Грейнджер

Нерегулярные крипто-тики нарушают причинные сигналы

Общие правила гигиены криптовалютных данных разобраны в других статьях: пропуски и отсутствующие свечи — в статье о соответствии бэктеста и реальной торговли, дисциплина временных меток — в таксономии смещения заглядыванием вперед и в статье о доказательстве отсутствия заглядывания вперед между таймфреймами. Но одна проблема специфична для теста Грейнджера и достаточно серьезна, чтобы выделить ее отдельно:

Заполнение впередом создает результат. Свеча, заполненная впередом, повторяет предыдущее закрытие и добавляет в ряд чистую автокорреляцию. Автокорреляция первого лага в YY, механически совпадающая с XX, неотличима для F-теста от причинной связи с одним лагом. То же относится к разным площадкам: сдвиг часов на одну секунду между двумя биржами на минутных барах может полностью создать или уничтожить значимость первого лага, поскольку меняет, по какую сторону границы бара попадет движение. Перед запуском теста убедитесь, что пропуски удаляются, а не заполняются, и что временные метки обоих рядов получены по одним и тем же часам.

Результаты об опережении и запаздывании, описанные в литературе

Созвездие доказательств для исследований опережения и отставания

Ниже приведены цитаты, а не измерения из этой статьи. Они показывают, что утверждает опубликованная литература, чтобы у описанного выше сравнения была проверяемая точка отсчета.

  • BTC и альткоины. Исследование 2026 года в Asia-Pacific Financial Markets (Springer) описывает высокочастотную передачу цен от биткоина к альткоинам: монеты с малой капитализацией реагируют с заметной задержкой, а меньшая ликвидность связана с более медленной реакцией. Часто цитируемые в этой работе величины лагов — примерно 1-3 минуты от BTC к ETH, больше для активов средней и малой капитализации и все более однонаправленный поток по мере снижения капитализации — это результаты, приведенные в источнике, а не пересчитанные здесь. Градация по капитализации совпадает с той, что используется в статье о проверке нескольких символов.
  • CEX опережает DEX. Исследование микроструктуры крипторынка (MDPI) показывает, что централизованные площадки доминируют в формировании цены: информационный поток идет от CEX к DEX, а значимой обратной причинности нет. Это согласуется с сопоставлением заявок за доли миллисекунды на CEX и расчетами через время блока на DEX.
  • ETH как независимый актив. Работа VAR-SVAR (MDPI) выявляет режимы, в которых Ethereum выступает источником, а Bitcoin получает перелив, особенно в периоды, обусловленные DeFi и NFT.

Межбиржевое опережение: кратко

Лаг между двумя площадками для одной монеты — реальная торговая постановка, но ее экономика, пороги комиссий, лестница задержек и причины существования лага уже разобраны в статьях о статистическом арбитраже и парной торговле и кимчи-премии. Грейнджер добавляет только вопрос устойчивости: остается ли причинность Binance -> Coinbase стабильно значимой или, как и все остальное, лишь мерцает? Запустите скользящий тест для пары площадок; ответом будет временной ряд p-значений, а не "да".

Как превратить устойчивый лаг в позицию

Порог сигнала превращается в управляемую позицию

Если пара проходит скорректированный порог и скользящий тест, сам сигнал прост: это кумулятивная доходность предиктора за окно лага с применением порога.

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

Три темы не рассматриваются в этой статье, поскольку они уже разобраны в других разделах:

  • Все решают издержки. Лаговый сигнал на альткоинах с минутным горизонтом должен перекрывать спред, ценовое воздействие и комиссии — см. модели проскальзывания и издержек, экономику maker-taker и результат применения издержек к выбранному победителю.
  • Размер позиции не является преобразованием p-значения. Масштабировать экспозицию обратно пропорционально p-значению Грейнджера неправильно: p-значение свидетельствует против нулевой гипотезы, но не измеряет размер эффекта и меняется вместе с длиной выборки. Размер позиции следует связывать с оцененным эффектом и его неопределенностью — см. определение размера по Келли.
  • Проверка выполняется по принципу walk-forward. Переоценивайте модель на скользящем окне и проверяйте ее вне выборки; walk-forward-оптимизация — это протокол.

Ограничения

Границы причинно-следственной неопределенности

Причинность по Грейнджеру не равна причинности. Скрытые факторы (макрособытие, двигающее оба актива при небольшом рассогласовании временных меток), общие драйверы (два альткоина следуют за BTC с разной скоростью) и пропущенные переменные (тест BTC -> DOGE без ETH в системе) могут дать значимую статистику без прямого потока информации. Ограничение двумерными тестами, как в этой статье, усугубляет проблему пропущенных переменных, а не решает ее.

Линейность. Тест видит только линейную прогнозную структуру условного среднего. Для криптовалют характерны кластеризация волатильности, эффекты кредитного плеча и смена режимов — см. прогнозирование волатильности GARCH и динамическую корреляцию DCC-GARCH для анализа второго момента. Среди нелинейных альтернатив стоит знать ядерный тест Дикса-Панченко (2006), трансферную энтропию как теоретико-информационный аналог и причинность по Грейнджеру на основе копул для полного совместного распределения.

Структурные разрывы. Любая связь, оцененная на одном окне, условна относительно режима этого окна. Приведенный выше скользящий тест — минимальная диагностика, но не исправление структурного разрыва.

Резюме

Надежный сигнал опережения-запаздывания, выделенный из шума

  1. Оцените VAR на уровнях, выберите pp по BIC для каждой пары, добавьте dmaxd_{max} и проведите тест Вальда только для первых pp лагов. Постройте матрицу ограничений вручную: библиотека test_causality ограничивает и дополнительный лаг, что не соответствует методу Тода-Ямамото.
  2. Запустите тест на разностях доходностей как базовый для тех же пар и того же лага и сообщите о расхождениях. Именно эта таблица является результатом, а не матрица со звездочками.
  3. Скорректируйте матрицу для коррелированного семейства тестов через эффективное N, а не через Бонферрони, и сообщите, сколько ячеек прошло порог.
  4. Проверьте устойчивость во времени. Сообщите долю значимых окон и число пересечений порога. Мерцающий результат не является торгуемым лагом, каким бы малым ни было объединенное p-значение.
  5. Удаляйте пропуски, а не заполняйте их впередом, и проверяйте синхронизацию часов между площадками, прежде чем доверять любому результату для первого лага.

Вполне достойным результатом этой процедуры может быть отрицательный вывод: связь BTC с альткоином, которая значима внутри выборки, переживает поправку, но все равно пересекает линию 0,05 в обоих направлениях каждые несколько сотен баров, дает важный результат о неторгуемости и заслуживает публикации именно в таком виде.

Ссылки

Дисклеймер: Информация в этой статье предоставлена исключительно в образовательных и ознакомительных целях и не является финансовым, инвестиционным или торговым советом. Торговля криптовалютами сопряжена с высоким риском убытков.

Авторы

Eugen Soloviov
Eugen Soloviov

Инженер торговых систем

Разработка торговых ботов с 2017 года: межбиржевой арбитраж (подключал до 30 бирж), парный арбитраж на коинтеграции между спотом и фьючерсами, скальпинг, фронтраннинг, торговля по новостям, сентиментный анализ, трендовые алгоритмы, а также алгоритмы управления и балансировки портфелей. Делает выставление ордеров до 1 мс, warehouse для big data, бэктестинг-движки, AI-агентов и интерфейсы для ботов (в т.ч. open-source profitmaker.cc). Стек: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, архитектура.

Newsletter

Будьте в курсе событий

Подпишитесь на нашу рассылку, чтобы получать эксклюзивную аналитику по AI-трейдингу и обновления платформы.

Мы уважаем вашу конфиденциальность. Отписаться можно в любой момент.