Заглядывание в будущее: как автоматический аудит нашёл три настоящие утечки в нашей модели
Любая просочившаяся крупица будущего делает бэктест красивее. Именно поэтому утечки так трудно заметить — свидетельством бага оказывается результат, которого вы и ждали.
Инженеры Vizanix · об авторе
- РАЗДЕЛ
- Исследование и проверка
- ОПУБЛИКОВАНО
- 2026-08-28
- ГЛАВ
- 6
- ЧИТАТЬ ДАЛЬШЕ
- 3
- ЯЗЫК
- написано на русском
Утечка в будущее — это любой путь, по которому информация с момента t+1 попадает в решение, принимаемое в момент t. В бэктесте она невидима и приятна: метрики улучшаются, кривая эквити сглаживается, и все ваши стимулы направлены прочь от того, чтобы это проверить.
Откуда утечки берутся на самом деле
Не из экзотики. Источники повторяются и они прозаичны:
- Сдвиг на единицу при выравнивании. Открытый интерес, фандинг или ряд старшего таймфрейма приклеиваются к вашим барам, и берётся индекс
-1— то есть текущее, ещё не завершённое значение. Это самая частая причина. - Использование той свечи, на которой вы торгуете. Если сигнал использует закрытие бара t, войти на открытии бара t нельзя.
- Нормализация по всему набору данных. Среднее, стандартное отклонение или скейлер считаются на всех данных, а потом делаются train и test. Статистики тестовой выборки теперь внутри обучающих признаков.
- Метки, которые подглядывают. Целевая переменная «максимальный ход за следующий час» нормальна; признак, посчитанный по тому же окну, — нет.
- Выживаемость в универсуме. Отобрать сегодняшние ликвидные монеты и тестировать их по истории — это ставка на знание того, кто выживет.
- Ресемплинг с протяжкой через границу. Переиндексация медленного ряда на быстрый с forward-fill, который начинается раньше, чем значение существовало.
Аудит, который действительно работает
Читать код в поисках утечек не масштабируется и не находит ошибок выравнивания. Работает механический метод: пересчитать каждый признак на баре t на ряде, обрезанном по t, где будущего физически нет, и сравнить со значением, которое выдал обычный конвейер.
Любое расхождение — это утечка по определению. Здесь нет шага интерпретации и нет места для оценочного суждения.
def audit(df, feature_fn, sample_points, tol=1e-9):
"""Пересчитываем каждый признак на обрезанном ряде. Любое расхождение — утечка."""
full = feature_fn(df)
leaks = []
for t in sample_points:
truncated = df.iloc[: t + 1] # будущего здесь не существует
partial = feature_fn(truncated)
for col in full.columns:
a, b = full[col].iloc[t], partial[col].iloc[-1]
if not close_enough(a, b, tol):
leaks.append((col, t, a, b))
return leaksПрогоняйте на нескольких тысячах случайных баров из разных режимов и держите это в CI, чтобы утечку нельзя было вернуть следующим изменением.
Что нашёл наш
Мы построили такой аудит для сигнальной модели разворота пампа — 59 признаков по структуре эпизода, старшему таймфрейму, фону BTC и ликвидности. Он нашёл три настоящие утечки, и все три оказались ошибками выравнивания, а не концептуальными.
Схема каждый раз была одинаковой: при выравнивании открытого интереса, фандинга и фона BTC по времени к ряду баров индекс -1 указывал на значение, которое станет известно только после точки принятия решения. Каждая из них делала модель красивее. Ни одну не нашли бы чтением кода, потому что код выглядел правильно — соединение было написано так, как такие соединения обычно и пишут.
Purge и embargo
Кросс-валидация, придуманная для независимых строк, неверна для временных рядов. Если ваша метка смотрит на час вперёд, то обучающий образец непосредственно перед валидационным делит с ним этот час — разбиение течёт через собственную границу.
Лечится зазором размером с горизонт метки: выбросить обучающие образцы, чьи окна меток перекрываются с валидационной выборкой, и добавить embargo после неё с учётом автокорреляции. Это не факультативное уточнение: случайное разбиение на упорядоченных во времени данных обыграет честное всегда, и разница целиком вымышленная.
В TideGBM purging и embargo встроены как поведение по умолчанию, а не как опция, вместе со строгим режимом, отказывающимся обучаться на признаках, не прошедших линт заглядывания. Сделать честный путь путём по умолчанию — единственная версия этой идеи, которая переживает дедлайн.
И опубликуйте честную цифру
После закрытия трёх утечек одиночный AUC вне выборки у трёх наших геометрических формул составляет примерно 0,50–0,52. Это едва лучше подбрасывания монеты, и мы так и пишем на странице кейса.
Предсказательная сила этой модели приходит из другого места: структура эпизода, сколько ног движение уже прошло, как долго оно идёт, какой была самая глубокая просадка, ликвидность инструмента и волатильность BTC. Формулы оправдывают своё место как независимая перекрёстная проверка и как объяснение для оператора. Выдавать их за источник прибыли было бы неправдой.
Короткий протокол
- Пишите аудит до модели. Это сотня строк, и она меняет то, что вы построите.
- Сэмплируйте тысячи баров из разных режимов, а не десяток.
- Purge и embargo по умолчанию в каждом разбиении.
- Держите аудит в CI. Утечки возвращаются со следующим рефакторингом.
- Публикуйте честную цифру вне выборки, в том числе когда она невпечатляющая.
Последнее — самое трудное и самое полезное. Модель, чьи слабости задокументированы, можно улучшать. Модель с прекрасным бэктестом по причинам, которых никто не проверял, — это обязательство в красивой обёртке.
Статья описывает инженерную практику. Это не инвестиционная рекомендация. Vizanix разрабатывает программное обеспечение и не обещает торговую доходность.