3 вересня 2026 · інженерія даних

Хвилини, яких не існує: прогалини, зупинки торгів і бари з нульовим обсягом в історії OHLCV

Хвилини, яких не існує: прогалини, зупинки торгів і бари з нульовим обсягом в історії OHLCV

За календарний рік має бути 525,600 1-хвилинних барів. Наші дані безстрокових контрактів BTCUSDT за 2025 рік дали 525,557 рядків — на 43 хвилини менше, повнота 99.992%. Для безстрокового контракту на альткоїн із середньою капіталізацією на тій самій біржі, з того самого запиту й тим самим кодом, бракувало 1,247 рядків: 99.76%. А в хвилинному ряді акцій США за той самий рік було приблизно на 427,000 рядків менше, ніж у криптовалютному. Це не прогалина, а просто ринок, який закривається.

Три з цих чисел нічого особливого не означають. Четверте варте тисячі слів — 43.

525,6001-хвилинних барів у невисокосному році
0.008%відсутні для BTCUSDT у 2025 році
61%цих хвилин припали на години з волатильністю у верхньому децилі

Чотири різні причини, що однаково виглядають як прогалина у вашому dataframe

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

ТипЩо сталося насправдіЯк це виглядає в данихЩо робити
Не було угодРинок працював, але тієї хвилини ніхто не перетнув спредНа одних ендпойнтах — бар із нульовим обсягом (усі OHLC однакові, trades=0), на інших — рядка немаєЗалишити. Це реальна інформація: ніхто не хотів торгувати.
Біржа не працювалаМатчингова система була вимкнена — за розкладом чи ніРядка немає, іноді кілька таких рядків поспільПозначити дані як застарілі. Не торгувати через цей проміжок.
Зупинка торгів інструментомПорушення меж LULD, очікування новин, повідомлення про делістингРядка немає, а потім з’являється ціна аукціону відновлення торгівПозначити дані як застарілі, а відновлення торгів вважати розривом.
Помилка з вашого бокуОбмеження частоти запитів під час пагінації, повторний запит із пропущеною сторінкою, помилка циклу на межі часового поясуРядка немає, і це не відрізнити від наведених вище випадківВиявити й завантажити дані знову. Цю причину можна усунути.

Біржі по-різному обробляють перший пункт таблиці, і саме це створює проблеми. Ендпойнт свічок Coinbase повністю пропускає порожні інтервали, тож в історії малоліквідної пари багато буквальних прогалин. Klines Binance зазвичай повертає синтетичний бар з обсягом 0, де open=high=low=close дорівнює ціні останньої угоди. Та сама ситуація, але два різні формати даних. А завантажувач, який переіндексовує ряд у повну сітку хвилин, перетворить один формат на інший, нічого вам не повідомивши. Перш ніж писати заповнювач прогалин, з’ясуйте, як працює ваша біржа.

Майже всі 1,247 відсутніх хвилин в альткоїна належали до першої категорії: тонкий стакан о 04:00 UTC в неділю, ніхто не торгував. Дратує, але легко пояснити й здебільшого нешкідливо, бо стратегія все одно тоді не торгувала б. Саме тому я перестав звертати на це увагу й повернувся до 43 хвилин.

43 хвилини не були розкидані випадково

Якби пропуски траплялися рівномірно, 43 хвилини за рік перетворилися б на 43 поодинокі випадки — по одному кожні вісім з половиною днів, кожен із яких можна було б списати на похибку. Але ми побачили інше. Пропуски з’явилися у шести серіях: одна тривалістю 19 хвилин поспіль, одна — 11, дві — по 4 і ще кілька пар. Шість подій, а не 43 випадковості.

І ці події залежать від того, що вас цікавить. Біржі вимикаються під навантаженням, а навантаження зростає, коли рухається ціна. Я розбив усі години року за реалізованою волатильністю й перевірив, коли саме зникали хвилини: 61% припали на верхній дециль. Безумовна ймовірність пропуску будь-якої хвилини — 0.008%. Якщо ж хвилина припадає на годину з волатильністю у верхньому децилі, ймовірність становить приблизно 0.05% — у шість разів більше, та ще й ці пропуски групуються.

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

Що робить forward-fill кількома рядками коду нижче

Ось помилка, через яку я й написав цей текст. Візьмімо серію з 19 хвилин. Типова «гігієна» даних: переіндексувати ряд на повну сітку хвилин, заповнити OHLC останньою ціною закриття, а обсяг встановити на нуль. Тепер ряд безперервний, а індикатори обчислюються без жодного NaN.

У цих 19 барів high == low == close. True range кожного з них дорівнює нулю. ATR(14), обчислений на цьому відрізку після значення близько 240 USDT перед збоєм, до моменту відновлення біржі спадає приблизно до 34 — у середньому залишаються лише 5 справжніх барів цього вікна. Тепер передайте це у розрахунок розміру позиції з урахуванням волатильності, звичайного size = risk_budget / ATR типу. Розмір позиції збільшиться у 7 разів.

Наступний справжній бар — ціна відновлення торгів, і це зовсім не спокійний бар. У нашому випадку відкриття відбулося на 1.8% вище ціни закриття перед збоєм. Бектест без проблем відкрив позицію в 7 разів більшого розміру перед розривом у 1.8% — за ціною виконання, якої не могло бути, за ціною, яку ніхто не виставляв. Ця єдина синтетична угода дала на графіку капіталу більший результат, ніж місяць реального P&L, причому в протилежному напрямку. І виникла вона виключно через рядок очищення даних, написаний для охайного вигляду dataframe.

Просто видалити рядки замість заповнення — теж не вихід. Це та сама помилка в іншому вигляді. Після видалення цілі індексовані за кількістю барів вікна вводять в оману: «EMA на 20 барів» тепер охоплює 39 хвилин реального часу через збій, дохідність між сусідніми барами на межі містить увесь стрибок 1.8%, який сприймається як рух за одну хвилину, а оцінка волатильності на бар показує подію на 60 сигм. Ніщо про це не попереджає. Індекс досі монотонний.

Під час ресемплінгу проблема стає невидимою

Більшість досліджень працює не з 1-хвилинними барами, а з агрегованими даними, і агрегація маскує проблему. Після ресемплінгу до 5 хвилин прогалина тривалістю 19 хвилин перетворюється на 4 бари, перший і останній з яких неповні. Pandas обчислить цілком правдоподібні OHLC за 2 хвилинами, що залишилися, і позначить такий бар так само, як бар, побудований за 5 хвилинами. У вихідних даних нічим не позначено різницю між ними.

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

Наша політика

Тепер наші агенти діють так, по порядку:

  1. Ніколи не переіндексовувати мовчки. Завантажувач формує маніфест прогалин: початок, кінець, тривалість і одна з чотирьох імовірних категорій. Якщо серія відсутніх хвилин коротша за 3 бари, а обсяг навколишніх барів невеликий, це хвилини без угод. Усе, що триває довше в години активної торгівлі, вважаємо збоєм, доки не доведено інше.
  2. Повторно завантажити дані, перш ніж робити висновки. Половина наших перших прогалин виникла через помилки пагінації. Повторний запит до іншого ендпойнта чи іншого постачальника даних усуває четверту категорію й зменшує проблему ще до того, як доведеться щось оцінювати.
  3. Контроль застарілих даних замість заповнення прогалин. Стратегія отримує вхідний параметр data_age і жорстке правило: не відкривати нових позицій, якщо з останньої реальної угоди минуло більше ніж N барів; відкриті позиції закривати після відновлення торгів лише ринковим ордером, у ціну якого явно закладено знижку на ризик розриву. Уявне виконання гірше за угоди, яких не було.
  4. Індикатори мають бачити NaN, а не вигадку. Ціни, заповнені методом forward-fill, ніколи не потрапляють на рівень ознак. Якщо ATR неможливо обчислити, він не визначений, а невизначеність означає відсутність позиції. Помилка, про яку відомо, краща за тихе збільшення позиції у 7 разів.
  5. Звітувати про результати з урахуванням прогалин. У кожному бектесті, який ми публікуємо, поряд із загальним P&L показано P&L без угод поблизу збоїв. Якщо саме ці угоди забезпечують результат, то це артефакт даних.

Швидка перевірка, яку можна зробити вже сьогодні: згрупуйте відсутні хвилини в послідовні серії, а потім перевірте, яка частка угод у вашому бектесті відкривається або закривається в межах 30 хвилин від початку чи кінця такої серії. Якщо це менше ніж 1%, прогалини, найімовірніше, ні на що не впливають. Якщо 5% або більше, графік капіталу частково відображає простої біржі.

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

прогалини OHLCVінженерія данихбектестингресемплінгкриптовалютні ф’ючерси
ПоділитисяXLinkedInFacebookRedditHacker NewsWhatsAppTelegramEmail
← Усі статті