SARIMAモデルを用いたSPY ETF価格の予測

# 概要

Python初心者がSARIMA（Seasonal Autoregressive Integrated Moving Average）モデルを用いて、オープンデータをもとにSPY ETF価格予測を行った手順と結果のまとめです。

#### SPYとは
SPDR S&P 500 ETF Trustは、NYSE Arcaという取引所で取引されていて、SPYという略号が使われています。これは、S&P 500という株価指数を追跡する投資信託の一種で、世界で最も大きくて歴史のあるものです。
[参考リンク_wikipedia_SPDR S&P 500 Trust ETF](https://en.wikipedia.org/wiki/SPDR_S%26P_500_Trust_ETF)

# 目次
- 1.はじめに
- 2.実行環境
- 3.利用データセット
- 4.実行内容
- 5.実行結果
- 6.課題/改善点
- 7.終わりに


# 1.はじめに
業務では主にエクセルを使用して簡単なデータ分析を行っていますが、大規模データの処理や効率的な分析手法には不慣れでした。Pythonを学ぶことで、より効率的な分析やデータ処理が可能になり、業務効率の向上が期待できると感じました。
そのため、Pythonと関連する知識を学び、業務に活かすためのモチベーションが高まりました。今回のETF関連の価格分析は、Pythonの学習成果を活かし、実際の業務に役立てるための第一歩として取り組んでいます。

# 2.環境
- PC：MAC
- 言語：Python3
- 実行環境：Google Colaboratory

# 3.利用データセット
Kaggleの[US Funds dataset from Yahoo Finance](https://www.kaggle.com/datasets/stefanoleone992/mutual-funds-and-etfs)データセットの中からETF prices.csvを選定しました。

- データ分析を容易にスタートするために以下を理由に選定
    - 時系列データになっていること
    - 取引ボリュームがあること
        - 価格数値の変動があることで、分析がわかりやすそう

###### ■データイメージ
|fund_symbol|price_date|open|high|low|close|adj_close|volume|
|:---|:---|:---|:---|:---|:---|:---|:---|
|AAA|2020-09-09|25.1|25.12|25.07|25.07|24.85|17300|
|SPY|2020-09-09|25.06|25.07|25.05|25.07|24.85|23500|


#### ■ETF prices.csvのカラム説明
- fund_symbol: ETFのシンボルコード
    - fund_symbolは、上場投資信託（ETF）を特定するための一意のコードです
- price_date:価格データの日付
- open: その日の開始価格
- high: その日の最高価格
- low: その日の最低価格
- close: その日の終了価格
- adj_close: 配当や分割などの調整を加えた後の終了価格
    - adj_close（調整後終了価格）は、その日の取引が終了した時点でのETFの価格を、配当、株式分割、権利発行などの要因で調整した価格です
- volume: その日の取引量
    - volumeは、指定された日におけるETFの株式が取引された総数を示します。取引量は、そのETFに対する市場の関心や流動性の指標として使用されます


#### ■分析に利用するデータ
  - fund_symbol = SPY
    - 他のシンボルコードと比べて流通量と流動性が高いため選択
  - adj_close
   - 実際の価格変動に対する配当や株式分割などの影響を調整しており、長期的なトレンド分析や予測に最も適しているため


# 4.実行内容
#### 4-1.データの取り込みとデータの前処理
データを読み込んで、SPY ETFの時系列データと取引ボリュームを抽出します。
週末や祝日などの取引がなかった日のデータが欠損しているので、前日の価格情報で補完します。
期間は2016年から2019年までのデータに絞り、処理をはやめるために不要な行は削除します。
```
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split


# 指定されたCSVファイルからデータを読み込み、"fund_symbol"列が"SPY"である行のみを抽出
df = pd.read_csv("ETF prices.csv")
df = df[df["fund_symbol"]=="SPY"]
df

# データフレームのインデックスを日付型に変換し、日次で合計した新しいデータフレームを作成
df.index = pd.to_datetime(df["price_date"], format="%Y-%m-%d")
df_2 = df.resample("1D").sum()

# 休日や取引が行われなかった日の価格を0.00からNaNに変換し、最も最近の取引日のデータで補完する。
df_2.replace(0, np.nan, inplace=True)
df_2.fillna(method="ffill", inplace=True)

# CSVからのデータの取得期間の指定
start_date = "2016-01-01"
end_date = "2019-12-31"

# 指定された期間のデータをフィルタリングする
filter = (df_2.index >= start_date) & (df_2.index <= end_date)
df_2 = df_2[filter]

# 不要な列を削除する処理
df_2.drop(['open', 'high', 'low', 'close', 'volume'], axis=1, inplace=True)
```

#### 4-2.取得データをトレイン/テストデータに分割
時系列データをトレーニングデータ（3年間）とテストデータ（1年間）に分割するため、トレーニングとテストの期間を指定し、その期間に基づいてデータをフィルタリングしてトレーニングデータとテストデータを作成します。最後に、特徴量とターゲット変数をそれぞれのセットに分割します。
```
# train/testデータ分割の取得期間の指定
start_train_date = "2016-01-01"
end_train_date = "2018-12-31"
start_test_date = "2019-01-01"
end_test_date = "2019-12-31"

# trainデータ分割の取得期間のフィルタリング
train_filter = (df_2.index >= start_train_date) & (df_2.index <= end_train_date)
df_train = df_2[train_filter]

# testデータ分割の取得期間のフィルタリング
test_filter = (df_2.index >= start_test_date) & (df_2.index <= end_test_date)
df_test = df_2[test_filter]

# データを特徴量(X)とターゲット変数(y)に分割する
X_train = df_train.drop(columns=["adj_close"])  # 特徴量
y_train = df_train["adj_close"]  # ターゲット変数
X_test = df_test.drop(columns=["adj_close"])  # 特徴量
y_test = df_test["adj_close"]  # ターゲット変数

```

#### 4-3.SARIMAモデルを用いたデータ解析
SARIMAモデルの時系列解析に使う最適なパラメータを選ぶ関数を定義。
この関数は、パラメータ空間内の候補を試し、最適なパラメータセットを見つけるためにBICを最小化します。
最後に、トレーニングデータに基づいて最適なパラメータを見つけ、それを使ってSARIMAモデルをフィットさせます。
季節性の周期は、s=4で定義理由として、株式の決算発表が四半期ごとに行われ、価格に影響を与える可能性があるためです。
```
# 必要なライブラリーのインポート
import warnings
import itertools
import statsmodels.api as sm
import matplotlib.pyplot as plt

# SARIMAモデルを用いて時系列解析を行うためのパラメータを選択するための関数
def selectparameter(DATA, s):
    p = d = q = range(0, 2)
    pdq = list(itertools.product(p, d, q))
    seasonal_pdq = [(x[0], x[1], x[2], s) for x in list(itertools.product(p, d, q))]
    parameters = []
    BICs = np.array([])
    for param in pdq:
        for param_seasonal in seasonal_pdq:
            try:
                mod = sm.tsa.statespace.SARIMAX(DATA,
                                                order=param,
                                                seasonal_order=param_seasonal)
                results = mod.fit()
                parameters.append([param, param_seasonal, results.bic])
                BICs = np.append(BICs, results.bic)
            except:
                continue
    return parameters[np.argmin(BICs)]

# SARIMAモデルを用いて時系列解析
best_params = selectparameter(df_train.adj_close, s=4)
```

#### 4-4.SARIMAモデルを構築
トレーニングデータから最適なパラメータを使用してSARIMAモデルを構築し、モデルを使用して指定されたテスト期間のデータを予測します。
SARIMAX 関数は、トレーニングデータセットとその最適なパラメータを受け取り、モデルを適合させます。

```
SARIMA_model = sm.tsa.statespace.SARIMAX(df_train.adj_close, 
                                         order=best_params[0],
                                         seasonal_order=best_params[1],
                                         enforce_stationarity=False, 
                                         enforce_invertibility=False).fit()

# predに予測期間の代入
pred = SARIMA_model.predict(start=start_test_date, end=end_test_date)
```


#### 4-5.トレイン/テストデータと予測の価格をグラフ化
matplotlib ライブラリを使用して、トレーニングデータ、テストデータ、および SARIMA モデルによる予測をグラフ化します。

```
plt.figure(figsize=(10, 6))
plt.plot(df_train.adj_close, label='Train')
plt.plot(df_test.adj_close, label='Test')
plt.plot(pred, "r", label='Prediction')
plt.legend()
plt.show()
```

# 5.実行結果/課題事項
![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113787/63a1ecb0-ed43-c010-06b0-df2990dc6008.png)

トレーニング/テストデータの価格変動をプロットできましたが、SARIMA_modelによる予測値（pred）が横線になっており、モデルの予測がうまくいっていないようです。
現在のbest_paramsは[(0, 1, 0), (0, 0, 0, 4), 4085.304574877976]となっています。

**この問題の原因として考えられることは以下の通りです。**
- 1.データセットに欠損値や異常値などの問題がある可能性
- 2.データの日付の指定が適切でない可能性
- 3.best_paramsのパラメータが最適値でない可能性
    - 現在のbest_paramsは[(0, 1, 0), (0, 0, 0, 4), 4085.304574877976]であり、微調整が必要かもしれません
- 4.周期設定が適切ではない可能性
    - 周期を2-12で設定/実行しまたが結果はかわりませんでした
    - 取引所は年間250日前後市場が開いているため、こちらの周期で最適化される可能性はあるが計算リソースの制約で実行できていません
- 5.rangeの範囲が適切でない可能性
    - p = d = q = の範囲であるrange(0, 2)をrange(0, 3)に拡張することで、最適化が期待できますが、計算範囲が広がり計算リソースの制約で実行できていません


# 6.終わりに
初心者が予測分析を行いましたが、現状予測結果が上手く出力できていないので、原因を究明していきます。
統計の知識が圧倒的に不足しているので、今後学習を進めていく予定です。

--------
Aidemy Premiumのカリキュラムの一環で、受講修了条件を満たすためにアウトプットをしています。