SARIMAでUSD/JPY相場を予測してみた

# はじめに
AIプログラミング初心者が、勉強を兼ねてUSD/JPY相場を予測アプリをSARIMAモデルで作成してみました。
筆者自身が本業で金融システムの開発を担当しており、本テーマに取り組んでみることにしました。

# 実行環境
GoogleColaboratory

# 実装の流れ
①データの取得
②データの確認と前処理
③学習用と検証用にデータ分割
④コレログラムの可視化
⑤SARIMAモデル構築
⑥予測値の算出と評価

# SARIMAモデルとは
ARIMAモデルは、以前の値に影響されるモデルであり、直前p個の値と相関をもつようなARモデルAR(p)と、以前の誤差に影響されるモデルで直前q個の値の影響を受けるようなMAモデルMA(q)を合成したARMA(p, q)を、d時点前の階差系列に適応したものである。
そして、SARIMAモデルとはARIMAモデルをさらに季節周期を持つ時系列データにも拡張できるようにしたモデルであり、(p, d, q)のパラメーターに加えて(sp, sd, sq, s)というパラメーターも持つ。
今回は筆者の勉強も兼ねて、過去のUSD/JPYのレートから未来のレートの予測をSARIMAモデルでやってみた。

# データの取得
今回は下記からデータを取得
https://www.mizuhobank.co.jp/market/csv/quote.csv
```python
#CSVファイルを取得する。時間情報をインデックスにするためにindexは0列目に指定。カラム名は2列目を指定する。(元のデータの0,1列目は今回使用しない。)
df = pd.read_csv('https://www.mizuhobank.co.jp/market/csv/quote.csv',index_col=0,header=2,encoding="shift-jis")
#indexをdatetime型に変換
df.index = pd.to_datetime(df.index)
```

# データの確認と前処理
### データの確認
取得したデータの上から5行を出力する。
今回取得したデータは日付毎の各通貨対JPYの為替データが入っていることが分かる。
```python
df.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/ea50b29c-9c9e-2c3a-8ba7-0c8125c0a0f9.png)
### データの整形
今回はUSD/JPYの為替レートのみ使用するため、"USD"以外のカラムを削除する。
カラム名も分かりやすいように、"USD/JPY"に変更する。
```python
#データの整形
#USD行以外使用しないため削除
df = df.drop(df.columns[1:],axis=1)
#分かりやすいようにカラム名を変更
df = df.rename(columns={'USD':'USD/JPY'})
df.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/b12b4194-7dcd-94c9-2c8b-b2d101880f36.png)
### 欠損値の確認
念の為欠損値がないかどうか確認する。
```python
#欠損値の有無確認
df.isnull().sum()
```
下記が出力されるため欠損値なし。
USD/JPY    0
dtype: int64
### データを描写してみる
横軸に日付、縦軸にレートを指定して折れ線グラフを作成して、どのような推移になっているか見てみる。
```python
# 描画
plt.title("USD/JPYの日次レート推移")
# グラフのx軸とy軸の名前設定
plt.xlabel("date")
plt.ylabel("rate")
# データのプロット
plt.plot(df["USD/JPY"], label="USD/JPY")
plt.legend()
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/95ee055e-5264-34e8-c81b-d6dbd83b045c.png)
# 学習用と検証用にデータ分割
取得したデータの一部を検証用データとして使用したいため、日付が若い方から95%までを学習用、それ以降を検証用に分解する。
```python
train_df = df[:int(len(df) * 0.95)]
test_df = df[int(len(df) * 0.95):]
```

# コレログラムの可視化
自己相関係数と偏自己相関係数を確認するために、コレログラムを作成する。
コレログラムを見ることで、過去のどの時点の影響を受けるのかや、周期性を確認できる。
```python
#自己相関係数の算出
fig = plt.figure(figsize = (20,10))
ax = fig.add_subplot()
sm.graphics.tsa.plot_acf(df, lags=330, ax = ax)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/21e402be-7aba-2a35-b0f4-62d6d040ce3d.png)
```python
#偏自己相関係数の算出
fig = plt.figure(figsize = (20,10))
ax = fig.add_subplot()
sm.graphics.tsa.plot_pacf(df, lags=100, ax = ax)
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/86b1b4bd-8a7f-8b3b-e991-1df929de5bb6.png)
```python
#波形分解
#periodは四半期単位で周期がくると仮定し62(営業日ベース)で設定
res = sm.tsa.seasonal_decompose(df, period = 250)
fig = res.plot()
plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/3e89738d-6f47-de62-0ed7-08e0e50974ff.png)


以上の結果から分かること
・コレログラムからは、周期性が見られない。
・波形分解からは、トレンドはうまく抽出できていそうであるが、Seasonalの振幅が0.5に対して、Residが5であるため、周期性が抽出できていない。
→周期性が見られないため、本来はSARIMAモデルは向かなそうだが、勉強を兼ねているため、SARIMAモデルを使用する。

# SARIMAモデル構築
### パラメータの選定
周期は月単位と仮定し22(営業日ベース)で設定（本来は四半期ベースや年ベースでも試してみたかったが処理が重く不可であった）
処理時間が多大になるため、p,d,qは0か1から選定
（本当は周期含めてパラメータの最適値を算出したかったが処理時間を考慮して簡素化。）
```python
%%time
# 最適なパラメータを選定する
# 周期は月単位として22(営業日ベース)で設定
# 処理時間が多大になるため、p,d,qは0か1から選定
# 関数の定義
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)]

# 最適なパラメータを求める
selectparameter(train_df,62)
```
算出結果は下記
CPU times: user 17min 21s, sys: 9min 11s, total: 26min 32s
Wall time: 17min 8s
[(0, 1, 0), (0, 0, 0, 22), 9516.06862038468]

d=1であるが、それ以外が0になりほぼ過去のデータを使用しないパラメータ選定となった。

### SARIMAモデル構築
SARIMAモデルに上記に選定したパラメータを入力して、モデルを作成する。
```python
#モデルの作成
model = sm.tsa.statespace.SARIMAX(train_df,order=(0,1,0),seasonal_order=(0,0,0,22)).fit()
```

# 予測値の算出と評価
### 予測値の算出
テストデータ(test_df)を用いて予測値を算出する。
```python
#予測値の算出
train_pred = model.predict() #学習データの期間の予測値
test_pred = model.forecast(len(test_df)) #テストデータの期間の予測値
test_pred_ci = model.get_forecast(len(test_df)).conf_int() #予測区間
```
### 予測値の可視化
予測がうまくいっているかどうかを確認するために折れ線グラフで可視化する。
```python
#予測値の描写
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot()
plt.title("USD/JPYの日次レート推移")
ax.plot(train_df.index, train_df.values, label='Actual(train)', color = "b")
ax.plot(test_df.index, test_df.values, label= 'Actual(test)', color = "r")
ax.plot(train_df.index, train_pred.values, color = "c")
ax.plot(test_df.index, test_pred.values, label= 'SARIMA', color = "y")
ax.set_xlim(["2002-04-01","2022-12-21"])
ax.set_ylim([70, 160])
ax.legend()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3076897/b5f89c50-06c6-2688-f154-59dd672d3de1.png)

赤色が実際の値、黄色が予測値であるが、全く予測できないという結果になった。

# 考察と感想
為替の相場予測はSARIMAのみでは予測が難しいという結果となった。株価指標や経済指標も取り込んだ上で予測を行うと精度が高いモデルが構築できるのではないかと推測ができるため、今後取り組んでみたい。また今回は日次データを使用したが、月次で実施すれば季節性(周期性)が少しは見られる可能性も考えられるが、業務的に月次での予測値を使用する場面は多くなさそうだと考える。
今回GoogleColaboratoryで実施したが、パラメータの選定処理ではかなり処理時間がかかった。(元々は周期(s)の選定もプログラムで算出しようとしたが処理が終わらなかったため諦めた。)本格的に最適なパラメータを選定するためには、処理自体の工夫や処理性能の強化が必要と感じた。
さいごに、今回初めてPythonとAIプログラミングを一から学習し、一連の処理を自分で調べながら実装したが、中々うまくいかないことが多く大変苦労した(Qiitaへの投稿も初であった)。今回プログラムングスクールで学習をしたが、正直実務で使えるレベルには到達できていないと感じている。ただ、普段の本業の中ではできない貴重な経験ができたと思うので、細々とでも学習を続けて、実務でPythonやAIを使ってみたい。