InvestmentTechHack

TradingViewで検証:ドンチアン・トレンド・システム

Posted on December 7th, 2018Updated on May 4th, 2019

どんな記事

  • 1980年代にウォール・ストリート・ジャーナルに取り上げられた投資集団の手法
  • 年利平均80%を、リアルマネーで実現 した手法
  • 10年前に出版された本でも有効であるとされた手法

そんな手法が「現在でも通用するのか」がわかります。

タートルズ流 投資の魔術 からの引用

まずは、「ドンチアン・トレンド・システム」について書かれている箇所を「伝説のトレーダー集団 タートルズ流投資の魔術」から引用します。

 ここで紹介するドンチアン・トレンド・システムは、5章で説明しているが、わたしたちがタートル時代用いていたものを単純にしたバージョンだ。これは20日ブレイクアウトを仕掛けに、10日ブレイクアウトを手じまいに使用し、350日/25日移動平均をトレンドフィルターとして用いる。

 取引は、速いほうの移動平均が示す方向にのみ行われる。25日移動平均が350日移動平均を上回っていれば、買いもちのみ、25日移動平均が350日移動平均を下回っていれば、売り持ちのみということになる。システムは当初のタートル・システムと同じように2ーATRストップを用いる。

引用元 : 伝説のトレーダー集団 タートルズ流投資の魔術 (P.167)

この記事は、上記の手法をTradingViewで再現することを目的としたものです。

ストラテジーを作成

TradingViewストラテジー ドンチアン・トレンド・システム

エントリーとイグジット

買いエントリー

  • 「EMA25 が EMA350 を上回っている + 20日間の最高値をブレイク」で買いエントリー
  • 買い持ちの状態で「10日最安値をブレイク」で決済

※ EMA●●:●●日指数平滑移動平均

TradingViewストラテジー ドンチアン・トレンド・システム 買いシグナルとイグジット

売りエントリー

  • 「EMA25 が EMA350 を下回っている + 20日間の最安値をブレイク」で売りエントリー
  • 売り持ちの状態で「10日最高値をブレイク」で決済

TradingViewストラテジー ドンチアン・トレンド・システム 売りシグナルとイグジット

今回は、なんといっても取引回数が大幅に増えています。これが全体の成績にどのような影響を与えるかがポイントになりそうです。

バックテスト

同じ条件で手法を比べるために、以下の「基本条件」でテストしていきます。

バックテストの基本条件

期間

  • 2005年1月1日 ~ 2017年12月31日(12年間)

資金管理

  • 単利

銘柄

為替USDJPY、EURJPY、GBPJPY、CHFJPY、CADJPY
株価指数NKY日経225、DJI NYダウ、DAXドイツ、UKXイギリスFTSE、HSI香港ハンセン
日本株6098リクルート、4452花王、5711三菱マテリアル、7201日産、9984ソフトバンクグループ
米株AAPLアップル、AXPアメリカン・エクスプレス、BAボーイング、JNJジョンソン・エンド・ジョンソン、MCDマクドナルド
海外商品金、白金、原油、コーン、大豆

手法は、市場との相性があるケースも多いので、主要な各市場から5銘柄ずつピックアップしてテストしています。

テスト1

本に掲載されていたものと同じ設定でバックテストをしてみます。

テスト1 設定

DC-enDC-exEMA-mEMA-lLosscutPyramiding
2010253502-ATRNone

テスト1 結果

損益最大DD取引数勝数勝率RR比
Total204377337.842.200
USDJPY11.9715.49892528.092.971
EURJPY12.3934.69892831.462.391
GBPJPY88.0920.92824048.782.125
CHFJPY3.2912.29893134.831.948
CADJPY3.5935.85852934.122.012
NKY11113.523743.34763546.052.135
DJI8294.762905.75883236.363.035
DAX2261.613540.36893134.832.322
UKX-2364.93704.3943335.111.266
HSI13234.815877.74843946.431.783
609865924811459.094.433
445217531085843238.12.213
571160751250692739.133.056
7201-439.29739.1822631.711.688
998441921920733041.12.168
AAPL85.6423.38813745.682.399
AXP-6.7430.85932931.182.059
BA189.0350.53853338.823.817
JNJ7.4124.87843136.91.87
MCD12.9926.03873337.931.863
GOLD3275021400824048.781.342
PLATINUM2660530440853935.292.275
WTI4220069770873337.932.089
CORN-462520625892426.972.44
SOY BEANS3237.0530600863237.211.75

このシステムは取引回数が圧倒的です。総取引回数が 2043回で、「ボリンジャー・ブレイクアウト」の 159回、「ATRチャネル・ブレイクアウト」の 383回と比べても〝ダントツ〟です。

勝率やRR比は、「それぞれで比較しても分かりづらい」ので、「破産の確率」や「期待値」、「収益の見込み」を算出して比較します。本来は他にもたくさんの「比較する要素」があるのですが、TradingViewのバックテストだと、このあたりが(たぶん)限界です。手軽さを考えると十分すぎるくらいですが。

テスト1 統計

破産の確率期待値/リスク収益の見込み
0.01%0.21429.89

破産の確率は、損失の許容=2%で算出しています。期待値と収益の見込みは以下の計算式で算出しています。

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|収益の見込み=期待値×取引回数\text{収益の見込み} = \text{期待値} \times \text{取引回数}

実際に算出してみると、以下のようなことがわかりますね。

  • 破産の心配はない
  • 期待値はこれまでのテストでもっとも低い
  • しかし、収益の見込みはこれまでのテストでもっとも多い

すこし変数を調整してバックテストをしてみます。

テスト2

以前、「ロスカットは必ずしも有効ではない」という記事を書いたことがあるのですが、それに習って「ロスカットなし」のテストをしてみます。

テスト2 設定

DC-enDC-exEMA-mEMA-lLosscutPyramiding
201025350NoneNone

テスト2 結果

損益最大DD取引数勝数勝率RR比
Total199377138.692.07
USDJPY15.0219.04892630.232.784
EURJPY10.1741.29872933.332.147
GBPJPY98.1713.538040502.279
CHFJPY-1.6813.85903134.441.866
CADJPY5.0740.77812935.81.901
NKY9679.244072.17763546.051.93
DJI9088.32292.62843238.13
DAX2014.54051.45863136.052.142
UKX-3025.74818.1903336.671.1
HSI10194.48126.61843946.431.546
609859626011436.363.932
445212921217813239.511.863
571162101590672841.792.723
7201-504.1834.1802733.751.492
998437252076733151.031.922
AAPL107.7518.85753850.672.506
AXP-1.4230.69902932.222.073
BA199.9542.04793443.043.492
JNJ3.221.45833137.351.742
MCD15.6525.65853338.831.845
GOLD38340208408040501.342
PLATINUM3324034585833036.142.307
WTI1847079120863338.371.775
CORN-565019937.5872427.592.32
SOY BEAN-4012.534475863237.211.616

ロスカットがない分、取引回数が減っています。勝率が上がって、RR比は低下。

テスト2 統計

破産の確率期待値/リスク収益の見込み
0.01%0.187372.6

破産の確率は、損失の許容=2%で算出しています。期待値と収益の見込みは以下の計算式で算出しています。

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|収益の見込み=期待値×取引回数\text{収益の見込み} = \text{期待値} \times \text{取引回数}

収益の見込みを算出すると、テスト1と比較して「悪化した」ことが分かりますね。ドンチアン・トレンド・システムにおいては「ロスカットは有効(かもしれない)」ということが言えそうです。

テスト3

最後に、テスト1に「ピラミッティングを加えたシステム」のバックテストをしてみます。

テスト3 設定

DC-enDC-exEMA-mEMA-lLosscutPyramiding
2010253502-ATR1-ATR 上限4

「エントリーした価格からプラス方向にATRの1倍動いたらポジションを追加する」というものです。最初のものを含めて4回のエントリーが上限です。タートルズの用語で言うと「ロード」の状態ですね。

テスト3 結果

損益最大DD取引数勝数勝率RR比
Total5187191636.94%2.23
USDJPY51.942.892047034.312.492
EURJPY-19.4775.982156932.091.961
GBPJPY77.8882.572378736.712.114
CHFJPY-12.3467.282116530.812.109
CADJPY0.486.212046933.821.946
NKY24196.857703.552128238.682.608
DJI29991.635830.94217102272.564
DAX9162.115504.512189041.282.06
UKX-7690.88475.62347331.21.222
HSI3538.8432801.442359540.431.543
60982752454301033.337.354
4452188828582137535.212.09
57111566028701918041.882.59
7201-2447.52660.42176027.651.483
9984799151402007939.52.178
AAPL247.1639.3423511147.232.455
AXP-26.53100.922277633.481.789
BA518.5477.842239040.363.857
JNJ49.5747.191927136.982.246
MCD75.769.332188036.72.332
GOLD25220839802219542.991.437
PLATINUM42340991252227935.592.115
WTI1381701701502117435.072.544
CORN20900345002026130.22.754
SOY BEAN50212.550712.51987336.872.17

テスト3 統計

破産の確率期待値/リスク収益の見込み
0.02%0.191009.2

破産の確率は、損失の許容=2%で算出しています。期待値と収益の見込みは以下の計算式で算出しています。

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|収益の見込み=期待値×取引回数\text{収益の見込み} = \text{期待値} \times \text{取引回数}

ピラミッティングはかなり効果的なようです。

  • 「収益の見込み」がテスト1の倍以上に
  • 「期待値」は低下、「取引回数」が大幅増
  • 破産の危険が少し上昇
  • 「期待値」が 0.2 くらいになると「破産の確率」が顕在化する?(単利・仮説)

テスト結果の比較

さて、今回もテスト結果をまとめて比較してみたいと思います。

取引回数勝率RR比破産の確率期待値収益の見込み
テスト1204337.842.2000.010.210429.88
テスト2199338.692.0700.010.187372.60
テスト3518736.942.2300.020.1901009.02
  • もっとも成績が良いのはテスト3
  • 手法の良し悪しは「期待値 ✕ 取引回数」
  • 期待値の低下 ≒ 破産の確率の上昇
  • RR比よりも勝率のほうが「破産の確率」に及ぼす影響が強そう

こんなところでしょうか。以前もやったことのあるテストですが、あらためて行ってみると、また新しい発見がでてきます。ここから考えられそうなことを少し書き出してみようと思います。

考察

ここまで、「儲かる」「勝てる」という結果ばかりで思わず忘れてしまいそうですが、(当然)いくつかの注意点もあります。大事な、決して忘れてはいけない注意点です。

  • 破産の確率が「1トレードのリスクを全資金の2%」にしたものだということ
  • ピラミッティングは「やれば良い」というものでもない
  • 勝てるシステムは絶妙なバランスのもとに成り立っている

この辺りがわからない方は、わかるまでやらないほうが良いです。

何かひとつでも崩れたら、あっという間に破産してしまう――

勝てるシステムは、そんな絶妙なバランスのもとに成り立っています。別に、特別むずかしいことではないのですが、知らないとムリです。センスとかじゃなく「知ってるか知らないか」だと思いますので。

さて、今回のバックテストで強く感じたのは、次の点でした。

  • 期待値よりも取引回数の方が増やしやすい
    • 期待値(勝率もしくはRR比)を上げるの大変
    • 取引回数を増やすのはそこまで大変じゃない
  • つまり、儲けたいなら取引回数を増やす方が良い
    • 短期のシステムにする
    • 銘柄数を増やす
    • 足種を短期間のものにする

リスクと資金の管理は必須です。あとは生活との相談。そして、ルールを遵守すること。

Pineスクリプト

今回行ったバックテストに用いたストラテジーのコード(Pineスクリプト)を公開します。販売や二次配布以外は自由にご利用いただいて差し支えありません。ご自由にお使いください!

Pineスクリプト

strategy("Strategy Turtle Donchian Trend System"
  ,default_qty_type=strategy.fixed
  ,default_qty_value=1
  ,pyramiding=4
  ,overlay=true)



src = close
len_dc_entry = input(20   ,minval=1 ,title="length of dc entry")
len_dc_exit  = input(10   ,minval=1 ,title="length of dc exit")
len_ema_m    = input(25   ,minval=1 ,title="length of middle ema")
len_ema_l    = input(350  ,minval=1 ,title="length of long ema")
SO_bool  = input(false,type=bool    ,title="loss cut")
SO_len   = input(20   ,type=integer ,minval=1 ,title="loss cut ATR length")
SO_N     = input(2    ,type=float   ,minval=0.5 ,title="loss cut ATR*N")
MAX_N    = input(1    ,type=integer ,minval=1 ,maxval=4 ,title="maximun num of unit")
LO_len   = input(20   ,type=integer ,minval=1 ,title="pyramiding ATR length")
LO_N     = input(1    ,type=float   ,minval=0.5 ,title="pyramiding ATR*N")
Tm_bool  = input(false,type=bool    ,title="timed exit")
Tm_len   = input(80   ,type=integer ,minval=1 ,title="timed exit length")
fromYear = input(2005 ,type=integer ,minval=1900 ,title="test start")
endYear  = input(2017 ,type=integer ,minval=1900 ,title="test end")

isWork   = timestamp(fromYear ,1 ,1 ,00 ,00) <= time and time < timestamp(endYear+1 ,1 ,1 ,00 ,00)

upper_en = highest(high ,len_dc_entry)[1]
upper_ex = highest(high ,len_dc_exit)[1]
lower_en = lowest(low ,len_dc_entry)[1]
lower_ex = lowest(low ,len_dc_exit)[1]
ema_m    = ema(src ,len_ema_m)
ema_l    = ema(src ,len_ema_l)

atr_SO_ = ema(tr ,SO_len)
atr_LO_ = ema(tr ,LO_len)
atr_SO = atr_SO_*SO_N
atr_LO = atr_LO_*LO_N



countTradingDays     = na
countNonTradingDays  = na
countTradingDays    := strategy.position_size==0 ? 0 : countTradingDays[1] + 1
countNonTradingDays := strategy.position_size!=0 ? 0 : countNonTradingDays[1] + 1
entry1   = close
entry2   = close
entry3   = close
entry4   = close
entry1  := strategy.position_size==0 ? na : entry1[1]
entry2  := strategy.position_size==0 ? na : entry2[1]
entry3  := strategy.position_size==0 ? na : entry3[1]
entry4  := strategy.position_size==0 ? na : entry4[1]
lo2      = close
lo3      = close
lo4      = close
lo2     := strategy.position_size==0 ? na : lo2[1]
lo3     := strategy.position_size==0 ? na : lo3[1]
lo4     := strategy.position_size==0 ? na : lo4[1]



L_EntrySig = strategy.position_size==0 and high >= upper_en and ema_m >= ema_l
S_EntrySig = strategy.position_size==0 and low  <= lower_en and ema_m <= ema_l
lo_sig2    = strategy.position_size>0 ? lo2 < high : strategy.position_size<0 ? lo2 > low : na
lo_sig3    = strategy.position_size>0 ? lo3 < high : strategy.position_size<0 ? lo3 > low : na
lo_sig4    = strategy.position_size>0 ? lo4 < high : strategy.position_size<0 ? lo4 > low : na
losscut    = close
losscut   := SO_bool==false ? na
          :  L_EntrySig ? close - atr_SO
          :  S_EntrySig ? close + atr_SO
          :  strategy.position_size>0 and (lo_sig2 or lo_sig3 or lo_sig4) ? close - atr_SO
          :  strategy.position_size<0 and (lo_sig2 or lo_sig3 or lo_sig4) ? close + atr_SO
          :  strategy.position_size!=0 ? losscut[1]
          :  na
ExitPrice  = close
ExitPrice := L_EntrySig or strategy.position_size>0 ? SO_bool ? max(losscut ,lower_ex) : lower_ex
          :  S_EntrySig or strategy.position_size<0 ? SO_bool ? min(losscut ,upper_ex) : upper_ex
          :  na



if(strategy.position_size != 0)

    L_ExitSig = (low  <= lower_ex or S_EntrySig) and strategy.position_size > 0
    S_ExitSig = (high >= upper_ex or L_EntrySig) and strategy.position_size < 0
    TimedSig  = countTradingDays > Tm_len and Tm_bool

    strategy.close_all(when = L_ExitSig or S_ExitSig or TimedSig)

    if(L_ExitSig or S_ExitSig)
        entry1  := na
        entry2  := na
        entry3  := na
        entry4  := na
        lo2     := na
        lo3     := na
        lo4     := na
        losscut := na



if(strategy.position_size > 0)

    strategy.exit("L-Entry1" ,stop=ExitPrice)

    if(entry2!=na)
        strategy.exit("L-Entry2" ,stop=ExitPrice)
    if(entry3!=na)
        strategy.exit("L-Entry3" ,stop=ExitPrice)
    if(entry4!=na)
        strategy.exit("L-Entry4" ,stop=ExitPrice)

    if(lo_sig2 and MAX_N >= 2)
        lo2 := na
        if(SO_bool)
            strategy.entry("L-Entry2" ,strategy.long ,stop=ExitPrice ,comment="L-Entry2")
            strategy.exit("L-Entry1"  ,stop=ExitPrice)
        else
            strategy.entry("L-Entry2" ,strategy.long ,comment="L-Entry2")
    if(lo_sig3 and MAX_N >= 3)
        lo3 := na
        if(SO_bool)
            strategy.entry("L-Entry3" ,strategy.long ,stop=ExitPrice ,comment="L-Entry3")
            strategy.exit("L-Entry2"  ,stop=ExitPrice)
            strategy.exit("L-Entry1"  ,stop=ExitPrice)
        else
            strategy.entry("L-Entry3" ,strategy.long ,comment="L-Entry3")
    if(lo_sig4 and MAX_N >= 4)
        lo4 := na
        if(SO_bool)
            strategy.entry("L-Entry4" ,strategy.long ,stop=ExitPrice ,comment="L-Entry4")
            strategy.exit("L-Entry3"  ,stop=ExitPrice)
            strategy.exit("L-Entry2"  ,stop=ExitPrice)
            strategy.exit("L-Entry1"  ,stop=ExitPrice)
        else
            strategy.entry("L-Entry4" ,strategy.long ,comment="L-Entry4")



if(strategy.position_size < 0)

    strategy.exit("S-Entry1" ,stop=ExitPrice)

    if(entry2!=na)
        strategy.exit("S-Entry2" ,stop=ExitPrice)
    if(entry3!=na)
        strategy.exit("S-Entry3" ,stop=ExitPrice)
    if(entry4!=na)
        strategy.exit("S-Entry4" ,stop=ExitPrice)

    if(lo_sig2 and MAX_N >= 2)
        lo2 := na
        if(SO_bool)
            strategy.entry("S-Entry2" ,strategy.short ,stop=ExitPrice ,comment="S-Entry2")
            strategy.exit("S-Entry1"  ,stop=ExitPrice)
        else
            strategy.entry("S-Entry2" ,strategy.short ,comment="S-Entry2")
    if(lo_sig3 and MAX_N >= 3)
        lo3 := na
        if(SO_bool)
            strategy.entry("S-Entry3" ,strategy.short ,stop=ExitPrice ,comment="S-Entry3")
            strategy.exit("S-Entry2"  ,stop=ExitPrice)
            strategy.exit("S-Entry1"  ,stop=ExitPrice)
        else
            strategy.entry("S-Entry3" ,strategy.short ,comment="S-Entry3")
    if(lo_sig4 and MAX_N >= 4)
        lo4 := na
        if(SO_bool)
            strategy.entry("S-Entry4" ,strategy.short ,stop=ExitPrice ,comment="S-Entry4")
            strategy.exit("S-Entry3"  ,stop=ExitPrice)
            strategy.exit("S-Entry2"  ,stop=ExitPrice)
            strategy.exit("S-Entry1"  ,stop=ExitPrice)
        else
            strategy.entry("S-Entry4" ,strategy.short ,comment="S-Entry4")



if((L_EntrySig or S_EntrySig) and isWork)
    countTradingDays := 0
    entry1           := close

    if(L_EntrySig)
        if(SO_bool)
            strategy.entry("L-Entry1" ,strategy.long ,stop=ExitPrice ,comment="L-Entry1")
        else
            strategy.entry("L-Entry1" ,strategy.long ,comment="L-Entry1")
        lo2 := MAX_N >= 2 ? close + atr_LO     : na
        lo3 := MAX_N >= 3 ? close + atr_LO * 2 : na
        lo4 := MAX_N >= 4 ? close + atr_LO * 3 : na

    if(S_EntrySig)    
        if(SO_bool)
            strategy.entry("S-Entry1" ,strategy.short ,stop=ExitPrice ,comment="S-Entry1")
        else
            strategy.entry("S-Entry1" ,strategy.short ,comment="S-Entry1")
        lo2  := MAX_N >= 2 ? close - atr_LO     : na
        lo3  := MAX_N >= 3 ? close - atr_LO * 2 : na
        lo4  := MAX_N >= 4 ? close - atr_LO * 3 : na


plot(strategy.position_size ,transp=0 ,title="保有ポジションの数")
plot(strategy.openprofit    ,transp=0 ,title="未決済の損益")
plot(strategy.netprofit     ,transp=0 ,title="決済済みの損益")
plot(strategy.closedtrades  ,transp=0 ,title="決済済み取引数")
plot(countTradingDays       ,transp=0 ,title="取引日数")
plot(countNonTradingDays    ,transp=0 ,title="ノンポジ日数")
plot(entry1    ,title="entry1"    ,color=blue ,transp=0 ,style=linebr)
plot(lo2       ,title="lo2"       ,color=red  ,transp=0 ,style=linebr)
plot(lo3       ,title="lo3"       ,color=red  ,transp=0 ,style=linebr)
plot(lo4       ,title="lo4"       ,color=red  ,transp=0 ,style=linebr)
plot(ExitPrice ,title="ExitPrice" ,color=red  ,transp=0 ,style=linebr)
plot(atr_SO    ,transp=0 ,title="ATR_SO")
plot(atr_LO    ,transp=0 ,title="ATR_LO")



p1 = plot(ema_m ,color=#303F9F ,title="ema_m" ,style=line ,linewidth=1, transp=0)
p2 = plot(ema_l ,color=#4CAF50 ,title="ema_l" ,style=line ,linewidth=1, transp=0)
fill(p1 ,p2 ,color=#2196F3 ,title="fill" ,transp=80)

p3 = plot(lower_en ,color=gray ,title="lower_entry" ,style=linebr ,linewidth=1 ,transp=40)
p4 = plot(upper_en ,color=gray ,title="upper_entry" ,style=linebr ,linewidth=1 ,transp=40)
fill(p3 ,p4 ,color=gray ,title="fill" ,transp=90)

plot(strategy.position_size>0 ? lower_ex : na ,color=red  ,title="lower_exit"  ,style=linebr ,linewidth=1 ,transp=30)
plot(strategy.position_size<0 ? upper_ex : na ,color=red  ,title="upper_exit"  ,style=linebr ,linewidth=1 ,transp=30)

TradingViewでも公開しています!

TradingView で ストラテジー「Donchian Trend System」を見つける

TradingView のインジケーターの検索で「donchian trend system」と検索するとでてきます。上記のコードと同じストラテジーを使用することができます。

バックテストならTradingView

TradingViewのテストは大変便利で、かなり自由の効くバックテストを短時間で簡単に、価格データを用意することなく豊富な銘柄と足種を対象に行うことができます。

一方で、その簡便さと引き換えに、TradingViewではできないバックテストも多くあります。

たとえば「分散投資」や「資金管理」があげられますが、これらは投資において非常に重要な要素でもあり、これらを含めたバックテストをするならリアルさが不可欠です。こういったバックテストをするためには、やはりPythonなどのプログラミング言語で自作していくしかないと考えています。

とはいえ、TradingViewが便利であることは間違いなく、最近では、手法の検証はTradingViewで行い、良い手法が見つかったら更に詳細なテストをPythonで行うようにしています。できるものはTradingViewでサクサクやってしまいます。

タカハシ / 8年目の兼業トレーダー

元・日本料理の板前。現在は、投資やプログラミング、動画コンテンツの撮影・制作・編集などを。更新のお知らせは、各SNSやLINEで。LINEだと1対1でお話することもできます!

このブログと筆者について運用管理表

  • 記事をシェア
© Investment Tech Hack 2021.