テスラの今後の株価をLSTMで予測

### 1. このページについて
最近，株価の変動が激しいすね．
なので，AIでテスラの未来の株価を予測してみました．

### 2. プログラム
早速プログラムを紹介していく．

##### 必要なライブラリのインポート
まず最初は，必要なライブラリをインポートします．

```Python
# 必要なライブラリのインポート
import numpy as np  # 数値計算用のライブラリ
from sklearn.metrics import r2_score  # モデル評価用の関数
import yfinance as yf  # Yahoo Financeからデータを取得するためのライブラリ
from datetime import datetime  # 日付と時間を扱うためのライブラリ
from sklearn.preprocessing import MinMaxScaler  # データの正規化用の関数
from keras.models import Sequential  # ニューラルネットワークモデルを構築するためのクラス
from keras.layers import Dense, LSTM, Dropout  # ニューラルネットワークの層を構築するためのクラス
import matplotlib.pyplot as plt  # グラフを描画するためのライブラリ
import pandas as pd  # データ操作用のライブラリ
```

##### テスラの株価データの取得
ヤフーファイナンスからテスラの株価データを取得します．

```Python
# テスラの株価データを取得する
s_target = 'TSLA'  # テスラのティッカーシンボル
df = yf.download(s_target, start='2014-01-01', end=datetime.now())  # 2014年から現在までのデータを取得
print(df.head())  # データの最初の5行を表示
```
##### 株価データのプロット
取得した株価データをプロットします．
ここまでは難しくないですね．

```Python
# グラフのスタイルを設定（見やすくする）
plt.style.use("fivethirtyeight")

# 株価の終値をプロットする
plt.figure(figsize=(16, 6))  # グラフのサイズを設定
plt.title(s_target + ' Close Price History')  # グラフのタイトルを設定
plt.plot(df['Close'])  # 終値をプロット
plt.xlabel('Date', fontsize=14)  # x軸のラベルを設定
plt.ylabel('Close Price USD ($)', fontsize=14)  # y軸のラベルを設定
plt.show()  # グラフを表示
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3700895/30c28780-f654-a238-b886-f24a392f1186.png)


##### 移動平均を算出してプロット
今度は，移動平均を算出してプロットします．


```Python
# 移動平均を計算する日数
ma_day = [10, 20, 50]  # 10日、20日、50日の移動平均を計算
for ma in ma_day:
    column_name = f"MA for {ma} days"  # 列名を設定
    df[column_name] = df['Adj Close'].rolling(ma).mean()  # 移動平均を計算して新しい列に追加

# 移動平均をプロットする
plt.figure(figsize=(16, 6))  # グラフのサイズを設定
plt.title(s_target + ' Close Price MA History')  # グラフのタイトルを設定
plt.plot(df['Close'][-300:])  # 終値をプロット（直近300日分）
plt.plot(df['MA for 10 days'][-300:])  # 10日の移動平均をプロット
plt.plot(df['MA for 20 days'][-300:])  # 20日の移動平均をプロット
plt.plot(df['MA for 50 days'][-300:])  # 50日の移動平均をプロット
plt.xlabel('Date', fontsize=14)  # x軸のラベルを設定
plt.ylabel('Close Price USD ($)', fontsize=14)  # y軸のラベルを設定
plt.legend(['Close', 'MA for 10 days', 'MA for 20 days', 'MA for 50 days'], loc='upper right')  # 凡例を設定
plt.show()  # グラフを表示
```

<font color="rgb(107, 142, 35)">df[column_name] = df['Adj Close'].rolling(ma).mean()</font> について少し解説すると，
これは新しい列を追加する処理になります．

<font color="rgb(107, 142, 35)">df['Adj Close']</font> は，データフレーム df の「Adj Close」列を選択しているという意味です．この列に終値が入っています．

<font color="rgb(107, 142, 35)">.rolling(ma)</font> は，ma で指定された期間（例えば10日、20日、50日）の移動ウィンドウを作成するという意味です．

<font color="rgb(107, 142, 35)">.mean()</font>は，移動ウィンドウ内のデータの平均を計算するという意味になります．

つまり，まとめると「移動平均を計算して新しい列に追加」ということになります．

ちなみに↑を実行すると，以下のようなグラフが表示されます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3700895/67459739-f7e0-c72c-3ff6-7385cef4a4e4.png)



##### LSTMモデルの構築の準備
LSTMモデルを構築するための準備をします．

```Python
# 終値データを抽出
data = df.filter(['Close'])  # 終値のみを抽出
dataset = data.values  # numpy配列に変換

# データを0〜1の範囲に正規化
scaler = MinMaxScaler(feature_range=(0, 1))  # 正規化の範囲を0〜1に設定
scaled_data = scaler.fit_transform(dataset)  # データを正規化

# データの80%をトレーニングデータとして使用
training_data_len = int(np.ceil(len(dataset) * .8))  # トレーニングデータの長さを計算．（データセットの80%の長さを求めているだけ．）

# 予測に使用する期間
window_size = 60  # 60日分のデータを使用して予測

# トレーニングデータを作成
train_data = scaled_data[0:int(training_data_len), :]  # トレーニングデータを抽出

# トレーニングデータをx_trainとy_trainに分割
x_train, y_train = [], []
for i in range(window_size, len(train_data)):
    x_train.append(train_data[i - window_size:i, 0])  # 過去60日分のデータをx_trainに追加
    y_train.append(train_data[i, 0])  # 61日目のデータをy_trainに追加

# numpy arrayに変換
x_train, y_train = np.array(x_train), np.array(y_train)  # リストをnumpy配列に変換

# LSTMモデルに入力するためにデータの形状を変更
x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))  # データの形状を変更
```
少し補足します．
<font color="rgb(107, 142, 35)">scaled_data[0:int(training_data_len), :]</font>は、scaled_data の最初の training_data_len 行をすべての列（:）と共に選択するという意味です．つまり、この行のコードは、scaled_data の最初の training_data_len 行を train_data に格納するということっす．

また，<font color="rgb(107, 142, 35)">x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))</font>
は，LSTMモデルに入力するためにデータの形状を変更する処理になります．
LSTMは3次元の入力データを期待するため、データを (サンプル数, 特徴量の数, 1) の形状に変換する必要があるため，この処理をやっているということです．


##### LSTMモデルの構築
さて，LSTMモデルを構築してみます．

```Python
# LSTMモデルを構築
model = Sequential()  # モデルを初期化
model.add(LSTM(units=50, return_sequences=True, input_shape=(x_train.shape[1], 1)))  # LSTM層を追加
model.add(Dropout(0.2))  # ドロップアウト層を追加
model.add(LSTM(units=50, return_sequences=True))  # LSTM層を追加
model.add(Dropout(0.2))  # ドロップアウト層を追加
model.add(LSTM(units=50, return_sequences=True))  # LSTM層を追加
model.add(Dropout(0.2))  # ドロップアウト層を追加
model.add(LSTM(units=50))  # LSTM層を追加
model.add(Dropout(0.2))  # ドロップアウト層を追加
model.add(Dense(units=1))  # 出力層を追加

# モデルをコンパイル
model.compile(optimizer='adam', loss='mean_squared_error')  # モデルをコンパイル
```

##### LSTMモデルの学習
構築したLSTMモデルを学習させます．

```Python
# モデルをトレーニング
history = model.fit(x_train, y_train, batch_size=32, epochs=100)  # モデルをトレーニング

# モデルの概要を表示
model.summary()  # モデルの概要を表示

# テストデータを作成
test_data = scaled_data[training_data_len - window_size: , :]  # テストデータを抽出

x_test = []
y_test = dataset[training_data_len:, :]  # テストデータの実際の値を抽出
for i in range(window_size, len(test_data)):
    x_test.append(test_data[i-window_size:i, 0])  # 過去60日分のデータをx_testに追加

# numpy arrayに変換
x_test = np.array(x_test)  # リストをnumpy配列に変換
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1 ))  # データの形状を変更
```
補足すると，
<font color="rgb(107, 142, 35)">test_data = scaled_data[training_data_len - window_size: , :]</font>については，
scaled_dataの後ろ80%の位置からウインドウサイズ（60日分）を引いた位置から、データセットの最後までを抽出しています．



##### 予測の実行
予測してみます．

```Python
# 予測を実行する
predictions = model.predict(x_test)  # 予測を実行
predictions = scaler.inverse_transform(predictions)  # 予測結果を元のスケールに戻す

# 二乗平均平方根誤差（RMSE）: 0に近いほど良い
rmse = np.sqrt(np.mean(((predictions - y_test) ** 2)))  # RMSEを計算
print(rmse)  # RMSEを表示

# 決定係数(r2) : 1に近いほど良い
r2s = r2_score(y_test, predictions)  # 決定係数を計算
print(r2s)  # 決定係数を表示

# トレーニングデータと検証データをプロット
train = data[:training_data_len]  # トレーニングデータを抽出
valid = data[training_data_len:].copy()  # 検証データを抽出
valid['Predictions'] = predictions  # 予測結果を検証データに追加

plt.figure(figsize=(16,6))  # グラフのサイズを設定
plt.title('Model')  # グラフのタイトルを設定
plt.xlabel('Date', fontsize=14)  # x軸のラベルを設定
plt.ylabel('Close Price USD ($)', fontsize=14)  # y軸のラベルを設定
plt.plot(train['Close'])  # トレーニングデータをプロット
plt.plot(valid[['Close', 'Predictions']])  # 検証データと予測結果をプロット
plt.legend(['Train', 'Val', 'Predictions'], loc='lower right')  # 凡例を設定
plt.show()  # グラフを表示
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3700895/59af6748-ce4f-6fd0-4afe-c0abc72e0db0.png)

上記のグラフについて少し補足しますと，

Train：2014年～2022年2月くらいまでのテスラの株価
Val：2022年2月くらい～2024年7月くらいまでのテスラの株価
Predictions：過去60日のデータからAIで予測した株価

という感じですね．

あれ・・・？なんか検証データと予測結果が一致しすぎではないか・・・？
なんで・・・？？

**原因は単純です．**

このプログラム内のLSTMモデルは，**過去60日のデータを元に，その次の日の株価データを予測する** というモデルになっております．
このプログラムだと，常に最新の実際の60日分のデータに基づいて予測を行うようなプログラムになってます．
たとえば，2024/8/1のデータを予測するために，2024年の6月と7月の実際の株価を使っちゃうようなプログラムになってます．そりゃ精度はよくなりますね・・・・


##### 未来の株価予測
さらに2026年12月末までの株価を予測し、グラフにプロットしてみます．

```Python
# 未来の株価予測
# 2026年12月末までの株価を予測し、グラフにプロットします。

# 未来の日付を生成
future_dates = pd.date_range(start=df.index[-1], periods=365*2, freq='D')  # 未来の日付を生成（2年間）

# 直近のデータを使用して予測を行う
future_data = df['Close'].values[-window_size:].reshape(-1, 1)  # 直近60日分のデータを抽出
future_data = scaler.transform(future_data)  # データを正規化

future_predictions = []  # 未来の予測結果を格納するリスト
for _ in range(len(future_dates)):
    future_input = future_data[-window_size:]  # 直近60日分のデータを使用
    future_input = future_input.reshape((1, window_size, 1))  # データの形状を変更
    future_pred = model.predict(future_input)  # 予測を実行
    future_data = np.append(future_data, future_pred)  # 予測結果をデータに追加
    future_predictions.append(future_pred[0, 0])  # 予測結果をリストに追加

future_predictions = scaler.inverse_transform(np.array(future_predictions).reshape(-1, 1))  # 予測結果を元のスケールに戻す

# 未来の予測結果をプロット
plt.figure(figsize=(16, 6))  # グラフのサイズを設定
plt.title('Future Predictions')  # グラフのタイトルを設定
plt.xlabel('Date', fontsize=14)  # x軸のラベルを設定
plt.ylabel('Close Price USD ($)', fontsize=14)  # y軸のラベルを設定
plt.plot(df['Close'])  # 過去の株価データをプロット
plt.plot(future_dates, future_predictions, color='red')  # 未来の予測結果をプロット
plt.legend(['Historical', 'Future Predictions'], loc='upper left')  # 凡例を設定
plt.show()  # グラフを表示
```
ちょっと補足します．

上記でやってることとしましては，以下のような感じですね．

**①:** まず最初は，直近60日のデータ（実際の株価のデータ）から，その次の日のデータを予測．

**②:** “①で予測したデータを含めた直近60日のデータ”から，その次の日を予測する．

**③:** “②”をどんどん繰り返していく・・・

で，，，，，上記を実行した結果は，↓のような感じになりました．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3700895/707e8b83-dfa3-b596-df84-c6af22d03674.png)

ワロスwwww


### 3.参考にさせてもらった記事
https://supersoftware.jp/tech/20220907/17599/