mplfinanceでDMIとADXを描写する

2022-12-06

概要

目的

 前回の「mplfinanceでMACDを描写する」の続きで、今回はDMIを描写していきます

 変数については過去の内容を反映しています。そのため、まだ過去の記事を読まれていない方は、「mplfinanceでチャートを描写する」から始めてください。

DMI

DIMとは

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

プログラム

  以下のプログラムでDMIの描写が可能です。青色が+DI(14)、黄緑色が-DM(14)、水色がADX(4)を表しています。

DMを算出する

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

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

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

 先程の図をPythonで表現してみます。計算が多いためget_dmという関数にまとめました。

def get_dm(df: pd.DataFrame) -> pd.Series:
    """DMを計算する
        -DMはDM+と区別をするため、マイナスの値を入れる

    Args:
        df (pd.DataFrame): 株価データ

    Returns:
        pd.Series: DM
    """
    dm_plus = df['High'] - df['High'].shift()
    dm_minus = df['Low'] - df['Low'].shift()
    dm = []
    for index in dm_plus.index:
        if (dm_plus[index] < 0 and dm_minus[index] > 0) or dm_plus[index] == -dm_minus[index]:
            dm.append(0)
        elif dm_minus[index] > 0:
            dm.append(dm_plus[index])
        elif dm_plus[index] < 0:
            dm.append(dm_minus[index])
        else:
            if dm_plus[index] > abs(dm_minus[index]):
                dm.append(dm_plus[index])
            else:
                dm.append(dm_minus[index])
    return pd.Series(dm, index=df.index)

 プログラムでは+DMか-DMかを判別するために、わざとreturn値に符号をつけています。DMが必要な場合はabs関数で絶対値を算出します。

TRを算出する

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

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

 最大値なので想像がつくと思いますが、max関数を使用して算出します。Dataframeのデータを扱う場合はnumpyを使用します。インストールしていない方はpipよりインストールをしてください。

import numpy as np
def get_tr(df:pd.DataFrame) -> pd.Series:
    """TRを計算する

    Args:
        df (pd.DataFrame): 株価データ

    Returns:
        pd.Series: TR
    """
    tr1 = df['High'] - df['Low']
    tr2 = np.abs(df['High'] - df['Close'].shift())
    tr3 = np.abs(df['Low'] - df['Close'].shift())
    ranges = pd.concat([tr1, tr2, tr3], axis=1)
    tr = np.max(ranges, axis=1)
    return pd.Series(tr, index=df.index)

 関数については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で表すと以下のようになります。

atr = tr.ewm(span=10, adjust=False).mean()
di_plus = dm.where(dm > 0, 0).ewm(span=10, adjust=False).mean()/ atr * 100
di_minus = -dm.where(dm < 0, 0).ewm(span=10, adjust=False).mean()/ atr * 100

 百分率をグラフに描写するために値を100倍しています。

 ATRはTRの指数平滑移動平均になります。

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

 次に各値の指数平滑移動平均を取ります。今回は10日間の移動平均を求めています。

ADXを算出する

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

 まずはDXの計算方法を下記に示します。

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

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

dx = ((di_plus - di_minus).abs()) / (di_plus + di_minus) * 100

 次にADXを計算します。こちらはSBI証券を参考にして10日間の指数平滑移動平均を取ります。数式とPythonでそれぞれ表現をしていきます。

 百分率をグラフに描写するために値を100倍しています。

 指数平滑移動平均の計算方法については「mplfinanceで指数平滑移動平均線(EMA)を描写する」を参照してください。

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

adx = dx.ewm(span=10, adjust=False).mean()

グラフを描写する

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

 y軸の範囲はylim=(0,100)とすることで0~100までを表示するようにしています。

adx_line = [
    mpf.make_addplot(di_plus, panel=2, color='blue', width=0.7, ylim=(0,100)),
    mpf.make_addplot(di_minus, panel=2, color='lime', width=0.7, secondary_y = False),
    mpf.make_addplot(adx, 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, **setup, scale_width_adjustment=dict(volume=0.6), addplot=adx_line)

まとめ

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