Для получения данных фондового рынка используется библиотека yFinance. Эта библиотека разработана специально для загрузки соответствующей информации о конкретной акции с веб-страницы Yahoo Finance. Используя yFinance, мы можем легко получить доступ к последним рыночным данным и включить их в модель.
Данные представленны в виде:
- Дата
- Цена открытия
- Цена закрытия
- Максимальная цена
- Минимальная цена
- Скорректированная цена закрытия
- Объём Периодичность данных - 1 день. Временной отрезок ('2008-08-01') - ('2023-08-01'). Разбивка данных на train\test произведена в соотношении 0.8\0.2.
В данной работе я использовал популярные индикаторы технического анализа, такие как:
- Exponential Moving Average(разновидность взвешенной скользящей средней, веса которой убывают экспоненциально)
- Moving Average Convergence/Divergence (индикатор схождения-расхождения скользящих средних)
- Relative Strength index (Индекс относительной силы)
- Average True Range
- upper and lower Bollinger Bands(индикатор, отражающий текущие отклонения цены)
- Relative Strength Value(индикатор используются в техническом анализе для определения условий перекупленности и перепроданности ценной бумаги)
- Компоненты преобразования Фурье - 3, 6, 9, 27, 81 и 100
Признаки извлекались с разными размерами окна в 7, 14, 21 дней.
Для отбора фичей, т.е. определения значимости признаков использовалась библиотека XGBoost. Пример отбора признаков:
Можно оставить любое желаемое количество признаков, рекомендованное количество - 10+
Пусть X(t) представляет собой набор базовых индикаторов, а Y(t) обозначает цену закрытия одной акции за 1-дневный интервал времени t (t = 1, 2, . . . , T), где T - максимальное запаздывание по времени. Дана историческая информация об основных показателях X (X = {X(1),X(2),...,X(T)}) и прошлая цена закрытия Y (Y ={Y(1), Y(2),...,Y(T)}), цель - предсказать цену закрытия Y(T+1).
Задача предсказания цен рынка идентична задаче прогнозирования временных рядов. Необходимо на основе исторического отрезка временного ряда длиной N спрогнозировать значения следующих M отрезков. Поэтому данные нужно трансформировать, пройдясь по ним скользящим окном длиной N. В коде используется обозначение time_steps для N, и n_futures для M.
Обычно в таких задачах хорошо себя показывают рекуррентные сети, в частности LSTM. В обзорной статье A Survey of Forex and Stock Price Prediction Using Deep Learning приведна статистика свежих исследований в этом направлении, больше всего работ связанных с рекуррентными и свёрточными нейросетями. Также в статье показано, что LSTM показывают неплохие результаты. Ещё полезные статьи, в которых используются рекуррентные сети 1, 2. В связи с этим в первой baseline попытке, я принял решение использовать сеть долгой краткосрочной памяти, с одномерным входом, то есть единственным признаком будет цена закрытия за прошлые n дней. Предсказываемое значение - цена закрытия на 1 день вперёд. В качестве данных использовался показатели акий Apple. Для оценки качества моделей используется метрика RMSE. RMSE_baseline = 246.097.
Результаты приведены в ноутбуке.
После отбора признаков, описанного выше, возникает необходимость повысить размерность на входе LSTM, а также улучшить её архитектуру. Сами данные должны быть приведены к виду: [количество семплов, time_steps, количество фичей]. Я использовал time_step 30 дней, так как много сгенерированых признаков имеют окно до 21 дней. Веса модели инициализировались из нормального распредеделния. Архитектура LSTM сети:
LSTM(
(lstm1): LSTM(11, 1024, batch_first=True)
(dropout): Dropout(p=0.2, inplace=False)
(lstm2): LSTM(1024, 512, batch_first=True)
(dropout): Dropout(p=0.2, inplace=False)
(lstm3): LSTM(512, 256, batch_first=True)
(dropout): Dropout(p=0.2, inplace=False)
(linear1): Linear(in_features=256, out_features=128, bias=True)
(linear2): Linear(in_features=128, out_features=1, bias=True)
)
Результаты на датасете акций Apple: Test RMSE: 3.136
Этот вариант уже показыват хорошие результаты, поэтому я предсказал ещё несколько позиций (Netflix, Alphabet, Disney, Tesla, Amazon). Усреднённая по всем позициям RMSE: 6.073.
Во время обучения на разных датасетах я заметил, что в некоторых случаях маленькая корреляция между целевой переменной и признаками, поэтому незначимые признаки лучше убирать, так как они мешают обучению. Но с другой стороны чем меньше признаков использовать, тем больше шанс переобучиться. Оптимальное значения эпох ~ 400, learning_rate=0.00016. Так как стоит задача регрессии, в качестве loss функции использовал MSELoss().
Работа алгоритма представлена в ноутбуке
Мне попалась интересная статья: Stock Market Prediction on High-Frequency Data Using Generative Adversarial Nets. В ней предлагается использовать GAN для предсказания временных рядов. В качестве генератора выступает LSTM, а в качестве дискриминатора - CNN. Архитектуру LSTM я взял из прошлого пункта, а CNN - из статьи. Сами данные предобрабатывались таким же образом, но трансформировать их нужно было чуть чуть по другому. Ещё в этот вариант была добавлена возмонжость прогнозирования на M дней вперёд.
На вход Генератору поадётся исторический ряд фичей X = {X(1),X(2),...,X(T)}, трансформированный (всё как в прошлой модели), на выходе он выдаёт X(T+M), Дискриминатор выполняет операции свертки с одномерной входной последовательностью, чтобы оценить вероятность того, что последовательность {X(1),X(2),...,X(T), X(T+M)} взята из набора данных или сгенерирована LSTM.
Веса моделей инициализировались из нормального распредеделния, обучение по классическому для gan сценарию. С гиперпараметрами из статьи обучалась модель очень плохо, меняя параметры, я заметил что Дискриминатору тяжело отличать данные, loss функции не сходятся и постоянно скачут, скорее всего из-за специфики данных. В поисках методов улучшения стабильности обучения я пришёл к Wasserstein GAN. Использовал оптимайзеры RMSprop (как предложено в статье WGAN), learning_rate = 0.00005 одинаковые для генератора и дискриминатора, а самое главное навесил ограничение на обучение генератора, теперь он обучался только одну из 7 итераций. clip_value = 0.1 стабилизировало обучение дискриминатора. Егор архитектура:
Critic(
(conv1): Conv1d(31, 32, kernel_size=(1,), stride=(2,))
(conv2): Conv1d(32, 64, kernel_size=(1,), stride=(2,))
(batch1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv1d(64, 128, kernel_size=(1,), stride=(2,))
(batch2): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(linear1): Linear(in_features=128, out_features=64, bias=True)
(leaky): LeakyReLU(negative_slope=0.01)
(linear2): Linear(in_features=64, out_features=1, bias=True)
(sigmoid): Sigmoid()
)
Реузльтаты за 200 эпох на датасете Apple
Training dataset RMSE:1.459052644127705. Test dataset RMSE:10.758294215871336.
По графику видно, что модель хорошо улавливает направление цены, но плохо её масштабирует. Ноутбук
RMSE | baseline | LSTM multivariate | GAN |
---|---|---|---|
Apple | 246.097 | 3.136 | 10.758 |
All | - | 6.073 | - |