Currency (FX) prediction using RNN
[In the previous article, I tried to predict the stock price, so I will try to predict the forex market. As with stock prices, there are a variety of factors that can cause fluctuations, so it will be difficult to make a prediction using only a neural network model, but I’ll try it as an exercise in Keras.
github
- The file in jupyter notebook format is here
google colaboratory
- If you want to run it in google colaboratory here
Author’s environment
The author’s OS is macOS, and the options are different from Linux and Unix commands.
! sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.6
BuildVersion: 18G6020
Python -V
Python 3.7.3
Import the basic libraries and keras and check their versions.
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
import matplotlib
import matplotlib.pyplot as plt
import scipy
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
print('matplotlib version :', matplotlib.__version__)
print('scipy version :', scipy.__version__)
print('numpy version :', np.__version__)
print('tensorflow version : ', tf.__version__)
print('keras version : ', keras.__version__)
matplotlib version : 3.0.3
scipy version : 1.4.1
numpy version : 1.19.4
tensorflow version : 2.4.0
keras version : 2.4.0
Getting the data
This time, we will make predictions for three currency pairs (daily): USD/JPY, EUR/USD, and GBP/USD. We have downloaded the data from the following website.
Checking the data
First of all, let’s look at the USDJPY data.
!ls . /data/
01_USDJPY_D.csv 03_EURUSD_D.csv 05_GBPUSD_D.csv eurusd_utf8.csv gbpusd_utf8.csv usdjpy_utf8.csv
%%bash
head . /data/01_USDJPY_D.csv
"ʉʉ݃y�A","����t","��n�l","����l","����l","��I�l"
"1", "2021/01/07",103.001,103.958,102.950,103.814
"1", "2021/01/06",102.697,103.445,102.592,103.069
"1", "2021/01/05",103.054,103.192,102.605,102.719
"1", "2021/01/04",103.083,103.316,102.713,103.152
"1", "2020/12/31",103.128,103.317,102.995,103.292
"1", "2020/12/30",103.530,103.584,102.962,103.211
"1", "2020/12/29",103.722,103.806,103.463,103.531
"1", "2020/12/28",103.496,103.899,103.404,103.788
"1", "2020/12/25",103.615,103.656,103.486,103.585
%%bash
nkf --guess . /data/01_USDJPY_D.csv
Shift_JIS (LF)
Change to utf-8.
%%bash
nkf -w . /data/01_USDJPY_D.csv > . /data/usdjpy_utf8.csv
nkf -w . /data/03_EURUSD_D.csv > . /data/eurusd_utf8.csv
nkf -w . . /data/05_GBPUSD_D.csv > . /data/gbpusd_utf8.csv
The character encoding is shift-jis, so change it to utf-8.
. %%bash
head . /data/usdjpy_utf8.csv
"Currency Pair", "Date", "Open", "High", "Low", "Close"
"1", "2021/01/07",103.001,103.958,102.950,103.814
"1", "2021/01/06",102.697,103.445,102.592,103.069
"1", "2021/01/05",103.054,103.192,102.605,102.719
"1", "2021/01/04",103.083,103.316,102.713,103.152
"1", "2020/12/31",103.128,103.317,102.995,103.292
"1", "2020/12/30",103.530,103.584,102.962,103.211
"1", "2020/12/29",103.722,103.806,103.463,103.531
"1", "2020/12/28",103.496,103.899,103.404,103.788
"1", "2020/12/25",103.615,103.656,103.486,103.585
This looks fine, so we’ll load it in pandas.
df_u = pd.read_csv('. /data/usdjpy_utf8.csv')
df_e = pd.read_csv('. /data/eurusd_utf8.csv')
df_g = pd.read_csv('. /data/gbpusd_utf8.csv')
df_u.head()
Currency pair | Date | Open price | High price | Low price | Close price | |
---|---|---|---|---|---|---|
0 | 1 | 2021/01/07 | 103.001 | 103.958 | 102.950 | 103.814 |
1 | 1 | 2021/01/06 | 102.697 | 103.445 | 102.592 | 103.069 |
2 | 1 | 2021/01/05 | 103.054 | 103.192 | 102.605 | 102.719 |
3 | 1 | 2021/01/04 | 103.083 | 103.316 | 102.713 | 103.152 |
4 | 1 | 2020/12/31 | 103.128 | 103.317 | 102.995 | 103.292 |
df_u.tail()
Currency pair | Date | Open price | High price | Low price | Close price | |
---|---|---|---|---|---|---|
195 | 1 | 2020/04/08 | 108.775 | 109.099 | 108.500 | 108.845 |
196 | 1 | 2020/04/07 | 109.188 | 109.263 | 108.670 | 108.796 |
197 | 1 | 2020/04/06 | 108.388 | 109.382 | 108.388 | 109.200 |
198 | 1 | 2020/04/03 | 107.800 | 108.678 | 107.781 | 108.460 |
199 | 1 | 2020/04/02 | 107.119 | 108.091 | 107.011 | 107.857 |
Sort by date.
df_u.sort_values(by=["date"], ascending=True, inplace=True)
df_e.sort_values(by=["date"], ascending=True, inplace=True)
df_g.sort_values(by=["date"], ascending=True, inplace=True)
Graphing Data
ticks = 10
xticks = ticks * 5
plt.plot(df_u['date'][::ticks], df_u['close'][::ticks], label='usd/jpy')
plt.grid()
plt.legend()
plt.xticks(df_u['date'][::xticks], rotation=60)
plt.show()
plt.plot(df_e['date'][::ticks], df_e['close'][::ticks], label='eur/usd')
plt.grid()
plt.legend()
plt.xticks(df_u['date'][::xticks], rotation=60)
plt.show()
plt.plot(df_g['date'][::ticks], df_g['close'][::ticks], label='gbp/usd')
plt.grid()
plt.legend()
plt.xticks(df_u['date'][::xticks], rotation=60)
plt.show()
df_u.shape
(200, 6)
df_e.shape
(200, 6)
df_g.shape
(200, 6)
Data shaping
Compute the percentage change from the initial data and train on the list of values.
def shape_data(data_list):
return [d / data_list[0] - 1 for d in data_list].
df_u['data_list'] = shape_data(list(df_u['closing']))
df_e['data_list'] = shape_data(list(df_e['closing price']))
df_g['data_list'] = shape_data(list(df_g['closing price']))
plt.plot(df_u['date'][::ticks], df_u['data_list'][::ticks], label='usd/jpy')
plt.grid()
plt.legend()
plt.xticks(df_u['date'][::xticks], rotation=60)
plt.show()
Preparing the constants
Since we have 200 bars of daily data, we will split the forecast into two parts.
### We have about four years of data, so we will divide it into 8 parts and make forecasts in each area.
TERM_PART_LIST = [0, 100, 200].
# Number of data to use for prediction.
# Predict the next 25 data from 75 data
NUM_LSTM = 75
# Number of intermediate layers
NUM_MIDDLE = 200
# Constants for the neural network model
batch_size = 100
epochs = 50
validation_split = 0.25
Prepare the data
Prepare the data for submission to keras.
def get_x_y_lx_ly(currency='usdjpy', term_part=0):
if currency == 'usdjpy':
date = np.array(df_u['date'][TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
x = np.array(df_u.index.values[TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
y = np.array(df_u['data_list'][TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
if currency == 'eurusd':
date = np.array(df_e['date'][TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
x = np.array(df_e.index.values[TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
y = np.array(df_e['data_list'][TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
if currency == 'gbpusd':
date = np.array(df_g['date'][TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
x = np.array(df_g.index.values[TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
y = np.array(df_g['data_list'][TERM_PART_LIST[term_part]: TERM_PART_LIST[term_part + 1]])
n = len(y) - NUM_LSTM
l_x = np.zeros((n, NUM_LSTM))
l_y = np.zeros((n, NUM_LSTM))
for i in range(0, n):
l_x[i] = y[i: i + NUM_LSTM].
l_y[i] = y[i + 1: i + NUM_LSTM + 1].
l_x = l_x.reshape(n, NUM_LSTM, 1)
l_y = l_y.reshape(n, NUM_LSTM, 1)
return n, date, x, y, l_x, l_y
n, date, x, y, l_x, l_y = get_x_y_lx_ly('usdjpy', 0)
print('shape : ', x.shape)
print('ndim : ', x.ndim)
print('data : ', x[:10])
shape : (100,)
ndim : 1
data : [199 198 197 196 195 194 193 192 191 190].
print('shape : ', y.shape)
print('ndim : ', y.ndim)
print('data : ', y[:10])
shape : (100,)
ndim : 1
data : [ 0. 0.00559074 0.01245167 0.00870597 0.00916028 0.0058967
0.0050159 -0.00100133 -0.00599868 -0.0039404 ]
print(l_y.shape)
print(l_x.shape)
(25, 75, 1)
(25, 75, 1)
Model building
This function defines the construction of the model. The default is RNN.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import LSTM
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Activation
from tensorflow.keras.layers import SimpleRNN
from tensorflow.keras.layers import GRU
def build_model(model_name='RNN'):
# Build an LSTM neural net
model = Sequential()
# Make it possible to choose between RNN, LSTM and GRU
if model_name == 'RNN':
model.add(SimpleRNN(NUM_MIDDLE, input_shape=(NUM_LSTM, 1), return_sequences=True))
if model_name == 'LSTM':
model.add(LSTM(NUM_MIDDLE, input_shape=(NUM_LSTM, 1), return_sequences=True))
if model_name == 'GRU':
model.add(GRU(NUM_MIDDLE, input_shape=(NUM_LSTM, 1), return_sequences=True))
model.add(Dense(1, activation="linear"))
model.compile(loss="mean_squared_error", optimizer="sgd")
return model
# Deepen the neural net (not used in this case)
def build_model_02():
NUM_MIDDLE_01 = 50
NUM_MIDDLE_02 = 50
# Build the LSTM neural net
model = Sequential()
model.add(SimpleRNN(NUM_MIDDLE_01, input_shape = (NUM_LSTM, 1), return_sequences=True))
model.add(Dropout(0.2))
# model.add(SimpleRNN(NUM_MIDDLE_02, return_sequences=True))
# model.add(Dropout(0.2))
model.add(Dense(1))
model.add(Activation("linear"))
model.compile(loss="mean_squared_error", optimizer="sgd")
# model.compile(loss="mse", optimizer='rmsprop')
return model
model = build_model('RNN')
Model Details
model.summary()
Model: "sequential".
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn (SimpleRNN) (None, 75, 200) 40400
_________________________________________________________________
dense (Dense) (None, 75, 1) 201
=================================================================
Total params: 40,601
Trainable params: 40,601
Non-trainable params: 0
_________________________________________________________________
# use validation_split to use the last 10% for validation
history = model.fit(l_x, l_y, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0)
Visualization of the loss function
Let’s visualize the error decreasing with learning. val_loss is increasing and we are overlearning. We can see that val_loss is increasing, indicating overtraining, and we need to normalize and DropOut.
loss = history.history['loss'].
val_loss = history.history['val_loss']
plt.plot(np.arange(len(loss)), loss, label='loss')
plt.plot(np.arange(len(val_loss)), val_loss, label='val_loss')
plt.grid()
plt.legend()
plt.show()
Checking the results with RNN
The period filled in light orange is the period we used for prediction. In that period, the prediction is consistent with the actual trend. The solid orange line is the actual stock price trend, and the blue line is the forecast.
def plot_result():
# Initial input values
res = [].
res = np.append(res, l_x[0][0][0])
res = np.append(res, l_y[0].reshape(-1))
for i in range(0, n):
_y = model.predict(res[- NUM_LSTM:].reshape(1, NUM_LSTM, 1))
# Use the predicted data as input data for the next prediction
res = np.append(res, _y[0][NUM_LSTM - 1][0])
res = np.delete(res, -1)
plt.plot(date, y, label="price", color='coral')
plt.plot(date, res, label="prediction result", color='blue')
plt.xticks(date[::12], rotation=60)
plt.legend()
plt.grid()
plt.axvspan(0, NUM_LSTM, color="coral", alpha=0.2)
plt.show()
print('{} - {} results'.format(date[0], date[NUM_LSTM - 1]))
plot_result()
Results for 2020/04/02 - 2020/07/15
USDJPY
Predicts the second half of the usdjpy period.
currency = 'usdjpy'
for term in [1]:
n, date, x, y, l_x, l_y = get_x_y_lx_ly(currency, term)
model = build_model('RNN')
history = model.fit(l_x, l_y, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0)
print('{} :'.format(currency))
print('Prediction period : {} - {} results'.format(date[0], date[NUM_LSTM - 1]))
plot_result()
usdjpy :
Results for the forecast period : 2020/08/20 - 2020/12/02
EURUSD
currency = 'eurusd'
for term in [0, 1]:
n, date, x, y, l_x, l_y = get_x_y_lx_ly(currency, term)
model = build_model('RNN')
history = model.fit(l_x, l_y, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0)
print('{} :'.format(currency))
print('Prediction period : {} - {} results'.format(date[0], date[NUM_LSTM - 1]))
plot_result()
eurusd :
Results for the forecast period : 2020/04/02 - 2020/07/15
eurusd :
Results for the forecast period : 2020/08/20 - 2020/12/02
GBPUSD
currency = 'gbpusd'
for term in [0, 1]:
n, date, x, y, l_x, l_y = get_x_y_lx_ly(currency, term)
model = build_model('RNN')
history = model.fit(l_x, l_y, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0)
print('{} :'.format(currency))
print('Prediction period : {} - {} results'.format(date[0], date[NUM_LSTM - 1]))
plot_result()
gbpusd :
Results for the forecast period : 2020/04/02 - 2020/07/15
gbpusd :
Result for the forecast period : 2020/08/20 - 2020/12/02
GBPUSD ver2
Let’s try the model with Dropout to counter overtraining.
currency = 'gbpusd'
n, date, x, y, l_x, l_y = get_x_y_lx_ly(currency, 1)
model = build_model_02()
history = model.fit(l_x, l_y, epochs=epochs, batch_size=batch_size, validation_split=validation_split, verbose=0)
print('{} :'.format(currency))
print('Prediction period : {} - {} results'.format(date[0], date[NUM_LSTM - 1]))
plot_result()
# Loss function
loss = history.history['loss'].
val_loss = history.history['val_loss']
plt.plot(np.arange(len(loss)), loss, label='loss')
plt.plot(np.arange(len(val_loss)), val_loss, label='val_loss')
plt.grid()
plt.legend()
plt.show()
gbpusd :
Results for the forecast period : 2020/08/20 - 2020/12/02
Unlike the previous example, the overlearning measure is indeed working, but the prediction is way off the trend…
Summary
Currency forecasting is said to be more difficult than stock forecasting because there are many more component factors. I think it is reckless to make such a simple model prediction, but the purpose of this article is to get used to keras, so I’ll stop here. There are some parts that are 180 degrees out of phase, and some parts that are way off…