機械学習で株価を予測してみる（入門）

機械学習における時系列データの予測方法を学習したので、備忘録としてまとめます。かなりシンプルな方法だと思いますが、時系列データ処理の入門的な位置付けだと考えています。解析環境はGoogle Colaboratory(Colab)を使いました。

対象は株価です。今回の目的は「翌日の終値が当日の終値より上がるか下がるか」を予測します。

# 時系列データを取得する

時系列データとして米Amazonの株価を使います。株価データはyfinanceで取得します。yfinanceは株価以外にも様々な金融系のデータを取得できるすごいライブラリだと思います。Colabにはyfinanceが入っていないのでインストールから始めます。

```python
!pip install yfinance
```

上場から2023年末までを訓練データ、2024/01/01〜2024/03/31を評価データとしてデータを取得します。

```python
# データダウンロード
import yfinance as yf
df_train=yf.download("AMZN", end="2023-12-31", interval = "1d")
df_test=yf.download("AMZN", start="2024-01-01", end="2024-03-31", interval = "1d")
df_train
```

このように始値(Open)、高値(High)、安値(Low)、終値(Close),出来高(Volume)が１日ごとに取得できました。調整後終値(Adj Close)も取得されますが、今回は使いません。

![price_data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/3300ee51-1788-6945-8bd4-db046965f781.png)

どんなデータかグラフで見てみましょう。ついでに今後使うライブラリもインポートしておきます。

```python
import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import train_test_split
df_train["Close"].plot()
```

![price_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113146/cdbe3f7c-6aaa-f875-1201-d91cf7624f78.png)

上場当初と最高値の差が激しいですね。現在は１００＄を超えていますが、上場当初は０．１＄以下だったようです。このままだと解析しにくいので、あとで差分データに変換します。

# 目的変数を作る

今回の目的は、翌日の終値が上がるか下がるかです。pandasで終値の差分を取得し、１日ずらします。この列を「Up」とします。

```python
# 目的変数生成
df_train = pd.concat([df_train, df_train["Close"].diff(1).shift(-1).rename("Up")], axis=1)
```
これを上がったら1、下がったら0、に変換します。

```python
for i, row in df_train.iterrows():
  if row["Up"] > 0 :
    df_train.at[i, "Up"] = 1
  else :
    df_train.at[i, "Up"] = 0
```

# 差分データを作る

前日データとの差分を作成します。１日前の差分データ、２日前の差分データ・・・５日前の差分データを列として追加します。

始値、高値、安値、終値、出来高の全てに対して５日分の差分データを生成するので、これを関数にします。

```python
# 差分データを作る
def add_diff(df, col):
  return pd.concat([df, df[col].diff(1).rename(col+"_diff1"),
          df[col].diff(1).shift(1).rename(col+"_diff2"),
          df[col].diff(1).shift(2).rename(col+"_diff3"),
          df[col].diff(1).shift(3).rename(col+"_diff4"),
          df[col].diff(1).shift(4).rename(col+"_diff5")],
          axis=1)

df_train = add_diff(df_train, "Open")
df_train = add_diff(df_train, "High")
df_train = add_diff(df_train, "Low")
df_train = add_diff(df_train, "Close")
df_train = add_diff(df_train, "Volume")
```

不要な列は訓練の妨げになるので削除します。

```python
cols = ["Open","High","Low","Close","Volume","Adj Close"]
df_train = df_train.drop(cols,axis=1)
```
評価データも同様に差分を作ります。

```python
df_test = add_diff(df_test, "Open")
df_test = add_diff(df_test, "High")
df_test = add_diff(df_test, "Low")
df_test = add_diff(df_test, "Close")
df_test = add_diff(df_test, "Volume")

df_test = df_test.drop(cols,axis=1)
```

# モデルを作って予測する

過学習を避けるため、訓練データから検証データを分離します。

```python
# 訓練データから検証データを分ける
df_train2, df_val = train_test_split(df_train, test_size=0.2)
```

LightGBMでモデルを作ります。

```python
# モデル構築
goal = "Up"
train_y = df_train2[goal]
train_x = df_train2.drop(goal, axis=1)
val_y = df_val[goal]
val_x = df_val.drop(goal,axis=1)

trains = lgb.Dataset(train_x, train_y)
valids = lgb.Dataset(val_x, val_y)

params= {"objective": "binary",
          "metric" : "binary_logloss"}

model = lgb.train(params, trains, valid_sets=valids, num_boost_round=1000, callbacks=[lgb.early_stopping(stopping_rounds=100)])
```

次の予測します。結果は確率なので、しきい値を0.5として上がったか下がったかに分類します。

```python
# 予測
predict = model.predict(df_test)
thresh_hold = 0.5
predict_list = []
for i in predict :
  if i >= thresh_hold :
    Up = 1
  else :
    Up = 0
  
  predict_list.append(Up)

```

予測結果を元のデータセットと結合します。

```python
df_pre = pd.Series(predict_list).rename("Up")
df_pre.index = df_test.index
df_predict = pd.concat([df_test, df_pre], axis=1)
```

# まとめ

では予測結果を評価してみましょう。

```python
df_t = pd.concat([df_predict["Up"], df_predict["Open_diff1"].shift(-1)], axis=1)

cnt = 0
for i, row in df_t.iterrows():
  if(row["Up"] > 0 and row["Open_diff1"] > 0) : 
    cnt+=1
  if(row["Up"] <= 0 and row["Open_diff1"] <= 0) : 
    cnt+=1

cnt/len(df_t)

# => 0.6229508196721312
```

シンプルな手法でしたが、正答率62％というのはまずまずじゃないでしょうか。以下やってみた感想です。

* 時系列データとはいえ、使うモデルはよく見かけるもので良さそう
* 始値とか高値とか使ってるけど、相関係数高いのでは
* 出来高関係ないのではと思って試しに外してみたところ、正答率54％まで落ちてしまい影響大きかった
* 差分でいいのか、比率のほうがよいか、５日分は適切なのか
* 他にも影響あるデータはいろいろあるはず
* Qiitaのなかにも時系列データの予測について予測手法の解説をやっている記事があるので、理解できるようになりたい

ここまで読んでいただき、ありがとうございました。初心者なので次に学習したほうが良さそうな記事や本があればコメントで教えてください。