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

В моем предыдущем посте я поделился своими первыми результатами исследования по прогнозированию цен на акции, которые впоследствии будут использоваться в качестве входных данных для торгового бота с глубоким обучением. Увеличивая масштаб моих наборов данных до тысяч тикеров акций, что соответствует почти 1 терабайту историй курсов акций и новостных статей, я пришел к выводу, что мой первоначальный подход к работе с нейронными сетями, состоящими из LSTM (L - S hort T M Emory Models) и CNN (C эволюционных сетевых сетевых компонентов) имеет свои ограничения. Таким образом, чтобы преодолеть ограничения, мне пришлось реализовать преобразователь, специализирующийся на стандартных временных рядах.

В последние годы трансформеры приобрели популярность благодаря своим выдающимся характеристикам. Сочетание механизма самовнимания, распараллеливания и позиционного кодирования под одной крышей обычно дает преимущество перед классическими моделями LSTM и CNN при работе над задачами. где требуется извлечение семантических признаков и большие наборы данных [1].

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

Данные

В целях пояснения в этой статье мы будем использовать историю курсов акций IBM как упрощенную версию набора данных по акциям объемом 1 терабайт. Тем не менее, вы можете легко применить код из этой статьи к значительно большему набору данных. Набор данных IBM начинается с 1962–01–02, заканчивается на 2020–05–24 и содержит в общей сложности 14699 торговых дней. Кроме того, для каждого тренировочного дня у нас есть цена открытия, максимума, минимума и закрытия, а также торговый объем (OHLCV) акций IBM.

Подготовка данных

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

Наконец, наборы для обучения, проверки и тестирования разделены на отдельные последовательности продолжительностью 128 дней каждая. Для каждого дня последовательности присутствуют 4 ценовых характеристики (открытие, максимум, минимум, закрытие) и функция объема, что дает 5 характеристик в день. В течение одного шага обучения наша модель Transformer получит 32 последовательности (batch_size = 32) длиной 128 дней (seq_len = 128) с 5 функциями на день в качестве ввода.

Вложения времени

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

При обработке данных временных рядов время является важной характеристикой. Однако при обработке временных рядов / последовательных данных с помощью преобразователя последовательности передаются сразу через архитектуру преобразователя, что затрудняет извлечение временных / последовательных зависимостей. Таким образом, преобразователи, которые используются в сочетании с данными на естественном языке, как правило, используют позиционное кодирование, чтобы предоставить модели понятие порядка слов. В деталях, позиционное кодирование - это представление значения слова и его позиции в предложении, позволяющее преобразователю получить информацию о структуре предложения и взаимозависимостях слов. Пример позиционного кодирования можно найти, заглянув под капот модели BERT [2] , которая для многих достигла высочайшего уровня производительности. языковые задания.

Аналогичным образом, преобразователю требуется понятие времени при обработке цен на наши акции. Без вложений времени наш Трансформатор не получил бы никакой информации о временном порядке наших акций. Следовательно, цена акций с 2020 года может иметь такое же влияние на прогноз будущей цены, как и цена с 1990 года. И конечно, это было бы нелепо.

Time2Vector

Чтобы преодолеть временное безразличие Трансформатора, мы реализуем подход, описанный в статье Time2Vec: изучение векторного представления времени [2] . Авторы статьи предлагают не зависящее от модели векторное представление времени, называемое Time2Vec . Вы можете думать о векторном представлении как о обычном слое внедрения , которое можно добавить в архитектуру нейронной сети для повышения производительности модели.

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

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

Комбинируя идеи периодических и непериодических паттернов, а также инвариантность к масштабированию во времени, мы получаем следующее математическое определение. Не беспокойтесь, это проще, чем кажется, и я подробно объясню. 😉

Вектор / представление времени t2v состоит из двух компонентов, где ωᵢτ + φᵢ представляет непериодический / линейный, а F(ωᵢτ + φᵢ) периодический элемент вектора времени.

Переписывая t2v(τ) = ωᵢτ + φᵢ более простым способом, новая версия y = mᵢx + bᵢ должна выглядеть знакомо, поскольку это обычная версия линейной функции, которую вы знаете с самого начала. школа. ω в ωᵢτ + φᵢ - это матрица, которая определяет наклон нашего временного ряда τ, а φ простыми словами - это матрица, которая определяет, где наш временной ряд τ пересекает ось y. Следовательно, ωᵢτ + φᵢ - не что иное, как линейная функция.

Второй компонент F(ωᵢτ + φᵢ) представляет периодическую особенность временного вектора. Как и раньше, у нас снова есть линейный член ωᵢτ + φᵢ, однако на этот раз линейная функция заключена в дополнительную функцию F(). Авторы экспериментировали с различными функциями, чтобы лучше всего описать периодические отношения (сигмоид, tanh, ReLU, mod, треугольник и т. Д.). В конце концов, синусоидальная функция дает наилучшие и наиболее стабильные характеристики (косинус дает аналогичные результаты). При объединении линейной функции ωᵢτ + φᵢ с синусоидальной функцией двухмерное представление выглядит следующим образом. φ сдвигает синусоидальную функцию по оси x, а ω определяет длину волны синусоидальной функции.

Давайте посмотрим, как изменяется точность сети LSTM в сочетании с различными нелинейными функциями вектора времени (Time2vec). Мы можем ясно видеть, что функция ReLU работает хуже всего, в отличие от нее синусоидальная функция превосходит все остальные нелинейные функции. Причина, по которой функция ReLU дает такие неудовлетворительные результаты, заключается в том, что функция ReLU не инвариантна к изменению масштаба времени. Чем выше инвариант функции относительно масштабирования времени, тем выше производительность.

Улучшение производительности Time2Vector

Прежде чем мы начнем внедрять время, давайте посмотрим на разницу в производительности нормальной сети LSTM (синий) и сети LSTM + Time2Vec (красный). Как вы можете видеть, предложенный вектор времени ведет к нескольким наборам данных, никогда не приводит к ухудшению производительности и почти всегда улучшает производительность модели. Обладая этими идеями, мы переходим к реализации.

Реализация Time2Vector Keras

Хорошо, мы обсудили, как периодические и непериодические компоненты нашего вектора времени работают в теории, теперь мы реализуем их в коде. Чтобы вектор времени можно было легко интегрировать в архитектуру нейронной сети любого типа, мы определим вектор как слой Keras. Наш пользовательский слой Time2Vector имеет две подфункции def build(): и def call():. В def build(): мы инициируем 4 матрицы, 2 для ω и 2 для φ, поскольку нам нужен ω и φ как для непериодических (линейных), так и для периодических (sin) признаков.

seq_len = 128
def build(input_shape):
   weights_linear = add_weight(shape=(seq_len), trainable=True)
   bias_linear = add_weight(shape=(seq_len), trainable=True)
   weights_periodic = add_weight(shape=(seq_len), trainable=True)
   bias_periodic = add_weight(shape=(seq_len), trainable=True)

После запуска наших 4 матриц мы определяем шаги вычислений, которые будут выполняться после вызова слоя, отсюда и функция def call():.

Вход, который будет получен слоем Time2Vector, имеет следующую форму (batch_size, seq_len, 5) → (32, 128, 5). batch_size определяет, сколько последовательностей котировок акций мы хотим передать в модель / слой одновременно. Параметр seq_len определяет длину отдельной последовательности цен акций. Наконец, число 5 получено из того факта, что у нас есть 5 характеристик ежедневного учета акций IBM (цена открытия, максимальная цена, минимальная цена, цена закрытия, объем).

Первый шаг расчета исключает объем и берет среднее значение по ценам открытия, максимума, минимума и закрытия, в результате чего получается форма (batch_size, seq_len).

x = tf.math.reduce_mean(x[:,:,:4], axis=-1)

Затем мы вычисляем непериодическую (линейную) характеристику времени и снова увеличиваем размерность на 1. (batch_size, seq_len, 1)

time_linear = weights_linear * x + bias_linear
time_linear = tf.expand_dims(time_linear, axis=-1)

Тот же процесс повторяется для периодического временного признака, что также приводит к той же форме матрицы. (batch_size, seq_len, 1)

time_periodic = tf.math.sin(tf.multiply(x, weights_periodic) + bias_periodic)
time_periodic = tf.expand_dims(time_periodic, axis=-1)

Последний шаг, который необходим для завершения вычисления вектора времени, - это объединение линейной и периодической функции времени. (batch_size, seq_len, 2)

time_vector = tf.concat([time_linear, time_periodic], axis=-1)

Time2Vector слой

Объединяя все шаги в одну функцию слоя, код выглядит следующим образом.

Трансформатор

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

Объединение функций данных IBM и времени - питание преобразователя

После внедрения Time Embeddings мы будем использовать вектор времени в сочетании с характеристиками цены и объема IBM в качестве входных данных для нашего Transformer. Слой Time2Vector получает функции IBM price и volume в качестве входных данных и вычисляет непериодические и периодические временные функции. На следующем этапе модели вычисленные временные характеристики объединяются с характеристиками цены и объема, формируя матрицу формы (32, 128, 7).

Одностороннее внимание

Временные ряды IBM плюс временные характеристики, которые мы только что вычислили, образуют исходные данные для первого уровня внимания с одной головой. Слой внимания с одной головой занимает всего 3 входа (запрос, ключ, значение). Для нас каждый входной запрос, ключ и значение отражает характеристики цены, объема и времени IBM. Каждый входной запрос, ключ и значение получает отдельное линейное преобразование, проходя через отдельные плотные слои. Обеспечение плотных слоев 96 выходными ячейками было личным архитектурным выбором.

После первоначального линейного преобразования мы рассчитаем оценку внимания / веса. Веса внимания определяют, насколько большое внимание уделяется отдельным шагам временного ряда при прогнозировании будущей цены акций. Веса внимания рассчитываются путем взятия скалярного произведения линейно преобразованных входных данных запроса и ключа, тогда как преобразованный входной ключ был преобразован, чтобы сделать возможным умножение скалярного произведения. Затем скалярное произведение делится на размерный размер предыдущих плотных слоев (96), чтобы избежать взрывных градиентов. Разделенное скалярное произведение затем проходит через функцию softmax, чтобы получить набор весов, которые в сумме составляют 1. На последнем этапе вычисленная матрица softmax, которая определяет фокус каждого временного шага, умножается на преобразованную v-матрицу, которая завершает механизм внимания одной головы.

Поскольку иллюстрации отлично подходят для первого начального обучения, но не имеют аспекта реализации, я подготовил для вас чистую функцию слоя SingleAttention Keras 🙂.

Многоголовое внимание

Для дальнейшего совершенствования механизма самовнимания авторы статьи Все, что вам нужно [4] предложили реализовать многоголовое внимание. Функциональность слоя внимания с несколькими головами заключается в том, чтобы c объединить веса внимания n слоев внимания с одной головой , а затем примените нелинейное преобразование с плотным слоем. На рисунке ниже показано объединение 3-х слоев с одной головкой.

Наличие на выходе n слоев с одним заголовком позволяет кодировать преобразование нескольких независимых слоев с одним заголовком в модель. Следовательно, модель может сосредоточиться на нескольких шагах временного ряда одновременно. Увеличение количества головок внимания положительно влияет на способность модели улавливать зависимости на большом расстоянии. [1]

То же, что и выше, чистая реализация слоя внимания с несколькими головами.

Слой преобразователя кодировщика

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

Кстати, плотные слои можно заменить одномерными сверточными слоями, если Conv-слои имеют размер ядра и шаг 1. Математика плотного слоя и сверточного слоя с описанной конфигурацией одинакова.

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

Теперь у нас есть готовый к использованию слой Transformer, который можно легко сложить, чтобы улучшить производительность модели. Поскольку нам не нужны слои декодера Transformer, наша реализованная архитектура Transformer очень похожа на архитектуру BERT [2]. Хотя разница заключается во встраивании времени, и наш преобразователь может обрабатывать трехмерный временной ряд вместо простой двухмерной последовательности.

Если вместо этого вы хотите погрузиться в код, начнем.

Архитектура модели со слоями Time Embeddings и Transformer

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

Полученные результаты

Тренировочный процесс насчитывает 35 эпох. После обучения мы видим, что наша модель преобразования просто предсказывает ровную линию, центр которой находится между дневными изменениями цен акций. При использовании только истории акций IBM даже модель трансформатора способна просто предсказать линейный тренд развития акции. Делаем вывод о том, что исторические данные о цене и объеме акции содержат достаточно пояснительной ценности только для линейного предсказания тренда. Однако при масштабировании набора данных до тысяч биржевых тикеров (набор данных 1 терабайт) результаты выглядят совсем иначе 🙂.

Применение скользящего среднего к данным о запасах - Разработка функций

Как показано выше, даже самые продвинутые архитектуры моделей не могут извлекать нелинейные прогнозы акций из исторических цен и объемов акций. Однако при применении простого эффекта сглаживания скользящего среднего к данным (размер окна = 10) модель может обеспечить значительно лучшие прогнозы (зеленая линия). Вместо того, чтобы предсказывать линейный тренд акций IBM, модель также может предсказывать взлеты и падения. Однако при внимательном наблюдении вы все равно можете увидеть, что модель имеет большую дельту прогноза в дни с экстремальной дневной скоростью изменения, следовательно, мы можем сделать вывод, что у нас все еще есть проблемы с выбросами.

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

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

Весь код, представленный в этой статье, является частью записной книжки, которую можно запускать от начала до конца. Записную книжку можно найти на GitHub.

Примечание редакторам Data Science. Хотя мы разрешаем независимым авторам публиковать статьи в соответствии с нашими правилами и рекомендациями, мы не поддерживаем вклад каждого автора. Не стоит полагаться на работы автора без консультации с профессионалами. См. Подробности в наших Условиях для читателей.

Спасибо,

Янв

Отказ от ответственности

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

использованная литература

[1] Почему внимание к себе? Целенаправленная оценка архитектур нейронного машинного перевода https://arxiv.org/abs/1808.08946

[2] BERT: Предварительная подготовка глубоких двунаправленных преобразователей для понимания языка https://arxiv.org/abs/1810.04805

[3] Time2Vec: изучение векторного представления времени https://arxiv.org/abs/1907.05321

[4] Внимание - все, что вам нужно https://arxiv.org/abs/1706.03762