RUEN
Обсудить проект

Инженерный кейс

Настоящая заявка в стакане или ложная

Исследовательский стенд: собирает книгу заявок из потока обновлений, отличает реальную плотность от нарисованной и проверяет торговые гипотезы на живых данных. Ордера не выставляет ни при каких настройках.

ордеров выставляет
0
скрипта только на чтение
23
снимков на проверку
8

Стенд проверяет тезисы об устройстве биржевого стакана на живых публичных данных: собирает локальную книгу заявок из потока обновлений, ищет крупные скопления лимитных заявок, отличает настоящие от выставленных напоказ и прогоняет гипотезы на симулированном счёте. Ключей и подписи запросов в проекте нет физически — выставить заявку он не может даже случайно.

Порог значимости — не сумма в деньгах, а степенная функция от оборота

Первая версия работала как в учебниках: «крупной» считалась заявка от фиксированной суммы. Теперь порог считается как оборот инструмента в степени больше единицы, а коэффициенты подобраны по двум опорным точкам.

Фиксированный порог ошибается дважды сразу. Та же сумма на тонком инструменте — плита, которая реально держит цену, а на самом ликвидном — рядовой уровень, который проедается за секунды. Отсюда поток ложных сигналов сверху и слепота снизу. Показатель степени больше единицы не выдуман: ёмкость, разворачивающая цену, растёт быстрее оборота, а не пропорционально ему.

Значимость проверяется двумя разными вопросами

Скопление признаётся значимым, только если выполняются оба условия: размер перекрывает порог и превышает медианный уровень книги в десятки раз.

Одного порога мало. На инструменте с большим оборотом толстая вся книга, и крупнейший уровень перебивает порог просто потому, что там всё крупное — это не стена, а ровный стакан, сквозь который цена пройдёт не заметив. Два условия отвечают на два разных вопроса: хватит ли ёмкости развернуть цену и есть ли тут вообще что-то выделяющееся на фоне собственной книги.

Скопление ищется в кармане цен, а не на одной цене

Суммируется объём всех уровней в узком кармане вокруг кандидата — и тот же карман используется потом, когда проверяется, что от скопления осталось.

Крупный участник почти никогда не ставит весь объём одной заявкой: он раскладывает его по соседним ценам или доливает порциями. Измерение строго по одной цене такую слоёную плиту просто не найдёт. Карман фиксированной ширины — компромисс: он собирает разложенный объём, но не растягивается настолько, чтобы слепить «стену» из размазанной ликвидности.

Позже выяснилось, что несовпадение карманов — это ещё и источник дорогой ошибки. О ней ниже.

Реальность доказывается временем, а не снимком

Отдельная проверка делает восемь снимков книги с шагом в несколько секунд и смотрит две вещи: сколько разных цен занимало скопление и насколько скакал его размер.

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

Высокая изменчивость — ещё не улика

Монитор различает не «есть или нет», а шесть состояний: держится, клюют, разъедают у самой цены, сняли вдали, переставили, пробили. Развилка после факта «съели больше половины» — по расстоянию до цены и по тому, появилось ли рядом новое крупное предложение.

Наивный детектор «изменилось — значит ложная» ловит в основном честного маркет-мейкера: при дрейфе цены он обязан переставлять котировку каждые несколько секунд. Свой же прогон это показал: верхнее скопление держалось все восемь снимков, а нижнее у самого касания меняло цену в пяти случаях из восьми — и это был нормальный пересчёт, а не манипуляция. Без разделения «переставили» от «сняли» стенд выходил бы из живых сделок на каждом движении мейкера.

Книга собирается по протоколу, а не «снимок и подписка»

Порядок жёсткий: подключиться к потоку обновлений, сначала только копить приходящие события, затем взять снимок, затем прогнать накопленное, отбросив всё, что старше снимка, и дальше применять обновления тик в тик.

Очевидный порядок — снимок, потом подписка — оставляет между двумя действиями дыру: события, прошедшие в зазор, теряются навсегда. Расхождение не падает с ошибкой, оно копится тихо: висят давно снятые уровни, скопление держится там, где его уже нет. Это разница между книгой, которой можно верить, и книгой, которая врёт тем сильнее, чем дольше работает стенд.

Ошибка, которая стоила счёта, лечилась не подкруткой порога

В ранней версии скопление искали в одном кармане, а остаток в открытой позиции мерили в другом, вдвое уже. Отношение «осталось к исходному» из-за этого всегда было меньше единицы, ложный выход срабатывал через секунды после входа, и симулированный счёт просел на 83%.

Очевидная реакция на «слишком часто выходит» — поднять порог. Это замаскировало бы ошибку, не убрав её: систематическое смещение при любом пороге рано или поздно сработает. Починка — четыре независимых условия: тот же карман, что при поиске; минимальное удержание; остаток должен продержаться ниже порога несколько секунд; и цена должна реально давить на скопление, а не болтаться рядом.

Предохранители останавливают торговлю, а не сделку

После того случая в движок вписаны отсечки другого уровня: блокировка всех новых входов при просадке, пауза после серии убытков, остывание инструмента, лимит сделок по одному инструменту за сессию. Возобновление — только руками.

Риск на сделку защищает от одной плохой сделки, но не от системной поломки, когда сломан сам детектор и тиражирует одну ошибку. Именно это и произошло: десятки сделок, каждая формально в рамках риска. И ключевая деталь: предохранитель, который снимает себя сам, — не предохранитель.

Критерий успеха записан в код до того, как появились данные

В проверке фактора протокол лежит отдельным словарём: что проверяем, на каком горизонте, какая модель издержек, какой результат считается успехом и когда останавливаемся. Журнал только дописывается.

Без записанного заранее критерия любой результат становится «почти получилось»: не добрали значимость — подкрутим окно, не тот знак — поменяем сторону. Именно этот протокол позволил стенду опровергнуть собственные гипотезы: одна связь оказалась переобучением, другая — настоящей, но в двадцать пять раз слабее издержек, третья развалилась при строгой проверке.

Python · WebSocket · asyncio · стандартная библиотека