はじめに
株式相場を予測・分析するテクニカル指標を検証した記事を投稿しています。
過去には、TOPIX500銘柄の過去5年分のデータを使って、移動平均線のゴールデンクロス、MACD、RSI、ボリンジャーバンドについて検証した内容をQiitaやYouTubeに投稿してきました。※YouTube動画はQiitaに投稿した記事をSofTalkで読み上げています。
・【5年分データ分析】ゴールデンクロスの数日後に株価は上がっているのか
[Qiita][YouTube]
・【5年分データ分析】MACDの買いサインから上がる確率を検証しました
[Qiita][YouTube]
・【5年分データ分析】投資タイミングを判断するときに必ず見てほしい指標RSIをデータ分析
[Qiita][YouTube]
・【5年分データ分析】ボリンジャーバンドを利用した逆張り戦略は効率的に稼げない?
[Qiita][YouTube]
今後も様々なテクニカル指標について検証していきます。
今回の検証は
過去に投稿した記事ではボリンジャーバンドを有効性をTOPIX500構成銘柄の5年分の全てのデータを使って検証した結果、売り/買いのタイミングを高い確率で当てれるわけではないということがわかりました。
しかし、ボリンジャーバンドが全くの無意味な指標ではなく、多くの投資家はボリンジャーバンドを指標として、売り/買いタイミングを考えます。
そこで、どんな条件でボリンジャーバンドを使えば、高い確率で売り/買いのタイミングを捉えることができるかを見つけたいと思います。
今回の検証では、TOPIX500を構成する各銘柄毎に、ボリンジャーバンドの売り/買いのタイミングから高確率で株価が上昇/下落した銘柄を調査し、ボリンジャーバンドで勝てる銘柄は存在するのかについて調査しました。
【結果】
ボリンジャーバンドの売り/買いのタイミングから約60〜75%という高い確率で、売り/買いタイミングから株価が上昇/下落する銘柄が存在する。
ボリンジャーバンド
有名なテクニカル分析の手法の1つです。
ボリンジャーバンドは株価の移動平均に加えて、ばらつき具合(標準偏差)を取り入れた分析手法です。
ミドルバンド、アッパーバンド、ローバンドがあり、ミドルバンドは20日の移動平均、アッパーバンドは20日の移動平均に20日の標準偏差の2倍を加えたもの、ローバンドは20日の移動平均から20日の標準偏差の2倍を引いたものです。
アッパーバンド、ローバンドが示す範囲と実際の価格の関係を見て分析を行います。
基本的な使い方として、アッパーバンド、ローバンドで囲まれた領域から株価が外れた時が売りや買いのタイミングです。
ボリンジャーバンドの使われ方は2通りあります。
逆張り指標として使う(逆張り戦略)
まず1つ目は、ボリンジャーバンドのローバンドを支持線、アッパーバンドを抵抗線と見なす投資戦略です。
・買いシグナル:価格が下部バンドを下抜けた場合
・売りシグナル:価格が上部バンドを上抜けた場合
この戦略は株価が正規分布に従うと仮定する戦略です。
つまり、株の値動きが±2σのバンドの内側に約95%の確率で収まるだろうと考え、株価がバンドから外れた場合は、平均値への回帰するはずという考え方に基づいた戦略です。
※正規分布において、1σ(標準偏差)内に事象が存在する確率は約68%、2σ内に事象が存在する確率は約95%となる。
武田薬品工業(TYO: 4502)を例に見ていきましょう。
2020年8月から2021年3月の株価時系列データです。
図で示したように、上に抜けたときには高値になっていると考えられるため売り、下に抜けたときには安値になっていると考えられるため買いの判断をします。
武田薬品工業の例では、緑点で示したように買いタイミングが8回あります。その後の株価に注目すると、8回のうち6回は数日中に株価上昇しています。また赤点で示したように売りタイミングは2回あります。その後の株価に注目すると、2回のうち1回は数日中に株価下落しています。
この例を見ると、買いタイミングがうまく捉えられていることがわかります。
順張り指標として使う(順張り戦略)
次に2つ目は、ボリンジャーバンドのローバンドを下回ったとき、新しい下降トレンド発生、アッパーバンドを上回った時、上昇トレンド発生とみなす投資戦略です。
・買いシグナル:価格が上部バンドを上抜けた場合⇨新しい上昇トレンド
・売りシグナル:価格が下部バンドを下抜けた場合⇨新しい下降トレンド
この戦略は新しいトレンドが発生したという考えに基づく投資戦略です。
凸版印刷を例に見ていきましょう。
凸版印刷の例では、緑点で示すように売りタイミングが9回、赤点で示すように買いタイミングは5回あります。
それぞれの点に注目してみると、±2σラインに点が並ぶようなバンドウォークと呼ばれる現象が発生しており、トレンドの発生が見られます。したがって、ボリンジャーバンドは新しいトレンド発生のサインとして見なすことができ、ボリンジャーバンドの順張り戦略は、相場の勢いに乗るかたちでポジションをとる戦略です。
検証内容
武田薬品工業と凸版印刷の例を使って、ボリンジャーバンドを利用した逆張り戦略、順張り戦略を紹介しました。
今回の検証では、TOPIX500銘柄を対象に5年分のデータからボリンジャーバンドの逆張り戦略、順張り戦略が有効な銘柄を検証していきます。
大まかな流れとして、5年分のすべてのデータに対してボリンジャーバンドから外れたタイミングを見つけ、その数日後の株価上昇率を調査します。
また、検証プログラムは記事の最後に記載します。
【ボリンジャーバンドの設定期間】
20日(最も使われる)
【基準値】
・ローバンド(2σ)を下回るとき
・アッパーバンド(-2σ)を上回るとき
【検証期間】
2015年1月~2020年3月の約5年間
【対象銘柄】
TOPIX500を構成する各銘柄
※対象銘柄は月1回以上投資タイミングがあるものに限っています。
【株価の上昇を確認する日】
- 1日後
- 3日後
- 5日後
- 10日後
▼ ①逆張り戦略の買いサイン
ローバンドから外れたとき、数日後に株価が上昇しているかを確認します。
▼ ②逆張り戦略の売りサイン
アッパーバンドから外れたとき、数日後に株価が下落しているかを確認します。
▼ ③順張り戦略の売りサイン
ローバンドから外れたとき、数日後に株価が下落しているかを確認します。
▼ ④順張り戦略の買いサイン
アッパーバンドから外れたとき、数日後に株価が上昇しているかを確認します。
検証結果
検証内容①〜④の結果を紹介していきます。
ここでは数日後に上昇/下落している確率と分布図を示します。
基本的に緑が上昇、赤が下落を意味し、分布図中の数字は上昇/下落している確率を意味します。
分布図では、変動率の階級の幅を2%としており、-24%から24%までの階級ごとの相対度数を示しています。図が見にくい場合は画像をクリックして拡大してご覧ください。
【結果の図からわかること】
・約60〜75%という高い確率で、売り/買いタイミングから株価が上昇/下落する銘柄が存在する
▼ ①逆張り戦略の買いサイン
ローバンドを下回った数日後(1、3、5、10日後)の株価の上昇確率が高い銘柄です。
▼ ②逆張り戦略の売りサイン
アッパーバンドを上回った数日後(1、3、5、10日後)の株価の下落確率が高い銘柄です。
▼ ③順張り戦略の売りサイン
ローバンドを下回った数日後(1、3、5、10日後)の株価の下落確率が高い銘柄です。
▼ ④順張り戦略の買いサイン
アッパーバンドを上回った数日後(1、3、5、10日後)の株価の上昇確率が高い銘柄です。
まとめ
今回は、ボリンジャーバンド分析を使って、売り/買いのタイミングから高確率で、株価が上昇/下落する銘柄は存在するのかについて調査しました。
冒頭に記載したように、ボリンジャーバンドは順張り指標として使用する方法(順張り戦略)と逆張り指標として使う方法(逆張り戦略)の2種類の方法があります。今回の検証では、ボリンジャーバンドのアッパーバンドを下から上に抜けた時から数日後に株価が1.上昇する確率2.下落する確率、ローバンドを上から下に抜けた時から数日後に株価が3.上昇する確率、4.下落する確率を計算し、順張り戦略が有効な銘柄、または逆張り戦略が有効な銘柄を探していきました。
検証①〜④の結果から、どちらの戦略についても約60〜75%という高い確率で、売り/買いタイミングから株価が上昇/下落する銘柄が存在するということがわかりました。
次回はどんな条件でボリンジャーバンドを使えば、売り/買いのタイミングを高確率で捉えられるかを探していきたいと思います。ex)株価が上昇または下降トレンドにある場合、ボリンジャーバンドのそれぞれの戦略は使えるか?など。
付録(分析プログラム)
プログラム内で使用している自作の関数や株価データの取得方法は以下の記事をご参照ください。(執筆中)
・株分析ツールの使い方(備忘録)
import trade_package.get as get
import trade_package.tech as tech
import pandas as pd
# 銘柄コードの読み込み
stocks = get.topix500()
# 結果保存
df_result_low = pd.DataFrame()
df_result_upper = pd.DataFrame()
#start_time = '2020-09-22'
#end_time = '2021-02-22'
def roc(df, day):
return (df.shift(-day)["Close"]-df["Close"])/df["Close"]*100
for code in stocks.code:
#print(code)
data = get.price(code)#[start_time:end_time]
tech.bb(data, period=20, sigma=2)
data["sign_l"] = data["Close"]-data["bb_l"]
data["sign_u"] = data["bb_u"]-data["Close"]
data["roc1"] = roc(data,1)
data["roc3"] = roc(data,3)
data["roc5"] = roc(data,5)
data["roc10"] = roc(data,10)
low_total = len(data[data["sign_l"]<0])
uppper_total = len(data[data["sign_u"]<0])
low_roc1_up = len(data[(data["sign_l"]<0)&(data["roc1"]>0)])
low_roc3_up = len(data[(data["sign_l"]<0)&(data["roc3"]>0)])
low_roc5_up = len(data[(data["sign_l"]<0)&(data["roc5"]>0)])
low_roc10_up = len(data[(data["sign_l"]<0)&(data["roc10"]>0)])
uppper_roc1_up = len(data[(data["sign_u"]<0)&(data["roc1"]>0)])
uppper_roc3_up = len(data[(data["sign_u"]<0)&(data["roc3"]>0)])
uppper_roc5_up = len(data[(data["sign_u"]<0)&(data["roc5"]>0)])
uppper_roc10_up = len(data[(data["sign_u"]<0)&(data["roc10"]>0)])
res_low = pd.DataFrame([low_total,
low_roc1_up,
low_roc3_up,
low_roc5_up,
low_roc10_up],
columns=[code],index=['total', 'roc1', 'roc3', 'roc5', 'roc10']).T
res_upper = pd.DataFrame([uppper_total,
uppper_roc1_up,
uppper_roc3_up,
uppper_roc5_up,
uppper_roc10_up],
columns=[code],index=['total', 'roc1', 'roc3', 'roc5', 'roc10']).T
df_result_low = pd.concat([df_result_low,res_low])
df_result_upper = pd.concat([df_result_upper,res_upper])
#data.to_csv('result_{}.csv'.format(code))
df_roc_bb_low = pd.concat([df_result_low.total,
round(df_result_low.roc1/df_result_low.total*100,2),
round(df_result_low.roc3/df_result_low.total*100,2),
round(df_result_low.roc5/df_result_low.total*100,2),
round(df_result_low.roc10/df_result_low.total*100,2)],axis=1)
df_roc_bb_low.columns = ['total', 'roc1', 'roc3', 'roc5', 'roc10']
df_roc_bb_upper = pd.concat([df_result_upper.total,
round(df_result_upper.roc1/df_result_upper.total*100,2),
round(df_result_upper.roc3/df_result_upper.total*100,2),
round(df_result_upper.roc5/df_result_upper.total*100,2),
round(df_result_upper.roc10/df_result_upper.total*100,2)],axis=1)
df_roc_bb_upper.columns = ['total', 'roc1', 'roc3', 'roc5', 'roc10']
print(df_roc_bb_low.sort_values('roc1', ascending=False))
print(df_roc_bb_upper.sort_values('roc1', ascending=False))
df_roc_bb_low.to_csv('roc_low.csv')
df_roc_bb_upper.to_csv('roc_upper.csv')
sum_low = df_result_low.sum()
#print(sum_low)
print('1D: {}, 3D: {}, 5D: {}, 10D: {}'
.format(round(sum_low.roc1/sum_low.total*100,2),
round(sum_low.roc3/sum_low.total*100,2),
round(sum_low.roc5/sum_low.total*100,2),
round(sum_low.roc10/sum_low.total*100,2)))
sum_upper = df_result_upper.sum()
#print(sum_upper)
print('1D: {}, 3D: {}, 5D: {}, 10D: {}'
.format(round(sum_upper.roc1/sum_upper.total*100,2),
round(sum_upper.roc3/sum_upper.total*100,2),
round(sum_upper.roc5/sum_upper.total*100,2),
round(sum_upper.roc10/sum_upper.total*100,2)))
実行例
# ローバンドを下回ったタイミングから数日後の株価上昇確率(各銘柄)
total roc1 roc3 roc5 roc10
7337.T 2 100.00 100.00 100.00 100.00
5463.T 86 66.28 60.47 55.81 58.14
4578.T 73 65.75 63.01 57.53 54.79
7747.T 58 65.52 65.52 67.24 62.07
3382.T 77 64.94 64.94 66.23 59.74
... ... ... ... ... ...
6923.T 88 36.36 39.77 42.05 46.59
6770.T 89 35.96 43.82 42.70 41.57
7518.T 70 35.71 54.29 54.29 67.14
9601.T 96 34.38 35.42 42.71 48.96
9104.T 99 33.33 34.34 38.38 37.37
[498 rows x 5 columns]
# アッパーバンドを上回ったタイミングから数日後の株価上昇確率(各銘柄)
total roc1 roc3 roc5 roc10
7337.T 11 72.73 72.73 72.73 45.45
8304.T 76 63.16 67.11 56.58 46.05
6406.T 127 62.99 66.93 62.99 55.12
6981.T 124 62.90 64.52 67.74 64.52
8136.T 109 62.39 57.80 54.13 35.78
... ... ... ... ... ...
2502.T 85 35.29 40.00 36.47 47.06
3765.T 88 35.23 38.64 31.82 32.95
6417.T 85 31.76 41.18 44.71 55.29
3938.T 77 31.17 40.26 42.86 53.25
2229.T 88 30.68 40.91 47.73 48.86
[498 rows x 5 columns]
# ローバンドを下回ったタイミングから数日後の株価上昇確率(全銘柄)
1D: 50.08, 3D: 51.16, 5D: 51.95, 10D: 51.52
# アッパーバンドを上回ったタイミングから数日後の株価上昇確率(全銘柄)
1D: 48.46, 3D: 50.96, 5D: 51.81, 10D: 50.88