InvestmentTechHack

TradingViewで検証:ボリンジャー・ブレイクアウト

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

どんな記事

この記事を読むと、

  • 超長期のボリンジャーバンドのバックテストが自分でも
  • コピー → ペースト → 1クリック」の簡単操作で(コードを無料公開)
  • 22銘柄12年 ✕ 4テスト」の検証結果がわかる

追記

2018年12月9日
TradingViewでも公開しています!」を追加しました

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

まずは、この記事の主題でもある「ボリンジャー・ブレイクアウト」について書かれている箇所を「伝説のトレーダー集団 タートルズ流投資の魔術」から引用します。

 このシステムは、1992年に出版されたチャック・ルボーとデビット・ルーカスの共著『マーケットのテクニカル秘録』で説明されている(チャネル幅にさまざまな日数の移動平均とさまざまな標準偏差を用いている)。

 ボリンジャー・バンドはジョン・ボリンジャーによって考案された変動性チャネルだ。これは、終値の350日移動平均に標準偏差の2.5倍を加算・減算したもので、前日の終値がチャネルの上限を超えたら、寄り付きから買い持ちのトレードを仕掛け、前日の終値がチャネルの下限を下回れば、売り持ちとなる。終値が移動平均値とクロスして下へ抜けたら、取引は手じまいだ。

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

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

ストラテジーを作成

TradingView ボリンジャー・ブレイクアウト・ストラテジー

チャートが再現できているか確認するために、「伝説のトレーダー集団 タートルズ流投資の魔術」の P.167 図22 と同じものを作成してみました。

前回同様、限月とつなぎ足の違いはあると思いますが、貴金属が限月間の差が少ないのか大きな違いはなく(ほぼ)同じ形のチャートを作成することができました。かなり近い形で再現できていると思います。

パラメーターやコードは「Pineスクリプト」をご覧ください。

エントリーとイグジット

買いエントリー

  • 「SMA350+標準偏差の2.5倍」を終値で上抜けたら買いエントリー
  • 買い持ちの状態で「SMA350」を終値で下抜けたら決済

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

TradingView ボリンジャー・ブレイクアウト・ストラテジー 買いシグナルとイグジット

売りエントリー

  • 「SMA350-標準偏差の2.5倍」を終値で下抜けたら売りエントリー
  • 売り持ちの状態で「SMA350」を終値で上抜けたら決済

TradingView ボリンジャー・ブレイクアウト・ストラテジー 売りシグナルとイグジット

ボリンジャー・ブレイクアウトも「The トレンドフォロー」ですね。前回のATRチャネル・ブレイクアウトより勝率が高めな気がします。

バックテスト

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

バックテストの基本条件

期間

  • 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 設定

MAStev upperStev lowerLosscutPyramiding
3502.52.5NoneNone

テスト1 結果

損益最大DD取引数勝数勝率RR比
Total1597345.912.950
USDJPY48.644.985360.005.889
EURJPY15.6825.328337.502.462
GBPJPY141.555.225480.007.028
CHFJPY2.7214.577228.572.729
CADJPY-0.219.529444.441.236
NKY11027.161409.976466.672.620
DJI5444.313114.888450.002.127
DAX-443.162306.595240.001.212
UKX125.801186.907342.861.419
HSI-13836.2820811.908112.502.346
60981208.000.0011100.00-
44522316.00878.007342.863.168
5711-3385.004845.008225.001.049
7201-211.00392.005120.002.61
99844306.00183.006466.678.329
AAPL61.696.796583.332.017
AXP32.895.796350.003.157
BA164.4052.598450.003.591
JNJ14.7021.367238.573.851
MCD33.6016.157457.141.974
GOLD78130.0021370.005360.003.104
PLATINUM16365.0027100.004250.001.605
WTI-3380.0048590.007342.861.260
CORN-2900.008700.008337.501.336
SOY BEANS5212.5018325.006350.001.198

以下のことが言えそうです。

日本個別株は、超長期の手法が苦手な可能性がありそうです。5銘柄だけなので何とも判断がつきませんが。

テスト1 統計

破産の確率期待値/リスク
0.00%0.81

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

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|

破産の確率は0%ですね。期待値を算出してみると、前回のATRチャネル・ブレイクアウトよりもかなり優秀です。シリーズの終わりにすべての手法を比較してみたいと思いますが、かなり優秀な期待値じゃないかと思います。

テスト2

次にピラミッティングを追加してテストしてみます。

テスト2 設定

MAStev upperStev lowerLosscutPyramiding
3502.52.5None10-ATR 上限4

エントリー後、プラス方向に20日ATRの10倍ごとに追加します。最初のものを含めて4回のエントリーが上限です。タートルズの用語で言うと「ロード」の状態ですね。

テスト2 結果

損益最大DD取引数勝数勝率RR比
Total31915648.901.950
USDJPY116.306.8910770.004.233
EURJPY12.5924.4413538.462.030
GBPJPY170.9731.5616956.252.403
CHFJPY10.9536.2612541.671.699
CADJPY-23.4733.3414535.710.825
NKY19069.573866.40151173.331.335
DJI8957.345238.15191157.891.366
DAX2373.582306.598662.501.217
UKX946.901482.0012650.001.387
HSI-3980.3320811.9011436.361.421
60983030.000.0044100.00-
44527300.002524.0013646.153.167
5711-4005.006155.0012325.001.156
7201-830.00830.008112.502.261
9984409.003192.0015640.001.585
AAPL-28.54118.8721838.101.294
AXP34.4221.0412650.001.849
BA400.4755.42171058.824.32
JNJ37.7321.3612650.001.932
MCD16.9126.9516850.001.285
GOLD174830.0029490.0012866.673.232
PLATINUM79885.0027100.009666.671.787
WTI-87820.00145190.0014535.710.846
CORN-34387.0034387.0015426.670.744
SOY BEANS32000.0018325.009666.671.109

テストが終わった段階で、ちょっと悪くなった印象でした。とはいえ、勝率48.9%とRR比1.95倍なので、単利でやれば十分勝てる手法です。確認のため期待値を算出してみると次のようになります。

テスト2 統計

破産の確率期待値/リスク
0.00%0.44

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

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|

テスト1と比較して「0.81 → 0.44」とかなり悪化しています。「取引回数を考慮するとどうなるか」を「テスト結果の比較」で見てみたいと思います。

テスト3

次は、一般的によく使われる「標準偏差の2倍」にしてみます。

テスト3 設定

MAStev upperStev lowerLosscutPyramiding
35022NoneNone

テスト3 結果

損益最大DD取引数勝数勝率RR比
Total2048843.143.050
USDJPY42.5117.008337.505.622
EURJPY26.7813.3710440.002.990
GBPJPY149.0812.616466.676.409
CHFJPY23.678.658337.504.493
CADJPY-17.1322.3613538.460.968
NKY9443.432747.398450.003.004
DJI6484.982765.6210440.003.290
DAX3520.08868.847571.431.449
UKX-555.801940.8011436.361.443
HSI2364.608764.199222.224.444
60981173.00142.002150.009.261
44522145.001319.009333.333.979
5711-2080.003880.009333.331.102
7201-521.00521.009111.113.405
99844062.00585.008562.502.500
AAPL93.6315.317571.432.320
AXP50.068.107457.143.861
BA219.8240.318562.503.293
JNJ15.6821.918225.004.454
MCD31.9930.1811436.363.119
GOLD98100.009880.005360.007.286
PLATINUM530.0045625.007342.861.347
WTI10690.0074140.009444.441.461
CORN362.5013462.509444.441.277
SOY BEANS19837.5011825.006350.002.062

条件を標準偏差の「2.5倍」から「2倍」にした分、取引回数が増えています。これで期待値が悪くならなければ、テスト1よりも良いと言えそうです。

テスト3 統計

破産の確率期待値/リスク
0.00%0.75

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

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|

期待値を算出してみると、テスト1と比較して「0.81 → 0.75」となり、それほど悪化していないことがわかります。この点も「テスト結果の比較」で、もうちょっとだけ細かく見てみたいと思います。

テスト4

最後に、テスト3にピラミッティングを加えたものをテストしてみます。

テスト4 設定

MAStev upperStev lowerLosscutPyramiding
35022None10-ATR 上限4

テスト4 結果

損益最大DD取引数勝数勝率RR比
Total40118846.882.41
USDJPY119.7717.0014857.145.070
EURJPY12.3628.9418738.891.873
GBPJPY237.8232.55171164.712.803
CHFJPY33.2430.3414642.862.464
CADJPY-42.1850.4219631.580.888
NKY17695.866599.18181161.111.695
DJI12927.77454.2221150.002.232
DAX4252.883569.5515853.331.649
UKX-936.204056.8018844.441.029
HSI3241.9622692.0017635.292.060
60983528.00142.005480.006.461
44527513.00-2688.0015640.003.876
5711-2480.006110.0014535.711.149
7201-1829.001829.0015213.331.404
99842331.003424.0018844.441.635
AAPL197.4244.93211466.672.207
AXP50.2014.7813453.852.195
BA597.5642.44201365.004.362
JNJ47.3621.9115640.002.710
MCD44.3740.00211047.621.788
GOLD213750.0018000.0012975.004.310
PLATINUM66845.0045625.0013753.851.820
WTI-54260.00130270.0017741.180.909
CORN-36750.0040962.5018527.780.857
SOY BEANS11362.5040075.0012650.001.227

テスト4 統計

破産の確率期待値/リスク
0.00%0.6

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

期待値=勝率×RR比1勝率\text{期待値} = \text{勝率} \times \text{RR比} - | 1 - \text{勝率}|

期待値は 0.6 になりましたが、取引数はもっとも多くなりました。はたして「どのテストが優秀なのか」次で比べてみたいと思います。

テスト結果の比較

さて、テスト1からテスト4まで行いましたが、手法の良し悪しは「期待値と取引回数のバランス」で決まります。対象となる銘柄を増やせば高い期待値のまま取引回数を増やせますが、バックテストの比較ではまったく関係のない話です。

取引回数勝率RR比破産の確率期待値収益の見込み
テスト115945.912.9500.81128.79
テスト231948.901.9500.44140.36
テスト320443.143.0500.75153.00
テスト440146.882.4100.60240.60

収益の見込みは、それぞれ以下で算出しました。

  • テスト1: 0.81 ✕ 159 = 128.79
  • テスト2: 0.44 ✕ 319 = 140.36
  • テスト3: 0.75 ✕ 204 = 153.00
  • テスト4: 0.60 ✕ 401 = 240.60

※ この数値に「1トレードあたりのリスク」を掛けると収益を算出することもできます

こうやって計算してみると、成績が良いのは〝ダントツ〟で「テスト4」ですね。わかりやすい。

期待値が良くても取引回数(チャンス)が少なければ意味がないし、取引回数が多くても期待値がマイナスなら損してしまう。つまり、この2つのバランスが重要です。

考察

今回、2つの変数を調整してみました。「標準偏差を何倍にするか」と「ピラミッティングの有無」です。

まず、どちらの調整もプラスに作用したことがわかります。

  • テスト1 → テスト2: 128.79 → 140.36
  • テスト1 → テスト3: 128.79 → 153.00

組み合わせてみると、さらに良い結果になりました。

  • テスト4: 240.60

最適化の問題は、短期間の偏ったデータを対象に行なうテストで行うことで生じます。このように、バックテストの〝量〟と〝質〟を確保した上で効果的な変数を探ることは、非常に有意義だと考えています。

また、実際のトレードでは「取引対象を増やすこと」で「高い期待値を維持しながら取引回数を増やすこと」も可能です。実際のトレードとバックテスト、それぞれのメリットとデメリットを把握して良い点を活かしていきたいものです。

Pineスクリプト

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

Pineスクリプト

strategy("Strategy Turtle Bollinger Break Out"
  ,default_qty_type=strategy.fixed
  ,default_qty_value=1
  ,pyramiding=4
  ,overlay=true)

src      = close
len      = input(350  ,minval=1  ,title="ma length")
up_n     = input(2    ,minval=1  ,title="stdev upper n")
low_n    = input(2    ,minval=1  ,title="stdev lower n")
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(10   ,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)

SMA = sma(close ,len)
STD = stdev(close ,len)
UPPER = SMA + STD * up_n
LOWER = SMA - STD * low_n

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]
losscut  = close
losscut := strategy.position_size==0 or SO_bool==false ? na : losscut[1]



L_EntrySig = close >= UPPER
S_EntrySig = close <= LOWER



if(strategy.position_size != 0)
    L_ExitSig = (close <= SMA or S_EntrySig) and strategy.position_size > 0
    S_ExitSig = (close >= SMA 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)
    lo_sig2 = lo2 < high
    lo_sig3 = lo3 < high
    lo_sig4 = lo4 < high

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

if(strategy.position_size < 0)
    lo_sig2 = lo2 > low
    lo_sig3 = lo3 > low
    lo_sig4 = lo4 > low

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



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

    if(L_EntrySig)
        if(SO_bool)
            strategy.entry("L-Entry1" ,strategy.long ,stop=close-atr_SO ,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
        losscut := SO_bool ? close - atr_SO : na

    if(S_EntrySig)    
        if(SO_bool)
            strategy.entry("S-Entry1" ,strategy.short ,stop=close+atr_SO ,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
        losscut := SO_bool ? close + atr_SO : 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(losscut ,title="losscut" ,color=red  ,transp=0 ,style=linebr)
plot(atr_SO  ,transp=0 ,title="ATR_SO")
plot(atr_LO  ,transp=0 ,title="ATR_LO")



p1 = plot(UPPER ,color=#303F9F ,title="UPPER" ,style=line ,linewidth=2, transp=0)
p2 = plot(LOWER ,color=#4CAF50 ,title="LOWER" ,style=line ,linewidth=2, transp=0)
plot(SMA ,color=red ,title="EMA" ,style=line ,linewidth=2 ,transp=0)
fill(p1 ,p2 ,color=#2196F3 ,title="fill" ,transp=60)

TradingViewでも公開しています!

TradingView で ストラテジー「bollinger break out」を見つける

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

バックテストならTradingView

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

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

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

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

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

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

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

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