見出し画像

仮想通貨Botでテクニカル分析 〜戦略ロジック10選〜


前回作ったバックテストコードを使って、10パターンの戦略ロジックについてテクニカル分析・検証してみたいと思います。
分析結果だけでなく、戦略ごとにコード例も載せているので、各自で検証可能になるよう再現性を持たせています。
今回は、単一指標を用いたシンプルな戦略を中心に検証していきます。

はじめに


今回は戦略ロジック10パターンのテクニカル分析について、実際にバックテストを実行することで検証したいと思います。

ちなみに、テクニカル戦略の部分については、こちらの本を参考に組んでいます。
一応本記事に関しては、コードや分析手法自体は書籍を参照しなくても、個別で分かるようには書いています。

株式市場に対するテクニカル指標を用いた分析結果について書かれた本ですが、今回は仮想通貨市場に対して応用しています。
テクニカル分析自体が、金融市場を対象とした分析手法のため、そのまま流用することも可能です。

一つ注意点ですが、書籍では日足での分析ですが、今回はデータ数などの観点から時間足を採用しているため、数値設定などに関してはあくまで参考程度に見てください。

なお、今回の検証コードについては、前回の記事を前提にしているため、理解が難しい場合は参照頂くことをお勧めします。

コードに関しては、戦略ロジック部分のみを記述します。
本コードを使ってバックテスト検証される場合は、メイン処理も実行して下さい。


前提条件


戦略ロジック検証の前に、前提条件を定義しておきます。

検証期間:2022/01/01 〜 2022/06/25
仮想通貨シンボル:BTC/JPY
チャート情報:Cryptowatch
使用足:1時間足
トレード手法:買いのみ
注文管理:成行注文
ストップ(損切り):なし
初期資金:100万円
レバレッジ:2倍
1トレードあたり許容損失率:5%
スリッページ:なし

上記の前提に沿って、検証を進めていきます。
戦略自体の収益性を確認するため、なるべくシンプルな前提にしています。


戦略一覧


冒頭の通り、今回は単一指標を用いた、比較的シンプルな戦略ロジックを中心に検証します。
なお、戦略の解説や結果の考察については、簡潔に留めますので予め了承をお願いします。
基本的にはTA-Libを用いて指標を算出しますが、TA-Libで実装されていない指標や分析手法もあるため、一部自作しています。
戦略10選にかかる一覧は以下の通りです。

1.  ゴールデン/デッドクロス戦略
2.  パーフェクトオーダー戦略
3.  一目均衡表戦略
4.  乖離率戦略
5.  RSI戦略
6.  RCI戦略
7.  スローストキャスティクス戦略
8.  MACDクロス戦略
9.  DMI戦略
10.  パラボリックSAR戦略


1.  ゴールデン/デッドクロス戦略


まずはじめに、テクニカル分析では有名なゴールデンクロス・デッドクロスを使った戦略ロジックを検証します。

ゴールデンクロスとは、短期移動平均線(SMA)と長期移動平均線(LMA)が交差して、SMAがLMAを追い越して上に抜ける(上抜け)現象を言います。
デッドクロスは、その逆でSMAがLMAを下抜けする現象です。

移動平均線(MA)とは、期間内の終値の平均値を結んだ線のことです。
MAとは、Moving Averageの略称です。短期のSはShort、長期のLはLongです。

ざっくり言うと、ゴールデンクロス時は短期的な勢いが強く、デッドクロス時は勢いが弱くなっているイメージです。
この辺りのテクニカル論については、ネット・本などの他の文献で詳細に説明されているケースが多いので、各自で参照をお願いします。
以降の戦略ロジックに関しても同様にお願いします。

以下、コードを記述します。
雛形については、前回の記事の戦略ロジックの部分をご参照ください。

# -----------設定項目---------------

SMA_period = 25     # SMA(短期移動平均線)の期間設定
LMA_period = 75     # LMA(長期移動平均線)の期間設定


# ------------ロジックの個別部分-----------------

# 移動平均線を計算する関数
def calculate_MA(SMA_period, LMA_period):
    ta = tool.data_talib()
    # talibの単純移動平均線(SMA)の関数を用いる
    SMA = talib.SMA(ta["Close"], timeperiod=SMA_period)
    LMA = talib.SMA(ta["Close"], timeperiod=LMA_period)
    
    return SMA, LMA


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    SMA, LMA = calculate_MA(SMA_period, LMA_period)
    
    # エントリーシグナル:ゴールデンクロス( SMAがLMAを上抜ける )
    if SMA[i-1] < LMA[i-1]:
        if SMA[i] > LMA[i]:
            return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル:デッドクロス( SMAがLMAを下抜ける )
    if SMA[i-1] > LMA[i-1]:
        if SMA[i] < LMA[i]:
            return {"side":"SELL", "price":data["close_price"]}
    
    return {"side":None, "price":0}

各自説明していきます。

設定項目
ゴールデン/デッドクロスの判定に用いるSMA/LMAについて、予め期間を設定します。今回は基本的に採用されている25/75で設定します。

calculate_MA
移動平均線を計算する関数です。TA-Lib(以降talib)でカバー可能です。単純移動平均線(SMA : Simple Moving Average)の関数で計算します。略称がSMAで被っているので注意が必要です。SMA/LMAを返します。

logic_signal
ゴールデン/デッドクロスのシグナルを判定する関数です。SMA/LMAの配列データを上記関数から受け取ります。コードを見れば分かる通り、上抜けするということは、1足前の時点でSMAがLMAを下回っている必要があります。下抜けに関しても同様です。1足前の情報を使うため、メイン処理のneed_termを少なくとも1以上に設定する必要があることに注意してください。

それでは早速、上記の戦略を実行したバックテスト結果を示します。
ちなみに、バックテストの結果・パフォーマンスに関しては、特にコメントはしないので、予め留意ください。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  34回
勝率            :  23.5%
平均リターン      :  -0.97%
平均保有期間      :  53.9足分

最大の勝ちトレード :  154731円
最大の負けトレード :  -165468円
最大ドローダウン   :  -559069円/-57.0%
最大連敗回数      :  13回
利益合計         :  452870円
損失合計         :  -988498円
最終損益         :  -535628円

初期資金         :  1000000円
最終資金         :  464372円
運用成績         :  46.44%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -30.77%
MARレシオ           :  -0.94
シャープレシオ        :  -0.22
プロフィットファクター :  0.46
損益レシオ           :  1.77
期間内の資産推移


2.  パーフェクトオーダー戦略


続いて、パーフェクトオーダー戦略を検証します。
パーフェクトオーダーとは、上記のゴールデンクロスの応用形です。

短期線(SMA)と中期線(MMA)が長期線(LMA)の上にある場合、SMAがMMAを上抜けた時(=ゴールデンクロス)に買い注文を出します。
前文がエントリーフィルターで、後文がエントリーシグナルにあたります。
決済の売り注文に関しては、SMAがMMAを下抜けた時(=デッドクロス)です。

SMA、MMA、LMAの全てが同一で上昇傾向を示す形から、パーフェクトと言われているようです。
LMAをフィルターとすることで、長期的な上昇トレンドの補足が期待できそうです。

# -----------設定項目---------------

SMA_period = 25     # SMA(短期移動平均線)の期間設定
MMA_period = 75     # MMA(中期移動平均線)の期間設定
LMA_period = 200    # LMA(長期移動平均線)の期間設定


# ------------ロジックの個別部分-----------------

# 移動平均線を計算する関数
def calculate_MA(SMA_period, MMA_period, LMA_period):
    ta = tool.data_talib()
    # talibの単純移動平均線(SMA)の関数を用いる
    SMA = talib.SMA(ta["Close"], timeperiod=SMA_period)
    MMA = talib.SMA(ta["Close"], timeperiod=MMA_period)
    LMA = talib.SMA(ta["Close"], timeperiod=LMA_period)
    
    return SMA, MMA, LMA


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    SMA, MMA, LMA = calculate_MA(SMA_period, MMA_period, LMA_period)
    
    # エントリーシグナル:SMAとMMAのゴールデンクロス
    # エントリーフィルター:SMAとMMAがLMAより上回っている
    if LMA[i] < SMA[i] and LMA[i] < MMA[i]:
        if SMA[i-1] < MMA[i-1]:
            if SMA[i] > MMA[i]:
                return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル:SMAとMMAのデッドクロス
    if SMA[i-1] > LMA[i-1]:
        if SMA[i] < LMA[i]:
            return {"side":"SELL", "price":data["close_price"]}
    
    return {"side":None, "price":0}

設定項目
SMA、MMA、LMAについて、一般的な値である25/75/200を設定します。200期間分のデータは若干多いので、留意する必要があります。

calculate_MA
前述と大差ないので説明は省きます。

logic_signal
パーフェクトオーダーのシグナルを判定する関数です。ゴールデン/デッドクロスのロジックは前述の通りですが、新たにエントリーフィルターとして、LMAに関する条件式を追加します。

パーフェクトオーダー戦略のバックテスト結果は以下の通りです。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  8回
勝率            :  12.5%
平均リターン      :  0.06%
平均保有期間      :  118.8足分

最大の勝ちトレード :  321097円
最大の負けトレード :  -124565円
最大ドローダウン   :  -186075円/-19.1%
最大連敗回数      :  4回
利益合計         :  321097円
損失合計         :  -336934円
最終損益         :  -15837円

初期資金         :  1000000円
最終資金         :  984163円
運用成績         :  98.42%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -0.76%
MARレシオ           :  -0.08
シャープレシオ        :  0.01
プロフィットファクター :  0.95
損益レシオ           :  7.18


3.  一目均衡表戦略


一目均衡表とは、一目山人という日本の投資家が開発した日本発祥のテクニカル指標です。
時間論・波動論・水準論という3つの理論から成り立ち、多くのシグナルラインを持っています。

今回は、書籍に倣って以下の3つのエントリーラインを考えます。
・転換線が基準線を上抜け
・高値が先行スパンで作られる雲を上抜け(雲:max(span1,span2) - min(span1,span2)の幅)
・遅行スパンが過去の終値を上抜け

エントリーラインに必要な5つの指標の定義を示します。
・基準線:(過去26期間での高値+過去26期間での安値) / 2
・転換線:(過去9期間での高値+過去9期間での安値) / 2
・先行スパン1:(基準線+転換線) / 2 を26期間先にずらす
・先行スパン2:(過去26期間での高値+過去26期間での安値) / 2 を26期間先にずらす
・遅行スパン:終値を26期間前にずらす(=26期間モメンタムと同値)

一目均衡表はtalibで実装されていないため、一から関数を作成するしかなく、また指標の数も多いためモデリング的にはかなり手間がかかります。

# -----------設定項目---------------

base_period = 26      # 基準線の期間設定
turn_period = 9       # 転換線の期間設定
span_period = 26      # 先行スパンの(ずらし)期間設定


# ------------ロジックの個別部分-----------------

# 一目均衡表の基準線、転換線を計算する関数
def calculate_line(value_1, value_2, before=None):
    # 過去情報を参照する場合
    if before is not None:
        # 基準線の算出
        base_highest = max([i["high_price"] for i in last_data[-1*value_1 + before : before]])
        base_lowest = min([i["low_price"] for i in last_data[-1*value_1 + before : before]])
        base_line = round((base_highest + base_lowest) / 2)
        # 転換線の算出
        turn_highest = max([i["high_price"] for i in last_data[-1*value_2 + before : before]])
        turn_lowest = min([i["low_price"] for i in last_data[-1*value_2 + before : before]])
        turn_line = round((turn_highest + turn_lowest) / 2)
        
        return base_line, turn_line
    
    else:
        base_highest = np.max([i["high_price"] for i in last_data[-1*value_1 : ]])
        base_lowest = np.min([i["low_price"] for i in last_data[-1*value_1 :]])
        base_line = round((base_highest + base_lowest) / 2)
    
        turn_highest = np.max([i["high_price"] for i in last_data[-1*value_2 : ]])
        turn_lowest = np.min([i["low_price"] for i in last_data[-1*value_2 : ]])
        turn_line = round((turn_highest + turn_lowest) / 2)
        
        return base_line, turn_line
    
    
# 一目均衡表の先行スパンを計算する関数
def calculate_span():
    # span_term前の基準線、転換線の算出
    base_line, turn_line = calculate_line(base_period, turn_period, -1*span_period)
    span_1 = round((base_line + turn_line) / 2)
    span_2 = base_line
    
    return span_1, span_2


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    # エントリーシグナル(1):転換線が基準線を上抜け
    base_line, turn_line = calculate_line(base_period, turn_period)
    if turn_line > base_line:
        return {"side":"BUY", "price":data["close_price"]}  # シグナルごとに成績を分けたい場合、"side":"BUY_1"など区分け。その際は共通ロジックを変更する必要がある。
    
    # エントリーシグナル(2):高値が先行スパンで作られる雲の上限(max(s1,s2))を上抜け
    span_1, span_2 = calculate_span()
    if data["high_price"] > max(span_1, span_2): 
        return {"side":"BUY", "price":data["close_price"]}
    
    # エントリーシグナル(3):遅行スパンが過去の終値を上抜け(=26日間モメンタム)
    if data["close_price"] > last_data[-1*span_period]["close_price"]: 
        return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル:転換線が基準線を下抜け
    if turn_line < base_line:
        return {"side":"SELL", "price":data["close_price"]}
    
    return {"side":None, "price":0}

設定項目
一目均衡表の形成指標である基準線/転換線/先行スパンの期間設定を行います。今回は、書籍に倣って26/9/26に設定します。

calculate_line
一目均衡表の基準線/転換線を計算する関数です。先行スパンの計算に用いるために、過去情報の有無で条件分けしています。一応動きますが、やたら長いコードになってしまいました。改善の余地ありです。

calculate_span
一目均衡表の先行スパンを計算する関数です。先行しているということは、つまり現在の時点での先行スパンの値は、span_term(今回は26期間前)の時点で算出された値ということになります。そのため、過去情報を用いて値を計算します。

logic_signal
一目均衡表のシグナルを判定する関数です。エントリーシグナルは合計で3つあります。一つずつ分けて、定義の通り記述しています。なお、シグナルごとに集計を分ける場合は、一例として方向のラベル名の変更がありますが、共通ロジックをいじる必要性が別途発生するため、今回は全て同一で処理しています。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  118回
勝率            :  28.0%
平均リターン      :  -0.62%
平均保有期間      :  21.8足分

最大の勝ちトレード :  205139円
最大の負けトレード :  -84470円
最大ドローダウン   :  -742340円/-74.6%
最大連敗回数      :  10回
利益合計         :  755339円
損失合計         :  -1502261円
最終損益         :  -746922円

初期資金         :  1000000円
最終資金         :  253078円
運用成績         :  25.31%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -48.25%
MARレシオ           :  -1.0
シャープレシオ        :  -0.19
プロフィットファクター :  0.5
損益レシオ           :  1.32


4.  乖離率戦略


乖離率とは、移動平均線乖離率のことです。
基準線となる移動平均線と、現在の価格が何%離れているかを表す指標です。

短期間で買われすぎ・売られすぎの領域まで過熱した価格は、長期的な視点で見れば、移動平均線の値に収束する傾向を見せます。
そのため、乖離率は逆張りのシグナルに使われることが多いようです。

逆張りとは、順張りの逆で相場下落時に買いを入れて、相場上昇時に売りを入れる手法です。

# -----------設定項目---------------

MA_period = 25             # 移動平均線の期間設定
divergence_rate_high = 0   # 乖離率(%)の数値設定(上限)
divergence_rate_low = -2   # 乖離率(%)の数値設定(下限)


# ------------ロジックの個別部分-----------------

# 移動平均線を計算する関数
def calculate_MA(value_1):
    ta = tool.data_talib()
    # talibの単純移動平均線(SMA)の関数を用いる
    MA = talib.SMA(ta["Close"], timeperiod=value_1)
    for i in range(value_1):
        if np.isnan(MA[i]):
            MA[i] = MA[value_1]
    
    return MA


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    MA = calculate_MA(MA_period)
    
    # エントリーシグナル:乖離率下限を下回った時点(安値が乖離率ー20%ラインを下抜け)
    if data["low_price"] < round(MA[i] * ((100+divergence_rate_low)/100)):
        return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル:乖離率上限を上回った時点(高値が乖離率0%ラインを上抜け)
    if data["high_price"] > round(MA[i] * ((100+divergence_rate_high)/100)):
        return {"side":"SELL", "price":data["close_price"]}
    
    return {"side":None, "price":0}

設定項目
MA_periodは参考書籍に倣って25を設定しています。乖離率下限の値については、書籍では-20ですが、これではエントリー条件が絞り過ぎのため、-2として大幅に緩和しています。

calculate_MA
今回のロジックでは、数値計算が必要になるため、NaN値を返すと計算エラーになってしまいます。そのため、最初のMA_period分のMAに欠損値処理をして、疑似的に値を代入しています。

logic_signal
乖離率のシグナルを判定する関数です。移動平均線の値と乖離率で数値計算をしています。高値・安値で判定します。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  22回
勝率            :  59.1%
平均リターン      :  -0.32%
平均保有期間      :  16.5足分

最大の勝ちトレード :  65238円
最大の負けトレード :  -222707円
最大ドローダウン   :  -222707円/-22.5%
最大連敗回数      :  2回
利益合計         :  350614円
損失合計         :  -553848円
最終損益         :  -203234円

初期資金         :  1000000円
最終資金         :  796766円
運用成績         :  79.68%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -10.32%
MARレシオ           :  -0.9
シャープレシオ        :  -0.07
プロフィットファクター :  0.63
損益レシオ           :  0.56


5.  RSI戦略


RSIとは、Relative Strength Index の略称で、直訳で「相対力指数」と言います。
一定期間の価格変動のうち、上昇変動が占める割合を表す指標です。

一定期間の値上がり幅の平均を一定期間の値動きの幅すべての平均で割り、100を掛けることで計算されます。
RSIは割合の指標のため、値は0〜100%の間を振り子のように推移します。
そのため、RSIはオシレーター系指標にあたります。

オシレーター系の特徴は、一定の幅で振れる性質を持つことです。
オシレーター系の多くは、相場の過熱感を計るために用いられます。
また、逆張りのシグナルとしての使用例が多いようです。

# -----------設定項目---------------

RSI_period = 14     # RSIの期間設定
RSI_high = 75       # RSI上限の数値設定
RSI_low = 25        # RSI下限の数値設定


# ------------ロジックの個別部分-----------------

# RSIを計算する関数
def calculate_RSI(value_1):
    ta = tool.data_talib()
    # talibを用いたRSIの計算
    RSI = talib.RSI(ta["Close"], timeperiod=value_1)
    
    return RSI


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    RSI = calculate_RSI(RSI_period)
    
    # エントリーシグナル: RSIが下限値を下回る(1足前のRSIが下限値以上である必要がある)
    if RSI[i-1] >= RSI_low:
        if RSI[i] < RSI_low:
            return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル: RSIが上限値を上回る(1足前のRSIが上限値以下である必要がある)
    if RSI[i-1] <= RSI_high:
        if RSI[i] > RSI_high:
            return {"side":"SELL", "price":data["close_price"]}
    
    return {"side":None, "price":0}

設定項目
RSI_periodは一般的とされる14に設定しています。シグナルのラインとなるRSIの上限/下限値についても、書籍に倣って75/25としています。

calculate_RSI
RSIを計算する関数です。talibはRSIもカバーしているため、モデリング的には簡単に計算することができます。

logic_signal
RSIのシグナルを判定する関数です。RSIが下限値を下回れば買い、上限値を上回れば売りの逆張りの手法をとっています。価格の上昇力の強弱によって、シグナルを発生させます。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  7回
勝率            :  28.6%
平均リターン      :  -3.65%
平均保有期間      :  313.7足分

最大の勝ちトレード :  42813円
最大の負けトレード :  -461212円
最大ドローダウン   :  -523633円/-53.8%
最大連敗回数      :  3回
利益合計         :  83709円
損失合計         :  -592941円
最終損益         :  -509232円

初期資金         :  1000000円
最終資金         :  490768円
運用成績         :  49.08%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -28.91%
MARレシオ           :  -0.95
シャープレシオ        :  -0.37
プロフィットファクター :  0.14
損益レシオ           :  0.54


6.  RCI戦略


RCIとは、Rank Correlation Index の略称で、通称「スピアマンの順位相関係数」の名で知られています。
時間と価格の相関から買われすぎ・売られすぎを測るオシレーター系のテクニカル指標です。

時間系列と価格系列の相関係数を計算し、指標の値が大きいほど正の相関(時間が前に進むほど価格が上昇)、指標が小さいほど負の相関(時間が前に進むほど価格が下落)を示します。
時間と価格が綺麗な相関関係を示すことは珍しいようで、逆張りの手法が一般的に用いられます。

予め説明しますが、RCIはtalibで実装されていないため、自作関数を用意する必要があります。
自作するためには、RCIの定義を理解する必要がありますが、RCIの定義自体もそう単純ではありません。
そのため、RCIに関しては個人的に今回の10パターンの中で、そのモデリングの難易度においては最も高いと思います。

以下、RCIの定義に軽く触れていますが、かなり理論的な話になるので、興味がない方は飛ばして下さい。なお、説明を見ても分からない場合は、下記の記事をお勧めします。前半部分は特に分かりやすく書かれており、参考になりました。

RCIは数式で表すことができます。定義式を書くと、
RCI = (1 - (6 * Σd^2 / n*(n^2 - 1))) * 100 (n:期間)
です。
ここで出てくるdとは順位の差のことで、
d = x - y (x:時間の順位, y:価格の順位)
と表します。
ここでの時間の順位は、直近であるほど順位(優先度)が高くなります。

この順位の差を理解できれば、話は早いです。イメージとしては、順位の差が大きくなればRCIは小さくなるし(相関が弱い)、差が小さくなればRCIは大きくなります(相関が強い)。

以上、ざっくりですが定義の説明でした。その他、証明などは各自で調べて下さい。

# ----------ライブラリ--------------

import scipy.stats as st      # scipy.statsのインポート


# -----------設定項目---------------

RCI_period = 9     # RCIの期間期間
RCI_high = 80      # RCI上限の数値設定
RCI_low = -80      # RCI下限の数値設定


# -----------ロジックの個別部分-----------------

# RCIの値を計算する関数
def calculate_RCI(period):
    ta = tool.data_talib()
    RCI = []
    term = list(range(period, 0, -1))   # 時間の順位のリストを準備(今回は[9, 8,..., 1])
    
    for i in range(len(ta)):
        close = []   # 価格をperiodだけ記録する配列の準備
        if i < period:
            RCI.append(0)   # データ不足の間は0を記録
        else:
            for j in range(period):
                close.append(ta["Close"][i+j-period+1])   # periodの期間分の終値データ取得
            rci = st.spearmanr(close, term).correlation.round(4)*100   # spearmanr関数を使って1行で計算可能
            RCI.append(rci)
        
    return RCI


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    RCI = calculate_RCI(RCI_period)
    
    # エントリーサイン: RCIが下限値を下回る(1足前のRCIが下限値以上の必要がある)
    if RCI[i-1] >= RCI_low:
        if RCI[i] < RCI_low:
            return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットサイン: RCIが上限値を上回る(1足前のRCIが上限値以下の必要がある)
    if RCI[i-1] <= RCI_high:
        if RCI[i] > RCI_high:
            return {"side":"SELL", "price":data["close_price"]}
    
    return {"side":None, "price":0}

ライブラリ
今回は、RCIもといスピアマンの順位相関係数の計算の易化のために、scipy.statsモジュールのspearmanr関数を用います。そのため、事前にインポートする必要があります。ちなみにSciPyとは数値解析に特化したパッケージです。

設定項目
今回はそれぞれ一般的な値として、期間/上限/下限に、9/80/-80を設定しています。

calculate_RCI
RCIを計算する関数です。talibで実装されていないので、オリジナルで実装します。コード中に随時コメントを挟んでいるので参考にしていただければと思います。ポイントはspearmanr関数を使った簡素化です。前述の定義式をすっ飛ばして実装可能です。一応、RCIの定義に沿った一からの計算方法も考案しましたが、コードが長くなるので供養します。計算結果はどちらも一致しているため、コード自体の正確性は一定程度あると思います。

logic_signal
RCIのシグナルを判定する関数です。時間・価格の相関が弱い時に買い、相関が強い時に売る、逆張りの手法です。

バックテスト時の注意点ですが、今回のRCIのコード実行は処理が相当重いです。そのため、検証を回す際のデータ数は、多くても1,000程度(目安:数分)が妥当でしょう。今回は、対策として仮想環境にてバックテストを実行しています。
4,200件にもなると、検証終了まで約5時間ほどかかりました。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  85回
勝率            :  63.5%
平均リターン      :  -0.33%
平均保有期間      :  23.1足分

最大の勝ちトレード :  77980円
最大の負けトレード :  -309655円
最大ドローダウン   :  -943255円/-71.0%
最大連敗回数      :  6回
利益合計         :  1551341円
損失合計         :  -2126960円
最終損益         :  -575619円

初期資金         :  1000000円
最終資金         :  424381円
運用成績         :  42.44%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -33.7%
MARレシオ           :  -0.81
シャープレシオ        :  -0.09
プロフィットファクター :  0.73
損益レシオ           :  0.44


7.  スローストキャスティクス戦略


ストキャスティクスとは、一定期間の高値を100、安値を0とした時に、現在の価格がどの水準にあるかを示す指標です。

ストキャスティクスは以下の3本のラインで構成されています。
・現在の一定期間の価格水準:%K
・%Kの3期間で平滑化:%D
・%Dの3期間平均:Slow%D

このうち、%Kと%Dの組み合わせをファストストキャスティクス、%DとSlow%Dの組み合わせをスローストキャスティクスと呼びます。
今回は後者を使用し、フィルターを使って指標が安値圏にある時の売買シグナルを用います。

# -----------設定項目---------------

STOCH_period = 14   # ストキャスティクスの期間設定
STOCH_filter = 25   # ストキャスティクスフィルターの数値設定


# ------------ロジックの個別部分-----------------

# スローストキャスティクス(STOCH)を計算する関数
def calculate_STOCH(value_1):
    ta = tool.data_talib()
    # ここでのslowkは%D, slowdはSlow%Dを表す
    slowk, slowd = talib.STOCH(ta["High"], ta["Low"], ta["Close"], fastk_period=value_1, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0)
    
    return slowk, slowd


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    slowk, slowd = calculate_STOCH(STOCH_period)
    
    # エントリーシグナル:%DがSlow%Dを上抜け(1足前の%DがSlow%Dよりも小さい必要がある)
    # エントリーフィルター:%DとSlow%Dがフィルターラインよりも小さい
    if slowk[i-1] < slowd[i-1]:
        if slowk[i] > slowd[i]:
            if slowk[i] < STOCH_filter and slowd[i] < STOCH_filter:
                return {"side":"BUY", "price":data["close_price"]}
            
    # エグジットシグナル:%DがSlow%Dを下抜け(1足前の%DがSlow%Dよりも大きい必要がある)
    if slowk[i-1] > slowd[i-1]:
        if slowk[i] < slowd[i]:
            return {"side":"SELL", "price":data["close_price"]}
        
    return {"side":None, "price":0}

設定項目
期間設定は一般的な14に設定します。また前述の通り、エントリーフィルターの数値設定は、安値圏である25に設定します。

calculate_STOCH
スローストキャスティクスを計算する関数です。talibで計算可能です。構成ラインの期間は、予め3で統一して計算します。

logic_signal
スローストキャスティクスのシグナルを判定する関数です。エントリーフィルターを設けることで、安値圏での買いシグナル発生を実現させます。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  144回
勝率            :  41.0%
平均リターン      :  -0.09%
平均保有期間      :  5.7足分

最大の勝ちトレード :  125181円
最大の負けトレード :  -73684円
最大ドローダウン   :  -439446円/-35.9%
最大連敗回数      :  7回
利益合計         :  1291835円
損失合計         :  -1500952円
最終損益         :  -209117円

初期資金         :  1000000円
最終資金         :  790883円
運用成績         :  79.09%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -10.64%
MARレシオ           :  -0.58
シャープレシオ        :  -0.05
プロフィットファクター :  0.86
損益レシオ           :  1.23


8.  MACDクロス戦略


MACDとは、Moving Average Convergence Divergence の略称で、日本語訳で「移動平均線収束拡散法」と呼ばれます
算出期間の異なる2本の指数移動平均線の差によって計算される、移動平均線をベースとしたテクニカル指標です。

MACDには、シグナルラインと呼ばれる2本目のラインがあり、一般的にはMACDラインの9期間移動平均を表します。
MACDクロスとは、上記の2本のラインのクロスのことです。

MACDラインと、反応の緩やかなシグナルラインが交差するポイントを、トレンドの転換点と見ることができます。
今回は、MACDラインがシグナルラインを上抜けば買い、下抜けば売りの順張り手法で検証していきます。

# -----------設定項目---------------

fast_period = 12      # 短期MACDの期間設定
slow_period = 26      # 長期MACDの期間設定
signal_period = 9     # シグナルラインの期間設定


# ------------ロジックの個別部分-----------------

# MACDを計算する関数
def calculate_MACD(value_1, value_2, value_3):
    ta = tool.data_talib()
    # MACDの計算をtalibで行う
    MACD, MACD_signal, MACD_hist = talib.MACD(ta["Close"], fastperiod=value_1, slowperiod=value_2, signalperiod=value_3)
    
    return MACD, MACD_signal


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    MACD, MACD_signal = calculate_MACD(fast_period, slow_period, signal_period)
    
    # エントリーシグナル:MACDがMACDシグナルを上抜け(1足前のMACDがMACDシグナルより小さい必要がある)
    # エントリーフィルター:MACDとMACDシグナルが0より小さい
    if MACD[i-1] < MACD_signal[i-1]:
        if MACD[i] > MACD_signal[i]:
            if MACD[i] < 0 and MACD_signal[i] < 0:
                return {"side":"BUY", "price":data["close_price"]}
            
    # エグジットシグナル:MACDがMACDシグナルを下抜け(1足前のMACDがMACDシグナルより大きい必要がある)
    if MACD[i-1] > MACD_signal[i-1]:
        if MACD[i] < MACD_signal[i]:
            return {"side":"SELL", "price":data["close_price"]}
        
    return {"side":None, "price":0}

設定項目
算出期間の異なる2本の指数移動平均線の期間設定を行います。今回は、書籍に倣って短期/長期をそれぞれ12/26と設定します。また、シグナルラインは前述の通り9で設定します。

calculate_MACD
MACDを計算する関数です。talibを使うことで、MACDラインとシグナルラインの値を計算して返してくれます。

logic_signal
MACDクロスのシグナルを判定する関数です。一般的にはMACDが0以下の時に買いを入れるようなので、エントリーフィルターを設定しています。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  108回
勝率            :  32.4%
平均リターン      :  0.77%
平均保有期間      :  16.1足分

最大の勝ちトレード :  1922544円
最大の負けトレード :  -259551円
最大ドローダウン   :  -1072797円/-36.7%
最大連敗回数      :  7回
利益合計         :  6321565円
損失合計         :  -5204530円
最終損益         :  1117035円

初期資金         :  1000000円
最終資金         :  2117035円
運用成績         :  211.7%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  43.28%
MARレシオ           :  3.04
シャープレシオ        :  0.08
プロフィットファクター :  1.21
損益レシオ           :  3.53


9.  DMI戦略


DMIとは、Directional Movement Index の略称で、日本語訳で「方向性指数」と呼ばれています。
この指標は、オシレーター系が苦手とする、トレンド相場にも対応できるように作成された指標です。

DMIは以下の3本のラインで構成されています。
・買いの強さを表す:+DI
・売りの強さを表す:ーDI
・トレンドの有無とその強さを表す:ADX

以下、簡単に定義だけ示します。
+DI : (n期間の+DMの合計)÷(n期間のTRの合計)×100
ーDI : (n期間の-DMの合計)÷(n期間のTRの合計)×100
+DM = 当日の高値ー前日の高値, ーDM = 前日の安値ー当日の安値
TR = max(高値(i)ー安値(i), 高値(i)-終値(i-1), 安値(i)-終値(i-1))
DI : Direction Indicator, DM : Direction Movement, TR : True Range

ADXとは、Average Directional Movement の略で、+DIとーDIの差の期間平均を表す指標です。ADXは、トレンド性の捕捉の役割を果たします。この値が上昇傾向にあるときは、±DIのクロスによる売買シグナルの有効性が高まるとされています。

今回は、ADXをトレンドフィルターとして、DIのクロスを売買シグナルとする順張りの手法で検証します。

# -----------設定項目---------------

DMI_period = 14        # DMIの期間設定


# ------------ロジックの個別部分-----------------

# DMIを計算する関数
def calculate_DMI(value_1):
    ta = tool.data_talib()
    # DMIは、+DI、-DI、ADXをそれぞれ別のtalib関数で計算する必要がある
    plus_DI = talib.PLUS_DI(ta["High"], ta["Low"], ta["Close"], timeperiod=value_1)
    minus_DI = talib.MINUS_DI(ta["High"], ta["Low"], ta["Close"], timeperiod=value_1)
    ADX = talib.ADX(ta["High"], ta["Low"], ta["Close"], timeperiod=value_1)
    
    # DMIの算出に使う3本のラインの計算結果を返す
    return plus_DI, minus_DI, ADX


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    plus_DI, minus_DI, ADX = calculate_DMI(DMI_period)
    
    # エントリーシグナル:+DIがーDIを上抜け(1足前の+DIがーDIより小さい必要がある)
    # エントリーフィルター:ADXが1足前のADXより大きい
    if plus_DI[i-1] < minus_DI[i-1]:
        if plus_DI[i] > minus_DI[i]:
            if ADX[i] > ADX[i-1]:
                return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル:+DIがーDIを下抜け(1足前の+DIがーDIより大きい必要がある)
    if plus_DI[i-1] > minus_DI[i-1]:
        if plus_DI[i] < minus_DI[i]:
            return {"side":"SELL", "price":data["close_price"]}
        
    return {"side":None, "price":0}

設定項目
DMI(+DI、ーDI、ADX)の各期間設定は一般的な14にしています。

calculate_DMI
DMIを計算する関数です。talibを使うことで、DMIを形成する3本のラインの値をそれぞれ簡単に計算することができます。

logic_signal
DMIのシグナルを判定する関数です。ADXをフィルターにすることで、トレンド性の折り込みを期待できます。±DIのクロスを売買シグナルに設定します。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  14回
勝率            :  50.0%
平均リターン      :  0.77%
平均保有期間      :  20.0足分

最大の勝ちトレード :  158558円
最大の負けトレード :  -66313円
最大ドローダウン   :  -148612円/-11.2%
最大連敗回数      :  2回
利益合計         :  404082円
損失合計         :  -228565円
最終損益         :  175517円

初期資金         :  1000000円
最終資金         :  1175517円
運用成績         :  117.55%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  8.06%
MARレシオ           :  1.57
シャープレシオ        :  0.22
プロフィットファクター :  1.77
損益レシオ           :  2.07


10.  パラボリックSAR戦略


パラボリックSARとは、放射線のような軌道で価格に追従することを利用して、トレンドの転換点を判断するために使用されるトレンド系テクニカル指標です。
パラボリックとは放射線の意で、SARはストップアンドリリースを表します。

この指標は、価格トレンドと反対側の位置に表示され、実際の価格と指標が交差したポイントが売買シグナルとなります。
パラボリックSARのポイントは、「指標をどの程度の速さで価格に追従させるか」です。
これは「加速因子」と呼ばれるパラメータによって決定され、基本は0.02〜0.20の範囲で、0.02ずつ増加して推移します。

今回は、トレンド系の純粋な順張り手法で検証していきます。

# -----------設定項目---------------

SAR_ace = 0.02       # SARの加速度の数値設定
SAR_max = 0.20       # SARの加速上限の数値設定


# ------------ロジックの個別部分-----------------

# パラボリックSARを計算する関数
def calculate_SAR(value_1, value_2):
    ta = tool.data_talib()
    # パラボリックSARをtalibで計算する
    SAR = talib.SAR(ta["High"], ta["Low"], acceleration=value_1, maximum=value_2)
    
    return SAR


# 戦略ロジックのエントリー/エグジットシグナルを判定する関数
def logic_signal():
    
    SAR = calculate_SAR(SAR_ace, SAR_max)
    
    # エントリーシグナル:終値がパラボリックを上抜け(1足前の終値がパラボリックより小さい必要がある)
    if last_data[-1]["close_price"] < SAR[i-1]:
        if data["close_price"] > SAR[i]:
            return {"side":"BUY", "price":data["close_price"]}
    
    # エグジットシグナル:終値がパラボリックを下抜け(1足前の終値がパラボリックより大きい必要がある)
    if last_data[-1]["close_price"] > SAR[i-1]:
        if data["close_price"] < SAR[i]:
            return {"side":"SELL", "price":data["close_price"]}
        
    return {"side":None, "price":0}

設定項目
パラボリックSARのパラメータである、加速度と加速上限の2つの値を設定します。今回は最も基本的な0.02/0.2を設定します。

calculate_SAR
パラボリックSARを計算する関数です。talibに加速因子のパラメータを渡して、計算します。

logic_signal
パラボリックSARのシグナルを判定する関数です。価格が放射線に追いついた時点で、売買シグナルを発生させます。

--------------------------
テスト期間:
開始時点 : 2022/01/01 00:00
終了時点 : 2022/06/25 00:00
4200件のローソク足データで検証
--------------------------
バックテストの結果
--------------------------
総合の成績
--------------------------
全トレード数      :  169回
勝率            :  34.9%
平均リターン      :  -0.23%
平均保有期間      :  12.5足分

最大の勝ちトレード :  134226円
最大の負けトレード :  -61232円
最大ドローダウン   :  -494986円/-49.8%
最大連敗回数      :  9回
利益合計         :  1564804円
損失合計         :  -2060025円
最終損益         :  -495221円

初期資金         :  1000000円
最終資金         :  504779円
運用成績         :  50.48%
手数料合計       :  0円
-----------------------------------
各成績指標
-----------------------------------
CAGR(年間成長率)     :  -27.95%
MARレシオ           :  -0.99
シャープレシオ        :  -0.09
プロフィットファクター :  0.76
損益レシオ           :  1.45


最後に


以上が戦略ロジック10選のテクニカル分析・検証でした。

バックテスト結果については、ツッコミどころも所々あるかと思いますが、気になればすぐに検証を回せるようにコードを全て記述しているので、各自で再現性を持って検証を行えるのが、この記事の良さかと思っています。

上記の10選以外にも、まだまだテクニカル指標は存在するので、ストラテジーの開発はさらに奥が深いですね。
検証サイクルの際に、当記事を参考にして頂ければ、この記事を執筆した甲斐もあり幸いなことです。

それでは、本記事は以上で終わりたいと思います。

お読みいただきまして、ありがとうございました。

この記事が気に入ったらサポートをしてみませんか?