mplfinanceでDMIとADXを描写する

2022-05-08

 前回の「mplfinanceでMACDを描写する」の続きで、次はDMIを描写してみたいと思います。

変数については過去の内容を引き継いでいます。そのため、まだ過去の記事を読まれていない方は「mplfinanceでSBI証券Likeなチャートを記載してみる」から始めてください。

 DIMについては、auカブコム様のサイトに詳細が載っています。DMIはDirectional Movement Indexの略で、J.W.ワイルダーという方が開発されたそうです。DMIはトレンドがどちらの方向に進んでいるのかを表現しています。またADXではそのトレンドがどれくらいの強さがあるかを示しています。

DMを算出する

 DMIではまずDMを算出する必要があります。DMはDirectional movementと呼ばれる指標で、前日の株価からの変化量を表します。「量」すなわち大きさなのでDM自体は必ず正の値(絶対値)になります。ちなみにプラス方向に移動したことは+DM、マイナス方向に移動したことは-DMと記載します。

DMは以下ように5つのパターンに分けられます。

 上記をPythonで表記していくわけですが、その前に計算に必要な変数をいくつか作成します。上図を見ていただくと分かるようにDMを算出するためには前日のデータが必要になります。そのためshift関数を使って、前日のデータを列に追加しておきます。その際はもとのデータではなくcopy関数を使ってコピーした変数を使うようにしましょう。

df_copy = df.copy()
df_copy['preopen'] = df['open'].shift(1)
df_copy['prehigh'] = df['high'].shift(1)
df_copy['prelow'] = df['low'].shift(1)
df_copy['preclose'] = df['close'].shift(1)

 次に先程の図をPythonで表現してみます。計算が多いためget_DM(x)という関数にまとめています。
※プログラムでは+DMか-DMかを判別するためにわざとreturn時に符号をつけています。DMが必要な場合はabs関数で絶対値を算出します。

def get_DM(x):
    if x.prehigh is None:
        return None
    dmpos = x.high - x.prehigh
    dmneg = x.prelow - x.low
    if dmpos > 0 and dmneg < 0:
        return dmpos
    elif dmpos < 0 and dmneg > 0:
        return -dmneg
    elif (dmpos < 0 and dmneg < 0) or dmpos == dmneg:
        return 0
    elif (x.high == x.low):
        if dmpos > 0:
            return x.high - x.preclose
        else:
            return -(x.preclose - x.low)
    else:
        if dmpos > dmneg:
            return dmpos
        else:
            return -dmneg


#関数呼び出し方法
dm = df_copy.apply(get_DM, axis=1)

 関数について説明をしてきます。

dm = df_copy.apply(get_DM, axis=1)

 まずは、apply関数です。こちらについてはHyperion13fleet様の記事に使い方についての説明が載っておりましたのでご参照ください。apply関数に(関数,axis=1)を引数として渡すことで、すべての行に対して関数が実行されます。関数内では「.列名」とすることで、列を指定できます。get_DMの処理がすべての行に適応され、最終的にpandas.Seris型で値が各DMの計算値が取得できます。

 次にif文の中身について解説をしていきます。dmposとdmnegについて、引かれる対象が前日と当日で逆になっている点に注意してください。それ以外は図の内容をそのままif文に変換しています。
 おそらく、以下の文章について疑問に思われた方が多いと思います。

elif (x.high == x.low):
        if dmpos > 0:
            return x.high - x.preclose
        else:
            return -(x.preclose - x.low)

 x.high == x.lowですが、こちらは当日の安値もしくは高値を表しています。yahoo-finance-api2ではストップ高もしくはストップ安になると高値と安値が同じ価格になります。こちらのif文ではその性質を利用して判別を行っています。そして次のif文で「ストップ高」か「ストップ安」のどちらであるかを判定しています。

TRを算出する

 次にTRを求めていきます。TRはTrue Rangeの略で、1日の変動幅を表しています。大きさを表す指標のため、DMと同様に絶対値となります。TRは次の3つの値の中で最大値を取ります。

  • 当日の高値と当日の安値の差
  • 当日の高値と前日の終値の差
  • 前日の終値と当日の安値の差

 最大値なので想像がつくと思いますが、max関数を使用して算出します。

def get_TR(x):
    if x.prehigh is None:
        return None
    tr1 = abs(x.high - x.low)
    tr2 = abs(x.high - x.preclose)
    tr3 = abs(x.low - x.preclose)
    return max([tr1, tr2, tr3])

#関数呼び出し方法
tr = df_copy.apply(get_TR, axis=1)

 関数については3つの項目をそのまま数式に変換しています。各計算値はabs関数で絶対値を取るようにしてください。TRの条件の3つを条件結果から最大の値をmax関数で計算して返しています。

DIを算出する

 次にDIを計算します。DMIの略から想像がつくと思いますが、DIはDirectional indexの略で以下の数式で表されます。

$$+\mathrm{DI}=\frac{+\mathrm{DM}}{\mathrm{TR}}$$ $$-\mathrm{DI}=\frac{-\mathrm{DM}}{\mathrm{TR}}$$

 TRで割ることで標準化されるため、他の銘柄でも同様の基準で比較をすることができます。

 DIの求め方が分かったところで、実際に+DIと-DIを計算していきます。DMIでは各値の移動平均を使用します。今回はSBI証券のグラフに合わせて11日で計算を行います。計算式はそれぞれ以下のように表現できます。

$$+\mathrm{DI(11)}=\frac{+\mathrm{DM(11)}}{\mathrm{TR(11)}}$$ $$-\mathrm{DI(11)}=\frac{-\mathrm{DM(11)}}{\mathrm{TR(11)}}$$

上記をPythonで表すと以下のようになります。

dipos = dm.where(dm > 0, 0).rolling(11).mean().abs() / tr.rolling(11).mean()
dineg = dm.where(dm < 0, 0).rolling(11).mean().abs() / tr.rolling(11).mean()

 +DIと-DIをそれぞれdipos、dinegという関数に代入しています。データの振り分けにはwhere関数を用いています。where関数ですがデータの抽出だけでなく、該当しなかった値を第2引数の値で埋めてくれます。今回は0を指定することで、+DMを求める際は-DMを0に、-DMを求める際は+DMが0が代入されます。

 次にrolling関数です。こちらですが、株式会社pipon様のサイトを参考にさせていただきました。rolling関数とmean関数を組み合わせることで移動平均を算出することができるようです。今回は14日間の移動平均を求めたいためrolling関数の引数に14を指定しています。

 最後にdmは絶対値を取りたいのでabs関数を使用しています。trについての計算については移動平均を求めているだけなので省略します。

ADXを算出する

 最後にADXを算出していきます。ADXはAverage Directional Movement Indexと呼ばれていてトレンドの強さを表します。「ADIじゃないの?」と思いましたが、DIの計算結果をもとにDXを計算して、その移動平均を計算しているためADXとなっているようです。

 まずはDXの計算方法を下記に示します。(DXってなんの略なのでしょうか🤔)

$$\mathrm{DX}=\frac{|\mathrm{+DI-(-DI)}|}{\mathrm{+DI+(-DI)}}$$

こちらをPythonで表現すると以下になります。

(dipos - dineg).abs() / (dipos + dineg)

 次にADXを計算します。こちらはSBI証券を参考にして4日間の指数平滑移動平均を取ります。数式とPythonでそれぞれ表現をしていきます。指数平滑移動平均の計算方法については「mplfinanceで指数平滑移動平均線(EMA)を描写する」を参照してください。

$$\mathrm{ADX=EMA(DX)}$$

adx = ((dipos - dineg).abs() / (dipos + dineg)).ewm(span=4, adjust=False).mean()

DMIとADXを描写する

 やっとグラフを書く準備が整いました。いよいよグラフにプロットしていきます。plotの方法については何度も方法を紹介していますので、必要に応じて過去の記事をご参照ください。

adx_line = [
    mpf.make_addplot(dipos[datefrom:] * 100, panel=2, color='blue', width=0.7, ylim=(0,100)),
    mpf.make_addplot(dineg[datefrom:] * 100, panel=2, color='lime', width=0.7, secondary_y = False),
    mpf.make_addplot(adx[datefrom:] * 100, panel=2, color='aqua', width=0.7, secondary_y = False)
]
setup = dict(type='candle', volume=True, datetime_format='%Y/%m/%d', figsize=(9,5), style=cs, xrotation=False, ylabel='', ylabel_lower='')
mpf.plot(df[datefrom:], **setup, scale_width_adjustment=dict(volume=0.6), addplot=adx_line)

 すべての値を100倍しています。これはDI及びADXは百分率が出力されるため、グラフにプロットした際に分かりやすくするために100倍しています。また、y軸の範囲についてもylim=(0,100)とすることで0~100までを表示するようにしています。上記コマンドを入力すると以下のグラフが表示されます。

 青色が+DI(14)、黄緑色が-DM(14)、水色がADX(4)を表しています。

 今回はDMIとADXの計算と描写に挑戦しました。このあたりまで来るとプロットするより計算式を作るのが大変になってきました、、、大変ですがPythonで一度数式を作って検証してみると、数式の意味が理解が深まります。皆さんも是非チャレンジしてみてください。