задача прогнозирования, решаемая путем непрерывного движения цены и направления движения цены

Прогноз цен на природный газ с использованием нейронных сетей и классификационных сценариев

Примеры использования анализа временных рядов с регрессией и классификацией

Https://sarit-maitra.medium.com/membership

ПРОГНОЗИРОВАНИЕ временных рядов - сложная задача, особенно когда мы имеем дело со стохастическими ценовыми рядами биржевых данных. Что ж, здесь стохастик означает хаотичность в истинном смысле слова, забитую нестационарностью. Более того, из-за сложности данных о запасах разработка эффективных моделей прогнозирования очень затруднена. Тем не менее, доходность демонстрирует некоторую предсказуемость и применение современных методов машинного обучения, а эффективная разработка функций помогает расширить границы прогнозирования доходности акций.

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

Я использовал дневную спотовую цену на природный газ, с которой можно ознакомиться по адресу https://www.eia.gov/dnav/ng/hist/rngwhhdM.htm. Итак, мы загружаем данные и выполняем некоторую обработку, чтобы очистить данные и подготовить данные для машинного обучения.

Набор данных с 7 января 1997 года по 21 октября 2019 года с 5732 точками данных.

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

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

Мы убедились, что в окончательном наборе данных нет пропущенных значений.

df.plot(figsize=(10, 5))
plt.title('Daily Natural Gas Spot Prices', fontsize=12)
plt.ylabel('Dollars per Million Btu', fontsize=12)
plt.show()

Проверки качества данных

Технические индикаторы

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

Схождение-расхождение скользящих средних (MACD) сравнивает две скользящие средние цен. Первая скользящая средняя - это 26-дневная экспоненциальная скользящая средняя (EMA), а вторая скользящая средняя - это 12-дневная EMA. 26-дневная EMA вычитается из 12-дневной EMA.

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

Формула для расчета MACD:

MACD = EMA12 (цена) −EMA26 (цена)

Верхняя полоса = 21-дневная SMA + (21-дневное стандартное отклонение цены x 2)

Нижняя полоса = 21-дневная SMA - (21-дневное стандартное отклонение цены x 2)

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

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

Разделение данных на обучающий набор и тестовый набор:

Мы принимаем до 31 декабря 2018 года как тренировочный набор, а отдых - как тестовый. Итак, мы обучим нашу модель на данных за 21 год (5530 точек данных), чтобы протестировать (202 точки данных) и проверить, насколько точно наши разработанные модели могут предсказывать.

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

Визуализируйте разделение поездов / тестов:

ax = train_data.plot(figsize=(10, 5))
test_data.plot(ax=ax, color='r')
plt.legend(['train', 'test']);

Нормализация данных

Мы уменьшаем значение в диапазоне (0,1) и нормализуем данные. При масштабировании мы нормализуем как тестовые, так и обучающие данные по отношению к обучающим данным, потому что на этом этапе у нас нет доступа к тестовым данным.

scaler = MinMaxScaler(feature_range = (0,1))
train_data_scaled = scaler.fit_transform(train_data)
print(train_data_scaled); print(train_data_scaled.shape)

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

X_train = []
y_train = []
for i in range(60, len(train_data_scaled)):
    X_train.append(train_data_scaled[i-60:i,0])
    y_train.append(train_data_scaled[i,0])
X_train, y_train = np.array(X_train), np.array(y_train)
print(X_train); print(); print(y_train)

Нам нужно изменить форму данных; тензор принимает трехмерную форму (размер партии, временные шаги, размер_входа).

X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1))
print(X_train.shape); print(); print(X_train)

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

Рекуррентная нейронная сеть

Ячейка LSTM может запоминать значения за произвольные интервалы времени и создавать память в LSTM. Память обрабатывается тремя разными вентилями: (1) входным вентилем, (2) вентилем забывания и (3) выходным вентилем.
Следующие уравнения выражают вычислительный процесс одного LSTM

model = tf.keras.Sequential()
# adding 1st LSTM layer and some dropout regularization
model.add(tf.keras.layers.LSTM(units=50, input_shape=(X_train.shape[1], 1), return_sequences=True, activation = 'relu'))
model.add(tf.keras.layers.Dropout(0.2))
# adding 2nd LSTM layer and some dropout regularization
model.add(tf.keras.layers.LSTM(units=50, return_sequences=True))
model.add(tf.keras.layers.Dropout(0.2))
# adding 3rd LSTM layer and some dropout regularization
model.add(tf.keras.layers.LSTM(units=50, return_sequences=True))
model.add(tf.keras.layers.Dropout(0.2))
# adding 4th LSTM layer and some dropout regularization
model.add(tf.keras.layers.LSTM(units=50))
model.add(tf.keras.layers.Dropout(0.2))
# adding output layer
model.add(tf.keras.layers.Dense(units=1))
#compiling RNN
model.compile(loss='mean_squared_error', optimizer='adam')
early_stopping = EarlyStopping(monitor='loss', patience=10)
# fitting RNN on training set
model.fit(X_train, y_train, epochs= 100, batch_size=32, 
          verbose=2, callbacks=[early_stopping])

Для блоков LSTM используется функция активации сигмоида по умолчанию. Сеть обучена для 100 эпох и используется размер пакета 32. После того, как модель подобрана, мы можем оценить производительность модели на поезде и тестовых наборах данных.

Ниже сообщение усечено для лучшей наглядности.

Получим прогнозную цену на природный газ на 2019 год; нам нужно добавить предыдущие 60 записей из обучающего набора в тестовый набор

dataset_total = pd.concat((train_data, test_data), axis=0)
print(dataset_total)
dataset_total = pd.concat((train_data, test_data), axis=0)
inputs = dataset_total[len(dataset_total) - len(test_data)- 60:].values
inputs = inputs.reshape(-1,1)
inputs = scaler.transform(inputs) # transforming input data
X_test = []
y_test = []
for i in range (60, 262):
    X_test.append(inputs[i-60:i, 0])
    y_test.append(train_data_scaled[i,0])
      
X_test, y_test = np.array(X_test), np.array(y_test)
X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
pred_price = model.predict(X_test)
pred_price = scaler.inverse_transform(pred_price)
print(pred_price)

a = pd.DataFrame(pred_price)
a.rename(columns = {0: 'Predicted'}, inplace=True); 
a.index = test_data.index
compare = pd.concat([test_data, a],1)
compare

plt.figure(figsize= (15,5))
plt.plot(compare['gas price'], color = 'red', label ="Actual Natural Gas Price")
plt.plot(compare.Predicted, color='blue', label = 'Predicted Price')
plt.title("Natural Gas Price Prediction")
plt.xlabel('Time')
plt.ylabel('Natural gas price')
plt.legend(loc='best')
plt.show()

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

test_score = math.sqrt(mean_squared_error(compare['gas price'], compare.Predicted))
print('Test Score: %.2f RMSE' % (test_score))
# Explained variance score: 1 is perfect prediction
print('Variance score (test): %.2f' % r2_score(test_data, pred_price))

Вариант использования классификации

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

k = df.copy()
lags = 5
# Create the shifted lag series of prior trading period close values
for i in range(0, lags):
    k["Lag%s" % str(i+1)] = k["gas price"].shift(i+1).pct_change()
k['price_diff'] = k['gas price'].diff()
k['ret'] = k['gas price'].pct_change()
k.head()

Целевая переменная:

Цель здесь - предсказать (t + 1) значение на основе информации за N предыдущих дней. Следовательно, определение выходного значения как «target», которое представляет собой двоичную переменную, хранящую 1, когда разница в цене завтра ›сегодня.

# positive value = 1, otherwise, 0
k["target"] = np.where(k['price_diff']> 0, 1.0, 0.0)
k.head()

Здесь также для простоты мы будем использовать только 2 лага и возвращаемые значения.

x = k[['Lag1', 'Lag2', 'ret']].dropna()
y = k.target.dropna()
# # Create training and test sets
gkcv = GapKFold(n_splits=5, gap_before=2, gap_after=1)
"""
Introduced gaps between the training and test set to mitigate the temporal dependence.
Here the split function splits the data into Kfolds. 
The test sets are untouched, while the training sets get the gaps removed
"""
for tr_index, te_index in gkcv.split(x, y):
    xTrain, xTest = x.values[tr_index], x.values[te_index];
    yTrain, yTest = y.values[tr_index], y.values[te_index];
        
print('Observations: %d' % (len(xTrain) + len(xTest)))
print('Training Observations: %d' % (len(xTrain)))
print('Testing Observations: %d' % (len(xTest)))

Классификационные модели:

# Create the models
print("Accuracy score/Confusion Matrices:\n")
models = [("LR", LogisticRegression()),
          ("LDA", LinearDiscriminantAnalysis()),
          ("QDA", QuadraticDiscriminantAnalysis()),
          ("LSVC", LinearSVC()),
          ("RSVM", SVC(C=1000000.0, cache_size=200, class_weight=None,
                       coef0=0.0, degree=3, gamma=0.0001, kernel='rbf',
                       max_iter=-1, probability=False, random_state=None,
                       shrinking=True, tol=0.001, verbose=False)),
          ("RF", RandomForestClassifier(
              n_estimators=1000, criterion='gini',
              max_depth=None, min_samples_split=2,
              min_samples_leaf=1, max_features='auto',
              bootstrap=True, oob_score=False, n_jobs=1,
              random_state=None, verbose=0))]
# iterate over the models
for m in models:
    # Train each of the models on the training set
    m[1].fit(xTrain, yTrain)
    # array of predictions on the test set
    pred = m[1].predict(xTest)
    # Accuracy score and the confusion matrix for each model
    print("%s:\n%0.3f" % (m[0], m[1].score(xTest, yTest)))
    print("%s\n" % confusion_matrix(pred, yTest))

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

rfc = RandomForestClassifier(
              n_estimators=1000, criterion='gini',
              max_depth=None, min_samples_split=2,
              min_samples_leaf=1, max_features='auto',
              bootstrap=True, oob_score=False, n_jobs=1,
              random_state=None, verbose=0).fit(xTrain, yTrain)
pd.set_option('float_format', '{:f}'.format)
train_pred = rfc.predict(xTrain)
rmse = np.sqrt(mean_squared_error(yTrain, train_pred))
print("RMSE_train: %f" % (rmse))
print('Train prediction values:')
train_pred = pd.DataFrame(train_pred); 
train_pred.rename(columns = {0: 'TrainPrediction'}, inplace=True); 
print(train_pred);print()
pd.set_option('float_format', '{:f}'.format)
test_pred = rfc.predict(xTest)
print('Test prediction values:')
test_pred = pd.DataFrame(test_pred)
test_pred.rename(columns = {0: 'TestPrediction'}, inplace=True); 
actual = pd.DataFrame(yTest)
actual.rename(columns = {0: 'Actual PriceDiff'}, inplace=True); 
compare = pd.concat([actual, test_pred], 1)
print(compare)

Резюме

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

Со мной можно связаться здесь.

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