Совсем недавно решал задачу построения стакана котировок на основе исторических данных Московской Биржи. В открытых источниках ничего подобного не нашел, пришлось начинать с нуля и копать самому. Есть некоторые нюансы, о которых нужно знать. Про них буду упоминать по ходу.

Про биржевую торговлю, инфраструктуру и тестирование алгоритмов на исторических данных много писал и пишет IT Invest, спасибо ему. От себя добавлю, что на данных OrderLogs мы анализируем глубину рынка, ликвидность, спреды и еще много чего. Результаты используем в наших торговых алгоритмах.

Специально выбрал Фондовый рынок, так как тут больше всего вопросов. Валютный и Срочный рынок имеют свои особенности, но там проще. Реализация алгоритма на Java, код на GitHub.

Цель: Получить стакан котировок на любой момент времени.

Что на входе?


Файл со всеми заявками/сделками (Full Order Log), полученный от Биржи за 1 день, простой CSV формат, объем ~ 1 Гб. Описание формата и примеры данных есть на сайте Биржи. Вот фрагмент файла:



  • Код бумаги — торговый код инструмента
  • Buy/Sell — напраление заявки купить или продать
  • Время — в формате HHMMSSZZZ (101738829 = 10 ч 17 мин 38 сек 829 мс)
  • ID заявки — по нему отслеживаем жизнь заявки
  • Тип события — {1 — пришла заявка, 0 — удалена заявка, 2 — сделка}
  • Цена — цена в руб. за 1 бумагу
  • Объем — объем заявки/сделки в количестве бумаг
  • ID сделки — заполняется если тип события=2
  • Цена сделки — заполняется если тип события=2

Так как данные получены прямо из торговой системы, то поле «Время» — это время фиксации событий в торговой системе. Это надо принять к сведению. Кто получает маркетдату через FAST, тем данные приходят с задержкой (latency ~ 0.1 мс в зоне колокации). Это я к тому, что, если вы тестируете алгоритмы на данных Биржи, то там во времени не учитываются задержки, которые есть в наших реалиях. Так что если такие времена не критичны, можно «забить» на latency.

Фондовый рынок стартует в 9:50 и закрывается в 18:50, проходя 3 фазы:

09:50 – 10:00 — Аукцион открытия (Формируется цена открытия)
10:00 – 18:40 — Основные торги
18:40 – 18:50 — Аукцион закрытия (формируется цена закрытия)

Файл содержит только «Основные торги» + неисполенные заявки с аукциона открытия.

Алгоритм сборки


Понадобится 2 типа объекта «Orders» и «OrderBook». Orders — это дискретные заявки (строка в файле). А объект OrderBook — это агрегированные по цене заявки имеющий 2 поля: цена и объем, т.е. складываем все объемы с одинаковой ценой.

Давайте соберем стакан для инструмента «XXXX» на момент времени AABBCCDDD. С первой строки начинаем собирать заявки (объекты Orders), касающиеся бумаги «XXXX» в коллекции. Заявки Buy в одну, Sell в другую. И будем перебирать все заявки пока time<=AABBCCDDD.

Если тип события = 1 (поставить заявку), добавляем заявку в коллекцию Buy или Sell в зависимости от направления заявки.
Если тип события = 0 (удалить заявку), то по номеру заявки находим ее в коллекции и удаляем. Она там непременно должна быть, ибо по логике, нельзя удалить то, чего нет. Если не нашли нужную заявку, надо бить тревогу (либо ошибка в самих данных, либо ошиблись мы сами).
Если тип события = 2 (сделка), то по номеру заявки находим ее в коллекции и смотрим на ее объем:

  • Если V1=V2 (V1 — объем заявки в коллекции, V2 — объем сделки), то заявку удаляем из коллекции. Это значит, что заявка исполнилась полностью;
  • Если V1>V2, то в коллекции изменяем объем V1 на V1-V2. Это значит, что заявка исполнилась частично. А часть осталась в стакане;
  • Если V1<V2 (да-да и такое может быть, т.е. объем заявки 10, а сделка произошла на 20) это признак «айсберг» заявки. В таком случае, заявку удаляем из коллекции.

Типы событий = 2 всегда идут попарно — две стороны сделки. Как только нарушается условие time<=AABBCCDDD, выходим из цикла. На этот момент у нас в коллекциях содержатся все активные заявки на момент AABBCCDDD. Сортируем элементы коллекций по цене. Теперь настало время создания объектов типа OrderBook, напомню, это ценовые уровни (агрегированные показатели цена — объем), т.е. то, что привыкли видеть в стакане котировок. Циклом проходим по всем заявкам и складываем объемы заявок с одинаковой ценой. Вот и весь алгоритм.

Заявки типа «По рынку»


У рыночныех заявок в поле цена указывается «0». Такие заявки, так же обрабатываем как обычные. Они исполняются в моменте.

Заявки типа «Айсберг»


Айсберг заявки также встречаются в ордерлогах. Как показала практика, их невозможно идетифицировать наверняка. При формировании данных Биржа применяет свой алгоритм, который скрывает «айсберги». Но все равно, в некоторых случаях они мелькают, например висит заявка с объемом 10, а когда превращается в сделку — объем уже 100.

Знание этой информации достаточно, чтобы собирать стаканы, анализировать спреды и глубину рынка.

А теперь самое вкусное: давайте проверим наш алгоритм на практике. 2 февраля 2016 специально сделал скриншоты с терминала Bloomberg со стаканом, зафиксировав время. Инструменты брал не самые ликвидные: Яндекс (YNDX), Камаз (KMAZ)… дабы избежать сложности с синхронизацией времени. Паралельно собрал стакан на Биржевых данных описанным выше алгоритмом. Вот результат на 16:56:21:



Данные идентичны.

Пару слов о скорости


В потоке около 15 млн записей, где содержится около 300 инструментов. Самый ликвидный инструмент SBER за целый торговый день прогоняет за 20 сек. Для SBERа глубина стакана в ту или другую сторону — 500-600 ценовых уровней. Если через каждый «час» сохранять состояние стакана, время обработки сокращается до секунды, что меня вполне устраивает. А если еще записать нужный инструмет в отдельный поток, время сократится до десятых секунд.

Таким образом, стакан по любому инструменту на любой момент времени получаем менее чем за 1 секунду. Уверен, кто-то сможет предложить более эффективный алгоритм, буду рад.

P.S.: Кусок исторических данных можно взять на сайте Биржи бесплатно. Если нужны данные имено за 2 февраля, выгружу на Я.Диск (~1Gb). Дайте знать.

Комментарии (5)


  1. man_without_face
    06.02.2016 14:19

    Пишите про биржевые темы и разработки ещё, очень интересно


  1. rPman
    08.02.2016 00:22

    Цены на информация мягко говоря неадекватны:

    Все сделки и все заявки — Тип А
    1 рынок (месяц / год ) – 15 000 руб. / 150 000 руб.
    1 инструмент (месяц / год ) – 4 500 руб. / 45 000 руб.

    Все сделки и лучшие заявки — Тип B
    1 рынок (месяц / год ) – 4 500 руб. / 45 000 руб.
    1 инструмент (месяц / год ) – 1 500 руб. / 15 000 руб.

    Ежедневные итоги торгов (архивы за прошлые периоды) — Тип C
    Все рынки (месяц) – 2 700 руб.

    Архивная информация по сделкам не нужна за месяц… она нужна за несколько лет, иначе алгоритмы не обучить но цены таковы что получить их смогут те, у кого сделки с семи/восьми-значными суммами, полагаю таких тут не много :)


    1. fivelife
      08.02.2016 01:52

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

      Дорого? Ценность этих данных высока, во всем мире это дорогой продукт. Их приобретают банки, брокеры и, как правильно сказали, те, кто ворочает крупными суммами. Те, у кого развитая инфраструктура и быстрая маркет дата.


  1. webhamster
    08.02.2016 08:56

    > Кусок исторических данных можно взять на сайте Биржи бесплатно.

    Имеется в виду файлики в области «Примеры данных», или чтобы получить исторические данные надо зарегистрироваться на бирже и получить их посредством кнопки «Пробные данные»?


    1. fivelife
      08.02.2016 09:39

      Да, кнопка «пробные данные»