RSIとボリンジャーバンドとMACDを組み合わせた戦略をPythonで組んでバックテストをしてみた

プログラミング

以前ただMACDだけでトレードのバックテストをする記事を書きましたが、有名なインジケーターの組み合わせだけでで勝てるか検証してみました。
githubにプログラムをupしています。記事を読めば簡単に試せるので良い結果が出せるかいろんな通貨で試してみてください。

利用インジケーター

RSI (Relative Strength Index)

  • RSIは、価格の動きの勢いを測定する指標です。
    一定期間の値動きから、買われ過ぎ/売られ過ぎを判断します。
  • 通常、14日間のデータを用いて計算されます。
  • RSIが70を超えると「買われ過ぎ」、30を下回ると「売られ過ぎ」とされます。

ボリンジャーバンド

  • トレンドの強弱を判定するのに使用されます。
  • 中心の移動平均線と、この平均線の周囲の上下に設定された標準偏差線から成り立っています。

MACD (Moving Average Convergence Divergence)

  • 2つの移動平均線(通常は12日と26日の指数平滑移動平均)の差を示します。
  • MACDラインがシグナルラインを上回ると「買い」、下回ると「売り」と見なされます。

戦略概説

基本的には、トレンドが出て押し目で入るという方針です。ボリンジャーバンドはトレンドを判定するのにいいですが、トレンド終了判定ができないのでRSIで判定します。また、トレンドの騙しに引っかからないようにMACDで押し目を見てから入ります。

stragegy/macd_rsi_bb.py

class MacdBbRsi(StrategyInterface):
    def is_buy(self, df_candle):
        buy = False
        df_candle = bb(df_candle, timeperiod=60*6)
        last_row = df_candle.iloc[-1]
        if last_row.bb2p < last_row.low:
            rsi_value = rsi(df_candle, timeperiod=60*3)
            if rsi_value.values[-1] < 70 and macd_up(df_candle, slow=50, fast=20, smooth=7, limitv=10*0.01):
                buy = True
        return buy


    def is_sell(self, df_candle):
        sell = False
        df_candle = bb(df_candle, timeperiod=60*6)
        last_row = df_candle.iloc[-1]
        if last_row.bb2m > last_row.high:
            rsi_value = rsi(df_candle, timeperiod=60*3)
            if rsi_value.values[-1] > 30 and macd_dw(df_candle, slow=50, fast=20, smooth=7, limitv=10*0.01):
                sell = True
        return sell

ここでは1分足を使っています。
ボリンジャーバンドは2σにしています。標準偏差計算に使う間隔は60×6の6時間です。これはグラフを眺めて6時間くらいのレンジからの抜けが取れたらいいかなということでこの値にしました。
RSIは過去の値動きから売られすぎ、買われすぎを判定する指標です。ボリンジャーバンドの標準偏差の間隔以下くらいがいいかなと思って、とりあえず60×3の3時間にしました。
MACDはトレンド中の小さな押し目に反応させるために、slow=50, fast=20, smooth=7と1時間以内の押し目判定するようにしています。

プログラム解説

プログラム全容はgithubにアップしているのでそちらをご確認ください。

環境構築方法はこちらをご確認ください。

import abc

class StrategyInterface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def is_buy(self, df_candle):
        raise NotImplementedError()
    
    @abc.abstractmethod
    def is_sell(self, df_candle):
        raise NotImplementedError()

バックテスト実行プログラムはこの記事のプログラムをベースにmacd_dw()/macd_up()をこのクラスに置き換えています。これはオブジェクト指向GoFのデザインパターンのstragetyパターンを採用し、トレード戦略をいろいろ簡単に試せるように、戦略と、バックテスト実行を切り分けています。
MacdBbRsiクラスはStrategyInterfaceクラスの拡張クラスになります。
このStrategyInterfaceクラスの拡張クラスで簡単に実際のトレードができるプログラムも準備中ですので、現時点ではバックテストで成績が残せるように頑張ってみてください。

trading_test.py main()

    # stratgy = SimpleMacd()
    stratgy = MacdBbRsi()


stragetyにセットするクラスを切り替えることで戦略を切り替えることができる。

利確するpipsは30pips、損切りするpipsは15pipsにしました。

trading_test.py main()

            getpips = 30
            losspips = 15

macdのゴールデンクロス、デッドクロスの判定

analyzer/indicator.py

def get_macd(df_candle, slow=26, fast=12, smooth=9):
    '''
    macd を得る
    col:macd, signal, hist
        macd:macdライン、slow(長期EMA)とfast(短期EMA)の指数移動平均の差
        signal:macdシグナル、MACDのEMA
        hist:macdラインと、macdシグナルの差
    ゴールデンクロス:買い:signalをmacdがクロスして、macdが下から上に抜ける
    デッドデンクロス:売り:signalをmacdがクロスして、macdが上から下に抜ける
    追従買い:ゴールデンクロスの後、macdがマイナスからプラスに転換
    追従売り:デッドクロスの後、macdがプラスからマイナスに転換
    :param df_candle:
    :param slow:
    :param fast:
    :param smooth:
    :return:
    '''
    price = df_candle.close
    exp1 = price.ewm(span=fast, adjust=False).mean()
    exp2 = price.ewm(span=slow, adjust=False).mean()
    macd = exp1 - exp2
    signal = macd.ewm(span=smooth, adjust=False).mean()
    hist = macd - signal
    df = pd.concat([macd, signal, hist], axis=1)
    df.columns = ['macd', 'signal', 'hist']
    return df

def macd_dw(df_candle, slow=26, fast=12, smooth=9, limitv=None):
    if 'macd' not in df_candle.columns:
        df_macd = get_macd(df_candle, slow=slow, fast=fast, smooth=smooth)
    else:
        df_macd = df_candle
    df_up = df_macd[df_macd.signal < df_macd.macd]
    last_up = df_up.index[-1]
    df_dw = df_macd.loc[last_up:]
    if 4 > len(df_dw) > 1:
        df_dw = df_dw.iloc[1:]
        if (df_dw.signal > df_dw.macd).all():
            if limitv is None or (df_candle.iloc[-1*smooth:].high.max() - df_candle.iloc[-1].close) < limitv:
                print("MACD DW")
                return True
    return False

def macd_up(df_candle, slow=26, fast=12, smooth=9, limitv=None):
    if 'macd' not in df_candle.columns:
        df_macd = get_macd(df_candle, slow=slow, fast=fast, smooth=smooth)
    else:
        df_macd = df_candle
    df_dw = df_macd[df_macd.signal > df_macd.macd]
    last_dw = df_dw.index[-1]
    df_up = df_macd.loc[last_dw:]
    if 4 > len(df_up) > 1:
        df_up = df_up.iloc[1:]
        if (df_up.signal < df_up.macd).all():
            if limitv is None or (df_candle.iloc[-1].close - df_candle.iloc[-1*smooth:].low.min()) < limitv:
                print("MACD UP")
                return True
    return False

macd_dw() macd_up()でゴールデンクロス、デッドクロスを判定します。また行き過ぎ防止のため、損切り幅の15pipsより短い、10pipsを山谷の切り替え地点から行き過ぎている場合は判定をFalseにします。

実行結果

USDJPYの2023-12-05~2024-01-04 11時のMT5から吸い出したデータで実行した結果

利益が420 pis、損失が285pipsで、合計利益は135pipsでした。
勝率は14勝19敗でした。勝率はよくありませんが、33回トレードした結果135pips利益が出ているので、ノイズではなくそこそこ統計的に安定した結果のようにも思います。

streamlitを使って結果を確認してみます。streamlitでの確認方法はこちらの記事をご確認ください。

青がBUYのGET、パープルがBUYのLOSS、黄色がSELLのGET、緑がSELLのLOSSです。

グラフを見ると、売りの反応が悪いようです。下に落ちるのが速くて、macdが反応するころにはRSIが売られすぎ反応が出ているのかもしれません。このあたりを調整すればもっと利益が上がるかもしれません。また、トレンドと逆の買いでも利益が出ているので、ファンダメンタルに関係なく利益が出せそうです。ファンダメンタルを気にしなくてよいのが短期トレードの強み。



コメント