日経平均株価はランダムウォークなのか検証してみた

 

前回の記事は「確率的トレンド」や「ランダムウォーク」についての理論を簡単にまとめました。
本記事では、日経平均株価がランダムウォークか検証を行いました。

理論については前回の記事をご参照ください。
今回も「Python3ではじめるシステムトレード ──環境構築と売買戦略」を参考にしております。

statsmodelsによるADF検定

これまでの他の記事と同様に、内閣府が公表している景気の分類を以下のように分けてADF検定を行います。
なお、バブル経済期については、バブルのピークまでとそれ以降に分けた場合も検証しております。

循環期景気期間-始点終点
1, 2戦後復興期(recover)1949/5/161954/11/30
3, 4, 5, 6高度経済成長気(growth)1954/12/11971/12/31
7, 8, 8, 10安定期(stable)1972/1/11986/11/30
11バブル経済期(bubble)1986/12/11993/10/31
12, 13, 14, 15,16経済変革期(reform)1993/11/12019/11/30
独自設定新型コロナウィルス期(covid-19)2019/12/1現在(2020/11/25)

statsmodelでは以下について検定が行えます。今回は、1~3を対象とします。

  1. \(\Delta W_t = \gamma W_{t-1} + \sum^{t}_{i=1}\sigma_i W_{t-1} + w_t\)
  2. \(\Delta W_t = \alpha + \gamma W_{t-1} + \sum^{t}_{i=1}\sigma_i W_{t-1} + w_t\)
  3. \(\Delta W_t = \alpha + \beta \cdot t + \gamma W_{t-1} + \sum^{t}_{i=1}\sigma_i W_{t-1} + w_t\)
  4. \(\Delta W_t = \alpha + \beta \cdot t + \eta \cdot t^2+ \gamma W_{t-1} + \sum^{t}_{i=1}\sigma_i W_{t-1} + w_t\)

1. ドリフト無しモデル

ドリフト無しのモデル(1)についてADF検定を実施し、その後、各期間における回帰係数\(\gamma\)がマイナスであるか確かめます。回帰係数がプラスであると、\(\kappa\)が1より大きくなりモデルは発散してしまいます。

これらについてまとめた結果は以下の通り。

import numpy as np
import pandas_datareader as pdr
import matplotlib.pyplot as plt
%matplotlib inline
import statsmodels.api as sm
import pandas as pd
# 日経平均株価のデータを取得
N225 = pdr.DataReader('NIKKEI225', 'fred', '1949/5/16').dropna()
ln_N225 = np.log(N225) # 対数価格に変換
# 期間の設定
starts = ['1949/5/16', '1949/5/16', '1954/12/1', '1972/1/1', '1986/12/1', '1986/12/1', '1990/1/1','1993/11/1', '2019/12/1']
ends   = ['2020/11/25', '1954/11/30', '1971/12/31', '1986/11/30', '1993/10/31', '1989/12/31', '1992/8/31', '2019/11/30', '2020/11/25']
# 各期間においてADF検定を実施し、p値を格納。
p_values = []
for start, end in zip(starts, ends):
    p_value = sm.tsa.adfuller(ln_N225[start:end], regression='nc')[1]
    p_values.append(p_value)
gamma_list = []
# 各期間ドリフト無しランダムウォークの回帰係数を算出し、格納。
for start, end in zip(starts, ends):
    y = ln_N225[start:end].diff().dropna()
    x = ln_N225[start:end].shift(1).dropna()
    model = sm.OLS(y, x)
    results = model.fit()
    gamma = results.params[0]
    gamma_list.append(gamma)
# dfにまとめる
names = ['全期間', '戦後復興期(recover)', '高度経済成長期(growth)', '安定期(stable)', 'バブル経済期(bubble)',
         'バブルのピークまで', 'バブルのピークから谷', '経済変革期(reform)', '新型コロナウイルス期(covid-19)']
pd.options.display.precision = 4 # 小数点以下4桁に設定
df = pd.DataFrame([starts, ends, p_values, gamma_list], index=['期間始点', '終点', 'ADF p-値', 'γ'], columns=names).T
# 判定結果を加える
df['判定'] = ['X', 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X']

p値に着目すると、「バブルのピークから谷」以外では、帰無仮説を棄却できないです。
したがって、単純ランダムウォークという結果になりました。

しかし、最小二乗法でγを推定すると、「バブルのピークから谷」以外ではすべてプラスであることから、単純ランダムウォークである可能性である期間は見つからなかったです。

2.ドリフト付きモデル

上記と同様の手順でドリフト付きモデルでADF検定を実施しました。

sm.tsa.adfuller()の引数regression=’c’にするだけでドリフト付きモデルによるADF検定となります。また、今回の最小二乗法では、切片を追加している点が先ほどの検証と異なります。

# ドリフト付きモデル
# 各期間においてADF検定を実施し、p値を格納。
p_values = []
for start, end in zip(starts, ends):
    p_value = sm.tsa.adfuller(ln_N225[start:end], regression='c')[1] # c:ドリフト付き
    p_values.append(p_value)
const_list = []
gamma_list = []
# 各期間ドリフト付きモデルの回帰係数を算出し、格納。
for start, end in zip(starts, ends):
    y = ln_N225[start:end].diff().dropna()
    x = ln_N225[start:end].shift(1).dropna()
    x = sm.add_constant(x) # 切片(ドリフト)を追加
    model = sm.OLS(y, x)
    results = model.fit()
    const = results.params[0]
    gamma = results.params[1]
    # 回帰係数を保存
    const_list.append(const)
    gamma_list.append(gamma)
df = pd.DataFrame([starts, ends, p_values, gamma_list, const_list], index=['期間始点', '終点', 'ADF p-値', 'γ', '切片'], columns=names).T
# 判定結果を加える
df['判定'] = ['△', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK']

「全期間」を除いて、ドリフト付きモデルでランダムウォークの可能性があります。
「全期間」のp値は13.5%と他の期間に比べ低かった為、判定結果を”△”としました。

ドリフト+時間付きモデル

最後にドリフト付き+時間付きモデルでADF検定を実施します。

sm.tsa.adfuller()の引数regression=’ct’にするだけで検証が可能です。
今回の最小二乗法では、時間項を追加している点が先ほどとは異なります。

# ドリフト付きモデル
# 各期間においてADF検定を実施し、p値を格納。
p_values = []
for start, end in zip(starts, ends):
    p_value = sm.tsa.adfuller(ln_N225[start:end], regression='ct')[1] # ct:ドリフト+時間付き
    p_values.append(p_value)
const_list = []
gamma_list = []
beta_list = []
# 各期間においてドリフト+時間付きモデルの回帰係数を算出し、格納。
for start, end in zip(starts, ends):
    y = ln_N225[start:end].diff().dropna()
    x = ln_N225[start:end].shift(1).dropna()
    x = sm.add_constant(x) # 切片(ドリフト)を追加
    x['time'] = range(len(y)) # 時間項を追加
    model = sm.OLS(y, x)
    results = model.fit()
    const = results.params[0]
    gamma = results.params[1]
    beta = results.params[2]
    # 回帰係数を保存
    const_list.append(const)
    gamma_list.append(gamma)
    beta_list.append(beta)
df = pd.DataFrame([starts, ends, p_values, gamma_list, const_list, beta_list], index=['期間始点', '終点', 'ADF p-値', 'γ', '切片', '時間'], columns=names).T
# 判定結果を加える
df['判定'] = ['OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK', 'OK']

結果として、すべての期間でドリフト+時間付きモデルでランダムウォークである可能性があることが示されました。

まとめ

今回は、日経平均株価がランダムウォークであるか検証をしてみました。
結果としては、ほとんどでランダムウォークである可能性が示されました。

ランダムウォークの場合、株価の予測は困難と思われます。
ランダムウォークの世界で勝負しても確率50%の運任せになってしまう為、ランダムウォーク性が成り立たないようなフィールドを見つけなけれならないのかもしれないです。

例えば、今回の検証では、景気ごとに期間を分けていますが、更に期間を短くしてみたり、時間軸を変更してみたり、対象を個別銘柄にしてみると良いのかもしれないです。

効率的市場仮説が成立しないようなフィールドの探索が必要になってくるのかもしれないです。

コメント

タイトルとURLをコピーしました