Хабрахабр

[Перевод] Выбор места для сервера и софта, тестирование рыночной неэффективности: как на самом деле создают роботов для торговли н

Для примера используется стратегия арбитражной торговли на американских биржах. Автор блога Financial Hacker рассказал о том, как на самом деле устроен процесс разработки высокочастотных стратегий для торговли на бирже — от важности анализа возможных задержек, до вопросов получения данных и тестирования (все с примерами кода). Мы подготовили адаптированный перевод этого материала.

Введение

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

Итоговая прибыльность системы зависит от ее скорости задержки, временем между получением котировки и исполнением заявки в торговом ядре биржи. Преимущество HFT в получении рыночных данных и исполнении своих заявок раньше большинства участников. Его можно оптимизировать двумя способами: минимизируя физическое расстояние до биржи, и увеличивая скорость работы самой системы. Задержка (latency) — наиболее релевантный фактор при оценке HFT-системы. И первое гораздо важнее второго.

Местоположение

В идеале, HFT-сервер должен быть расположен прямо на бирже. И большинство торговых площадок в мире с удовольствием продают серверные места в своих дата-центрах — чем ближе к хабу главной сети биржи, тем лучше считается место. Электрические сигналы в экранированном проводе передаются со скоростью в 0,7 — 0,9 превышающую скорость света (300 км/мс). Сокращение расстояния до источника сигнала на один метр выливается в целых 8 наносекунд преимущества в раундтрипе (времени от отправк заявки до получения информации об ее исполнении). Сколько торговых возможностей можно упустить за 8 наносекунд? Никто не знает, но люди готовы платить за каждую сэкономленную наносекунду.

При этом для торговли ей нужно получать данные с бирж NYSE в Нью-Йорке и CME (Чикаго) одновременно. К сожалению (или к счастью с точки зрения экономии — размещение в дата-центрах бирж стоит огромных денег), анализируемая в данной статье HFT-система по ряду причин не может быть размещена на колокации в ЦОД торговой площадки.

В теории, идеальное расположение для системы c аналогичными требованиями — это городок Уоррен, штат Огайо. Между двумя этими городами протянуты высокоскоростные кабели, а также функционирует микроволновая сеть. Неизвестно, есть ли там хаб для высокоскоростных торговцев, однако расстояние в 357 миль до обеих бирж выливается примерно в 4 мс задержку раундтрипа. Он расположен ровно посередине между Нью-Йорком и Чикаго.

image

Уоррен, Огайо – Мекка HFT торговцев (изображение: Jack Pearce / Wikipedia Commons)

Идея для стартапа: купить пару гаражей в Уоррене, подключиться к высокоскоростному кабелю между Нью-Йорков и Чикаго и зарабатывать, сдавая серверные стойки! Вне всяких сомнений, сервер в этом чудесном городке обойдется гораздо дешевле сервера в стойке на бирже в Нью-Йорка.

Софт

Когда вы уже вложили деньги в выбор оптимальной локации и каналы связи для HFT-системы, вам определенно захочется получить и софт, который будет соответствовать необходимой скорости. Коммерческие торговые платформы обычно недостаточно быстры, к тому же их код всегда закрыт, точно неизвестно, что и как в них работает. Поэтому HFT-системы почти никогда не базируются на существующих платформах, а пишутся с нуля. Не на R или Python, а на каком-либо из «быстрых» языков. В этот список входят:

  • C или C++ — отличная комбинация высокоуровневости и высокой скорости. C легко читать, при этом он почти также быстр и эффективен, как машинные языки.
  • Pentium Assembler — напишите свой алгоритм с помощью машинных инструкций и он обгонит даже разработанные на C системы. Из минусов такого подхода: поддерживать такой код будет непросто, все программисты знают, насколько тяжело читать программы на ассемблере, написанные кем-то другим.
  • CUDA, HLSL или ассемблер GPU — если алгоритм активно использует векторные или матричные операции, то запустить его на видеокарте может быть отличное идеей.
  • VHDL — если любой софт будет слишком медленным, а успех сделки для конкретного алгоритма будет зависеть от наносекунд, то «ультимативным решением» здесь будет кодирование системы напрямую в железе. В VHDL можно определять арифметические единицы, цифровые фильтры и секвенсоры FPGA чипов с тактовой частотой до нескольких сотен мегагерц. Такие чипы можно напрямую подключать к сетевому интерфейсу.

За исключением VHDL, все вышеописанное должно быть знакомо многим специалистам (особенном разработчикам компьютерных игр в 3D). Но стандартным языком для высокочастотной стратегии можно назвать C/C++. В этом материале используется именно он.

Алгоритм

Многие HFT-системы «охотятся» на трейдеров-конкурентов с помощью «методов обгона». Они замечают вашу заявку, а затем покупают тот же актив по той же цене на пару микросекунд раньше вас и продают его вам чуть дороже, зарабатывая на этом. На некоторых биржах такая торговля запрещена для создания равных условий для всех участников, другие площадки могут это разрешать, надеясь больше заработать на комиссиях. В примере из этой статьи подобные механизмы использоваться не будут, вместо этого будет описана арбитражная стратегия. Предположим, что наши серверы расположены в Уоррене и у нас есть высокоскоростной канал до Чикаго и Нью-Йорка.

ES — это торгуемый в Чикаго фьючерс S&P500. Арбитраж будет происходить между финансовыми инструментами ES и SPY. Один пункт ES равняется 10 центам SPY, так что цена ES примерно в десять раз выше SPY. SPY — торгуемая в Нью-Йорке ETF, которая также привязана к индексу S&P500. Существуют публикации, авторы которых доказывают, что эта корреляция будет «ломаться» на небольших временных отрезках. Поскольку оба актива основаны на одном и том же индексе, можно ожидать высокой корреляции их цен. Алгоритм из примера будет работать по следующей стратегии: Любая возникающая на короткое время разница в ценах пары ES-SPY, превышающая спред бид-аск, создает возможности для арбитража.

  • Определять разницу SPY-ES.
  • Определить ее отклонение от среднего.
  • Если отклонение превышает спред бид-аск и выходит за определенное пороговое значение, то открываются позиции в ES и SPY в противоположных направлениях.
  • Если отклонение разворачивает свое направление и превышает заданный (чуть меньший) порог, позиции закрываются.

Алгоритм записан на C. Если вы до этого никогда не видели кода HFT-алгоритмов, он может показаться немного странным:

#define THRESHOLD 0.4 // Entry/Exit threshold // Алгоритм HFT арбитража // возвращает 0 для закрытия всех позиций
// возвращает 1 для открытия длинной позиции по ES, короткой по SPY // возвращает 2 для открытия короткой позиции по SPY, длинной по ES
// в противном случае возвращает -1 int tradeHFT(double AskSPY,double BidSPY,double AskES,double BidES)
else { if(Position == 1 && -Deviation > SpreadES+THRESHOLD/2) return Position = 0; if(Position == 2 && Deviation > SpreadSPY+THRESHOLD/2) return Position = 0; } return -1; }

Функция traderHFT вызывается из некого фреймворка (в статье он не рассматривается), который получает котировки и отправляет приказы. В качестве параметров используются текущие лучшие цены на покупку и продажу по ES и SPY из верхней части книги заявок (предполагается, что цена SPY умножается на десять, чтобы оба актива находились в одном масштабе). Функция возвращает код, который говорит фреймворку, открывать или закрывать позиции, или ничего не делать. Переменная Arbitrage представляет средняя разница цен между SPY и ES. Ее среднее (ArbMean) фильтруется медленной экспоненциальной скользящей средней, а Deviation от среднего также фильтруется быстрой скользящей средней для предотвращение реакций на котировки вне нужного диапазона. Переменная Position обозначает машинное состояние, которое может принимать значение лонг, шорт и ничего. Пороговое значение для входа или выхода из позиции (Threshold) установлен на отметке в 40 центов. Это единственный регулируемый параметр системы. Если бы стратегия предназначалась для реальной торговли, нужно было бы также оптимизировать пороговое значение с использованием нескольких месяцев данных по ES и SPY.

Однако такой необходимости нет: даже если использовать для компиляции компилятор фреймворка Zorro (его развивает автор статьи), функция tradeHFT исполняется всего за 750 наносекунд. Такую минималистичную систему совсем не трудно перевести на ассемблер или даже запрограммировать в чипе FPGA. Поскольку время между двумя котировками по ES составляет 10 микросекунд или более, скорости C вполне достаточно. Если использовать более продвинутый компилятор вроде Microsoft VC++, это значение можно снизить до 650 наносекунд.

Во-первых, действительно ли возникает разница в ценах двух инструментов достаточная для извлечения арбитражной прибыли? В ходе нашего HFT-эксперимента требуется ответить на два вопроса. Во-вторых, при какой максимальной задержке система по-прежнему будет работать?

Данные

Для бэктестинга HFT-системы данные, которые можно обычно получить у брокеров бесплатно, не подойдут. Нужно раскошелиться на покупку данных по книге заявок в нужном разрешении или данных BBO (Best Bid and Offer), с включенными временными метками биржи. Без информации о том, в какое время котировка была получена на бирже, определить максимальную задержку не получится.

У каждой из них свой формат данных, поэтому для начала их придется привести к общему формату. Некоторые компании записывают все котировки, приходящие с бирж, а затем продают эти данные. В данном примере используется следующий целевой формат данных:

typedef struct T1 // single tick
{ double time; // time stamp, OLE DATE format float fVal; // positive = ask price, negative = bid price } T1;

Одна из компаний, отслеживающих ситуацию на бирже CME, поставляет данные в формате CSV с множеством дополнительных полей, большинство из которых для решаемой задачи не нужны. Все котировки за день хранятся в одном CSV-файле. Ниже скрипт для «вытягивания» из него данных по ES за декабрь 2016 и его конвертации в датасет котировок Т1:

//////////////////////////////////////////////////////
// Convert price history from Nanotick BBO to .t1
////////////////////////////////////////////////////// #define STARTDAY 20161004
#define ENDDAY 20161014 string InName = "History\\CME.%08d-%08d.E.BBO-C.310.ES.csv"; // name of a day file
string OutName = "History\\ES_201610.t1";
string Code = "ESZ"; // December contract symbol string Format = "2,,%Y%m%d,%H:%M:%S,,,s,,,s,i,,"; // Nanotick csv format
void main()
{ int N,Row,Record,Records; for(N = STARTDAY; N <= ENDDAY; N++) { string FileName = strf(InName,N,N+1); if(!file_date(FileName)) continue; Records = dataParse(1,Format,FileName); // read BBO data printf("\n%d rows read",Records); dataNew(2,Records,2); // create T1 dataset for(Record = 0,Row = 0; Record < Records; Record++) { if(!strstr(Code,dataStr(1,Record,1))) continue; // select only records with correct symbol T1* t1 = dataStr(2,Row,0); // store record in T1 format float Price = 0.01 * dataInt(1,Record,3); // price in cents if(Price < 1000) continue; // no valid price string AskBid = dataStr(1,Record,2); if(AskBid[0] == 'B') // negative price for Bid Price = -Price; t1->fVal = Price; t1->time = dataVar(1,Record,0) + 1./24.; // add 1 hour Chicago-NY time difference Row++; } printf(", %d stored",Row); dataAppend(3,2,0,Row); // append dataset if(!wait(0)) return; } dataSave(3,OutName); // store complete dataset
}

Скрипт сначала парсит CSV в промежуточный двоичный датасет, который затем конвертируется в целевой формат Т1. Поскольку временные метки проставляются по чикагскому времени, к ним нужно еще добавить один час, чтобы конвертировать их во время по Нью-Йорку.

Компания, отслеживающая Нью-Йоркскую биржу, поставляет данные в сильно сжатом специально формате NxCore Tape, его нужно сконвертировать во второй список Т1 с помощью специального плагина:

//////////////////////////////////////////////////////
// Convert price history from Nanex .nx2 to .t1
////////////////////////////////////////////////////// #define STARTDAY 20161004
#define ENDDAY 20161014
#define BUFFER 10000 string InName = "History\\%8d.GS.nx2"; // name of a single day tape
string OutName = "History\\SPY_201610.t1";
string Code = "eSPY"; int Row,Rows; typedef struct QUOTE { char Name[24]; var Time,Price,Size;
} QUOTE; int callback(QUOTE *Quote)
{ if(!strstr(Quote->Name,Code)) return 1; T1* t1 = dataStr(1,Row,0); // store record in T1 format t1->time = Quote->Time; t1->fVal = Quote->Price; Row++; Rows++; if(Row >= BUFFER) { // dataset full? Row = 0; dataAppend(2,1); // append to dataset 2 } return 1;
} void main()
{ dataNew(1,BUFFER,2); // create a small dataset login(1); // open the NxCore plugin int N; for(N = STARTDAY; N <= ENDDAY; N++) { string FileName = strf(InName,N); if(!file_date(FileName)) continue; printf("\n%s..",FileName); Row = Rows = 0; // initialize global variables brokerCommand(SET_HISTORY,FileName); // parse the tape dataAppend(2,1,0,Row); // append the rest to dataset 2 printf("\n%d rows stored",Rows); if(!wait(0)) return; // abort when [Stop] was hit } dataSave(2,OutName); // store complete dataset
}

Функция Callback вызывается любой котировкой в исходном файле, однако большая часть данных не нужна, поэтому отфильтровываются только котировки по SPY (“eSPY”).

Подтверждение рыночной неэффективности

Получив данные из двух источников, теперь мы можем сравнивать цены ES и SPY в высоком разрешении. Вот типичный десятисекундный семпл из кривых цен:

image

ES (красный), 5 октября, 2017, 10:01:25 – 10:01. SPY (черный) vs. 35

ES отрисован в долларовых единицах, SPY — в десятицентовых. Разрешение здесь — одна миллисекунда. Кажется, что цены сильно коррелируют даже на столь малом интервале. Цены на графики — это цены «аск» (запрашиваемая цена). ES чуть отстает.

Причиной могло послужить какое-то событие вроде резкого скачка цен одной из акций, входящих в индекс S&P 500. Возможность для арбитража возникает на участке в центре — примерно в 10:01:30 ES реагировал на изменения чуть медленнее, но сильнее. В идеале, здесь можно было бы продать ES и купить SPY. На протяжение нескольких миллисекунд разница ES-SPY превысила спред бид-аск двух активов (обычно это 25 центов по ES и 1-4 цента по SPY). Таким образом, мы подтвердили ранее предполагаемое в теории наличие рыночной неэффективности, открывающей возможности для заработка.

Скрипт для отрисовки графиков в высоком разрешении:

#define ES_HISTORY "ES_201610.t1"
#define SPY_HISTORY "SPY_201610.t1"
#define TIMEFORMAT "%Y%m%d %H:%M:%S"
#define FACTOR 10
#define OFFSET 3.575 void main()
{ var StartTime = wdatef(TIMEFORMAT,"20161005 10:01:25"), EndTime = wdatef(TIMEFORMAT,"20161005 10:01:35"); MaxBars = 10000; BarPeriod = 0.001/60.; // 1 ms plot resolution Outlier = 1.002; // filter out 0.2% outliers assetList("HFT.csv"); dataLoad(1,ES_HISTORY,2); dataLoad(2,SPY_HISTORY,2); int RowES=0, RowSPY=0; while(Bar < MaxBars) { var TimeES = dataVar(1,RowES,0), PriceES = dataVar(1,RowES,1), TimeSPY = dataVar(2,RowSPY,0), PriceSPY = dataVar(2,RowSPY,1); if(TimeES < TimeSPY) RowES++; else RowSPY++; if(min(TimeES,TimeSPY) < StartTime) continue; if(max(TimeES,TimeSPY) > EndTime) break; if(TimeES < TimeSPY) { asset("ES"); priceQuote(TimeES,PriceES); } else { asset("SPY"); priceQuote(TimeSPY,PriceSPY); } asset("ES"); if(AssetBar > 0) plot("ES",AskPrice+OFFSET,LINE,RED); asset("SPY"); if(AssetBar > 0) plot("SPY",FACTOR*AskPrice,LINE,BLACK); }
}

Сначала скрипт считывает два файла с историческими данными, которые мы создали ранее, а затем парсит их построчно.

Тестирование системы

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

#define LATENCY 4.0 // milliseconds function main()
{ var StartTime = wdatef(TIMEFORMAT,"20161005 09:30:00"), EndTime = wdatef(TIMEFORMAT,"20161005 15:30:00"); MaxBars = 200000; BarPeriod = 0.1/60.; // 100 ms bars Outlier = 1.002; assetList("HFT.csv"); dataLoad(1,ES_HISTORY,2); dataLoad(2,SPY_HISTORY,2); int RowES=0, RowSPY=0; EntryDelay = LATENCY/1000.; Hedge = 2; Fill = 8; // HFT fill mode; Slippage = 0; Lots = 100; while(Bar < MaxBars) { var TimeES = dataVar(1,RowES,0), PriceES = dataVar(1,RowES,1), TimeSPY = dataVar(2,RowSPY,0), PriceSPY = dataVar(2,RowSPY,1); if(TimeES < TimeSPY) RowES++; else RowSPY++; if(min(TimeES,TimeSPY) < StartTime) continue; if(max(TimeES,TimeSPY) > EndTime) break; if(TimeES < TimeSPY) { asset("ES"); priceQuote(TimeES,PriceES); } else { asset("SPY"); priceQuote(TimeSPY,FACTOR*PriceSPY); } asset("ES"); if(!AssetBar) continue; var AskES = AskPrice, BidES = AskPrice-Spread; asset("SPY"); if(!AssetBar) continue; var AskSPY = AskPrice, BidSPY = AskPrice-Spread; int Order = tradeHFT(AskSPY,BidSPY,AskES,BidES); switch(Order) { case 1: asset("ES"); enterLong(); asset("SPY"); enterShort(); break; case 2: asset("ES"); enterShort(); asset("SPY"); enterLong(); break; case 0: asset("ES"); exitLong(); exitShort(); asset("SPY"); exitLong(); exitShort(); break; } } printf("\nProfit %.2f at NY Time %s", Equity,strdate(TIMEFORMAT,dataVar(1,RowES,0)));
}

Скрипт запускает бэктест для одного торгового дня в период с 9:30 до 15:30 по Нью-Йорку. По сути, просто происходит вызов функции HFT с ценами ES и SPY, а затем выполняется код для переключения состояний. Он открывает позиции по ста единицам каждого актива (2 контракта по ES и 1000 по SPY). Задержка устанавливается с помощью переменной EntryDelay. В режиме HFT (Fill = 8) сделка проходит по последней цене после времени задержки. Это позволяет приблизить симуляцию к реальным условиям.

В таблице ниже показана прибыль по итогам симуляции с разными значениями задержки:

Задержка

0.5 мс

4.0 мс

6.0 мс

10 мс

Прибыль / день

+ $793

+ $273

+ $205

– $15

Как видно, арбитражная стратегия ES-SPY может зарабатывать по $800 в день — при нереалистично маленькой задержке в 500 микросекунд. К сожалению, при наличии 700 миль между NYSE и CME, чтобы добиться такого результата понадобится машина времени (или какой-то инструмент квантовой телепортации). Сервер в Уоррене, штат Огайо, при задержке в 4 мс принесет примерно $300 в день. Если сервер будет чуть в стороне от высокоскоростного канала между Нью-Йорков и Чикаго, прибыль составит $200. Если инфраструктура для торговли будет еще дальше — скажем, в Нэшвилле — то заработать не удастся ничего.

Но для достижения такого результата понадобится, помимо железа и софта, еще и много денег. Даже $300 в день выльются в годовой доход на уровне $75 000. Так что годовой возврат на инвестиции не превысит 15%. Контракт SPY стоит $250, 100 единиц для торговли выльются в 100*$2500 + 100*10*$250 = полмиллиона долларов объема торгов. Результаты, однако, можно улучшить, добавив больше пар финансовых инструментов для арбитража.

Выводы

  • Если система реагирует достаточно быстро, заработать она может даже очень примитивными методами, вроде арбитража между сильно коррелированными финансовыми инструментами на разных биржах.
  • Физическое расположение сервера очень важно в HFT.
  • ES-SPY арбитраж нельзя проводить откуда угодно. Вам придется соперничать с теми, кто уже этим занимается, и весьма вероятно из Уоррена в штате Огайо.

Другие материалы по теме финансов и фондового рынка от ITI Capital:

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть