Методология
Hurst exponent, ADF-test, walk-forward бэктест без lookahead — математика за каждым сигналом PairScan.
Обновлено:
Если ты пришёл с r/algotrading или Quantocracy — эта страница для тебя. Ниже — точная математика за каждым сигналом, который выдаёт PairScan. Никаких чёрных ящиков, никакого «proprietary AI» — только классическая статистика 50-х и 70-х, применённая к крипто- и tokenized-equity ratio. Reference-имплементация каждого фильтра — open-source под MIT (см. pairscan-rmr — pip install pairscan-rmr).
Почему pair trading на ratio
Направленная сделка требует предсказывать цену. Ratio-сделка требует только распознать, когда отношение двух коррелированных активов вышло за свой исторический диапазон — это куда более слабая claim. Корреляция делает работу за тебя; ты просто продаёшь актив, который улетел вперёд, и покупаешь тот, что отстал.
Конкретный пример: предположим, ETH и BTC оба выросли на 50 % за год, но ETH вёл движение и оказался на отметке 0.075 BTC при средней за 18 месяцев 0.060. Направленная сделка — это «BTC до $90 k к Q4». Ratio-сделка — это «ETH/BTC = 0.075 это два стандартных отклонения выше скользящего диапазона; уменьшить ETH, добавить BTC». Тебе не нужно знать, куда пойдёт биткоин — нужно только чтобы ratio вернулся к 0.060. Если оба актива растут вместе — ratio дрейфует обратно; если оба падают — то же; ломает сделку только устойчивый сдвиг доминирования.
Чтобы это было прибыльным в реальном исполнении, три вещи должны выполняться одновременно:
- Ratio mean-revert'ит (не трендит).
- Границы достаточно широкие, чтобы покрыть round-trip slippage + fee.
- У пары достаточно ликвидности под твой размер.
Фильтры 1 и 2 — статистические. Фильтр 3 — операционный. Все три проверяются до того как пара попадёт в скрин.
Четыре фильтра
Каждая пара проходит четыре независимых теста на скользящем 540-дневном окне log(price_A / price_B). Пара должна пройти все четыре, чтобы попасть в скрин. Пороги ниже намеренно мягкие — см. секцию мягкие пороги.
Hurst exponent (R/S analysis)
Hurst exponent измеряет долгосрочную память серии. Для self-similar процесса rescaled range R/S на лаге n растёт как n^H. Мы оцениваем H посчитав R/S на нескольких лагах, рисуя log–log и читая наклон:
- H < 0.5 — anti-persistent / mean-reverting (то что нам надо)
- H ≈ 0.5 — random walk
- H > 0.5 — persistent / trending
Считаем через R/S analysis на 540-дневном log-ratio. Reference-имплементация:
import numpy as np
def hurst_rs(series, max_lag=100):
"""Hurst exponent через R/S analysis."""
lags = np.unique(np.geomspace(10, max_lag, 12).astype(int))
rs_values, valid_lags = [], []
for lag in lags:
n_chunks = len(series) // lag
if n_chunks < 2: continue
chunks = series[:n_chunks * lag].reshape(n_chunks, lag)
means = chunks.mean(axis=1, keepdims=True)
cumdev = (chunks - means).cumsum(axis=1)
ranges = cumdev.max(axis=1) - cumdev.min(axis=1)
stds = chunks.std(axis=1, ddof=1)
valid = stds > 0
rs_values.append(np.mean(ranges[valid] / stds[valid]))
valid_lags.append(lag)
slope, _ = np.polyfit(np.log(valid_lags), np.log(rs_values), 1)
return slope
Требуем H < 0.5 для включения. Это щедрый порог — многие quant-desks ставят H < 0.45 или даже < 0.4 — но мягкие пороги намеренные: лучше пропустим больше кандидатов и дадим walk-forward бэктесту отфильтровать дальше, чем срежем рано на шумном эстиматоре. У R/S статистики есть документированный small-lag bias (Lo, 1991), поэтому мы ограничиваем регрессию лагами ≥ 10 даже если пользователь передал меньший max_lag.
Тот же самый код живёт в pairscan-rmr на src/pairscan_rmr/filters.py.
Augmented Dickey-Fuller тест
ADF тестирует, стационарна ли серия (отклонение unit root) или нестационарна. Применяется к log(A/B) на 540 дней, длина лагов выбирается минимизацией AIC. Берём statsmodels напрямую:
from statsmodels.tsa.stattools import adfuller
def adf_pvalue(series, autolag="AIC"):
"""Возвращает только p-value; меньше = более стационарная."""
result = adfuller(series, autolag=autolag)
return float(result[1])
Требуем ADF p-value < 0.7 — снова щедрый порог. Строгая академия использует p < 0.05; мы — 0.7, потому что:
- ADF имеет низкую statistical power на коротких выборках (а крипто-истории короткие),
- У нас уже есть Hurst как параллельный фильтр — оба должны согласоваться,
- Walk-forward бэктест — это и есть реальный decision-maker; ADF и Hurst это просто guards против явно неподходящих пар.
Пара которая показывает Hurst 0.45 и ADF 0.6 — пограничный кандидат; пара с Hurst 0.55 отлетает сразу. И Hurst-failing, и ADF-failing пары встречаются в нашем universe — мы исключаем порядка 30–40 % номинально cointegrated пар на этапе фильтров.
Ширина диапазона и чередующиеся касания
Пара может пройти Hurst и ADF и при этом быть бесполезной — если диапазон слишком узкий (slippage съедает edge) или если цена касалась только одной границы (нет реального колебания). Два операционных фильтра ловят это:
- Ширина диапазона. Считаем
(P95 − P5)от log-ratio на 540-дневном окне. Требуем минимум 0.4 log-юнита, что эквивалентно ~50 % размаху underlying ratio. Меньше — и 0.1 % swap fee плюс реалистичный slippage съедают больше половины per-cycle gain. - Чередующиеся касания. Идём по серии и считаем low-side касания (
value ≤ P5) и high-side касания (value ≥ P95), но засчитываем только если предыдущее касание было на противоположной стороне. Монотонная серия, которая один раз посетила P5 и потом дрейфовала вверх — получает одно low-касание и одно high-касание, не пять каждого. Требуем ≥ 2 чередующихся касания на сторону, что значит пара реально колебалась четыре раза в lookback.
Alternating-constraint убивает «фейковых mean-reverter'ов» — пары, чей log-ratio выглядит ограниченным только потому что они обвалились в начале окна и с тех пор восстанавливаются.
Volume gate
Обе ноги должны иметь 24h спот-объём > $1 M хотя бы на одной из {Binance, Bybit, OKX, KuCoin}. Это не статистика — это операционный порог. Ниже $1 M/день slippage и спред съедают всю rebalancing-премию, и математика не работает в реальном исполнении. Gate non-negotiable; лучше пропустим красиво mean-reverting micro-cap чем рекомендуем пару, которую ты не можешь реально торговать в размере.
Walk-forward бэктест
Когда пара прошла все четыре фильтра — бэктестим 360 дней с той же логикой сигналов, что использует live-система. Критическая деталь: no lookahead.
Для каждого дня t в окне бэктеста мы считаем rolling P5 и P95 на последних rolling_window_days (default 180) днях — только тех данных, которые были бы доступны трейдеру на день t. Сегодняшние данные не используются в сегодняшнем сигнале. Это и есть разница между честным бэктестом и упражнением в подгонке.
import numpy as np
def walk_forward_backtest(price_a, price_b, lookback=540,
entry_low=0.2, entry_high=0.8, fee=0.001):
log_ratio = np.log(price_a / price_b)
a_qty, b_qty, holding_a = 100.0, 0.0, True
trades = []
for t in range(lookback, len(price_a)):
# КРИТИЧНО: окно строго ДО t — никаких future данных.
window = log_ratio[t - lookback:t]
p_low, p_high = np.percentile(window, [5, 95])
position = (log_ratio[t - 1] - p_low) / max(p_high - p_low, 1e-9)
if position <= entry_low and not holding_a:
a_qty = b_qty * price_b[t] / price_a[t] * (1 - fee)
b_qty = 0.0; holding_a = True
trades.append(("B→A", t))
elif position >= entry_high and holding_a:
b_qty = a_qty * price_a[t] / price_b[t] * (1 - fee)
a_qty = 0.0; holding_a = False
trades.append(("A→B", t))
return a_qty, b_qty, trades
Наивная альтернатива — использовать np.percentile(log_ratio, [5, 95]) на полной серии включая future данные — типично завышает accumulation стратегии на 10–30 %. Этот класс багов мы ловим regression-тестом, который запускает бэктест дважды: один раз на чистых данных, второй — со всеми ценами после midpoint заменёнными на garbage. Сделки до midpoint должны быть byte-identical между запусками. Если future-dependent статистика когда-нибудь проникнет — тест фейлится сразу. Probe лежит в tests/test_no_lookahead.py.
Почему мы используем мягкие пороги
Стандартный quant-инстинкт — затягивать фильтры до тех пор, пока бэктесты не станут чистыми. Мы намеренно так не делаем. Мягкий Hurst (< 0.5), мягкий ADF (< 0.7), щедрая ширина диапазона (40 %) — результат: больше кандидатов попадает в скрин, а walk-forward бэктест делает финальную фильтрацию, показывая какие реально накапливают количество.
Это размен false-positive на false-negative. Лучше показать десять кандидатов и дать тебе выбрать три, которым ты доверяешь — чем показать одну «чистую» пару, которая случайно бэктестилась хорошо. Quant trading на маленьких выборках — это фундаментально high-noise среда; затягивание фильтров сужает воронку, но реально не улучшает signal-to-noise того что проходит.
С другой стороны: у каждого фильтра есть явный override в API pairscan-rmr (is_mean_reverting(price_a, price_b, hurst_threshold=0.45, ...)). Если у тебя сильный prior — institutional desk policy, более жёсткая risk tolerance — поставь threshold сам.
Чего мы явно не делаем
- Не предсказываем цену. Никакого ML, sentiment, on-chain forensics.
- Не используем плечо, не шортим. Капитал всегда на 100% в одной из двух ног.
- Не auto-execute. Мы говорим, когда свопать; ордер ты ставишь сам на своей бирже.
- Не обещаем. Mean-reversion стратегии ломаются в направленных режимах — см. Где стратегия не работает.
Ограничения
Это известные failure-режимы методологии. Никаких сюрпризов и ничего лёгкого в починке.
- Hurst R/S — шумный эстиматор на коротких окнах. На 540-дневном окне standard error для
Hпримерно ±0.05. Две пары с идентичными underlying процессами могут оказаться по разные стороны 0.5-cutoff'а по случайности. Митигируем щедрым порогом и требованием чтобы все четыре фильтра прошли — но пара сH = 0.49не значимо отличается от пары сH = 0.51. - ADF предполагает stationary residuals. Структурные сломы (токен форкается, биржа делистит ногу, стейблкоин теряет peg) делают тест misleading. Pre-2022 LUNA выглядел идеально стационарным до того дня, когда не выглядел. Наш peg-check oracle layer ловит самые очевидные случаи для tokenized активов, но никакой статистический тест не детектит regime-changes которые ещё не случились.
- Тесты descriptive, не predictive. Hurst, ADF и range-фильтры все описывают прошлые 540 дней. Past mean-reversion не гарантирует future mean-reversion — он только говорит, что пара была mean-reverting в lookback'е. Если underlying relationship сломается после конца lookback'а, скрин будет продолжать показывать пару как кандидата пока не накопится достаточно свежих данных чтобы перевернуть verdict.
- Размер выборки имеет значение. Ниже 200 дней истории ни один из этих тестов не имеет meaningful power. Мы отказываемся скорить пары с менее 540 дней; даже там результаты слабее чем для пар с 5+ годами данных. Новые токены стартуют в нашем universe с verdict'ом «история слишком короткая», независимо от того как mean-reverting они выглядят intra-window.
- Реальное исполнение добавляет slippage, налоги, exchange downtime — ничего не моделируется. Бэктесты предполагают, что ты можешь исполниться по close-цене дня по 0.1 % taker. На практике ты исполняешься по следующей доступной цене, в понедельник утром твоя биржа может быть недоступна час, а твоя юрисдикция может забрать 20 % любого realised gain. Стратегия всё ещё работает после этих frictions для ликвидных пар; перестаёт работать для менее ликвидных.
- xStocks-specific риски. Tokenized equities младше 12 месяцев на момент написания. Mean-reversion claims на такой короткой истории — спекулятивные. Backed Finance в прошлом приостанавливал redemptions во время peg-events. Если твоя юрисдикция ограничивает tokenized securities, ничего из этого не применимо.
Open questions, над которыми мы работаем
Короткий список вещей, которые мы реально не знаем, в порядке частоты возникновения:
- Оптимальная длина lookback'а. Используем 540 дней потому что это примерно один BTC halving cycle. Короче окно — реагирует быстрее на regime shifts, но overfit'ится к недавнему шуму; длиннее — стабильнее, но привязано к истории, которая может уже не применяться (например, 2017 alt-season). Реально ли 540 правильный ответ для крипто-нативных пар vs. крипто-equity cross-asset пар — открытый вопрос.
- Hurst vs. variance-ratio тест. R/S Hurst — классический эстиматор, но variance-ratio (Lo & MacKinlay, 1988) и detrended fluctuation analysis (DFA, Peng et al. 1994) имеют меньший bias на коротких выборках. Сравниваем все три на production universe; пока результаты говорят что variance-ratio даёт номинально чище числа, но порядок пар по силе сигнала по сути тот же. Стоит дальнейшего исследования.
- Bayesian posterior на «эта пара ещё mean-revert'ит?». Сейчас verdict бинарный (прошла фильтры / не прошла). Posterior probability которая обновляется по мере прихода новых данных дала бы лучшее коммуникирование uncertainty и quantify regime-change risk. Открытый вопрос — стоит ли дополнительная сложность для retail-facing surfaces.
- Cross-asset peg integrity. Для tokenized equities cross-check'аем против Pyth oracles на каждом screening cycle. Для tokenized commodities (PAXG, XAUT) используем yfinance для underlying и допускаем большие drift bands. Правильное число для этих bands не well-established эмпирически; используем 1.5 % на основе observed drift distributions, но плausibly могло бы быть жёстче.
- Trade-cost моделирование. Допущение 0.1 % taker fee — консервативное для крупных CEX и агрессивное для тонких DEX-pool. Более честная модель скейлила бы fees по venue и pair liquidity. То же для slippage — сейчас игнорируем. Оба в roadmap; не сделано.
Если у тебя есть данные или pointer'ы по любому из этих вопросов — @pairscan в X или открой issue в pairscan-rmr репо.
Long-tail RWA — что подходит модели и что нет
Нас регулярно спрашивают, можно ли применить ratio screening к другим токенизированным real-world активам — фракционная недвижимость (RealT), yield-tokenized деривативы (Pendle PT/YT), токенизированные treasuries (USDY, OUSG, BUIDL, USDM). Честный ответ — "в основном нет", и стоит объяснить почему, чтобы скоуп был ясен:
Фракционная недвижимость (RealT, Lofty, etc.) — каждый property-токен уникален. Нет непрерывной live-цены; оценка приходит из квартальных off-chain appraisals. Вторичные рынки тонкие и эпизодические. Ratio между двумя RealT-токенами — это не market-discovered ratio, это соотношение двух устаревших оценок. Логика mean-reversion не применяется — нет high-frequency mean'а, к которому можно revert'иться. Мы их не скринем.
Pendle PT/YT (Principal / Yield Tokens) — PT торгуется со скидкой к underlying yield-bearing токену (например PT-aUSDC) и monotonically растёт к face value к maturity. YT накапливает yield и распадается до нуля к maturity. У обоих предсказуемые детерминистические price paths, определяемые time-to-maturity и underlying yield rate. Это противоположность mean-reversion — известный monotone trend. Правильная модель для Pendle — yield-curve fitting, не pair screening.
Rebase-style токенизированные treasuries (USDY / USTB / MTBILL) — добавлены в PairScan начиная с Phase 5, но с оговорками. On-chain цена каждого токена равна текущему per-token NAV ($1 + накопленный yield), не плоско $1. Пара одного из них против USDC даёт медленный upward drift ~5%/год (это yield) — это trend, не stationary ratio. Единственные plausible mean-reverting пары — в одном классе: USDY/USTB, USDY/MTBILL. Но даже они зависят от mean-reversion'а yield-spread'а, который намного медленнее чем equity / crypto пары для которых заточен скрин. Мы добавили asset class, чтобы peg-check infrastructure существовала для юзеров мониторящих drift, но не рекомендуем торговать эти пары только по screen-сигналам — 90-дневное окно, которое мы override'им вниз, на грани undersampled, noise floor высокий.
OUSG, BUIDL, USDM, redemption-only токены — нет Pyth или Chainlink oracle feed на 2026-05. Без reference-цены не можем запустить peg-check, а без peg-check on-DEX цена может произвольно отклоняться на любом блоке и мы не поймаем. Эти токены сознательно не whitelist'им до появления oracle coverage.
Короткая версия: ratio mean-reversion — острый инструмент для узкого набора asset classes. Работает на stationary log-ratios со стабильной variance и >100 дней joint history. Большинство "long-tail RWA" этим условиям не удовлетворяет, и лучше прямо это сказать, чем шипить screener, который misclassify'ит known-trending series как mean-reverting candidates.
Open-source утилита
Ядро фильтров и backtest-движок опубликованы как Python-библиотека под MIT: github.com/pairscan/ratio-mean-reversion.
pip install pairscan-rmr
from pairscan_rmr import is_mean_reverting, walk_forward_backtest
result = is_mean_reverting(price_a, price_b)
if result.passed:
backtest = walk_forward_backtest(price_a, price_b)
print(f"{backtest.n_trades} сделок, max DD {backtest.max_drawdown:.1%}")
Тот же код, что работает в продакшене, работает в библиотеке. Если найдёшь баг — это тот же баг что у нас.
Источники
- Hurst, H.E. (1951). «Long-Term Storage Capacity of Reservoirs». Transactions of the American Society of Civil Engineers, 116, 770–799.
- Dickey, D.A. & Fuller, W.A. (1979). «Distribution of the Estimators for Autoregressive Time Series with a Unit Root». JASA, 74(366a), 427–431.
- Lo, A.W. (1991). «Long-Term Memory in Stock Market Prices». Econometrica, 59(5), 1279–1313. — Стандартный reference на small-sample bias в R/S Hurst.
- Lo, A.W. & MacKinlay, A.C. (1988). «Stock Market Prices Do Not Follow Random Walks». Review of Financial Studies, 1(1), 41–66. — Variance-ratio test, альтернатива Hurst.
- Gatev, E., Goetzmann, W.N. & Rouwenhorst, K.G. (2006). «Pairs Trading: Performance of a Relative-Value Arbitrage Rule». Review of Financial Studies, 19(3), 797–827.
- Peng, C.-K. et al. (1994). «Mosaic organization of DNA nucleotides». Physical Review E, 49(2), 1685. — Detrended fluctuation analysis (DFA), обсуждается под «open questions».