Ты прислал мне ноутбук в воскресенье вечером, и с тех пор он открыт у меня на втором мониторе. Кросс-секционный импульс: 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 года. Вот и вся ошибка, и она важнее, чем вся остальная модель издержек вместе взятая.
Чего не расскажет эндпоинт
В 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 с корнем тикера, совпадающим с названием инструмента, который потерял почти всю стоимость. Если загрузчик использует строку тикера как ключ и склеивает найденные файлы, в данных есть как минимум один ряд с разрывом, не вызванным движением цены. Характеристика импульса прочитает этот разрыв как самый сильный сигнал в сечении.
Всё остальное, что меняется у тебя на глазах
Раз уж ты признал, что список инструментов меняется со временем, та же логика относится ко всем остальным полям в этом ответе. А ты используешь для них сегодняшние значения.
| Поле | Как меняется | Что ломается |
|---|---|---|
| status | TRADING → 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% того, что тебе понадобится в понедельник.
Что я бы попросил тебя построить, прежде чем снова менять сигнал
- Задачу cron, которая ежедневно получает exchangeInfo с каждой исследуемой тобой площадки и сохраняет снимок в объектном хранилище с ключом по дате. В сжатом виде это меньше половины мегабайта. Десять лет обойдутся тебе в сумму, которой можно пренебречь при оплате S3, и дадут исследовательские возможности, которые позже уже не купить.
- Справочник инструментов на основе этих снимков: одна строка на каждую комбинацию (venue, symbol, valid_from, valid_to) со всеми полями. Сравнивай последовательные снимки и формируй его, создавая новую строку при любом изменении поля.
- Функцию формирования набора с обязательным аргументом — временной меткой.
universe(ts), никогда неuniverse(). Сделай так, чтобы значение по умолчанию было невозможно использовать: тогда никто, даже ты сам в 1am, не сможет случайно выбрать список выживших. - Проверку до листинга в загрузчике данных: до
onboard_tsминус 1 день не должно быть ни одного бара. Останавливай расчёт, а не выдавай предупреждение. - Стабильный внутренний ID инструмента, который переживает переименования, а тикер оставь атрибутом. Свяжи POL и MATIC с одним ID и помечай изменения номинала, чтобы логика непрерывности не соединяла их.
Сделай это и повтори расчёт. Думаю, результат окажется около моих 0.74, и тогда главный вопрос будет в том, есть ли что-то стоящее для бумажной торговли в стратегии с Sharpe 0.74, учитывающей делистнутые инструменты. Возможно, есть. Кросс-секционный импульс на бессрочных контрактах — не пустой звук, а часть того, что останется после исправления набора, — настоящее преимущество от переноса коротких позиций. Ты также увидишь, что результаты бумажной торговли и бэктеста начинают совпадать: бумажная торговля всегда работала с набором на момент времени. Другого выбора у неё не было.
P.S. Это не особенность криптовалют, просто здесь эффект заметнее. Исследователи акций борются с доходностями делистнутых компаний и повторно используемыми тикерами ещё с тех пор, как CRSP начала их публиковать. На рынках предсказаний проблема достигает предела: срок действия каждого контракта истекает по определению, поэтому набор состоит только из листингов и делистингов. Если когда-нибудь перенесёшь этот фильтр на Kalshi, сначала построй справочник инструментов. Другой истории там не бывает.
← Все статьи
