В этом проекте мы будем работать с данными из Индекса S&P500. Мы будем использовать исторические данные о цене индекса S&P500, чтобы делать прогнозы относительно будущих цен. Предсказание того, пойдет ли индекс вверх или вниз, поможет нам спрогнозировать поведение фондового рынка в целом. Поскольку акции, как правило, коррелируют с тем, насколько хорошо работает экономика в целом, это также может помочь нам делать экономические прогнозы.

Мы будем работать с CSV-файлом, содержащим индексные цены. Каждая строка в файле содержит ежедневную запись цены индекса S&P500 с 1950 по 2015 год. Набор данных хранится в sphist.csv.

Столбцы набора данных:

  • Дата — дата записи.
  • Open — цена открытия дня (начало торгов).
  • High — самая высокая цена сделки в течение дня.
  • Low — самая низкая цена сделки в течение дня.
  • Close — цена закрытия дня (когда торговля завершена).
  • Объем — количество проданных акций.
  • Adj Close — дневная цена закрытия, скорректированная задним числом с учетом любых корпоративных действий. Подробнее здесь.

Мы будем использовать этот набор данных для разработки прогностической модели. Мы будем обучать модель на данных за 1950–2012 годы и попытаемся сделать прогнозы за 2013–2015 годы.

import pandas as pd

df = pd.read_csv('sphist.csv')
df.head()

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

from datetime import datetime

df['Date'] = pd.to_datetime(df['Date'])

Приведенный ниже код будет генерировать кадр данных, который сообщает нам, является ли каждый элемент в столбце «Дата» после 2015–04–01.

df[df['Date'] > datetime(year = 2015, month =4, day=1)]

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

df = df.sort_values('Date').reset_index()
df.head()

Теперь давайте проверим информацию о нашем наборе данных.

df.info()

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

Генерация индикаторов

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

Это означает, что мы должны быть особенно осторожны, чтобы не вводить «будущие» знания в прошлые строки, когда мы проводим обучение и прогнозирование. Внедрение знаний о будущем сделает нашу модель привлекательной, когда мы ее обучаем и тестируем, но приведет к ее провалу в реальном мире. Именно так многие алгоритмические трейдеры теряют деньги.

Вот некоторые индикаторы, которые интересно генерировать для каждой строки:

  • Средняя цена за последние 5 дней.
  • Средняя цена за последние 30 дня.
  • Средняя цена за последние 365 дня.
  • Соотношение между средней ценой за последние 5 дня и средней ценой за последние 365 дней.
  • Стандартное отклонение цены за последние 5 дней.
  • Стандартное отклонение цены за последние 365 дней.
  • Соотношение между стандартным отклонением за последние 5 дней и стандартным отклонением за последние 365 дней.

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

Мы выбрали 3 индикаторов для расчета и создали отдельный столбец для каждого из них.

#Calculate the mean for the past 5, 30, 365 days
df['day_5'] = df['Close'].rolling(5).mean().shift(1)
df['day_30'] = df['Close'].rolling(30).mean().shift(1)
df['day_365'] = df['Close'].rolling(365).mean().shift(1)

#Calculate the STD for the past 5, 365 days
df['std_5'] = df['Close'].rolling(5).std().shift(1)
df['std_365'] = df['Close'].rolling(365).std().shift(1)

#Calculate the mean volume for the past 5, 365 days
df['day365volume'] = df['Volume'].rolling(5).mean().shift(1)
df['day_365_volume'] = df['Volume'].rolling(365).mean().shift(1)

#Calculate the STD of the average volume over the past five days
df['5_volume_std'] = df['day365volume'].rolling(5).std().shift(1)

Теперь давайте проверим первые 10 и последние 10 строк нашего набора данных.

df.head(10)

df.tail(10)

Разделение данных

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

df = df[df['Date'] > datetime(year = 1951, month = 1, day = 3)]
df.head()

Теперь мы также должны удалить все строки со значениями NaN.

Давайте проверим, сколько значений NaN есть в каждом столбце.

df.isnull().sum()

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

df = df.dropna(axis = 0)

Снова давайте проверим нулевые значения.

df.isnull().sum()

Давайте теперь создадим два новых фрейма данных для использования при создании нашего алгоритма. train должен содержать все строки данных с датой меньше 01.01.2013. test должен содержать все строки с датой, превышающей или равной 01 – 2013–01.

train = df[df['Date'] < datetime(year = 2013, month = 1, day = 1)]
test = df[df['Date'] >= datetime(year = 2013, month = 1, day = 1)]

Теперь давайте напечатаем их форму.

train.shape
(15486, 16)
test.shape
(739, 16)
train.columns
Index([‘index’, ‘Date’, ‘Open’, ‘High’, ‘Low’, ‘Close’, ‘Volume’, ‘Adj Close’, ‘day_5’, ‘day_30’, ‘day_365’, ‘std_5’, ‘std_365’, ‘day365volume’, ‘day_365_volume’, ‘5_volume_std’], dtype=’object’)

Делать прогнозы

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

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

Исключение всех исходных столбцов (Close, High, Low, Open, Volume, Adj Close, Date) при обучении модели. Все они содержат знания о будущем, которые мы не хотим кормить моделью. Используйте столбец Close в качестве цели.

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

features = ['day_5', 'day_30', 'day_365', 'std_5', 'std_365', 'day365volume',
       'day_365_volume', '5_volume_std']
target = train['Close']

lr = LinearRegression()
lr.fit(train[features],target)
predictions = lr.predict(test[features])
mse = mean_squared_error(test['Close'], predictions)
mse
494.6605406939939

Давайте теперь сделаем прогноз всего на один день вперед.

train_1 = df.iloc[:-1]
test_1 = df.iloc[-1:]

lr.fit(train_1[features],train_1['Close'])
predictions_1 = lr.predict(test_1[features])
mse_1 = mean_squared_error(test_1['Close'], predictions_1)
mse_1
9.629910554420343

Со стороны индикатора еще многое предстоит сделать. Мы также можем внести значительные структурные улучшения в алгоритм и получить данные из других источников.

  • Точность значительно повысится, если делать прогнозы только на один день вперед. Например, обучите модель, используя данные от 1951-01-03 до 2013-01-02, сделайте прогнозы для 2013-01-03, а затем обучите другую модель, используя данные от 1951-01-03 до 2013-01-03, сделайте прогнозы для 2013-01-04 и так далее. Это более точно имитирует то, что мы бы сделали, если бы торговали с использованием алгоритма.
  • Мы также можем значительно улучшить используемый алгоритм. Попробуйте другие методы, такие как случайный лес, и посмотрите, работают ли они лучше.
  • Мы также можем использовать внешние данные, такие как погода в Нью-Йорке (где происходит большая часть торгов) за день до этого и активность Твиттера в отношении определенных акций.
  • Мы также можем перевести систему в режим реального времени, написав автоматический сценарий для загрузки последних данных при закрытии рынка и составления прогнозов на следующий день.
  • Наконец, мы можем сделать систему «более высокого разрешения». В настоящее время мы делаем ежедневные прогнозы, но мы могли бы делать прогнозы ежечасно, поминутно или посекундно. Однако для этого потребуется получить больше данных. Мы также могли бы делать прогнозы для отдельных акций вместо S&P500.

Вы можете найти ссылку здесь: Github