
日経平均を考慮して株価を予測してみる
目次
1. はじめに
時系列データから予測を行う際に利用するモデルとして、SARIMAモデルがあります。SARIMAモデルは、季節性のある時間系列データを扱うための拡張版のARIMAモデルです。SARIMAは、季節的な自己回帰(Seasonal Autoregression)、季節的な差分(Seasonal Differencing)、および季節的な移動平均(Seasonal Moving Average)の成分を持っています。
しかしながら、実際の予測には様々な外因を考慮する必要があるケースが多々あります。SARIMAXモデルは外生変数を含めることが可能であるため、外部要因の影響を考慮した予測が可能となります。
外生変数を利用するにあたり、データスケールを統一するために正規化を利用します。
SARIMAXモデルを利用して、日経平均を考慮したユニクロの株価予測にチャレンジしたいと思います。
私の環境
Python 3
Windows 11
Google Chrome
Google Colaboratory
2. 利用するモデルの説明
SARIMAXモデル
成分:
SARIMAの成分に加えて、外生変数を含む。
構造:
SARIMA(p, d, q)(P, D, Q)s +X
X: 外生変数(予測に影響を与える外部のデータ
用途:
・季節性と外生変数を持つ時間系列データの分析と予測。
・外部要因が重要な役割を果たすデータの予測。
3. 正規化
Pythonにおける正規化(Normalization)は、データのスケールを統一するために使用される手法です。これにより、異なる範囲を持つ特徴量を一貫したスケールに変換することができます。正規化は、特に機械学習やデータ前処理の段階で重要です。以下に正規化の詳細とPythonでの実装例を示します。
正規化の目的
計算の安定性: モデルの学習アルゴリズムが安定して動作するようにする。
比較の容易さ: 異なるスケールの特徴量を比較しやすくする。
学習速度の向上: 勾配降下法などの最適化アルゴリズムの収束を速める。
正規化
データの正規化は、機械学習や統計モデリングにおいて、特に多変数を扱う際に重要な前処理手法です。このプロセスは、異なるスケールや単位を持つ複数の変数を一般的な基準に揃えることを目的としています。正規化を行う主な理由は、以下の通りです。
特徴間の比較可能性を確保: 異なる尺度を持つ特徴(例:株価と取引量)を同一の尺度に揃えることで、モデルが特徴間の関係を適切に学習できるようにします。
収束速度の向上: 機械学習アルゴリズム、特に勾配降下法を用いる場合、特徴量のスケールが統一されていると、収束に要する時間を短縮できます。
過学習のリスク低減: 正規化により、特定の特徴量がモデルに与える影響が過大になることを防ぎ、より汎化能力の高いモデルを構築できます。
多変数データにおける正規化
多変数データを扱う場合、各変数が異なる単位や範囲を持つことが一般的です。例えば、株価予測モデルでは、価格(ドル)、取引量(株数)、および他の市場指標など、複数の異なる種類のデータを使用することがあります。これらの変数をそのままモデルに入力すると、特徴量のスケールの違いにより、モデルの学習プロセスが不均衡になる=正確な予測ができないことにつながります。
4. 今回使用する株価データ
使用するデータ
9983.T: ユニクロ株価。目的変数。
^N225: 日経平均株価
yfinanceライブラリ
株価の取得にyfinanceライブラリを利用します。
yfinanceライブラリは、Yahoo Financeから簡単に金融データを取得するためのPythonパッケージです。株価データ、財務データ、インデックスデータなど、多くの金融情報を手軽に取得できるので、金融分析やアルゴリズム取引などの用途に広く利用されています。
5. 株価予測、してみる
データの取得
yfinanceを使ってユニクロの株価データと日経平均株価データを取得します。
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
# ユニクロと日経平均のティッカーシンボル
uniqlo_ticker = '9983.T'
nikkei_ticker = '^N225'
# データの取得期間を設定
start_date = '2020-01-01'
end_date = '2023-12-31'
# ユニクロの株価データを取得
uniqlo_data = yf.download(uniqlo_ticker, start=start_date, end=end_date)
# 日経平均株価データを取得
nikkei_data = yf.download(nikkei_ticker, start=start_date, end=end_date)
# データの表示
print(uniqlo_data.head())
print(nikkei_data.head())
# 株価の終値を時間系列データとして使用
uniqlo_series = uniqlo_data['Close']
nikkei_series = nikkei_data['Close']
# データのプロット
plt.figure(figsize=(10, 6))
plt.plot(uniqlo_series, label='UNIQLO Stock Price')
plt.plot(nikkei_series, label='Nikkei 225 Index')
plt.title('UNIQLO Stock Price and Nikkei 225 Index')
plt.xlabel('Date')
plt.ylabel('Value')
plt.legend()
plt.show()

定常性の確認
データが定常でない場合、差分を取ってデータを定常にします。差分を取る前後のプロットを確認します。
# 定常性の確認: 差分を取る前と後のプロット
diff_uniqlo_series = uniqlo_series.diff().dropna()
diff_nikkei_series = nikkei_series.diff().dropna()
plt.figure(figsize=(10, 6))
plt.subplot(211)
plt.plot(uniqlo_series, label='Original UNIQLO Series')
plt.legend()
plt.subplot(212)
plt.plot(diff_uniqlo_series, label='Differenced UNIQLO Series')
plt.legend()
plt.show()

モデルの設定
SARIMAXモデルのパラメータを設定し、日経平均株価データを外因変数(exog)として指定します。
# SARIMAXモデルのパラメータ設定
order = (1, 1, 1)
seasonal_order = (1, 1, 1, 12)
# 日経平均株価データを外因変数としてSARIMAXモデルを構築
sarimax_model = sm.tsa.SARIMAX(uniqlo_series, order=order, seasonal_order=seasonal_order, exog=nikkei_series)
# モデルの訓練
sarimax_result = sarimax_model.fit()
# モデルの要約
print(sarimax_result.summary())
SARIMAX Results
==========================================================================================
Dep. Variable: Close No. Observations: 977
Model: SARIMAX(1, 1, 1)x(1, 1, 1, 12) Log Likelihood -7116.731
Date: Tue, 25 Jun 2024 AIC 14245.461
Time: 11:16:03 BIC 14274.688
Sample: 0 HQIC 14256.589
- 977
Covariance Type: opg
==============================================================================
coef std err z P>|z| [0.025 0.975]
------------------------------------------------------------------------------
Close 1.0650 0.033 31.924 0.000 1.000 1.130
ar.L1 0.9997 0.473 2.114 0.035 0.073 1.927
ma.L1 -0.9998 0.368 -2.714 0.007 -1.722 -0.278
ar.S.L12 -0.0510 0.037 -1.372 0.170 -0.124 0.022
ma.S.L12 -0.9747 0.018 -55.003 0.000 -1.009 -0.940
sigma2 1.456e+05 1.57e+04 9.294 0.000 1.15e+05 1.76e+05
===================================================================================
Ljung-Box (L1) (Q): 0.50 Jarque-Bera (JB): 1141.60
Prob(Q): 0.48 Prob(JB): 0.00
Heteroskedasticity (H): 1.00 Skew: 0.47
Prob(H) (two-sided): 0.98 Kurtosis: 8.25
===================================================================================
Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
予測期間の設定
12か月先まで予測します。
# 予測期間の設定
forecast_steps = 12 # 12ヶ月先まで予測
# 新しい日経平均株価データ(予測期間分)
new_nikkei_data = nikkei_series[-forecast_steps:].values # ここでは仮想のデータを使用しています
new_nikkei_series = pd.Series(new_nikkei_data, index=pd.date_range(start=uniqlo_series.index[-1], periods=forecast_steps + 1, freq='M')[1:])
print(new_nikkei_data)
print(new_nikkei_series)
予測の実行
予測を実行し、プロットします。
# 予測の実行
forecast = sarimax_result.get_forecast(steps=forecast_steps, exog=new_nikkei_series)
forecast_index = pd.date_range(start=uniqlo_series.index[-1], periods=forecast_steps + 1, freq='M')[1:]
# 予測結果の取得
forecast_series = pd.Series(forecast.predicted_mean.values, index=forecast_index)
conf_int = forecast.conf_int()
lower_series = pd.Series(conf_int.iloc[:, 0].values, index=forecast_index)
upper_series = pd.Series(conf_int.iloc[:, 1].values, index=forecast_index)
# 予測結果のプロット
plt.figure(figsize=(10, 6))
plt.plot(uniqlo_series, label='Observed')
plt.plot(forecast_series, label='Forecast', color='red')
plt.fill_between(forecast_index, lower_series, upper_series, color='pink', alpha=0.3)
plt.title('Fast Retailing (9983.T) Stock Price Forecast with Nikkei 225 Data')
plt.xlabel('Date')
plt.ylabel('Stock Price (JPY)')
plt.legend()
plt.grid(True)
plt.show()
結果
青線が実データ、赤線が予測値、ピンクは予測ぶれ範囲となります。
上昇傾向ではあるものの、季節性が極端ではないので、緩い角度の予測線となり、その分予測ぶれ範囲が広いように見受けられます。

6. 最後に
仕事がら、機械学習に触れざるを得ない状況にあり、Pythonによる統計分析の勉強を始めて3か月で、やっと入口にたどり着いたという感触です。
わかったことは、次のようなことです。
Pythonには豊富なライブラリがある
プログラミングに必要な環境がある(無償で)
コンテンツが豊富にあり、自習にことかかない
わからないことは ChatGPTが教えてくれることが多い
Python自体進化している
※ このブログはAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しています。