A recurrent
neural network (RNN), as opposed to feedforward neural networks, is a
bi-directional artificial neural network, that allows the output from some
nodes to affect subsequent input to the same nodes. Long short-term memory
(LSTM) network is an RNN designed to deal with the vanishing gradient problem. It
provides a short-term memory lasting thousands of timesteps, hence the term
"long short-term memory". It is useful for classification problems
and time series prediction.
Here we use Python’s
Keras RNN model to predict future price direction. We are trying to predict
whether the price of the EUR/USD currency pair will go up or down based on
historical price data using an LSTM (Long Short-Term Memory) neural network.
Importing libraries:
import pandas as pd
import yfinance as yf
import numpy as np
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.preprocessing.sequence import TimeseriesGenerator
from sklearn.metrics import accuracy_score
Downloading and preprocessing data: We get historical daily closing prices of the EUR/USD currency pair starting from January 1, 2022, using the “yfinance” library’s “download” function. Then, we calculate the logarithmic returns and standardize the data.
data = pd.DataFrame(yf.download('EURUSD=X', start='2022-01-01').Close)
data['r'] = np.log(data / data.shift(1))
data.dropna(inplace=True)
data = (data - data.mean()) / data.std()
We can use additional features besides returns and lagged returns. Here we try the 14-day rolling mean and standard deviation of the returns and stores them in the 'mom' and 'vol' columns, respectively.
r = data['r'].values
data['mom'] = data['r'].rolling(14).mean()
data['vol'] = data['r'].rolling(14).std()
Data
splitting and standardization:
split = int(len(r) * 0.8)
train = data.iloc[:split].copy()
mu, std = train.mean(), train.std()
train = (train - mu) / std
test = data.iloc[split:].copy()
test = (test - mu) / std
Building the LSTM model: We use a sequential model and add an LSTM layer with 100 units and a ReLU activation function is used as the first layer. A Dense layer with 1 unit and a sigmoid activation function is added as the output layer. The sigmoid function will give a probability score for the binary classification (price going up or down).
model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(lags, len(data.columns))))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])
Class weight function: The function `cw` calculates the class weights to handle class imbalance in the training data. It assigns higher weights to the minority class to give it more importance during training.
def cw(a):
c0, c1 = np.bincount(a)
w0 = (1 / c0) * (len(a)) / 2
w1 = (1 / c1) * (len(a)) / 2
return {0: w0, 1: w1}
Generating
time series sequences for training and training the model:
train_y = np.where(train['r'] > 0, 1, 0)
g = TimeseriesGenerator(train.values, train_y, length=lags, batch_size=5)
model.fit(g, epochs=10, steps_per_epoch=10,
verbose=False, class_weight=cw(train_y))
Generating
time series sequences for testing, making predictions and evaluating the model:
test_y = np.where(test['r'] > 0, 1, 0)
g_ = TimeseriesGenerator(test.values, test_y, length=lags, batch_size=5)
y = np.where(model.predict(g_, batch_size=None) > 0.5, 1, 0).flatten()
print('out-of-sample prediction accuracy')
print(accuracy_score(test_y[lags:], y))
We got about
55% accuracy, which is not impressive. However, it is a good starting point.
You can use the code to test for different timeframes and frequencies for different
types of assets and also try different inputs as predictors and try backtesting
to see if it produces reliable consistent results.
Have questions? I will be happy to help!
You can ask me anything. Just maybe not relationship advice.
I might not be very good at that. 😁