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

Look-Ahead Bias: как ошибка в один бар фабрикует Sharpe 15 из чистого шума

Look-Ahead Bias: как ошибка в один бар фабрикует Sharpe 15 из чистого шума
#алготрейдинг
#бэктест
#look-ahead bias
#утечка данных
#overfitting
#валидация

Статья из серии "Бэктесты без иллюзий".

📄 Эта статья выросла в исследовательскую работу. Три едва заметные look-ahead утечки проверены контролируемым тестом против известной истины (4,000 симулированных историй). Читайте статью онлайн (интерактивная версия + PDF) на lookahead.marketmaker.cc, код и данные — на github.com/suenot/lookahead-inflation.

Несколько недель назад наш бенчмарк поиска параметров нам врал, и мы почти этого не заметили.

Движок выглядел чистым. Логика на закрытых барах, честное rolling walk-forward разбиение, Sobol/QMC-поиск по пространству параметров, отложенное тестовое окно. Поиск находил конфигурации, которые хорошо выглядели in-sample. Единственная проблема: out-of-sample почти все было в минусе. Мы решили, что стратегия просто слабая.

Потом мы нашли одну строку. Сигнал определялся по закрытию бара i, но исполнение записывалось на том же баре i вместо открытия следующего бара. Одна ошибка на единицу в индексе исполнения. Мы перенесли исполнение на open[i+1] — единственную цену, по которой реально можно было бы совершить сделку после того, как вы увидели закрытие бара i, — и результат out-of-sample сменил знак. Sobol-поиск перешел из убытка в прибыль. В стратегии не изменилось ничего. Мы просто перестали торговать в прошлом.

Это и есть look-ahead bias, и тревожит именно то, насколько маленькой была ошибка и насколько большим — ее следствие. Эта статья — контролируемый самоаудит: мы строим симулятор, в котором истина известна по построению, по очереди внедряем едва заметные утечки и точно измеряем, насколько каждая из них раздувает бэктест. Главный результат: при полном отсутствии реального edge исполнение на баре сигнала фабрикует годовой Sharpe +14.8 из чистого шума.

Что такое look-ahead bias на самом деле

Три места, где прячется look-ahead: исполнение, нормализация и индикаторы — каналы неравной опасности, питающие одно торговое решение

Look-ahead bias — это любая точка в пайплайне, где решение или измерение использует информацию, которая в реальном времени, в момент использования, была бы недоступна. Хрестоматийные примеры грубые — использование годовой прибыли акции в январе или еще не опубликованного пересмотра отчетности. Их легко заметить. Те, что переживают код-ревью, тоньше, и прячутся они в трех местах:

  1. Исполнение — вы принимаете решение на баре i и исполняете сделку на том же баре i (или используете high/low бара i для стопов на том самом баре, что породил сигнал). Вы торгуете по цене, коррелирующей с тем, что вас триггернуло.
  2. Нормализация — вы z-score, min-max или иначе масштабируете признак, используя статистики, посчитанные по всему ряду, включая будущее. Скейлер "знает" тестовую выборку.
  3. Индикаторы / признаки — вы сглаживаете или фильтруете с окном, центрированным (или иначе заглядывающим вперед), так что значение на баре i уже содержит кусочек бара i+1.

Все три — формы того, что в литературе по машинному обучению называют leakage (утечкой): загрязнение обучения/оценки информацией из будущего целевой переменной (Kaufman et al., 2012; Kapoor & Narayanan, 2023). В финансах канонический источник — Advances in Financial Machine Learning Лопеса де Прадо (2018): purged cross-validation, embargo, опасности бэктестинга. Дисциплина point-in-time восходит как минимум к Fama & French (1992), которые намеренно сдвигают бухгалтерские данные на шесть месяцев назад, чтобы переменная была известна раньше доходности, которую она объясняет.

Вопрос, на который отвечает эта статья, — количественный: не "вредна ли утечка" (с этим все согласны), а "сколько очков Sharpe дает каждая форма и какие из них опасны?" Без числа об этом невозможно рассуждать. Нельзя понять, является ли раздутие +0.3 шумом, а раздутие +14 — уликой.

Симулятор с известной истиной

Контролируемый синтетический рынок с известной ручкой edge: нулевой мир без реального edge рядом с миром edge, чей equity действительно растет

Чтобы измерить раздутие, нужно знать истину. Реальные данные никогда не говорят вам истину — они дают одну реализацию и никакого оракула. Поэтому мы строим синтетический рынок, где edge задаем мы сами.

Процесс генерации данных строго каузален и не взрывоопасен:

gt=ϕgt1+1ϕ2  ut,utN(0,1)g_t = \phi\, g_{t-1} + \sqrt{1-\phi^2}\; u_t, \qquad u_t \sim \mathcal{N}(0,1)

rt=agt1+σεt,εtN(0,1)r_t = a\, g_{t-1} + \sigma\, \varepsilon_t, \qquad \varepsilon_t \sim \mathcal{N}(0,1)

Здесь gtg_t — это экзогенный персистентный латентный дрифт (AR(1) с ϕ=0.95\phi = 0.95), а доходность бара rtr_t имеет небольшой дрифт agt1a\,g_{t-1}, который известен на один бар вперед. Поскольку gg не зависит от прошлых доходностей, обратной связи нет и ничего не взрывается. Параметр aa — это ручка, задающая объем реального edge:

  • a=0a = 0нулевая гипотеза: edge отсутствует полностью. Любой положительный Sharpe в бэктесте на 100% артефакт.
  • a>0a > 0реальный, торгуемый edge: честное моментум-правило действительно зарабатывает деньги.

Стратегия намеренно простая — знаковое моментум-правило. Признак — это скользящая сумма доходностей за LL баров (L=24L = 24 бара), а позиция — это ее знак:

csum = np.concatenate(([0.0], np.cumsum(r)))      # csum[k] = sum r[0..k-1]
mom = np.full(n, np.nan)
tt = np.arange(L - 1, n)
mom[tt] = csum[tt + 1] - csum[tt - L + 1]

signal = np.sign(mom)                              # position for the next bar

Этот моментум-признак — идеальный инструмент для изучения утечки на том же баре, потому что у него есть свойство, общее с реальными индикаторами: он механически содержит текущий бар. mom[t] включает r[t]. Так что если вы записываете r[t] как результат своей сделки, вы отчасти делаете ставку на величину, которая уже находится внутри вашего собственного сигнала. Вот и вся утечка, в конкретном виде.

Настройки: σ=0.01\sigma = 0.01 (1% волатильности за бар), односторонняя комиссия 0.00045 (round-trip 0.09%, как в нашем движке), Sharpe аннуализирован через 8760\sqrt{8760} (часовые бары), 4,000 независимых историй по 4,000 баров каждая. Все с фиксированным seed и детерминировано.

Честный пайплайн (единственный торгуемый)

Решение принимается по закрытию бара t, доходность зарабатывается на следующем баре, комиссия платится при изменении позиции:

def sharpe(sig, ret_booked):
    dpos = np.abs(np.diff(np.concatenate(([0.0], sig))))
    pnl  = sig * ret_booked - FEE_ONEWAY * dpos
    return pnl.mean() / pnl.std() * np.sqrt(8760)

honest = sharpe(signal[idx], r[idx + 1])           # earn r[t+1]: tradable

Три утечки, каждая — одно хирургическое изменение

same_bar  = sharpe(signal[idx], r[idx])

z_full    = (mom - mom[valid].mean()) / mom[valid].std()
norm_full = sharpe(np.sign(z_full[idx]), r[idx + 1])

z_sm      = (mom[:-2] + mom[1:-1] + mom[2:]) / 3.0   # uses t-1, t, t+1
indicator = sharpe(np.sign(z_sm[idx]), r[idx + 1])

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

Результаты: величина каждой утечки

Чистый рыночный шум, пропущенный через утечку на том же баре, превращается во взмывающую фейковую equity-кривую и индикатор производительности, застрявший у максимума

По результатам прогона на 4,000 seed-ах — вот годовой Sharpe, который показывает каждый пайплайн, при нулевой гипотезе (без edge) и при реальном edge (a=0.0011a = 0.0011, подобранном так, чтобы честный Sharpe был правдоподобным +1.57):

Пайплайн Нулевая гипотеза (без edge) Реальный edge
Честный (истина) −0.74 +1.57
Исполнение на том же баре +14.79 +15.85
Подглядывание индикатора (1 бар) +4.76 +6.62
Нормализация по всему ряду −0.84 +1.46

95% доверительные интервалы по seed-ам не шире ±0.05 в каждой ячейке; парные t-тесты на раздутие астрономически значимы там, где эффект реален (t > 400, p ≈ 0).

Сначала читайте столбец нулевой гипотезы, потому что это самый чистый из возможных экспериментов: edge отсутствует, поэтому честный пайплайн корректно теряет деньги (−0.74 — расход на комиссии за торговлю шумом). Теперь посмотрите, что утечки делают с этим же самым нулем:

  • Исполнение на том же баре: −0.74 → +14.79. Стратегия с нулевой предсказательной силой, торгующая случайный шум, показывает годовой Sharpe почти 15. Это не тонкое смещение — это фабрикация. Механизм ровно тот, что мы заложили: моментум-признак содержит r[t], поэтому запись r[t] — это ставка на собственный сигнал.
  • Подглядывание индикатора: −0.74 → +4.76. Если позволить сглаживателю заглянуть на один бар в будущее, из шума фабрикуется Sharpe около 5, потому что сглаженное значение на t теперь коррелирует с r[t+1], которое вы вот-вот заработаете.
  • Нормализация по всему ряду: −0.74 → −0.84. Раздутия практически нет. Это честный, неочевидный результат (подробнее — ниже).

Столбец реального edge несет более коварное послание. Когда реальный edge действительно есть (честный +1.57), утечки не просто добавляют константу — они толкают измеренный Sharpe до +15.85 и +6.62, далеко выше тех +1.57, которые реально можно торговать. Значит, измеренное число не может отличить мастерство от утечки. Утекший +6 и честный +6 выглядят на отчете идентично. Узнать, какой из них был на самом деле, можно только после того, как вы уже развернули капитал.

Утечка — это градиент, а не переключатель

Утечка на том же баре как плавная дозозависимая реакция: захват большей доли бара сигнала монотонно поднимает equity-кривую через порог готовности к продакшену

Естественное возражение: "записывать весь бар сигнала — это крайняя, нереалистичная ошибка". Поэтому мы прошлись по дозе — доле ff бара сигнала, захватываемой утечкой, от 0 (честно) до 1 (полная утечка на том же баре):

Доля захвата ff Sharpe (нулевая гипотеза) Sharpe (edge)
0.00 (честно) −0.74 +1.57
0.25 +3.90 +6.41
0.50 +9.86 +12.20
1.00 (полная утечка) +14.79 +15.85

Захват всего четверти бара сигнала переводит стратегию без edge от −0.74 к +3.90. Чтобы обмануться, не нужна полная ошибка на единицу; исполнение, чуть слишком выгодное — капля оптимистичного слиппеджа на баре сигнала, внутрибарный стоп, проверяемый по тому же бару, что его вызвал, — этого достаточно, чтобы преодолеть большинство порогов "готово к продакшену". Раздутие плавно и монотонно зависит от того, сколько настоящего вы позволяете себе торговать.

Как часто это отправляет убыточную стратегию в продакшен?

Число, которое должно тревожить практика, — это частота ложного деплоя: как часто утечка позволяет по-настоящему убыточной конфигурации преодолеть планку, по которой вы бы дали ей зеленый свет. Используя "годовой Sharpe ≥ 1.0" как критерий деплоя, при нулевой гипотезе:

  • Исполнение на том же баре: 68% стратегий без edge выглядят готовыми к деплою и при этом по-настоящему убыточны. Две из трех конфигураций чистого шума прошли бы фильтр Sharpe ≥ 1 и потеряли бы деньги в реальной торговле. (Эта величина здесь корректно определена, потому что утечка чисто в исполнении — честный аналог — тот же сигнал с честным исполнением.)
  • Подглядывание индикатора: оно тоже проталкивает через планку деплоя практически каждую конфигурацию без edge (99.9% проходят Sharpe ≥ 1) — шум пропускается прямо в продакшен.
  • Нормализация по всему ряду: планку преодолевают 12% — по сути базовая частота для шума, никакой премии от утечки.

Таксономия и как обнаружить каждую утечку

Три утечки не одинаково опасны, и различия между ними поучительны.

1. Утечка исполнения (дорогая)

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

Симптом: цена исполнения коррелирует с сигналом, потому что оба берутся с одного бара. Величина: огромная (+15 из шума при полной дозе, +3.9 при четверти дозы). Почему это худшее: ваш сигнал, почти по определению, строится на недавнем движении цены, так что доходность бара сигнала — это именно то, с чем ваш признак коррелирует сильнее всего. Записать ее — почти то же самое, что подсмотреть ответ.

Обнаружение — тест сдвига на один бар. Это самая ценная диагностика во всей статье. Возьмите свой бэктест и сдвиньте каждое исполнение на один бар позже (решение на i, исполнение на open[i+1]). Если результат почти не меняется — исполнение было честным. Если результат обрушивается или меняет знак — вы торговали в прошлом. Именно это произошло с нашим Sobol-поиском: сдвинули исполнение — и "прибыльный" OOS оказался убытком, точнее, реальная зависимость проявилась, как только утечку убрали.

entry_price = open_[i + 1]      # NOT close[i], NOT open[i]

2. Утечка индикатора / признака (тихая)

Симптом: индикатор на баре i зависит от данных из i+1 или позже — центрированная скользящая средняя, фильтр без каузальной задержки, метка пика/впадины, для подтверждения которой нужны будущие бары, Heikin-Ashi-подобное преобразование, получающее на вход будущие свечи. Величина: большая (+4.8 из шума). Почему прячется: утечка зарыта внутри вызова библиотеки. scipy.signal.filtfilt — с нулевой фазой, а нулевая фаза означает некаузальность. Признак "этот бар — локальный максимум" непознаваем, пока не напечатается следующий бар.

Обнаружение: для каждого индикатора спросите — какой максимальный индекс он читает? Если вычисление значения на t хоть раз касается t+1, оно некаузально. Считайте индикаторы на расширяющемся/скользящем каузальном окне и проверьте, что значение на баре t одинаково независимо от того, есть ли в массиве бары после t. (Наши реализации HMA/ADX это проходят: каждый выход на t читает только входы ≤ t.)

3. Утечка нормализации (специфичная для канала)

Симптом: скейлер (StandardScaler, min-max, глобальный z-score) обучается на всем датасете, включая тестовую выборку. Канонические ML-предупреждения об этом прямо говорят — Hastie, Tibshirani & Friedman, Elements of Statistical Learning, §7.10.2 ("правильный и неправильный способ делать cross-validation"), и собственный гайд scikit-learn по типичным ошибкам: "среднее должно быть средним по обучающей подвыборке, а не средним по всем данным."

Величина в нашем тесте:ноль (−0.74 → −0.84). Это неожиданный, честный результат, и его стоит понять, а не просто запомнить.

Почему она не раздула результат? Потому что наша стратегия использует признак только через его знак (порог на нуле). Масштабирование через стандартное отклонение никогда не меняет знак, а центрирование по глобальному среднему лишь слегка сдвигает точку пересечения нуля. Поэтому стандартизация по всему ряду для чисто знакового правила почти безвредна.

Не обобщайте это чрезмерно. Утечка нормализации специфична для канала. В тот момент, когда ваша стратегия начинает использовать величину признака — размер позиции, пропорциональный z-score, ненулевой порог входа, подобранный по масштабированному распределению, нейросеть, потребляющую стандартизированные входы, — скейлер, знающий будущее, начинает иметь значение, и тем больше, чем сильнее глобальные статистики отличаются от каузальных. Наш результат — не "утечка нормализации безопасна". Это "величина утечки зависит от канала, через который утекшая величина попадает в решение, и ее нужно измерять, а не предполагать". Знаковое правило — это единственный случай, когда именно эта утечка обходится дешево.

Куда это ведет

Look-ahead bias — первое звено цепи, которую документирует эта серия:

  • Он портит вход для валидации. Утекший бэктест спокойно пройдет walk-forward разбиение и будет выглядеть как широкое плато, а не переобученный пик — утечка одинакова во всех фолдах, так что кросс-валидация ее не поймает. Утечка — это режим отказа выше по потоку от overfitting, и никакая честная валидация ниже по потоку вас не спасет.
  • Он взаимодействует с поиском параметров: поиск по тысячам испытаний на утекших данных найдет конфигурацию, которая эксплуатирует утечку агрессивнее всего. "Победитель" — это худший нарушитель.
  • Именно поэтому расходится паритет бэктеста и live-торговли. Утечка — самое чистое объяснение разрыва в 30–50% между бэктестом и ботом, потому что live-торговля — это, механически, единственное место, где подглядеть невозможно.

Дисциплина, которая ловит все это, — та же самая, к которой годами призывает академическая литература: относиться к бэктесту как к статистическому эксперименту со строгой информационной границей. Bailey, Borwein, López de Prado & Zhu показали, как легко overfitting фабрикует фейковую производительность (2014); протокол бэктестинга Arnott, Harvey & Markowitz (2019) кодифицирует эту гигиену. Look-ahead bias — самая базовая из всех границ, граница по времени, и ее проще всего нарушить случайно.

Главные выводы

Диагностика сдвигом на один бар: сдвиг каждого исполнения на один бар позже сдувает фейковую взмывающую кривую обратно до честной истины

  1. Look-ahead bias количественно огромен и качественно невидим. Одна-единственная ошибка исполнения на один бар превратила Sharpe −0.74 (чистый шум, корректно убыточный) в +14.79. Ошибка — одна строка; следствие — сфабрикованный трек-рекорд.
  2. Это градиент. Захват даже 25% бара сигнала дает +3.90 из ничего. Не нужен явный баг — достаточно чуть более оптимистичного исполнения.
  3. Измеренное число не может отличить мастерство от утечки. Когда реальный edge существует, утечки раздувают отчет далеко за пределы торгуемой истины. Единственная защита — процесс, а не метрика.
  4. Тест сдвига на один бар — ваша самая быстрая диагностика. Сдвиньте каждое исполнение на один бар позже. Если результат обрушивается — вы торговали в прошлом.
  5. Величина утечки специфична для канала. Утечки в исполнении и индикаторе разрушительны; нормализация по всему ряду для знакового правила почти бесплатна. Измеряйте утечку через тот канал, которым она реально входит, — не предполагайте.

Полное контролируемое исследование — все три утечки, развертка дозы, анализ ложного деплоя, формальные методы и каждое число, воспроизводимое из единого детерминированного скрипта, — в сопутствующей научной статье на lookahead.marketmaker.cc, код и данные — на github.com/suenot/lookahead-inflation.

У стратегии в нашем нулевом эксперименте не было вообще никакого edge. Тем не менее она показала Sharpe 15. Если ваш бэктест выглядит слишком хорошо, первое, что стоит заподозрить, — не собственную гениальность, а собственные часы.

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

Авторы

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-трейдингу и обновления платформы.

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