騙しに騙されるMACDテクニカル分析


# はじめに

移動平均線のゴールデンクロスよりもシグナルが早く出るということで人気のテクニカル指標macd。このmacdによる売買はテクニカル分析を紹介するサイトで数多く取り上げられています。曰く、「MACD」が「Signal」を上抜けるゴールデンクロスは買い。「MACD」が「Signal」を下抜けるとデッドクロスは売り。

例えば、次のチャート(3658)の例では、MACDゴールデンクロスとなる(A)で買ってMACDデッドクロスの(B)で売ればよいと。移動平均線のクロスよりもシグナルは早く出ていることも確認できます。

![macd_3658.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483341/d7b0008b-2679-79e9-9adb-2cec6408f4ec.png)

そりゃそうです。このチャートだけ見れば。

本稿では、このチャートに騙されないMACDの真実を暴き出します（大袈裟！）。


# MACDの真実

次のチャート(7181)を見てみましょう。赤矢印(B), (D), (F)で買って、青矢印(A), (C), (E), (G)で売る。

![macd_7181.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483341/b30ca56e-503e-a3aa-c436-8220cfacaba0.png)

(A), (D)は売り場、買い場でないことは明らかです。テクニカル分析においては、セオリーが当てはまらない虚報は「騙し」といって正当化されます。曰く、「売買シグナルにはまれに騙しがあります」

まれに？

そこで、実際に「騙し」がどの程度出現するのか確認してみます。

+ TOPIX500採用銘柄
+ 2015年1月1日～2020年1月20日

のデータを使用して検証します。

<a href="https://github.com/etekichi98/data_topix500/blob/main/data_topix500_20200120.zip">株価データはこちら</a>


何をもって「騙し」とするかは悩ましいところですが、まずは、

+ ゴールデンクロスから10日以内に次のデッドクロスが現れたら、このゴールデンクロスは「騙し」。
+ ゴールデンクロスから10日以内に、発現時の終値よりも3%を超える上昇が一度もなかったら「騙し」。
+ デッドクロスから10日以内に次のゴールデンクロスが現れたら、このデッドクロスは「騙し」。
+ デッドクロスから10日以内に、発現時の終値よりも3%を超える下落が一度もなかったら「騙し」。

としてみました。

検証データで確認すると、

+ ゴールデンクロス全事象：24183
+ 騙し：14584（60.3%）
+ デッドクロス全事象：24099
+ 騙し：15394（63.9%）

となりました。60%以上は「騙し」でしたね。

そもそも、半分も当たらないものを、それに基づき売買するってどうなんでしょう。

ということは置いておいて、このシグナルをもとに売買したとし、売り時／買戻し時は悩ましいのですが、仮に10日後に反対売買するとしたらどれくらいの利益があるのかを確認しておきます。

macdゴールデンクロスのシグナルが出たらその日の引値で買い（実際にシグナルが確定するのが引値が出た後なので、現実的にはその値段では買えないが）、その10日後の引値で売った場合のゲインの平均値を算出すると、0.3795%。ただし、売買手数料は未カウントです。
（ゲインの平均値は、TOPIX500のすべて銘柄を一定金額分このシグナルに基づき売買した場合の利益に相当します。）

macdデッドクロスのシグナルが出たらその日の引値で空売り（実際にシグナルが確定するのが引値が出た後なので、現実的にはその値段では売れないが）、その10日後の引値で買い戻した場合のゲインの平均値を算出すると、-0.53642%。ただし、売買手数料と空売りコストは未カウントです。

macdデッドクロスシグナルによる売買は論外ですね。
かろうじてmacdゴールデンクロスの売買はプラスですが、手数料を入れるとあやしい。

macd自体、単独では信頼できる指標なのかもあやしいですね。

「騙し」は後になってからしかわかりません。シグナルが出た時点で有効なmacdと「騙し」とを分類できる「何か」を見つけることができればいいんですけどね。他のテクニカル指標とmacdを組み合わせてごにょごにょしてるのは、みんなそれを目指してるってことかな。


# まとめ

移動平均線のゴールデンクロスよりもシグナルが早く出るということで人気のテクニカル指標macdなんですが、精査してみると、ほんとうに有効なのかはかなりあやしいということがわかった。


# 検証コード

~~~python:title=test_macd.py

import os
import glob
import pandas as pd
import math

DATA_PATH = "../data/"

def calc_gc(shorts, longs):
    # ゴールデンクロス検出
    gcs = []
    for i in range(2, len(shorts)-10): # 直近の10日前まで
        short0 = shorts[i-1]
        long0 = longs[i-1]
        short = shorts[i]
        long = longs[i]
        if not math.isnan(short0) and not math.isnan(long0) and not math.isnan(short) and not math.isnan(long) :
            d0 = short0 - long0
            d = short - long
            if d0<0 and d>0:
                gcs.append(shorts.index[i])
    return gcs

def calc_macd(df, es, el, sg):
    macd = pd.DataFrame()
    macd['ema_s'] = df['Close'].ewm(span=es).mean()
    macd['ema_l'] = df['Close'].ewm(span=el).mean()
    macd['macd'] = macd['ema_s'] - macd['ema_l']
    macd['signal'] = macd['macd'].ewm(span=sg).mean()
    macd['diff'] = macd['macd'] - macd['signal']
    f_plus = lambda x: x if x > 0 else 0
    f_minus = lambda x: x if x < 0 else 0
    macd['diff+'] = macd['diff'].map(f_plus)
    macd['diff-'] = macd['diff'].map(f_minus)
    return macd

def find_deceptions(df, gcs, dcs):
    # 騙し検出
    sigs = pd.DataFrame()
    sigs['Date'] = gcs
    sigs['signal'] = 'g'
    temp = pd.DataFrame()
    temp['Date'] = dcs
    temp['signal'] = 'd'
    sigs = pd.concat([sigs, temp])
    sigs = sigs.sort_values(by=["Date"], ascending=True)
    sigs = sigs.reset_index()
    
    dgn = 0 # 騙しゴールデンクロス数
    ddn = 0 # 騙しデッドクロス数
    ggain = 0
    dgain = 0
    for i in range(len(sigs)-1):
        d = sigs['Date'][i+1] - sigs['Date'][i]
        if sigs['signal'][i]=='g':
            if d.days<=10:
                # ゴールデンクロスから10日以内に次のデッドクロスが現れた
                dgn = dgn + 1
            else:
                idx = df.index.get_loc(sigs['Date'][i])
                buy = df['Close'][idx]
                # ゴールデンクロスから10日以内に、発現時の終値よりも3%を超える上昇が一度もなかった
                decept = True
                for j in range(10):
                    sell = df['Close'][idx+j]
                    gain = (sell-buy)/buy
                    if gain>=0.03:
                        decept = False
                        break
                if decept:
                    dgn = dgn + 1                    
        elif sigs['signal'][i]=='d':
            if d.days<=10:
                # デッドクロスから10日以内に次のゴールデンクロスが現れた
                ddn = ddn + 1
            else:
                idx = df.index.get_loc(sigs['Date'][i])
                buy = df['Close'][idx]
                # デッドクロスから10日以内に、発現時の終値よりも3%を超える下落が一度もなかった
                decept = True
                for j in range(10):
                    sell = df['Close'][idx+j]
                    gain = (sell-buy)/buy
                    if gain<=-0.03:
                        decept = False
                        break
                if decept:
                    ddn = ddn + 1
        # 10日後のゲイン
        idx = df.index.get_loc(sigs['Date'][i])
        buy = df['Close'][idx]
        sell = df['Close'][idx+10]
        if sigs['signal'][i]=='g':
            ggain = ggain + (sell-buy)/buy
        elif sigs['signal'][i]=='d':
            dgain = dgain -(sell-buy)/buy
    return dgn, ddn, ggain, dgain

def process(code, df, result):
    macd = calc_macd(df, 12, 26, 9)
    # macdゴールデンクロス検出
    gcs = calc_gc(macd['macd'], macd['signal'])
    # macdデッドクロス検出
    dcs = calc_gc(macd['signal'], macd['macd'])
    dgn, ddn, ggain, dgain = find_deceptions(df, gcs, dcs)
    result[0] = result[0] + len(gcs)
    result[1] = result[1] + len(dcs)
    result[2] = result[2] + dgn
    result[3] = result[3] + ddn
    result[4] = result[4] + ggain
    result[5] = result[5] + dgain

def walk_around(process, result):
    for i in range(1, 10):
        input_path = DATA_PATH + str(i*1000) + "/*.csv"
        csv_files = sorted(glob.glob(input_path))
        for csv_file in csv_files:
            filename = os.path.basename(csv_file)
            code = int(filename[0:4])
            code_dir = int(code/1000)*1000
            input_path = DATA_PATH + str(code_dir) + "/" + str(code) + ".csv"
            df = pd.read_csv(input_path, header=None,
                         names=['Date','Open','High','Low','Close','Volume'],
                         encoding='UTF-8')
            df['Date'] = pd.to_datetime(df['Date'])
            df = df.set_index("Date")
            process(code, df, result)

def main():
    result = [0, 0, 0, 0, 0, 0]
    walk_around(process, result)
    print(result)

if __name__ == '__main__':
    main()
~~~

# 検証コードの動作環境

<a href="https://github.com/etekichi98/data_topix500/blob/main/data_topix500_20200120.zip">検証用株価データはこちら</a>

ディレクトリ構成

~~~
working_dir/
    data/
        1000/
            1332.csv
            1333.csv
            ...
        2000/
        ...
        9000/
            9001.csv
            ...
    src/
        test_macd.py
~~~

working_dir/srcで、 test_macd.pyを稼働させる。

~~~bash
$ python test_macd.py
[24183, 24099, 14584, 15394, 91.78657327918098, -129.27271579432053]
~~~

結果リストの意味は以下の通りです。

+ 1要素目：ゴールデンクロス全事象数
+ 2要素目：デッドクロス全事象数
+ 3要素目：ゴールデンクロスの「騙し」数
+ 4要素目：デッドクロスの「騙し」数
+ 5要素目：ゴールデンクロス買いのゲイン合計
+ 6要素目：デッドクロス売りのゲイン合計

本文中の「ゲインの平均値」は、5要素目／1要素目 で算出。


以上