За календарний рік має бути 525,600 1-хвилинних барів. Наші дані безстрокових контрактів BTCUSDT за 2025 рік дали 525,557 рядків — на 43 хвилини менше, повнота 99.992%. Для безстрокового контракту на альткоїн із середньою капіталізацією на тій самій біржі, з того самого запиту й тим самим кодом, бракувало 1,247 рядків: 99.76%. А в хвилинному ряді акцій США за той самий рік було приблизно на 427,000 рядків менше, ніж у криптовалютному. Це не прогалина, а просто ринок, який закривається.
Три з цих чисел нічого особливого не означають. Четверте варте тисячі слів — 43.
Чотири різні причини, що однаково виглядають як прогалина у вашому 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 — ту саму інформацію у форматі, з яким може працювати рівень виконання угод.
Наша політика
Тепер наші агенти діють так, по порядку:
- Ніколи не переіндексовувати мовчки. Завантажувач формує маніфест прогалин: початок, кінець, тривалість і одна з чотирьох імовірних категорій. Якщо серія відсутніх хвилин коротша за 3 бари, а обсяг навколишніх барів невеликий, це хвилини без угод. Усе, що триває довше в години активної торгівлі, вважаємо збоєм, доки не доведено інше.
- Повторно завантажити дані, перш ніж робити висновки. Половина наших перших прогалин виникла через помилки пагінації. Повторний запит до іншого ендпойнта чи іншого постачальника даних усуває четверту категорію й зменшує проблему ще до того, як доведеться щось оцінювати.
- Контроль застарілих даних замість заповнення прогалин. Стратегія отримує вхідний параметр
data_ageі жорстке правило: не відкривати нових позицій, якщо з останньої реальної угоди минуло більше ніж N барів; відкриті позиції закривати після відновлення торгів лише ринковим ордером, у ціну якого явно закладено знижку на ризик розриву. Уявне виконання гірше за угоди, яких не було. - Індикатори мають бачити NaN, а не вигадку. Ціни, заповнені методом forward-fill, ніколи не потрапляють на рівень ознак. Якщо ATR неможливо обчислити, він не визначений, а невизначеність означає відсутність позиції. Помилка, про яку відомо, краща за тихе збільшення позиції у 7 разів.
- Звітувати про результати з урахуванням прогалин. У кожному бектесті, який ми публікуємо, поряд із загальним P&L показано P&L без угод поблизу збоїв. Якщо саме ці угоди забезпечують результат, то це артефакт даних.
Швидка перевірка, яку можна зробити вже сьогодні: згрупуйте відсутні хвилини в послідовні серії, а потім перевірте, яка частка угод у вашому бектесті відкривається або закривається в межах 30 хвилин від початку чи кінця такої серії. Якщо це менше ніж 1%, прогалини, найімовірніше, ні на що не впливають. Якщо 5% або більше, графік капіталу частково відображає простої біржі.
Я навчився довіряти радше формі пропусків, ніж їхній кількості. Набір із тисячами поодиноких прогалин у тихі години зазвичай цілком придатний. Набір із кількома щільними кластерами підказує, що під навантаженням щось ламається, а саме в такі моменти й працює ваша стратегія.
← Усі статті
