Kerasを使って米国株時系列データをRNNで機械学習させてみる

# はじめに
ニューラルネットワークの勉強のため、Kerasを使って米国株時系列データを機械学習させてみました。時系列データに最適と言われるRNN(LSTM)モデルを使いました。

# 動作環境
動作環境は以下の通りです。
>python==3.8.6
>numpy==1.19.4
>matplotlib==3.3.3
>pandas==1.1.4
>pandas_datareader==0.9.0

# ニューラルネットワークに何を予測させるか
任意の銘柄について、過去20営業日(約1ヶ月)の株価と取引出来高をインプットとして、翌営業日に過去20営業日の高値を更新するかを予測させることを目標にします。

# データの準備
今回分析の対象としたのは、S&P500採用銘柄(約500銘柄)の約5年分の日次データ(pandas-DataFrame形式)です。データはpandas-datareaderを使ってダウンロードし、pandas-DataFrameオブジェクトをpickleして保管しました。データ総量は30MB程度ですが、ダウンロードには20分くらいかかりました。

```python3
import pandas_datareader as pdr
from pandas import to_pickle

# Read S&P500 tickers
with open('SP500.txt', "r") as f_in:  #SP500.txtに米国株のティッカーシンボルを格納
    ticker_list = [v.rstrip() for v in f_in.readlines()]

# Get data from Yahoo
pd_data = pdr.get_data_yahoo(ticker_list)

# pickele the pandas-DataFrame object
to_pickle(pd_data, 'pd_data.pkl')
```

中身はこんな感じです。簡単のため銘柄をAAPL, IBM, MSFT の3つだけにしています。

```python3
Attributes   Adj Close                               Close  ...        Open       Volume                       
Symbols           AAPL         IBM        MSFT        AAPL  ...        MSFT         AAPL        IBM        MSFT
Date                                                        ...                                                
2015-12-21   24.859543  107.960846   50.034130   26.832500  ...   54.880001  190362400.0  5617500.0  37246300.0
2015-12-22   24.836382  109.896957   50.508629   26.807501  ...   54.990002  131157600.0  4263800.0  28322200.0
2015-12-23   25.156017  110.382957   50.937519   27.152500  ...   55.700001  130629600.0  5164900.0  27279800.0
2015-12-24   25.021677  110.151917   50.800640   27.007500  ...   55.860001   54281600.0  1495200.0   9558500.0
2015-12-28   24.741419  109.641991   51.056156   26.705000  ...   55.349998  106816800.0  3143400.0  22458300.0
...                ...         ...         ...         ...  ...         ...          ...        ...         ...
2020-12-14  121.779999  123.529999  214.199997  121.779999  ...  213.100006   79184500.0  5050000.0  28798400.0
2020-12-15  127.879997  125.930000  214.130005  127.879997  ...  215.169998  157572300.0  4359600.0  27018100.0
2020-12-16  127.809998  125.550003  219.279999  127.809998  ...  214.750000   98208600.0  4530100.0  35023300.0
2020-12-17  128.699997  125.550003  219.419998  128.699997  ...  219.869995   94215500.0  3787600.0  32473500.0
2020-12-18  127.561699  125.680000  218.440002  127.561699  ...  218.589996   51084709.0  1906637.0  16891181.0

[1259 rows x 18 columns]
```


# 学習用データの作成
以下gen_data()は、(ダウンロードした)pandas-DataFrameオブジェクト、サンプル数、時系列データ長(今回は20)を引数として学習用データ(X)と正解データ(t)を作成します。学習用データは、ランダムな銘柄とランダムな開始日から始まる所定期間(20+1営業日)を抽出して正規化します。正解データは、その期間の最後の日の株価が、期間中の最高値の場合は、正解データに1を格納、それ以外の場合は0を格納します。
尚、引数arg1==0(省略時値)の場合、過去sep=250営業日より前の開始日で時系列を抽出し、これを学習用データとします。arg1!=0の場合、過去250営業日以降の開始日で時系列を抽出し、これを評価テスト用データとします。

```python3
import numpy as np
import pandas as pd
import random

def gen_data(dataframe, nb_of_samples, length, arg1=0):
    ticker_list = df['Close'].columns #dfから銘柄リストを作成
    sequences = []  #時系列データを格納
    target = []  #正解データを格納
    sep = 250  #学習用データと評価テスト用データを分けるセパレーター
    max_start_index = len(dataframe) - length - sep
    count = 0  #動作には関係ありませんが、最後の日が最高値のサンプル数をカウント
    while len(sequences) < nb_of_samples:
        if arg1 == 0:  #過去sep=250営業日より前の時系列を抽出(学習用データ)
            start_index = np.random.choice(max_start_index)
        else:  #過去sep=250営業日以降の時系列を抽出(評価テスト用データ)
            start_index = np.random.choice(sep) + max_start_index
        end_index = start_index + length + 1
        ticker = random.choice(ticker_list)  #ランダムな銘柄を選択
        temp_df = dataframe[['Close', 'Volume']].xs(ticker, level='Symbols', axis=1)[start_index:end_index]
        #正規化
        f = lambda x: (x - np.nanmin(x)) / (np.nanmax(x) - np.nanmin(x))
        temp_df = temp_df.apply(f).astype('float32')
        if (temp_df.count() == length + 1).all():  #データ中に欠損値がない場合
            data_array = temp_df.values
            sequences.append(data_array[:-1])
            if data_array[-1][0] == 1:  #期間中の最後の日が最高値の場合
                target.append(1)
                count = count + 1
                print(count)  #進捗確認を兼ねてprint
            else:
                target.append(0)
    #学習用データを作成
    X = np.array(sequences).reshape(len(sequences), length, 2)  #株価と出来高で2
    #正解データを作成
    t = np.array(target).reshape(len(target))
    return X, t

len_sequence = 20  #時系列データ長
num_training_samples = 10000  #学習データのサンプル数

df = pd.read_pickle('pd_data.pkl')  #pickleしたpandas-DataFrameの読み込み
X, t = gen_data(df, num_training_samples, len_sequence)
```

# モデルの作成
簡単なLSTMの学習モデルを作成しました。LSTMについては，以下がとても分かりやすく勉強になりました。
[LSTMネットワークの概要](http://qiita.com/KojiOhki/items/89cd7b69a8a6239d67ca#lstmネットワーク)
コーディングは以下を参考にさせていただきました。
[Kerasで基本的なRNN (LSTM) を試してみる](https://qiita.com/everylittle/items/ba821e93d275a421ca2b)

```python3
import tensorflow as tf 
from tensorflow.keras import Sequential 
from tensorflow.keras.layers import LSTM, Dense

input_dim = 2  #入力データの次元数：株価と出来高で2
output_dim = 1  #出力データの次元数
num_hidden_units = 128  #隠れ層のユニット数

#モデル定義
model = Sequential()
model.add(LSTM(
    num_hidden_units,
    input_shape=(len_sequence, input_dim),
    return_sequences=False))
model.add(Dense(output_dim, activation='sigmoid'))

#モデルをコンパイル
model.compile(loss="binary_crossentropy", optimizer="rmsprop", metrics=["accuracy"])
model.summary()
```

図にするとこんな感じです。
![model.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/796836/dcf9bca6-f0ce-0f14-fb5e-331c62a94a65.png)


# 学習と評価テスト
モデルを作成したら、いよいよ機械学習をさせます。今回は250営業日以前が開始日の10,000サンプルを用いてミニバッチサイズ200、学習エポック数100としました。
評価テストでは250営業日以降のランダム銘柄のランダム開始日1,000サンプルを新たに生成して使用しました。

```python3
batch_size = 200  #ミニバッチサイズ
num_of_training_epochs = 100  #学習エポック数

#学習
model.fit(
    X, t,
    batch_size=batch_size,
    epochs=num_of_training_epochs,
    validation_split=0.1
)

#評価
X_test, t_test = gen_data(df, 1000, len_sequence, arg1=1)  #arg1=1を指定
loss, accuracy = model.evaluate(X_test, t_test)
print("\nloss:{} accuracy:{}".format(loss, accuracy))
```

# 結果
結果は以下の通りで、まずまずのaccuracyとなりました。実際この確度で高値更新を予測できたら、億万長者も夢じゃないですね！そううまくはいかないでしょうが。。。
ニューラルネットワークによって高値を更新しそうだと判定されたチャートについて、実際どのような形状のチャートなのか、形状はいわゆるチャート分析における有名形状に合致するのかを確認してみたいと思います。

```
loss:0.22777067124843597 accuracy:0.8999999761581421
```