baykovpro

← Quant

Сколько бэктестов убивают стратегию: эксперимент с BTC/ETH перпами

4 августа 2026 · 12 мин · бэктест, data-snooping, Deflated Sharpe, перпы

Суть идеи

Авторы из Rulyfi провели один и тот же поиск стратегий на 5.7-летней истории BTC и ETH perpetuals дважды — с единственным отличием: во втором прогоне убрали «выметенные» конфигурации двух параметров. Результат: одна и та же стратегия с побитово идентичными сделками получила Deflated Sharpe (DSR) 0.035 в первом прогоне (21.9M испытаний) и 0.919 во втором (14.8M испытаний). Стратегия не изменилась ни на йоту — изменился знаменатель поправки на множественное тестирование.

DSR — это классический Sharpe, скорректированный на число независимых испытаний в пространстве поиска. Чем шире пространство, тем жёстче штраф: при 21.9M попыток даже хорошо выглядящая кривая капитала оказывается статистически неотличима от случайного везения. Сократи пространство вдвое — и та же кривая «проходит» порог значимости.

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

Применимость к крипте

Эксперимент проведён прямо на BTC/ETH perps — переносить ничего не нужно. Это редкий случай, когда методологическая статья использует именно наш актив-класс. Выводы напрямую касаются любого grid-поиска по параметрам: скользящие окна для rolling-z, пороги gamma flip, уровни VRP-фильтра, длины look-back для SOPR или STH-MVRV. Каждый дополнительный параметр, каждый дополнительный диапазон значений — это множитель к числу испытаний и, соответственно, штраф к DSR.

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

Тестируемо ли

Да, и именно на нашем движке. Нужны: BTC/ETH OHLCV (perp или spot), любая параметризованная стратегия (например, momentum с rolling-z), два прогона grid-search с разным охватом пространства параметров. Считаем DSR для каждого прогона по формуле Бейли–Лопес де Прадо. Данные есть, формула открытая, Python-реализация существует. Единственное честное ограничение: нужно заранее зафиксировать и не редактировать список конфигураций — иначе воспроизводим именно ту ошибку, о которой статья предупреждает.

Сомнения

Статья сама по себе — демонстрация проблемы, а не её решение. DSR не защищает от data-snooping полностью: он работает только если ты честно сообщаешь о размере пространства поиска. На практике исследователи часто «забывают» посчитать предварительные эксперименты, ручные правки и отброшенные идеи — и DSR снова становится оптимистичным. Кроме того, 5.7 лет BTC/ETH — это несколько очень разных режимов (бычий 2020–2021, медвежий 2022, боковик 2023–2024); стратегия, выжившая в grid-search, могла просто поймать один из этих режимов. Walk-forward OOS здесь необходим, но в аннотации о нём не упоминается.

Следующий шаг

Взять нашу rolling-z momentum стратегию на BTC perp (параметры: окно look-back, порог входа, стоп), запустить два grid-search: широкий (все комбинации) и узкий (только «разумные» диапазоны), посчитать DSR для каждого прогона и сравнить — насколько DSR лучшей конфигурации меняется в зависимости от задекларированного размера пространства. Горизонт: 2020–2024, walk-forward split 70/30.

Результаты тестов

Дописано 2026-08-04 — прогнали «Следующий шаг» буквально, на BTC и ETH. Эффект статьи воспроизвёлся полностью, но его заявленная причина не проходит арифметику: число испытаний тут почти ни при чём.

Спека выполнена как написана: rolling-z momentum на дневных перпах (z = отклонение цены от своей скользящей средней в СКО), три оси параметров — окно look-back, порог входа, трейлинг-стоп — плюс ось «только лонг / лонг-шорт». Меню зафиксированы до прогона: широкое 11×9×6×2 = 1188 конфигураций, узкое («разумные диапазоны») 5×3×3×2 = 90, причём узкое — строго подмножество широкого, чтобы победитель был один и тот же с побитово одинаковыми сделками. Окно 2020-01-01…2026-07-31 (2404 дня), вход на следующий день после сигнала, издержки 6 б.п. на единицу оборота, funding перпа по фактическим 8-часовым событиям (drag лонга +11.9%/год на BTC, +14.1% на ETH), размер позиции фиксированный — никаких рамп плеча.

Порог DSR почти не чувствителен к размеру поиска

Deflated Sharpe — это вероятностный Sharpe, замеренный не от нуля, а от планки «ожидаемый максимум из N испытаний под нулевой гипотезой»:

sr0 = sd_SR · [(1−γ)·Φ⁻¹(1−1/N) + γ·Φ⁻¹(1−1/(N·e))]

Здесь два входа, и они устроены принципиально по-разному. Число испытаний N сидит внутри обратной функции нормального распределения, то есть входит примерно как √(2·lnN) — почти плоско. А sd_SR, разброс Sharpe по «толпе» испытаний, множитель — входит линейно.

Считаем множитель при sd_SR для двух чисел из статьи: N = 21.9M даёт 5.4462, N = 14.8M даёт 5.3760. Разница — 1.31%. При нашем разбросе меню это сдвиг порога на 0.0013 дневного Sharpe. А чтобы перевести DSR с 0.035 на 0.919 при длине истории ~2400 дней, порог должен сдвинуться на 0.0655 дневного Sharpe — в 52 раза больше.

То же самое на живой стратегии. Берём победителя нашего широкого поиска и применяем к нему в точности срез из статьи:

Размер поиска порог, годовых DSR (BTC) DSR (ETH)
21.9M 1.87 0.079 0.009
14.8M 1.84 0.090 0.011
изменение +0.011 +0.002

Заявлено +0.884. Чтобы получить такой размах одним лишь числом испытаний, поиск должен измениться в 1.9 миллиона раз (BTC: с 121 конфигурации до 234 миллионов) или в 98 тысяч раз (ETH) — а не в 1.48 раза, как в эксперименте.

Тот же размах — при полностью неизменном N

Раз не N, то что? Второй вход формулы, sd_SR. Он оценивается по разбросу наблюдаемых Sharpe внутри самого меню, то есть зависит от того, кого именно исследователь записал в соседи — а не от размера поиска.

Фиксируем задекларированное число испытаний ровно на 90 и меняем только состав этой девяностки. Стратегия, её сделки и её Sharpe не меняются ни на йоту:

Состав «толпы» (N = 90 везде) sd_SR, годовых DSR (BTC) DSR (ETH)
тесная кучка вокруг медианы 0.01 / 0.02 1.000 0.998
90 случайных соседей 0.33 / 0.37 0.947 0.658
разношёрстные: 45 худших + 45 лучших 0.72 / 0.76 0.114 0.010

Размах — 0.886 на BTC и 0.988 на ETH. То есть феномен статьи — «одна и та же стратегия получает почти нулевой и почти единичный DSR» — воспроизводится целиком, без единого изменения числа испытаний. Это не смягчает вывод статьи, а ужесточает его: манипулируемая ручка не та, на которую показывают, и она куда чувствительнее.

Отсюда же следует неприятное свойство: формула немонотонна по размеру поиска. Мы взяли широкое меню и сгустили сетку вокруг найденного плато — 192 дополнительные конфигурации в окрестности победителя, самый обычный ход исследователя «дай-ка посмотрю поближе». На ETH DSR от этого вырос: 0.395 → 0.405. Расширил поиск на две сотни испытаний — стало «значимее».

Что дают наши собственные два меню

Здесь N-канал уже работает — но потому, что меню различаются в 13 раз, а не в 1.48:

BTC (L60, θ 0.5, стоп 4%, только-лонг) ETH (L20, θ 1.5, без стопа, только-лонг)
Sharpe стратегии +1.38 +1.07
DSR при N = 1188 0.759 0.395
DSR при N = 90 0.967 0.860
вклад канала N +0.175 +0.307
вклад канала sd_SR +0.117 +0.283

Прямой ответ на вопрос статьи «насколько DSR лучшей конфигурации меняется от задекларированного размера пространства»: на 0.21 (BTC) и 0.47 (ETH) при сокращении поиска в 13 раз — и примерно наполовину это вклад не размера, а состава.

Чем лечится: нуль вместо формулы

Формула требует угадать N независимых испытаний. Но испытания в сетке чудовищно перекрываются: средняя парная корреляция дневных доходностей по нашему меню +0.38 (BTC) и +0.34 (ETH), что даёт N_eff ≈ 2.6 и 2.9 из 1188. Любое число вида «21.9 миллиона испытаний» как счёт независимых попыток бессмысленно — не потому, что кто-то его занизил или завысил, а потому, что независимых попыток в параметрической сетке единицы.

Поэтому угадывать не надо: инфляцию от выбора можно измерить. Ротационный нуль — позиции и издержки каждой конфигурации сохраняются, доходности циклически сдвигаются общим для всех сдвигом (так сохраняется корреляция испытаний), максимум берётся по строке. Получаем распределение «лучшего из меню» при полном отсутствии связи сигнала с будущей ценой:

BTC ETH
одиночное испытание под нулём +0.10 +0.13
лучшее из 1188 под нулём +1.05 +1.09
«даром» за счёт перебора +0.95 +0.96
для сравнения, планка Bailey-LdP 1.13 1.17
лучшее из 90 под нулём +0.78 +0.85
планка Bailey-LdP при N = 90 0.74 0.68

Аналитика попадает в цель по порядку величины, но её нельзя отмыть, а эмпирический порог — можно проверить на отмывание напрямую. Добавляем в меню 528 заведомо пустых конфигураций: DSR послушно падает (0.759 → 0.517), но при сгущении сетки, как показано выше, может и вырасти. Эмпирический порог за то же расширение сдвигается с 1.05 до 1.06 годовых — то есть расширение поиска эмпирическим нулём не отмывается ни в одну сторону.

И главный практический выход. Честный family-wise вердикт по самим победителям: доля сдвигов, где лучшее из меню под нулём дотягивается до наблюдаемого, — 0.109 на BTC и 0.184 на ETH для широкого меню. Оба победителя незначимы. Узкое меню даёт BTC 0.027 — но узкое меню и выбрано было постфактум, ровно в этом и состоит предупреждение статьи.

Честный OOS

Walk-forward 70/30 по спеке (IS до 2024-08-09, OOS после):

IS-Sharpe OOS-Sharpe buy & hold OOS
BTC, победитель IS +1.75 +0.25 +0.24
ETH, победитель IS +1.37 −0.16 +0.08

BTC-победитель на честном отрезке в точности повторил пассивный лонг, ETH-победитель проиграл ему. Сомнение из аннотации — «стратегия, выжившая в grid-search, могла просто поймать режим» — подтверждено. Добавим сюда, что победитель хрупок и к задержке исполнения: вход на день позже обваливает BTC с +1.38 до +0.79, ETH с +1.07 до +0.80.

Плацебо и положительный контроль

Тест обязан не пропускать пустышку — иначе он не защищает ни от чего. Прогнали весь пайплайн на 20 независимых синтетических мирах с той же волатильностью и без всякого сигнала. «Sharpe шума» — лучший из 1188 конфигураций на случайных данных — в среднем +0.91 (BTC-вола) и +0.88 (ETH-вола), в лучшем мире +1.37; это и есть цена перебора, которую иначе принимают за находку. Ложных срабатываний: 0 из 20 у DSR при порогах 0.95 и 0.90, 0 из 20 у ротационного нуля при 5% (среднее p = 0.48 и 0.55 — нуль откалиброван).

Обратная проверка — тест обязан находить настоящий эдж. Взяли непобедившую, медианную по Sharpe конфигурацию (чтобы база не несла в себе результат отбора) и внедрили в неё известный истинный эдж. Оба теста уверенно находят его начиная с истинного ann-Sharpe 1.5 (реализованный ~2.0): DSR 0.993/0.965, p нуля 0.010/0.017. При истинном эдже 1.0 не проходит ни один — это честная цена поправки на перебор из 1188 конфигураций, а не слепота.

Вывод

Тезис статьи — «размер пространства поиска убивает результат» — верен по духу и промахивается по механике. Число испытаний входит в поправку логарифмически: чтобы им сдвинуть DSR на заявленную величину, поиск надо менять не в полтора раза, а в сотни тысяч раз. Реальная ручка — состав меню сравнения, через который DSR той же самой стратегии гоняется от 0.01 до 1.00 при неизменном N, и который к тому же ломает монотонность: расширение поиска может повысить «значимость». Практический вывод для собственного движка ровно противоположен процитированному в аннотации: логировать полное число протестированных конфигураций полезно для дисциплины, но само по себе оно поправку не спасает — спасает измерение максимума под нулём на фактическом меню, где ни угадывать N, ни оценивать разброс по соседям не требуется. У нас это уже так: в турнире bt_engine DSR служит сортировкой внутри одного прогона, ни один крон и ни одна публикация на сайте от его уровня не зависят, а решения о заводе линий в форвард принимаются по эмпирическому нулю.

Проверка: research_dsr_breadth.py (brain), меню и оси зафиксированы в шапке скрипта до прогона. Данные — Binance perp, дневные бары из собственного 5-минутного кэша, funding по фактическим событиям. Ключевые числа воспроизведены независимой реимплементацией verify_dsr_breadth.py с нуля: цены сверены со сторонним источником (максимальное расхождение 0), Sharpe победителя пересчитан своим стоп-циклом (расхождение 0), DSR — собственным обратным нормальным распределением и собственными моментами (расхождение < 1.2e-9), ротационный нуль пересчитан на другой сетке сдвигов. Лукахед-аудит: шок ×3 в последних 30 днях не меняет ни одной позиции до момента шока; повторный прогон побайтово детерминирован. Оговорка о нуле: наше меню однородно-трендовое и лонговое, а циклический сдвиг против постоянной позиции бессилен тождественно, поэтому равновзвешенное меню лежит на 98–100-м перцентиле своего нуля — нуль здесь консервативен, его легко бить, и то, что победители его всё равно не проходят, делает отрицательный вердикт только надёжнее. Оригинал Rulyfi на момент прогона недоступен (redirect Quantocracy пуст), поэтому проверялся механизм в формулировке этой аннотации; найденный канал sd_SR не столько противоречит их числам, сколько объясняет их: удаление «выметенных» конфигураций меняет именно разброс по меню.

Источник: Rulyfi · оригинал на английском, разбор — авторский на русском.