27 Августа 2026 · инженерия данных

Ваш набор инструментов — машина времени: смещение выживших в бэктестах криптовалютных бессрочных контрактов

Ваш набор инструментов — машина времени: смещение выживших в бэктестах криптовалютных бессрочных контрактов

Ты прислал мне ноутбук в воскресенье вечером, и с тех пор он открыт у меня на втором мониторе. Кросс-секционный импульс: 150 бессрочных контрактов Binance USDⓈ-M с наибольшим долларовым объёмом за 30 дней, ребалансировка раз в неделю, покупка верхнего дециля и продажа нижнего, период с 2021-01 по 2026-06. Sharpe 2.31, максимальная просадка 14.2%, и модель издержек, которая выглядит добросовестной: taker на входе и выходе, фондирование начисляется за каждый интервал, проскальзывание зависит от размера позиции относительно книги заявок. Сложные части ты сделал правильно. Потом ты спросил, почему шесть недель бумажной торговли прошли без результата и не выродилось ли преимущество.

Оно не выродилось. Его просто никогда не было в бэктесте. Посмотри на ячейку 4:

info = requests.get(BASE + "/fapi/v1/exchangeInfo").json()
symbols = [s["symbol"] for s in info["symbols"]
           if s["status"] == "TRADING" and s["quoteAsset"] == "USDT"]

Ты вызвал этот эндпоинт в июне 2026 года и использовал ответ, чтобы определить, чем можно было торговать в марте 2021-го. По определению, каждый инструмент в том списке дожил до июня 2026 года. Вот и вся ошибка, и она важнее, чем вся остальная модель издержек вместе взятая.

2.31 → 0.74Sharpe: список выживших и данные на момент времени
~1 из 5инструментов твоего набора 2021 года больше не существует
380 KBежедневный архив exchangeInfo в сжатом виде

Чего не расскажет эндпоинт

В exchangeInfo нет истории. Нет параметра asOf, нет архива, нет журнала изменений. Это снимок текущего состояния, и Binance никогда не обещала ничего другого. Когда контракт делистят, его запись удаляют из ответа, и для API тикер перестаёт существовать. С 2019 года Binance добавила более 600 бессрочных контрактов USDⓈ-M и вывела из обращения более сотни. BTS, COCOS, TOMO, RAY, FTT, SC — длинный хвост контрактов альтсезона 2021 года, у каждого из которых был один славный квартал, а потом объёмы иссякли и биржа их убрала.

Подумай, какие названия особенно нравятся 30-дневному фильтру импульса. Не BTC. Ему нравится монета, которая только что утроилась на волне листинга и шума в Twitter. Эти множества — инструменты, проходящие фильтр, и инструменты, которые в итоге делистят — сильно пересекаются, а твой фильтр набора исключил это пересечение.

Я повторил расчёт на нашем архиве снимков, с набором на каждый момент времени, тем же сигналом и теми же издержками. Получился Sharpe 0.74 при просадке 31%. Примерно две пятых разницы дают делистнутые инструменты, которыми тебе не полагалось торговать. Ещё четверть — зеркальная проблема, более тонкая, и, подозреваю, она понравится тебе меньше.

Другой конец машины времени: инструменты, которых ещё не существовало

Твоя характеристика — доходность за 90 дней. В скользящем окне у тебя min_periods=20, потому что однажды ты выставил это значение, чтобы разогрев не съел первый квартал выборки, и больше к нему не возвращался. Поэтому контракт, добавленный 21 день назад, получает оценку импульса по трём неделям движения цены после листинга, попадает в верхний дециль примерно при каждом удачном листинге и оказывается в портфеле.

Эту часть ещё можно считать допустимой. А вот что недопустимо: провайдер восстановил историю некоторых инструментов по спотовым или индексным данным за период до появления бессрочного контракта. Поэтому у нескольких контрактов история kline начинается раньше их собственного onboardDate. Проверить это можно примерно за минуту. Соедини таблицу цен с датами листинга и посчитай строки, которые предшествуют листингу. В твоих данных у 41 инструмента есть бары до листинга. Один из них летом 2023 года добавляет к кривой капитала недельную доходность 6% за счёт позиции в контракте, до появления которого оставалось ещё 9 дней.

Строка тикера — не идентификатор. Это метка, которую биржа сдаёт в аренду, иногда дважды.

И тут мы подходим к переименованиям. MATICUSDT стал POLUSDT. FTMUSDT стал SUSDT после конвертации 1:1. История с LUNA в мае 2022 года оставила после себя LUNCUSDT, а позже появился совершенно новый LUNAUSDT с корнем тикера, совпадающим с названием инструмента, который потерял почти всю стоимость. Если загрузчик использует строку тикера как ключ и склеивает найденные файлы, в данных есть как минимум один ряд с разрывом, не вызванным движением цены. Характеристика импульса прочитает этот разрыв как самый сильный сигнал в сечении.

Всё остальное, что меняется у тебя на глазах

Раз уж ты признал, что список инструментов меняется со временем, та же логика относится ко всем остальным полям в этом ответе. А ты используешь для них сегодняшние значения.

ПолеКак меняетсяЧто ломается
statusTRADING → SETTLING → удалёнСмещение выживших; фиктивные выходы по цене закрытия, по которой никто не торговал
onboardDateНовые листинги каждую неделю; у делистнутых инструментов поле отсутствуетТорговля контрактами до их появления
tickSize / stepSizeМеняются вслед за уровнем ценыОкругление ордеров и лимитные цены, которые биржа отклонила бы
minNotionalСо временем повышается для малоликвидных книг заявокНебольшие позиции, которые твой боевой маршрутизатор ордеров отклонил бы
fundingIntervalHoursГодами было 8h, затем для многих инструментов стало 4h или 1hПеренос средств рассчитан с ошибкой в 2–3× именно для тех альткоинов, которые выбирает твой фильтр
leverage bracketsПересматриваются уровни и поддерживающая маржаМоделирование ликвидаций и доступная маржа

В твоём случае сильнее всего бьёт ошибка с фондированием. Цикл начисления предполагает три выплаты в день на протяжении всей истории. Значительная часть портфеля альткоинов перешла на четырёхчасовое фондирование, а на коротких позициях в инструментах с высоким фондированием приходится большая доля смоделированной прибыли и убытков. Здесь ошибка не в округлении. Она кратная.

Делистинг — это событие, а ты его не моделируешь

В повторном расчёте с данными на момент времени я заложил стратегии щедрый вариант выхода. В реальности делистинги идут по сценарию: объявление, обычно за 7–14 дней, затем период только для сокращения позиций, затем принудительный расчёт по mark price. Объявление — публичная информация, и ты можешь на него отреагировать, поэтому честная симуляция закрывает позицию по цене закрытия дня объявления. Но типичный альткоин перед делистингом уже теряет 10-20% за предыдущую неделю, книга заявок становится тонкой, и твоя модель проскальзывания должна учитывать, что рынок перешёл в другой режим. Если движок исполнения выдаёт цену расчёта без влияния на рынок, ты незаметно превращаешь умирающие активы в активы, доступные для торговли по справедливой стоимости.

Если ты не архивировал exchangeInfo, это ещё не конец. В открытом дампе по адресу data.binance.vision/data/futures/um/monthly/klines/ папки делистнутых инструментов хранятся долго после того, как API забывает о них. Перечисли папки: первый и последний месячные файлы по каждому инструменту дадут рабочий диапазон дат листинга и делистинга без стороннего провайдера. Это реконструкция, а не архивная запись; размеры шага цены и интервалы фондирования восстановить не получится. Но ты узнаешь, какие инструменты существовали в тот или иной момент, а это 80% того, что тебе понадобится в понедельник.

Что я бы попросил тебя построить, прежде чем снова менять сигнал

  1. Задачу cron, которая ежедневно получает exchangeInfo с каждой исследуемой тобой площадки и сохраняет снимок в объектном хранилище с ключом по дате. В сжатом виде это меньше половины мегабайта. Десять лет обойдутся тебе в сумму, которой можно пренебречь при оплате S3, и дадут исследовательские возможности, которые позже уже не купить.
  2. Справочник инструментов на основе этих снимков: одна строка на каждую комбинацию (venue, symbol, valid_from, valid_to) со всеми полями. Сравнивай последовательные снимки и формируй его, создавая новую строку при любом изменении поля.
  3. Функцию формирования набора с обязательным аргументом — временной меткой. universe(ts), никогда не universe(). Сделай так, чтобы значение по умолчанию было невозможно использовать: тогда никто, даже ты сам в 1am, не сможет случайно выбрать список выживших.
  4. Проверку до листинга в загрузчике данных: до onboard_ts минус 1 день не должно быть ни одного бара. Останавливай расчёт, а не выдавай предупреждение.
  5. Стабильный внутренний ID инструмента, который переживает переименования, а тикер оставь атрибутом. Свяжи POL и MATIC с одним ID и помечай изменения номинала, чтобы логика непрерывности не соединяла их.

Сделай это и повтори расчёт. Думаю, результат окажется около моих 0.74, и тогда главный вопрос будет в том, есть ли что-то стоящее для бумажной торговли в стратегии с Sharpe 0.74, учитывающей делистнутые инструменты. Возможно, есть. Кросс-секционный импульс на бессрочных контрактах — не пустой звук, а часть того, что останется после исправления набора, — настоящее преимущество от переноса коротких позиций. Ты также увидишь, что результаты бумажной торговли и бэктеста начинают совпадать: бумажная торговля всегда работала с набором на момент времени. Другого выбора у неё не было.

P.S. Это не особенность криптовалют, просто здесь эффект заметнее. Исследователи акций борются с доходностями делистнутых компаний и повторно используемыми тикерами ещё с тех пор, как CRSP начала их публиковать. На рынках предсказаний проблема достигает предела: срок действия каждого контракта истекает по определению, поэтому набор состоит только из листингов и делистингов. Если когда-нибудь перенесёшь этот фильтр на Kalshi, сначала построй справочник инструментов. Другой истории там не бывает.

смещение выжившихданные на момент временикриптовалютные фьючерсыбэктестингинженерия данных
ПоделитьсяXLinkedInFacebookRedditHacker NewsWhatsAppTelegramEmail
← Все статьи