Kerasで実践的な為替予測をしてみたかった

ソースはこちら

ロジック

RNNではよく「次の日の終値を予測する」といった記事を見かけますが、
実際のトレードではそのような予測は行いません。

実際のトレードでは必ずストップロスを設定するので、
たとえ終値が上昇していても日中下がってしまってストップロスに引っかかってしまったら意味がありません。

今回は「10pips下がる前に30pips上昇する」という条件をつけてエントリーポイントを学習できるかを考えます。

まずはデータを取得

こちらからEUR/USDの1分足データをダウンロードします。

そのデータを読み込み、シグナルを付与します。
シグナルは上述したとおりですが、より詳しく言うと、

  • 対象は4時間(240分)。
  • 下にnegative pips動く前に上にpositive pips動いていたら買い
  • 上下にnegative pipsも動かなかった場合は凪
  • 上にnegative pips動く前に上にpositive pips動いていたら売り
import pandas
 
positive_pips = 30
negative_pips = 10
point = 0.0001
 
# ファイルはここからダウンロードしている
# http://www.histdata.com/download-free-forex-historical-data/?/metatrader/1-minute-bar-quotes/EURUSD
 
df = pandas.read_csv('DAT_MT_EURUSD_M1_201706.csv',
                     names=['date', 'time', 'o', 'h', 'l', 'c', 'v'],
                     parse_dates={'datetime': ['date', 'time']},
                     )  # type DataFrame
df.index = df['datetime']
# print(df[1707:1950])
df['signal'] = 0
print(len(df))
 
for i in range(len(df) - 241):
    base = df.ix[i]
    target = df[i:i + 240]
    upper_negative = target.ix[target['h'] > df['h'][i] + negative_pips * point]
    upper_positive = target.ix[target['h'] > df['h'][i] + positive_pips * point]
    lower_negative = target.ix[target['l'] < df['l'][i] - negative_pips * point]
    lower_positive = target.ix[target['l'] < df['l'][i] - positive_pips * point]
 
    if len(upper_positive) > 0:
        if len(lower_negative) == 0:
            df['signal'][i] = 1
        elif upper_positive.ix[0]['datetime'] < lower_negative.ix[0]['datetime']:
            df['signal'][i] = 1
    if len(lower_positive) > 0:
        if len(upper_negative) == 0:
            df['signal'][i] = -1
        elif lower_positive.ix[0]['datetime'] < upper_negative.ix[0]['datetime']:
            df['signal'][i] = -1
 
df.dropna()
df.to_csv('EURUSD_M1_201706_with_signal.csv')

訓練・検証データを吐き出す

ここから訓練・検証データを作ります。

1回の学習で使用する時系列データは1日分の1分足終値とします。
それを1時間ごとに取得してnumpyデータとして保存します。

import numpy
import pandas
 
df = pandas.read_csv('EURUSD_M1_201706_with_signal.csv')
df.drop('datetime', axis=1)
maxlen = 1440  # ひとつの時系列データの長さ(24時間)
 
x = numpy.empty((0, 1440), float)
y = numpy.empty((0, 1), int)
df.drop('v', axis=1)
signal = df['signal']
df.drop('signal', axis=1)
 
# 1時間ごとにトレーニングデータを作る
for i in range(1440, len(df), 60):
    # とりあえず終値だけにしてみる
    append_x = numpy.array([df['c'].ix[i - maxlen: i - 1]])
    append_y = numpy.array([0, 0, 0])
 
    # 1-of-K 表現に変換
    if signal.ix[i] < 0:
        append_y[0] = 1
    elif signal.ix[i] > 0:
        append_y[2] = 1
    else:
        append_y[1] = 1
 
    x = numpy.append(x, append_x)
    y = numpy.append(y, append_y)
 
numpy.save('EURUSD_M1_201706_x.npy', x)
numpy.save('EURUSD_M1_201706_y.npy', y)

学習・検証を行う

学習を行います。

ロジックはGRUを用い、勾配降下法はAdamで最適化します。

import numpy
from keras.callbacks import EarlyStopping, TensorBoard
from keras.layers import GRU, Dense, Activation
from keras.models import Sequential
from keras.optimizers import Adam
from matplotlib import pyplot
from sklearn.model_selection import train_test_split
 
 
def weight_variable(shape, name=None):
    return numpy.random.normal(scale=.01, size=shape)
 
 
maxlen = 1440  # ひとつの時系列データの長さ(24時間)
x = numpy.load('EURUSD_M1_201706_x.npy')
y = numpy.load('EURUSD_M1_201706_y.npy')
 
# 正規化
x = x / numpy.linalg.norm(x)
 
x = x.reshape((int(len(x) / maxlen), maxlen, 1))
y = y.reshape((len(x), 3))
 
X_train, X_test, Y_train, Y_test = train_test_split(x, y, train_size=0.8)
X_train, X_validation, Y_train, Y_validation = \
    train_test_split(X_train, Y_train, train_size=0.8)
 
n_in = len(x[0][0])  # 1
n_hidden = 16
n_out = len(y[0])  # 3
 
epochs = 10
batch_size = 10
 
early_stopping = EarlyStopping(monitor='loss', patience=100, verbose=1)
 
model = Sequential()
model.add(GRU(n_hidden,
              kernel_initializer=weight_variable,
              input_shape=(maxlen, n_in)))
model.add(Dense(n_out))
model.add(Activation('softmax'))
 
optimizer = Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
model.compile(loss='categorical_crossentropy',
              optimizer=optimizer)
 
hist = model.fit(X_train, Y_train,
                 batch_size=batch_size,
                 epochs=epochs,
                 validation_data=(X_validation, Y_validation),
                 callbacks=[early_stopping, TensorBoard(log_dir="log", histogram_freq=1)])
 
'''
予測精度の評価
'''
loss_and_metrics = model.evaluate(X_test, Y_test)
print(loss_and_metrics)

実行結果

C:\Users\tono\Anaconda3\python.exe C:/Users/tono/Python/keras-fx/predict_rnn.py
Using TensorFlow backend.
Train on 316 samples, validate on 80 samples
Epoch 1/10
...
290/316 [==========================>...] - ETA: 4s - loss: 0.5638
300/316 [===========================>..] - ETA: 2s - loss: 0.5605
310/316 [============================>.] - ETA: 1s - loss: 0.5636Traceback (most recent call last):
 
316/316 [==============================] - 59s - loss: 0.5627 - val_loss: 0.5350

一応もっとエポック数多いものも試しましたが、
0.5台から下がりませんでした。

損失関数の指定が難しい

今回は正解ラベルを買い・凪・売りの1-of-K表現にしていましたが、
正解ラベルがほとんど0であるということと、凪のときに売買して10pipsの損切りにあったかあわなかったかが全く考慮されていないというのが問題です。
ここらへんが考慮できる高速な損失関数の設定はできないかもしれないですね…

ここまでやるなら強化学習?

そこまで詳細に損益計算をしなければならないのであれば、
損益を報酬とした強化学習を行ったほうがいいのではないかと思えてきました。

というわけで、次回は強化学習で実装してみようと思います。


投稿日

カテゴリー:

,

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください