Реализация HFT роботов на устройствах CEPappliance

    Мы уже 2 года плотно общаемся с HFT трейдерами и разработчиками решений для HFT торговли. И испытываем некоторую неловкость от того, что никто в этой среде открыто не рассказывает о своих технологических успехах. Так как мы делаем устройства CEPappliance на основе FPGA, применимые в том числе для HFT торговли, мы неустанно интересуемся кто и как использует FPGA в этой сфере. Складывается навязчивое впечатление, что FPGA в HFT торговле, как секс у подростков — все о них говорят, но мало кто ими занимается, да еще и успешно.

    На самом деле FPGA дает заметное преимущество по сравнению со всеми остальными технологиями (кроме, наверное, специализированных ASIC) в HFT и алгоритмический торговле. Наши тесты показывают, что заявка, пришедшая на Московскую биржу на 500 наносекунд быстрее других, будет исполнена первой с вероятностью 75%. То есть, если автоматическая торговая система быстрее других на 500 наносекунд принимает пакет (FAST, FIX или TWIME), разбирает его, обновляет книгу заявок (“стакан”), “понимает” что делать (создать/передвинуть/отменить ордер), формирует пакет с заявкой (FIX или TWIME) и отправляет его на биржу, то ее заявка будет исполнена раньше других в 75% процентах случаев.

    Другие наши тесты показывают, что используя продвинутые сетевые платы и ряд трюков на CPU можно получить задержку tick-to-trade в 2-4 микросекунды для перцентилей 97% и выше. А можно ли получить задержку менее 1-1.5 микросекунды, чтобы быть быстрее подавляющего большинства HFT трейдеров?

    На сегодня только FPGA1 может обеспечить такую задержку. И те, кто умеет извлекать из этого выгоду — HFT трейдеры, использующие FPGA — не спешат об этом рассказывать товарищам по цеху. Это затрудняет оценку нашего собственного решения, его позиционирование относительно конкурентов.

    В этой статье мы в деталях расскажем о возможностях CEPappliance в применении к HFT торговле. Может быть, кто-то еще наберется смелости и расскажет о своем решении…

    Основные особенности HFT системы


    Чтобы обеспечить малую задержку реакции системы на сигнал с биржи, нужно как можно раньше “узнавать” о появлении этого сигнала. Для этого HFT система должна:

    • “слушать” множество различных потоков рыночных данных, в которых нужный сигнал может быть транслирован, например, потоки ордеров, сделок, статистики и т.д. — любой из этих потоков может быстрее других;
    • реконсилировать несколько (2 или 4) фидов в рамках одного потока данных — любой из этих фидов может быть быстрее других, но данные не должны дублироваться;
    • фильтровать потоки по значениям критериев, которые могут динамически меняться;
    • строить “стакан” по данным из 2-х потоков, например, ордеров и сделок — любой из этих потоков может быть быстрее другого.

    В CEPappliance все это реализовано непосредственно в FPGA, включая разбор сообщений в форматах FAST, FIX и TWIME (FIX SBE), в которых и транслируются сигналы с биржи.

    После получения сигнала, торговая стратегия должна быстро сформировать реакцию. В CEPappliance алгоритм стратегии может быть реализован как непосредственно в FPGA, так и на языке высокого уровня HLL, программы на котором после компиляции выполняются оригинальными процессорами собственной разработки, размещенными на том же FPGA чипе.

    Особняком стоят функции контроля за работой робота и его мониторинга: отслеживание состояния робота, его расчетов, изменение параметров стратегии и т.п.

    В CEPappliance для этого есть

    • логирование;
    • зеркалирование всего (!) обмена между “железкой” и биржей без каких либо изменений на отдельном SFP+ порту FPGA платы;
    • бинарные адаптеры для приема/передачи значений параметров стратегии и возможность их обработки с помощью HLL, что существенно ускорит и сократит затраты по сравнению с реализацией этой обработки на языке описания аппаратуры, например, Verilog;
    • получение событий от адаптеров FIX и TWIME об установке/разрыве сессий для контроля состояния, в котором робот может нормально работать, имея все необходимые подключения.

    Архитектура


    CEPappliance представляет собой FPGA плату с чипом Altera Stratix V, устанавливаемую в PCI слот сервера (достаточно высоты 1U). Взаимодействие с внешним миром осуществляется по 10Gb Ethernet через SFP+ порты или PCIe.

    Архитектура HFT системы на основе CEPappliance

    Все компоненты реализованы непосредственно на чипе и “заточены” на минимальную задержку. Прошивка (firmware) CEPappliance содержит все возможные компоненты. А их включение в цепочку обработки событий осуществляется с хоста при помощи специальной программы-конфигуратора. Сначала “железка” прошивается, затем запускается, а потом конфигурируется. Конфигуратор читает программу обработки событий (или схему), компилирует ее в карту внутренней памяти FPGA и загружает на CEPappliance по TCP. После загрузки конфигурации CEPappliance стартует описанные в схеме адаптеры и осуществляет подключение к внешним системам.

    Для выполнения пользовательской логики в CEPappliance есть оригинальные процессоры (CPU) собственной разработки. Пользовательская логика записывается на HLL и компилируется в микропрограмму для процессоров, которых может быть несколько. Компилятор автоматически разобьет программу на независимые части, которые могут выполняться параллельно на разных процессорах. При разбиении программы учитываются поток управления и зависимости между операторами по данными.

    Разработка на HLL и компиляция написанных на нем программ значительно проще и быстрее, чем на языках описания аппаратуры, используемых для программирования FPGA.

    Благодаря возможности описывать торговые стратегии на языке высокого уровня программировать CEPappliance легче, чем программировать FPGA непосредственно на языках описания аппаратуры (Verilog, VHDL и т.п.) при сравнимых задержках. Программирование торговой стратегии на CEPappliance по простоте сравнимо с запуском той же стратегии с использованием языков C/C++, Java и т.п. благодаря наличию в CEPappliance готовых блоков сборки/разборки сообщений в соответствии с протоколами, сборки “стакана” и т.п., и однопоточной модели программирования (хотя на уровне микропрограммы выполнение может быть параллельным, как упоминалось ранее) при меньших задержках и джиттере (jitter). Позиционирование CEPappliance относительно других технологий, применяемых в HFT, в координатах “простота программирования” и “задержка” изображена на следующей диаграмме:


    Для приближения скорости работы CEPappliance к скорости работы решения, полностью реализованного в FPGA, любую часть схемы можно реализовать непосредственно на Verilog. Для этого есть оператор <wire/> (см. ниже). При этом менее критичные к скорости части (например, управление торговым роботом, мониторинг) можно оставить на HLL. Такой подход позволяет получить максимальную скорость работы торгового робота при значительной экономии усилий на разработке.

    Схема (программа) HFT стратегии


    Вот пример схемы, которая после получения обновления “стакана” USD000UTSTOM, строящегося на данных из потока ордеров (сообщения X-OLR-CURR) валютной секции Московской биржи, отправляет на биржу заявку на покупку по лучшей цене продажи:

    Пример схемы для CEPappliance
    <schema name="all-in-fpga">
        <adapters>
            <fast name="moex-fx-fast-orderlog" templates="FIX50SP2-2017-Mar.xml">
                <accept over="udp">
                    <on port="16001">
                        <multicast group='192.168.200.2'> <!-- IP-address of CEPappliance -->
                            <source ip='192.168.200.1' /> <!-- IP-address of the host publishing FAST messages -->
                        </multicast>
                    </on>
                </accept>
                <trading venue='moex' market='fx' />
            </fast>
            <fix name="moex-fx-fix" version="FIX.4.4">
                <initiate over="tcp">
                    <to host="192.168.200.1" port="3336" />
                </initiate>
                <sender>
                    <comp id="cep" />
                </sender>
                <target>
                    <comp id="moex" />
                </target>
                <heartbeat interval="30sec" />
            </fix>
        </adapters>
        <global>
            <instruments>
                <instrument name='i_main' symbol='USD000UTSTOM' session='CETS' maxpricelevels='1000' />
            </instruments>
            <!-- Price livel of the Order Book -->
            <type name='PriceLevel' def='tuple < money price, uint size >' />
            
            <constant name='SIDE_BUY'           type='uint'         value="1" />
            <constant name='SIDE_SELL'          type='uint'         value="2" />
            <constant name='ACC_TRADE'          type='string(32)'   value="'ABCDE'" />
            <constant name='ACC_CLIENT'         type='string(32)'   value="'OPQRSTUVWXYZ'" />
    
            <variable name='LotSize'            type='uint'         value="50" />
            <variable name='orderID'            type='uint'         value="1200000" />
            <variable name='waterline'          type='money'        value="0" />
        </global>
        <input from='moex-fx-fast-orderlog' as='orderlog'>
            <accept message='X-OLR-CURR' />
            <sequence name='GroupMDEntries'>
                <field name='MDUpdateAction'    type='uint' />
                <field name='MDEntryType'       type='string(1)' />
                <field name='MDEntryID'         type='string(16)' />
                <field name='Symbol'            type='string(16)' />
                <field name='MDEntryPx'         type='money' />
                <field name='MDEntrySize'       type='uint' />
                <field name='TradingSessionID'  type='string(8)' />
            </sequence>
        </input>
        <!-- Order Book -->
        <book orders='orderlog' as='fxbook'>
            <accept instruments='i_main' />
            <field name='instrument'            type='uint' />
            <field name='time'                  type='uint' />
            <field name='book'                  type='tuple < PriceLevel bid, PriceLevel ask >[ 16 ]' />
        </book>
        <!-- Calculate the best price  -->
        <map stream="fxbook in" as="algo out" >
            <field name="price"                 type="money"        expression="in.book[0].bid.price" />
            <field name="size"                  type="uint"         expression="in.book[0].bid.size" />
            <program>
                money newWaterline = in.book[0].bid.price + in.book[0].ask.price;
                if(newWaterline == waterline) {
                    skip; // do not send a new order
                }
                waterline = newWaterline;
            </program>
        </map>
        <!-- Increment orderID and use the new value to issue an Order. -->
        <!-- This operator caoud be merged with its source operator 'algo' and the compiler will do it for us -->
        <map stream='algo in' as='fix out'>
            <field name='ClOrdID'               type='uint' />
            <field name='OrderQty'              type='uint'         expression="in.size" />
            <field name='Price'                 type='money'        expression="in.price" />
            <program><![CDATA[
                orderID = orderID + 1;
                out.ClOrdID = orderID;
            ]]></program>
        </map>
        <output stream="fix" to="moex-fx-fix" >
            <as message="NewOrderSingle" />
            <format field='MsgSeqNum'           as="%5d" />
            <format field='Account'             as="{ACC_CLIENT}" />
            <format field='ClOrdID'             as="{ACC_TRADE}//%6d" />
            <format field='HandlInst'           as="1" />
            <format field='OrderQty'            as="%5d" />
            <format field='OrdType'             as="2" />
            <format field='Price'               as="%11m" />
            <format field='Side'                as="{SIDE_SELL}" />
            <format field='Symbol'              as="USD000UTSTOM" />
            <format field='TransactTime'        as="20170502-17:20:50" />
            <format field='NoTradingSessions'   as="1" />
            <format field='TradingSessionID'    as="CETS" />
            <format field='NoPartyIDs'          as="1" />
            <format field='PartyID'             as="{ACC_TRADE}" />
            <format field='PartyIDSource'       as="D" />
            <format field='PartyRole'           as="3" />
            <format field='SecondaryClOrdID'    as="8" />
        </output>
    </schema>
    


    Схема для CEPappliance представляет собой набор операторов, преобразующих входные потоки (streams) событий, получаемые через адаптеры от внешних систем, в выходные потоки событий, передаваемые через адаптеры во внешние системы.

    Событие в схеме — это набор полей. Каждое поле имеет имя и тип. События имеют одинаковую структуру, если у них один и тот же набор полей, а именно количество, порядок, имена и типы полей совпадают.

    Схема состоит из нескольких секций:

    • <adapters/> описывает адаптеры, через которые схема получает/отправляет данные; доступны адаптеры <fast/>, <fix/>, <twime/>, <bin/>. <fast/> работает только на прием FAST сообщений по UDP датаграмм. <fix/> и <twime/> работают на прием и/или отправку по TCP сообщений FIX и TWIME (FIX Simple Binary Encoding), соответственно. <bin/> может работать как на прием, так и на отправку по TCP или UDP и используется, как правило, для управления работой торговой стратегией — запрос состояния, установка параметров и т.п. Описание адаптеров может быть вынесено в отдельный файл. Тогда можно одну и туже схему запускать с адаптерами, сконфигкрированными под разные окружения. Напрмер, для тестового окружения можно иметь одну конфигурацию адаптеров, а для “боевого” окружения — другую конфигурацию адаптеров.
    • <global/> описывает финансовые инструменты <instruments/>, типы <type/>, константы <constant/> и (глобальные) переменные <variable/>, доступные в любой другой секции схемы.
    • <input/>, <output/>, <book/>, <map/>, <combine/>, <aggregate/>, <join/>, <wire/> описывают операторы, применяемые к потокам событий, “проходящих” по схеме:
      • <input/> принимает из адаптеров данные — входные поля схемы, задает им имена и типы, задает на входе фильтр <accept/> по типу сообщения;
      • <output/> выводит данные, упаковывая/форматируя их в виде сообщения определенного типа <as message=’...’/>;
      • <book/> строит “стаканы” для заданных инструментов <accept instruments=’...’/>; если после применения к стакану полученных изменений (транслируемых Московской биржей по протоколу FAST) он изменится, в схему будет отправлена “верхушка” измененного стакана — 16 лучших цен на покупку и 16 лучших цен на продажу в виде массива из 16 элементов типа PriceLevel; с “верхушкой” в схему также “прилетит” временная метка (поле time в <book/>), полученная из пакета с обновлениями, которую можно использовать для реконсиляции данных с другими потоками (например, с потоком статистики).
      • <map/> преобразовывает данные согласно выражениям, заданным в выходных полях <field… expression=’...’ /> оператора, или в программе в секции <program/>;
      • <combine/> объединяет несколько потоков в один;
      • <aggregate/> вычисляет агрегирующие значения на последовательности <window/> событий, размер которой определяется либо временем накопления, либо количеством событий;
      • <join/> объединяет, “склеивает” два потока событий по нескольким полям;
      • <wire/> описывает часть схемы, которая реализована на Verilog.

    Обработку событий в CEPappliance можно концептуально описать так. Получив событие от внешней системы, адаптер ищет <input/>, через который событие должно быть передано в схему. Для этого проверяются условия фильтрации <accept/> каждого <input/>, подключенного к этому адаптеру. Если событие не удовлетворяет ни одному такому условию, то оно отбрасывается и никак не обрабатывается. Если подходящий <input/> найден, то событие передается операторам, для которых этот <input/> является входным. При этом в событии сохраняются только те поля, которые объявлены в <input/>. Остальные поля события отбрасываются и не передаются в схему.

    Получив на входе одно событие, каждый оператор схемы, порождает другое (одно или несколько, как, например, <join/>) событие, набор и значения полей которого могут отличаться от входного. Только <combine/> не порождает новых событий а передает то, что получил на входе без изменений.

    “Пройдя” по схеме, преобразовавшись не раз, а, может быть, и “размножившись” и “добравшись” до <output/>, событие отправляется через адаптер, указанный в этом <output/>. Одно и то же событие может быть отправлено в несколько разных адаптеров. <output/> определяет в виде какого сообщения (например, NewOrderSingle или OrderCancelReplaceRequest в FIX) событие будет отправлено наружу.

    Взаимодействие разнородных частей схемы


    Это тот случай, когда часть схемы реализована на HLL, а другие ее части реализованы на Verilog. Части схемы, реализуемые на Verilog, описываются в схеме оператором <wire/>, поэтому мы такие части называем wire-логика.

    <wire streams=”orderbook, stats” />
    	<param ref=”variable1” />
    	<param ref=”constant1” />
    	<out as=”todtom”>
                <field name='ClOrdID'   type='uint' />
                <field name='OrderQty'  type='uint' />
                <field name='Price'     type='money' />
    	</out>
    </wire>
    


    Потоки, от которых wire-логика получаeт данные, перечисляются через запятую в атрибуте streams.

    Тегами <param/> описываются параметры, которые wire-логика использует. В качестве параметров могут передаваться значения констант или переменных. При этом во время выполнения wire-логика может изменять значения переменных, используя специальный Verilog-модуль, после чего измененные значения будут доступны частям схемы на HLL.

    Wire-логика может порождать несколько результирующих потоков, описываемых тегами <out/>, и которые могут идти сразу на выход схемы (операторы <output/>) либо в другие операторы схемы.

    На основании такого описания создается конфигурация (карта памяти) wire-логики, которая через специальный Verilog-модуль доступна в пользовательском Verilog-модуле, реализующем wire-логику.

    Чтобы реализация wire-логики стала частью итоговой прошивки для FPGA, код wire-логики на Verilog компилируется с прошивкой CEPappliance, поставляемой в виде Net-листа.

    Что дальше?


    В наших планах разработка адаптеров для подключения к другим биржам. Для этого у нас есть задел в виде модулей FIX, FIX SBE и FAST. Например, для получения рыночных данных с Чикагской товарной биржи (Chicago Mercantile Exchange, CME) нужно “научить” наш FIX SBE модуль разбирать пакеты, которые содержат несколько FIX SBE сообщений и другую служебную информацию, отправляемую CME.



    1Еще, конечно, специализированные ASIC могут, но нам не известны ASIC, которые бы разбирали, например, FAST или FIX. Имея аппаратную реализацию всех модулей в FPGA мы могли бы сами сделать специализированный ASIC и еще больше сократить задержки, но пока нам не хватает примерно 0,5-1 миллиона долларов для этого.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 18

    • UFO just landed and posted this here
        +1
        Как показывают наши замеры HFT вполне может существовать в мире C/C++. Но в то же время, наши замеры показывают, что достаточно на 500 наносекунд быстрее реагировать на сигналы с биржи, чтобы она обрабатывала заявки раньше других (пришедших позднее) в 75% случаев. Таким образом, вложив несколько десятков или сотню тысяч долларов (стоимость сервера, сетевой карты и разработчиков) можно получить решение с задержкой 2-4 микросекунды и встать в один ряд с множеством других, кто может себе позволить такие же расходы (справедливости ради нужно признать, что еще необходимы правильные знания и опыт, чтобы сделать или организовать разработку действительно быстрого решения в разумные сроки). Но чтобы вырваться из этой достаточно многочисленной когорты нужно переходить на FPGA.
        • UFO just landed and posted this here
      • UFO just landed and posted this here
          +1
          Ваша оценка стоимости труда FPGA разработчика несколько занижена. Во-первых, уровень оплаты того, кто способен качественно и быстро решить (самостоятельно качественно спроектировать и запрограммировать, учитывая риски и планы развития и т.п.) задачу стоит дорого независимо от используемой технологии. Во-вторых, сложность/скорость разработки под FPGA в несколько раз (по нашим оценкам — около 6) выше. Поэтому в совокупности стоимость FPGA решения получается существенно выше.
          • UFO just landed and posted this here
              +1
              Я правильно понял, что Вы считаете, что стоимость разработчика C/C++ — от $8k до $16k?
              • UFO just landed and posted this here
                  +1
                  Не могли бы Вы привести какие-либо подтверждения Ваших слов?
                  • UFO just landed and posted this here
                      0
                      Представляю себе так, что есть достаточно ресурсов с вакансиями, и российских, и в мире. И если стоимость FPGA-разработчика, действительно, в 4 раза ниже стоимости C/C++ разработчика, то это будет достаточно легко продемонстрировать.
                      Пока что я вижу одну вакансию с $8k до налогов и одно сообщение от человека, что ему впервые предложили $10k.
                      Ни $16k, ни «4 раза», к сожалению не увидел.
                      Да и одна конкретная вакансия совершенно не показатель.
                      А если брать в среднем, то общеизвестный факт, что зарплаты C++/C#/Java-программистов выше, чем у FPGA-разработчиков. Но, опять же, не в 4 раза.
                      • UFO just landed and posted this here
            • UFO just landed and posted this here
                +1
                Я думаю это справедливо для любой технологии с точностью до величины множителя: покупая решение мы берем на себя меньше рисков и выигрываем время.
          • UFO just landed and posted this here
              0
              Как я понял, PureFPGA автор называет реализацию на Verilog/VHDL, в противоположность трансляции с языка С.
              • UFO just landed and posted this here
                  +1
                  Вы правы. PureFPGA — это реализация ВСЕЙ стратегии в FPGA на языке проектирования аппаратуры, например, на Verilog. Этот вариант самый быстрый с точки зрения задержек, но и самый долгий и дорогой с точки зрения разработки. CEPappliance имеет свой язык HLL, который упрощает программирование, но платой за упрощение является некоторое увеличение задержки. В этой статье мы сравниваем по задержкам варианты реализации простого алгоритма на HLL и на Verilog.

            Only users with full accounts can post comments. Log in, please.