Алготрейдинг

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

Одним из самых популярных языков программирования для анализа данных и машинного обучения является Python. Он прост в изучении, имеет множество доступных сред разработки, реализован под всеми распространенными операционными системами и на множестве архитектур.

В статье мы рассмотрим математические основы моделирования поведения простейшей стратегии (buy and hold), торгующей акциями известных американских компаний, работать будем на основе исторических данных. Рассмотренные идеи и концепции реализованы в библиотеках на платформе QuantNet, они используются в виде шаблонов для удобства и сокращения записей.

Инициализация

Перед разработкой алгоритма мы должны определить:

  • список активов, которые мы хотим торговать
  • даты начала и конца моделирования

В этом примере мы выберем 4 популярные, успешные и высокотехнологичные компании, которые торгуют акциями: Apple (AAPL), Amazon (AMZN), Facebook (FB) и Alphabet (GOOG).

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

Методы, позволяющие избежать ошибки выжившего, состоят в следующем:

  • для каждого момента времени торговать только теми акциями, которые на тот момент входили в какой-то фондовый индекс (например, индекс S&P 500)
  • для каждого момента времени торговать только теми акциями, которые на тот момент торговались на определенной бирже и удовлетворяли требованиям ликвидности (например, объем торгов был достаточно большим)

Входные данные

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

Для обработки будем использовать стандартные библиотеки numpy, xarray, а также библиотеку qnt.

Данные, используемые на платформе, представлены в 3D:

> data.coords

При работе с таким форматом данных очень удобно выделять произвольное подмножество, например, вывести данные по определенной акции.

Следует отметить, для вывода краткой информации в таблице по данным конкретной акции можно использовать срез «NASDAQ:FB» и функцию xarray to_pandas(…) вместе с head(), тогда получим:

> data.loc[:, :, "NASDAQ:FB"].to_pandas().head()

Поля open-high-low-close относятся к цене акции, vol означает количество сделок по данной акции, совершенных в течение этого дня.

Столбцы divs и split относятся к дивидендам и сплитам, соответственно.

Cплиты

Дробления акций используются компаниями для того, чтобы сделать акцию более привлекательной для частных инвесторов. Предположим, что в определенный день данные имеют split=2.0. Это означает, что в этот день произошло дробление акций 2 к 1: количество акций увеличилось в два раза, а их цена уменьшилась вдвое.

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

Чтобы учесть поправку на сплиты, необходимо в дату очередного сплита акции, допустим 2 к 1, уменьшать в 2 раза все её прошлые биржевые цены, а объемы увеличить вдвое.

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

В действительности, цены могут быть скорректированы по сплитам в обратном направлении:

Такой подход более удобен, поскольку при каждом сплите нет необходимости пересчитывать все цены, а соответствующий мультипликатор для цен указан в столбце split_cumprod. Фактически, цены увеличены в 6 раз по сравнению с предыдущим рисунком.

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

Сейчас в полях open, high, low, close, vol и divs записаны значения, скорректированные на сплиты в обратном направлении. При желании вы можете загрузить цены без поправки на сплиты с помощью функции restore_origin_data модуля qnt.data:

> raw_data = restore_origin_data(data)

Однако, по умолчанию мы советуем работать со скорректированными ценами.

Дивиденды

Это прибыль, которую, с некоторой периодичностью, получают владельцы акций (акционеры) от компании, которая выпустила эти акции, она формируется за счет прибыли компании. Доля прибыли компании, которая инвестируется в её дальнейшее развитие, и доля на выплату прибыли владельцам акций определяются на собраниях акционеров.

Предположим, что в некоторый день данные имеют divs=8.0. Это означает, что в этот день держателю акции был выплачен дивиденд в размере 8 долларов США. Как правило, после этого цена акции падает на размер вознаграждения, поскольку, за счет выплат, капитализация компании (произведение числа выпущенных акций на их стоимость) уменьшилась на величину равную произведению величины дивиденда на число выпущенных акций.

Моделирование прибыли и убытков, не учитывающих дивиденды, будет недооценивать эффективность алгоритма в случаях, когда на момент выплаты дивидендов мы владеем акцией (длинная позиция, в этом случае мы выигрываем от роста цены акции), поскольку цена упадет, а мы получим убыток.

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

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

Можно скорректировать все цены, предшествующие дню выплаты дивидендов, вычитая из них USD $8, и получить временной ряд, в котором нет эффекта дивидендов.

Отметим, что в загруженных данных дивиденды скорректированы на сплиты.

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

Результат работы хорошей стратегии не должен зависеть от изменения всех скорректированных цен какого-либо актива на любую величину. Например, нельзя считать обычное процентное изменение цены для таких данных. Именно по этой причине по умолчанию мы не предоставляем данные с поправкой на дивиденды, но, естественно, учитываем их при моделировании стратегий (см. Бэктест).

Тем не менее, мы рекомендуем использовать данные скорректированные на дивиденды при написании стратегий.

Пример кода, корректирующего цены, представлен ниже (в рамках модели полагается, что дивиденды в день выплаты начисляются до открытия).

def adjust_by_dividends(data): 
  divs_local = data.loc[:, "divs", :].shift({"time": 1}).fillna(0) 
  delta = divs_local.cumsum(dim="time") 
  bk = data.loc[:, ["open", "close", "high", "low"], :] - delta 
  bk = bk.assign_coords(field=["open_bk", "close_bk", "high_bk", "low_bk"]) 
  return bk

Заключение

Как уже говорилось, алгоритм, который использует скорректированные исторические цены, должен быть инвариантным:

  • к произвольному сдвигу (в случае наличия корректировки на дивиденды), поскольку не должен зависеть от будущих дивидендов
  • к пропорциональному увеличению цен (в случае корректировки на сплиты), по аналогичной причине

Например, при работе с ценами, скорректированными на сплиты, нельзя писать стратегии вроде:

  • если цена пересекает уровень в $10 — закрыть позицию

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

r(t) = 𝑐̃(t)−𝑐̃(t−1) / c(t−1)

где 𝑐̃ — цена закрытия (скорректированная на дивиденды и сплиты)

и c — цена закрытия (скорректированная только на сплиты).

Алгоритм: в момент времени (t−1) считываются все доступные данные, вплоть до цены ct(t−1) для каждого актива i, возвращает vi(t−1) — долю капитала, которая будет инвестирована в актив i на следующем открытии по цене oi(t).

В следующей статье вы узнаете как разработать простейшую стратегию Buy and Hold.