今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

日経平均のテクニカル指標を計算してヒストグラムや散布図を書いてみる

はじめに

以前、日経平均が日中にどの程度動くかの予想にチャレンジしていました。
www.madopro.net

S&P500、ハンセン、DAXなど、世界各国の指標をもとにして予想するというアプローチだったのですが、以前のアプローチだと日中の値動きを予測するのにはあまり役に立たないという結論でした。

日中の値動きを予測するのは難しそうなので、今回は予測するものを少し変えて、「m日以内に株価がn%上昇するか」を予想できないか挑戦してみます。
今回はまず、様々なテクニカル指標を実際に日経平均に適用してみて、その指数のヒストグラムを描くことによってその指標と株価の上下に何か関係性がありそうか、見てみようと思います。
単純な分布を見ただけではうまくいかなさそうですが、、とりあえずやってみましょう。

(普段私はテクニカルな取引をしていないので、何か間違ったことを書いているかもしれません。その場合はご指摘いただけると幸いです。)

対象とするデータ

今回は、Yahoo!ファイナンスからダウンロードできる日経平均の日足データ2010年~2016年の7年分を使用します。
上記リンクからダウンロードもできますし、私の手元のプログラムではQuandl経由でデータを取得しています。

Buy, Hold, Sellのラベル付け

日経平均のデータがある日に対して、Buy, Hold, Sellの3つのラベル付けを行います
以前のアプローチとは異なり、今回は次のようにラベル付けします。

  • Buy: その日からm日以内に日経平均がn%上昇する場合
  • Sell: その日からm日以内に日経平均がn%下降する場合
  • Hold: BuyでもSellでもない場合

例えば、m = 10, n = 5の場合、3日目に株価が(0日目に比べ)+6%まで上がり、その後10日までに株価がどんどん下がり0日目と比べて-6%まで下がったとします。
この場合は、先に一度+5%以上まで上がっているので、Buyというラベル付けになります。ちゃんと利確・損切のルールを運用した場合に勝つか負けるかがラベル付けの基準になっています。

2010~2016年のデータに(m, n) = (10, 5)と(10, 3)という2つの組でラベル付けしてみたところ、次のような分布になりました。

m=10, n=5 m=10, n=3
Buy 311 670
Sell 313 572
Hold 1107 489

「10日以内に5%上下」だと3つのクラスの数が偏る(Holdが多くなる)ようですが、「10日以内に3%上下」だと3つのラベルが大体同じような数になりました。

これ以降の章では、m = 10, n = 5に固定して話を進めます*1。つまり、「10日以内に5%上がるか、5%下がるか、それ以内の変化か」でBuy, Sell, Holdに分類することにします。

テクニカル指標の実装とグラフ

テクニカル指標のうちメジャーなものを、いくつか実装してみました。簡単なコードとともにご紹介します。
コード中で使っているテクニカル指標を格納するpandas.Panelは次のように定義しています。

xs = pd.Panel(items=markets, major_axis=wp.major_axis, minor_axis=self.indicators)

itemsは["N225"](日経平均のみ)、major_axisは2010年~2016年のdate_range、minor_axisはこの後紹介するテクニカル指標の一覧を設定しています。

また、記事中では、始値、終値などのpandas.Seriesを次のような短い名前の変数で扱います。
c=終値, o=始値, h=高値, l=安値

df = wp[market].dropna()
c = df["Adjusted Close"]
o = df["Open"]
h = df["High"]
l = df["Low"]

数式を記述する場合は、これらのアルファベットに日付を表す添え字を付けて表現します。
例えば、ある日tでの終値は c_tのように書くことにします。

終値の前日比(日次リターン)

まずは終値の前日比を実装しておきます。
c.shift()で1日前の終値を取得し、その変動率を計算しています。

xs[market]["DailyReturn"] = c / c.shift() - 1

これをヒストグラムに落とすと次のようになりました。
Buy, Hold, Sellの間に大きな分布の形の差は見られません。

f:id:kanohk:20170114105946p:plain

モメンタム

モメンタムは通常次の式で計算されるようです。
 c_t - c_{t - N}

将来的に機械学習に使うかもしれないので、今回は引き算ではなくて変化率を計算することにしました。
 c_t / c_{t - N} - 1

値の範囲は変わりますが、符号は変わりません。
今回はN=10, 25で実装しました。

for n in [10, 25]:
    xs[market]["Momentum%d" % n] = c / c.shift(n) - 1

まずこれがN=10のグラフで、
f:id:kanohk:20170114110212p:plain

こちらがN=25のグラフです。
f:id:kanohk:20170114110217p:plain

実際の取引をするときは、モメンタムの値が0を上下方向どちらかに抜けたかによって売り買いを判断するようです。
この実装だと、どちら側に抜けたかの情報は入っていないので、BuyとSellに有意な差がでないのは納得できます。
方向性を表す指標と組み合わせると、ちょっと分布がばらついてくるかもしれませんね。

移動平均線

次に、N日移動平均線を実装しました。
過去N日分の終値の平均をとるだけなのですが、pandasのrolling関数を使うと簡単に実装できます。
移動平均線との差を、こちらも変動率の形で計算しておきます。

for n in [10, 25]:
    xs[market]["MovingAverage%d" % n] = c / c.rolling(window=n).mean() - 1

N=10のグラフと
f:id:kanohk:20170114110842p:plain

N=25のグラフです。
f:id:kanohk:20170114110853p:plain

どちらの場合も、0より少し大きい値の場合はBuyよりもSellが多く、0より少し小さい値の場合はSellよりもBuyになることが多いようです。
といっても、圧倒的にHoldのほうが多いので使い物になるかといったら…。

ボリンジャーバンド

以前の記事でも少し紹介したボリンジャーバンドも実装しました。
過去N日間の終値の平均をM、標準偏差をsとした場合 M + 2sが上部バンド、M - 2sが下部バンドとなります。
ここでは、以前紹介したUdacityの講義でおすすめされていた実装方法で次のように実装しました。
現在の終値が上部バンドに近ければプラス、下部バンドに近ければマイナスの値となります。

for n in [10, 25]:
    xs[market]["Bollinger%d" % n] = (c - c.rolling(window=n).mean()) / c.rolling(window=n).std() / 2

N=10のグラフと
f:id:kanohk:20170114111311p:plain

N=25のグラフです。
f:id:kanohk:20170114111314p:plain

またどの値の場合もBuy,Hold,Sellが分布してるのは同じなのですが、今までよりは分布が偏っている気がします。
例えばN=25の図を見ると、0から値が小さくなるにつれてSellは現象していますがBuyは増加しています。
ちょうど-0.5, -1.0の部分が-s, -2sの下部バンドになるのですが、その近辺では確かにBuyラベルに分類されることが多そうです。

ADX

次はADXという指標を計算します。
詳細な定義はほかの記事に任せますが、市場の上昇や下落の強さを測る指標のようです。

途中でDXという指標の過去14日の指数平滑化平均をとる必要がありますが、pandasのewm*2という関数を使うと簡単に実現できます。

pdm = h - h.shift() # +DM
mdm = l.shift() - l # -DM
pdm[pdm[:] < 0] = 0
mdm[mdm[:] < 0] = 0
mdm[pdm[:] > mdm[:]] = 0
pdm[mdm[:] > pdm[:]] = 0
tr = h - l
tr2 = h - c.shift()
tr3 = c.shift() - l
tr[tr[:] < tr2[:]] = tr2[tr[:] < tr2[:]]
tr[tr[:] < tr3[:]] = tr3[tr[:] < tr3[:]]
pdi = pdm.rolling(window=14).sum() / tr.rolling(window=14).sum() # +DI
mdi = mdm.rolling(window=14).sum() / tr.rolling(window=14).sum() # -DI
dx = (pdi - mdi).abs() / (pdi + mdi)
adx = dx.ewm(span=14).mean()
xs[market]["+DI"] = pdi
xs[market]["-DI"] = mdi
xs[market]["ADX"] = adx

結果はこうなります。+DIにはちょっと偏りが見られます。
f:id:kanohk:20170114112437p:plain
f:id:kanohk:20170114112441p:plain
f:id:kanohk:20170114112116p:plain

実際の売買時には、これらの値を組み合わせて使ったりするみたいなので、ばらばらに分布を見ても仕方ないのかもしれませんね…。

MACD

次はMACDです。
短期と長期の終値指数平滑平均をとることで、短期と長期のトレンドの差を見る指標のようです。
過去N日の終値指数平滑平均をEMA(n)と表現すると今回は次の式を使いました。
 {\rm MACD} = {\rm EMA}(12) - {\rm EMA}(26)

これも、pandasのewmを使うと簡単に実装できます。

macd = c.ewm(span=12).mean() - c.ewm(span=26).mean()
xs[market]["MACD"] = macd

取引時には、このMACDの移動平均の過去9日分の平均をシグナルとして使うことが多いようです。
なので、MACDの現在の値と過去9日分の平均値の差も保持しておくことにします。

xs[market]["MACD2"] = macd - macd.rolling(window=9).mean()

MACDの分布はこうなりました。いろいろとみていると、MACDがプラス圏だと上昇トレンド、マイナス圏だと下降トレンドだとのことですが、今回のラベル付けだとマイナスの時ほどBuyのラベルが多くなっています。
f:id:kanohk:20170114232023p:plain

MACDとシグナルの差はこういう結果です。0の前後でBuyとSellの個数が逆転していますね。0付近はMACDとシグナルがクロスするポイントなので、それがちゃんとグラフに出ているということでしょうか。
f:id:kanohk:20170114232028p:plain

RSI

最後にRSIも実装しておきます。
RSIは、過去14日間の上昇幅・下落幅の合計のうち、上昇幅が占める割合です。
この値が小さいと売られすぎ、大きいと買われすぎと判断して逆張りするようです。

up = c - o
down = o - c
up[up[:] < 0] = 0
down[down[:] < 0] = 0
xs[market]["RSI"] = up.rolling(window=14).mean() / (up.rolling(window=14).mean() + down.rolling(window=14).mean())

実際の分布はこちら。確かに値が極端に大きいところと小さいところを比べると、BuyやSellの比率が偏っているようです。
f:id:kanohk:20170114233611p:plain

散布図行列

以上、いくつかの指標について日経平均過去7年分に対してヒストグラムを書いてきました。
ひとつの指標を使っただけでは、多少Buy/Hold/Sellで分布の形の異なる指標はあったものの、実際の取引に有効と言えそうな指標はありませんでした。
では、指標を組み合わせた場合はどうでしょうか。
まずは一番簡単な、2つの指標を組み合わせた場合の分布を散布図で可視化してみます。
ちょっと大きな画像になりますが…今回実装した全指標に対する散布図行列がこちらです。

f:id:kanohk:20170115000412p:plain

各点の色はヒストグラムと同様、赤がBuy、青がSell、グレーがHoldです。
3つのクラスがきれいに分かれているような組み合わせは、残念ながら存在しませんでした。
しかし、組み合わせによっては赤い点が密集している場所があったりなど、工夫したらある程度の成績が出せるのでは…と少し期待してしまいます。別にすごく高い精度がでなくてもいいんです。コンスタントに利益が出せる程度の精度があれば。。

まとめ

今回は、いくつかのテクニカル分析指標をPythonで実装し、過去7年分の日経平均のデータに対して適用し、その結果をヒストグラムで可視化しました。
可視化の際には、「10日以内に5%株価が上下する」を基準に取引日をBuy、Sell、Holdの3つのクラスに分類し、クラスごとに色分けして可視化しました。
指標によってはBuy, Sell, Holdで分布の形に差が見られたものの、1つの指標だけで3つのクラスを予測することはできないということがわかりました。

2つの指標を組み合わせた散布図も描いてみましたが、3つのクラスの偏りは見られるものの、まだ分類するには不十分です。

実際の取引では、〇〇がプラス圏からマイナス圏に抜けたとき、のように直近と比べてどのように変化したかを取引ルールとして使用することも多いので、当日のデータだけでなく過去数日の指標の動きかたもうまいこと取り入れてあげないと、テクニカル指標をもとに株価の動きは予測できなさそうです。

今までこのブログでやってきた内容だと、そういう時系列データを扱うにはRNNがよさそうです。今回実装したテクニカル指標を正規化して、過去何日か分をRNNに突っ込んであげたら何か起きないでしょうか。
私は理論を追って考えるよりも、実装してしまうのが得意なので、次はそんな感じのRNNを実装してしまおうと思います。


*1:クラスの分布はn=3のほうが理想的なのですが、実際の取引を視野に入れると3%の上下で利確・損切ってどうなんだ、と思ったので5%にしました。。

*2:何の略?Exponentially-weighted moving windowとか?