Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Упередження випередження: як уникнути помилок у бектесті

Упередження випередження (look-ahead bias) – це використання майбутніх даних у бектесті, що призводить до завищених результатів. Дізнайтеся про механізми виникнення цього упередження

Упереджувальне упередження — це використання інформації під час бектесту, якої ніхто не міг мати на момент розміщення симульованої угоди. Крива власного капіталу зростає, статистика виглядає чудово, але нічого з цього не витримує контакту з реальною заявкою. Ця сторінка визначає упередження, розглядає три механізми, що його створюють, і вимірює кожен з них на реальних ринкових даних.

Що таке зміщення випередження

Бектест – це твердження щодо рішення: у цей момент часу, з цією інформацією, правило спрацювало б так. Твердження є дійсним лише тоді, коли кожен вхід, який читає правило, вже існував у своїй остаточній формі до цього моменту часу. Зміщення випередження – це будь-яке порушення цієї умови, і воно є непомітним. Ніщо в електронній таблиці чи скрипті не скаржиться, коли рядок повертає значення, позначене пізніше за дату рішення, яке воно живить.

Три порушення охоплюють більшість випадків, що виникають на практиці. Рішення, яке читає ціновий бар, всередині якого воно також торгує. Всесвіт, зібраний з імен, які досі котируються сьогодні. Поле даних, яке було переглянуто після дати, яку воно несе.

Механізм перший: ухвалення рішення щодо бару, яким ви також торгуєте

Почніть з правила, яке звучить нешкідливо. Купуйте на силі, тобто в сесії, коли фонд закривається вище, ніж відкрився. При необережному кодуванні тест входить на відкритті цієї сесії та виходить на закритті, тоді як умова, за якою він фільтрує, — це ціна закриття того ж бару. Ніхто не знає ціну закриття на відкритті ринку.

Панель нижче запускає обидві версії на SPY, трекері S&P 500, протягом десяти календарних років. Перша колонка — неможлива. Друга відстає від ідентичної умови на одну сесію, що є найранішим моментом, коли фінансований рахунок міг діяти на ній.

ЗапитРішення в той самий день проти рішення з лагом в один день: SPY, середня денна прибутковість, 2016-2025
Точний SQL-код для кожного числа
WITH bars AS (
    SELECT date,
           toFloat64(open) AS o,
           toFloat64(close) AS c,
           toFloat64(close) > toFloat64(open) AS up_day
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
lagged AS (
    SELECT date, o, c, up_day,
           any(up_day) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prior_up
    FROM bars
)
SELECT toYear(date) AS year,
       countIf(up_day) AS signal_days,
       round(100 * avgIf(c / o - 1, up_day), 3) AS same_bar_avg_pct,
       round(100 * avgIf(c / o - 1, prior_up), 3) AS lagged_avg_pct,
       round(100 * (avgIf(c / o - 1, up_day) - avgIf(c / o - 1, prior_up)), 3) AS gap_pp
FROM lagged
WHERE date >= toDate('2016-01-01')
GROUP BY year
ORDER BY year
Run this yourself

Розбіжність не є тонкою. У 2016 версія з тим самим баром в середньому показала 0.447% на кваліфікуючу сесію, тоді як версія з затримкою показала -0.008%, різниця становить 0.455 відсоткових пункти на день. У 2025 ці два показники становлять 0.636% та -0.03%. При приблизно 140 кваліфікуючих сесій на рік, різниця в пів відсоткового пункту на сесію накопичується в криву власного капіталу, яку жоден реальний рахунок ніколи не друкував.

Весь прибуток є за визначенням. Вибір сесій, які закрилися вище свого відкриття, а потім вимірювання від відкриття до закриття на тих самих сесіях, вимірює величину, гарантовано позитивну. Правило виявило власний фільтр.

Варіанти цього ховаються в денних даних весь час: ковзна середня, яка включає сьогоднішнє закриття і використовується для торгівлі сьогодні, сигнал, обчислений на основі цін, вже скоригованих на спліт, який ще не відбувся, внутрішньоденний стоп, встановлений на максимумі сесії, який ще не був зафіксований. Загальна форма — часова мітка рішення, що знаходиться раніше, ніж один з її власних вхідних даних.

Механізм другий: всесвіт, витягнутий із сьогоднішніх виживальників

Другий механізм ніколи не торкається правила. Він знаходиться у списку імен, які правило може бачити.

Візьміть список тікерів від поточного постачальника даних, відмотайте його на десять років назад, і вибірка тихо виключає кожну компанію, яка збанкрутувала, була придбана або була виключена з біржі протягом цього періоду. Правилу ніколи не пропонували збиткових компаній. Панель вимірює розмір цього упущення: кожне ім'я, зареєстроване в США, яке торгувалося щонайменше 200 сесій вище $1 у певному календарному році, частка цієї когорти, яка все ще показує ціни в липні 2026 року, та медіанний річний дохід усієї когорти поряд із медіанним доходом лише виживальників.

ЗапитВиживання у всесвіті: назви, що торгувалися щороку, частка, що все ще котирувалася у липні 2026, та медіанна дохідність
Точний SQL-код для кожного числа
WITH recent AS (
    SELECT DISTINCT ticker
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2026-06-15')
      AND date <= toDate('2026-07-28')
),
per_name AS (
    SELECT toYear(date) AS year,
           ticker,
           argMin(toFloat64(close), date) AS first_close,
           argMax(toFloat64(close), date) AS last_close,
           count() AS sessions
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2015-01-01')
      AND date <= toDate('2024-12-31')
      AND close > 1
    GROUP BY year, ticker
    HAVING sessions >= 200
)
SELECT year,
       uniqExact(ticker) AS names_trading,
       round(100 * uniqExactIf(ticker, ticker IN (SELECT ticker FROM recent)) / uniqExact(ticker), 1) AS still_listed_pct,
       round(100 * quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker)), 2) AS median_return_all_pct,
       round(100 * quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent)), 2) AS median_return_survivors_pct,
       round(100 * (quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent))
                    - quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker))), 2) AS survivor_gap_pp
FROM per_name
GROUP BY year
ORDER BY year
Run this yourself

З 7063 імен, які подолали бар у 2015, 52.5% все ще торгувалися в липні 2026 року. Для когорти 2024 цей показник становить 87.5%, що здебільшого є питанням минулого часу: ім'я 2024 року мало два роки, щоб зникнути, а ім'я 2015 року — одинадцять.

Колонки доходу несуть у собі саму упередженість. У 2017 медіанне ім'я принесло 7.51%, тоді як медіанний виживальник приніс 11.32%, розрив у 3.81 відсоткових пунктів, переданий безкоштовно. Розрив є позитивним у кожному з 10 років, що відображені на графіку. Стратегія, протестована на виживальниках, успадковує його ще до прийняття першого рішення, а правила, які відсіюють слабкі компанії, успадковують більше, ніж медіана, оскільки зниклі імена непропорційно потрапляють до цього кошика.

Корекція — це всесвіт у точці часу: список імен таким, яким він був на кожну дату перебалансування, включаючи виключення з біржі, з позицією виключеної компанії, що закривається за її останнім показником, а не видаляється з обліку.

Механізм третій: дані, переглянуті після дати їх випуску

Ціни встановлюються один раз. Фундаментальні показники – ні. Квартальна цифра має дату кінця кварталу, але може бути змінена пізнішим звітом. База даних, яка зберігає лише поточну версію, надасть змінене число з оригінальною датою. Правило, що фільтрує за цим числом, читає виправлення, яке з'явилося через кілька місяців.

Така ж схема спостерігається у списках членів індексів, які оновлюються до поточних складових, історіях аналітичних прогнозів, що перезаписуються остаточним консенсусом, та коригуваннях корпоративних дій, що застосовуються ретроспективно до всієї серії цін. У кожному випадку збережений запис є знімком теперішнього часу з минулочасною датою.

Цьому запобігають дві практики. Використовуйте джерело, яке зберігає версії (vintages), тобто значення, яким воно було відоме на певну дату, а не значення, яким воно відоме зараз. Якщо версії недоступні, застосуйте затримку звітності, достатньо велику, щоб покрити реальну затримку, і розглядайте тривалість цієї затримки як параметр, який варто тестувати, а не як деталь, яку варто вгадувати.

Як виглядає крива, коли майбутнє просочується

Упередженість щодо майбутнього має свою ознаку: ефективність, яка значно перевищує те, що пропонував базовий ринок. Корисним орієнтиром є стеля ретроспективного аналізу — зростання, яке ідеальний прогноз, зроблений за один день до події, забезпечив би для одного інструменту.

ЗапитСтеля ретроспективного аналізу: SPY 'купи та тримай', той самий рік без найбільших днів зростання та ідеальна передбачуваність на один день
Точний SQL-код для кожного числа
WITH bars AS (
    SELECT date,
           toFloat64(close) AS c,
           any(toFloat64(close)) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
rets AS (
    SELECT toYear(date) AS year,
           c / prev_c - 1 AS r
    FROM bars
    WHERE date >= toDate('2016-01-01')
      AND prev_c > 0
)
SELECT year,
       count() AS sessions,
       round(100 * (exp(sum(log(1 + r))) - 1), 1) AS buy_and_hold_pct,
       round(100 * (exp(sumIf(log(1 + r), r < 0.02)) - 1), 1) AS without_big_up_days_pct,
       round(exp(sum(log(1 + abs(r)))), 1) AS hindsight_ceiling_multiple
FROM rets
GROUP BY year
ORDER BY year
Run this yourself

Ця стеля величезна. У 2020 ідеальне передбачення на один день для SPY примножило капітал у 27.7 разів за 253 сесій, порівняно з 16.2% для простого володіння фондом. У найспокійніший рік з десяти, 2017, той самий ідеальний прогноз приніс 2.2 разів більше капіталу.

Середня колонка показує, наскільки концентрована ця стеля. Якщо виключити сесії, які принесли понад 2% прибутку, то 2020 перетворюється з 16.2% на -44.9%. Витік, вартий лише кількох таких сесій на рік, значно впливає на результати бектесту. Крива з незначними просадками, високим коефіцієнтом успішних угод та найкращими днями, що припадають на найбурхливіші ринкові сесії, заслуговує на аудит.

Калібрування має більше значення, ніж будь-який окремий поріг. Правило, яке стверджує про стабільні 40% на рік протягом десятиліття, претендує на значну частину стелі ретроспективного аналізу, і чесне запитання полягає в тому, який вхідний сигнал повідомив йому про те, що має статися.

Як перевірити бектест на наявність упередженості випередження даних

  • Зсуньте все на один період. Зсуньте кожен сигнал на один повний період пізніше і повторно запустіть. Справжні переваги трохи зменшуються. Витоки зникають, часто до нуля.
  • Збурте майбутнє. Візьміть дані після кожного часового штампу прийняття рішення, замініть їх шумом або перетасуванням і повторно запустіть логіку прийняття рішень. Кожне рішення має залишатися ідентичним. Будь-яке рішення, що змінюється, прочитало щось, чого воно не могло знати.
  • Ставтеся до часових штампів як до даних. Для кожного вхідного значення зберігайте момент, коли значення стало доступним, поруч з моментом, який воно описує. Об'єднання, яке ніколи не порівнює ці дві колонки, не може забезпечити дотримання правила.
  • Відновіть всесвіт станом на дату. Включіть вилучені з лістингу та придбані назви, закрийте їх за останньою ціною і запустіть те саме правило на обох всесвітах, щоб оцінити внесок виживання.
  • Виміряйте відносно стелі. Розмістіть прибутковість стратегії поруч з показниками "купи і тримай" та "ідеальне передбачення" за той самий період. Відстань від стелі — це контекст, який коефіцієнт Шарпа сам по собі не дає.

Визначення розміру позиції відбувається після всього цього. Збільшений коефіцієнт виграшів призводить до збільшеного розміру ставки, що призводить до того, що визначення розміру позиції за критерієм Келлі перетворює помилку обліку на просадку. Така ж дисципліна застосовується до переміщення реального капіталу: паперова торгівля перед реальними грошима тестує механіку виконання ордерів, а автоматизовані системи несуть ту саму небезпеку на швидкості машини, що розглядається в багатоагентних системах торгівлі зі штучним інтелектом. Ефекти, які виживають після ретельного тестування в реальному часі, такі як аномалія низької волатильності, цікаві саме з цієї причини.

FAQ

Що таке упередження випередження (look-ahead bias) у бектестингу?

Це використання даних у симульованій угоді, які не були доступні на момент розміщення угоди. Поширені форми включають визначення ціни на барі, на якому угода також виконується, відбір всесвіту компаній, які все ще котируються сьогодні, та читання фінансового показника, який був переглянутий після дати, до якої він прив'язаний.

Як виявити упередження випередження?

Затримайте кожен сигнал на один повний період і повторно запустіть: витік зазвичай колапсує, тоді як реальна перевага м'яко згасає. Сильніший тест замінює всі дані після кожного часового штампу рішення шумом і підтверджує, що кожне рішення залишається незмінним.

Чи є упередження виживання (survivorship bias) тим самим, що й упередження випередження?

Вони відмінні, але часто йдуть разом. Упередження виживання — це вибірка, побудована з назв, які проіснували, і знання, які назви проіснували, саме по собі є інформацією з майбутнього, тому всесвіт лише тих, хто вижив, є витоком випередження, розташованим у даних, а не в правилі.

Чому бектести з упередженням випередження виглядають так добре?

Витік відбирає результати, а не прогнозує їх. На панелі вище сеанси відбору за власною ціною закриття дали середній приріст сесії приблизно на 0.455 відсоткових пунктів вище, ніж у затриманої версії в 2016, що є перевагою за визначенням, а не заробленою.

Що таке дані в точці часу (point-in-time data)?

Дані в точці часу зберігають кожне значення так, як воно було відоме на кожну історичну дату, включаючи початковий показник та кожен пізніший перегляд, поряд із всесвітом цінних паперів, як він стояв того дня. Це довідник, який потрібен бектесту, щоб показати, що його вхідні дані існували до його рішень.


Кожна цифра вище є збереженим, версіонованим запитом до реальних щоденних барів. Розгорніть SQL будь-якої панелі або запустіть тест затримки на власному правилі на терміналі Strasmore.

#backtesting#look-ahead bias#survivorship bias#quant#research process