古典的な戦略を再構築する(第16回):ダブルボリンジャーバンドブレイクアウト
金融市場は固定的で静的な機械のようなものではありません。むしろ、金融市場は情報に反応する、生きて呼吸しているかのような知的システムです。市場は常に変化し、決して永続的に安定した状態に留まることはありません。それにもかかわらず、多くの取引戦略は、市場をあたかも固定された状態にあるかのように扱います。
ほとんどの戦略は、まず市場を「トレンド相場」か「レンジ相場」のいずれかに分類し、その状態に応じた手順を提示します。しかし、本記事では読者に示したいのは、この市場の捉え方はあまりにも単純すぎて、実務ではあまり有効ではないということです。もし、常に変化する市場、時に逆行的に振る舞う市場に対応する戦略を構築したいのであれば、市場を「常に進化する、部分的にしか観測できないシステム」として捉える必要があります。
そこで登場するのが、ダブルボリンジャーバンド戦略です。これは古典的ボリンジャーバンド戦略を拡張したもので、機関投資家に由来すると言われています。市場の変化に柔軟に対応できるよう設計されており、後ほど紹介するバックテスト結果でもその有効性を確認できます。多くのボリンジャーバンド戦略は、教科書的なルールに基づき、好条件下での運用を前提としています。「ブレイクアウトを狙え」「フェード戦略を取れ」と指示しますが、市場が実際に示す複雑で絶え間ない変化には対応できません。
本記事の手法では、コミュニティ内で広く知られる古典的ボリンジャーバンド戦略のバリエーションを実装し、ダブルボリンジャーバンド戦略が上回る収益性のベンチマークを設定しました。標準戦略をテストしたところ、実際には損失が出る結果となりました。
同じ5年間(2020年1月~2025年)を対象に両戦略のバックテストをおこない、可能な限りパラメータを統一しました。古典的戦略は一貫して損失を出しましたが、ダブルボリンジャーバンド戦略は目覚ましく上回る成果を示しました。これは、ボリンジャーバンドの期間や取引ロットサイズなど、固定パラメータを使用した状態での結果です。これらの結果については後ほど詳細に説明しますが、まずここで主要なポイントを簡単にまとめます。
古典的戦略は5年間で総損失が−169ドルとなったのに対し、ダブルボリンジャーバンド戦略は同一条件下で、可能な限りパラメータを固定した状態で総利益228ドルを記録しました。公正を期すために、すべての取引は最小ロットサイズで開かれ、各戦略では同時に開ける取引は1件に制限しました。また、古典的戦略はシャープレシオも-0.53とマイナスでしたが、改良版では0.5のプラスを達成しており、収益性と安定性の明確な改善が示されています。
勝率について見ると、古典的戦略は5年間で46%の勝率に留まり、決して印象的ではありませんでした。しかしダブルボリンジャーバンド戦略は56%の勝率を達成しており、精度は相対的に21%向上しています。さらに注目すべきは、利益と損失の分布を非対称に変化させた点です。本手法によりバックテストの総利益は70%増加しましたが、総損失はわずか17%の増加にとどまりました。この非対称的な効果、つまり利益の増加が損失の増加を上回る形で分布を変化させることは、ダブルボリンジャーバンド戦略が取引結果の分布をより望ましい形に再構築できることを示しています。
最後に、プロフィットファクターの変化を考慮すると、古典的戦略は0.82であり、時間とともに資本を失う結果となりました。一方、ダブルボリンジャーバンド戦略は1.2を記録しており、資本の増加を示す重要な指標となっています。これはプロフィットファクターの50%改善を意味します。
これらの結果を総合すると、ダブルボリンジャーバンド戦略は単なる古典的手法のアップグレードではなく、パラダイムシフトであることが分かります。古典的アプローチを祖先として共有しつつも、結果を根本的に変える新しい戦略を代表しているのです。このシステムは、損失戦略を利益戦略に変え、収益性を劇的に改善し、同時にリスク管理も優れた形で維持しています。
最も重要なのは、これらの改善を達成しながら、余計な複雑さを追加していない点です。ダブルボリンジャーバンド戦略は、学習・実装・解釈が容易でありながら、より大きな利益をもたらします。将来的に、エントリーやエグジットを最適化するための機械学習モデルや、優勢パターンに適応するフィードバック制御を導入する場合でも、この基盤がすでに利益を生む構造になっているため、その労力は確実に価値のあるものとなります。
そのような追加をおこなわなくても、ダブルボリンジャーバンド戦略はすでに「シンプルでありながら優位性を持つ」という、取引において非常に望ましい特徴を備えています。それでは始めましょう。
古典的取引戦略の概要
まずは、通常通りチャートに古典的ボリンジャーバンドのインジケーターを適用するところから始めます。本記事ではEURUSDペアを選択し、日足で分析をおこないます。したがって、以下の図1では、ボリンジャーバンドの期間を20本の日足ローソク足に設定しています。上部バンドと下部バンドは、中央のバンドからそれぞれ2標準偏差離れた位置に設定しており、これが古典的な設定です。

図1:古典的な入力設定でEURUSDペアにボリンジャーバンドを適用する
古典的取引戦略は非常にシンプルです。価格が上部バンドを上抜けた場合はショートエントリーをおこない、逆に下部バンドを下抜けた場合はロングエントリーをおこないます。この古典的ボリンジャーバンド戦略の背後にある考え方は、市場は平均回帰的なサイクルで動くというものです。したがって、いずれかの極端なバンドを突破した後には、価格が中央バンドに戻る動きが続くと期待されます。

図2:古典的ボリンジャーバンド戦略の視覚化
しかし、どの取引戦略にも固有の制限があります。古典的設定で定義した取引ルールには、偽のブレイクアウトに弱いというよく知られた課題があります。簡単に言えば、市場の動きが緩やかに進んだ結果、エントリーシグナルが発動した後に急激に方向を変えて元の軌道に戻るという状況が発生するのです。このような市場環境では、投資家の資本は一貫して削られることになります。
以下の図3は、読者がこの戦略の弱点を視覚的に理解する助けとなるでしょう。図中では、これまでに定義したルールに従って正しく認識された2つの売りのセットアップが示されています。しかし、期待された結果を得られたのは1件のみです。最初の取引(緑のボックスで囲まれた部分)では、価格は一時的に上部バンドを突破した後、中央バンドに戻りました。しかし、赤のボックスで示された期間では同じパターンが繰り返されるように見えましたが、これは偽のブレイクアウトでした。価格は上昇を続け、古典的戦略が予測した通り中央バンドに戻ることはありませんでした。

図3:古典的ボリンジャーバンド戦略の限界の視覚化
MQL5で始める
ほとんどの取引アプリケーションと同様に、まずシステム定数を定義することから始めます。これらの定義はテストの基盤となるもので、両方の実験においてできる限り多くのパラメータを固定するために重要です。ボリンジャーバンドの期間やシフト、インジケーターのルックバック期間、時間足、バンドに使用する標準偏差などの重要なパラメータは、戦略の挙動を大きく左右するため、固定しておく必要があります。
//+------------------------------------------------------------------+ //| DBB Benchmark.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/ja/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/ja/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System definitions | //+------------------------------------------------------------------+ #define BB_PERIOD 20 #define BB_SHIFT 0 #define ATR_PERIOD 14 #define ATR_PADDING 2 #define BB_SD_1 2 #define BB_SD_2 1 #define TF_1 PERIOD_D1定数の後に、グローバル変数を定義します。ほとんどのグローバル変数は、使用するテクニカル指標やストップロスの設定に関連しています。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int bb_handler,bb_handler_2,atr_handler; double bb_reading_u[],bb_reading_m[],bb_reading_l[],bb_reading_u_2[],bb_reading_m_2[],bb_reading_l_2[],atr_reading[]; double padding,close;
さらに、取引やBidとAsk価格などの取引情報を記録するために必要なライブラリをインポートします。 ライブラリはほとんどのアプリケーション開発で重要な役割を果たし、コードをすべて一から書くことはほとんどありません。
//+------------------------------------------------------------------+ //| Library | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> CTrade Trade; TradeInfo *TradeInfoHandler;
アプリケーションが初めて読み込まれると、グローバル変数を初期化します。データ構造を空にし、カスタム取引情報クラスの新しいインスタンスを生成します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators bb_handler = iBands(Symbol(),TF_1,BB_PERIOD,BB_SHIFT,BB_SD_1,PRICE_CLOSE); bb_handler_2 = iBands(Symbol(),TF_1,BB_PERIOD,BB_SHIFT,BB_SD_2,PRICE_CLOSE); atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); TradeInfoHandler = new TradeInfo(Symbol(),TF_1); //--- return(INIT_SUCCEEDED); }
アプリケーションの使用を終了する場合は、以前に割り当てたハンドラーのメモリリソースを解放する必要があります。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicators we are no longer using IndicatorRelease(bb_handler); IndicatorRelease(bb_handler_2); IndicatorRelease(atr_handler); }
古典的設定の戦略では、エントリーのロジックは非常にシンプルで解釈も容易です。ボリンジャーバンドの上限が終値で突破された場合は売り、下限が突破され、かつポジションがない場合は買いをおこないます。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Keep track of time static datetime timestamp; datetime current_time = iTime(Symbol(),TF_1,0); if(timestamp != current_time) { //--- Update indicator readings CopyBuffer(bb_handler,0,0,1,bb_reading_m); CopyBuffer(bb_handler,1,0,1,bb_reading_u); CopyBuffer(bb_handler,2,0,1,bb_reading_l); CopyBuffer(bb_handler_2,0,0,1,bb_reading_m_2); CopyBuffer(bb_handler_2,1,0,1,bb_reading_u_2); CopyBuffer(bb_handler_2,2,0,1,bb_reading_l_2); CopyBuffer(atr_handler,0,0,1,atr_reading); //--- Get updated market readings close = iClose(Symbol(),TF_1,0); padding = atr_reading[0] * 2; //--- If we have no open positions if(PositionsTotal() == 0) { if(close > bb_reading_u[0]) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),TradeInfoHandler.GetAsk()+padding,TradeInfoHandler.GetAsk()-padding); else if(close < bb_reading_l[0]) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),TradeInfoHandler.GetBid()-padding,TradeInfoHandler.GetBid()+padding); } } } //+------------------------------------------------------------------+
アプリケーションの実行が終了した後は、初めに定義したシステム定数を解除します。これはMQL5コードにおけるベストプラクティスです。
//+------------------------------------------------------------------+ //| Undefine system definitions | //+------------------------------------------------------------------+ #undef BB_PERIOD #undef BB_SD_1 #undef BB_SD_2 #undef BB_SHIFT #undef ATR_PADDING #undef ATR_PERIOD #undef TF_1 //+------------------------------------------------------------------+
以上を組み合わせると、古典的アプリケーションの制御構造はこのようになります。
//+------------------------------------------------------------------+ //| DBB Benchmark.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/ja/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/ja/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System definitions | //+------------------------------------------------------------------+ #define BB_PERIOD 20 #define BB_SHIFT 0 #define ATR_PERIOD 14 #define ATR_PADDING 2 #define BB_SD_1 2 #define BB_SD_2 1 #define TF_1 PERIOD_D1 //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int bb_handler,bb_handler_2,atr_handler; double bb_reading_u[],bb_reading_m[],bb_reading_l[],bb_reading_u_2[],bb_reading_m_2[],bb_reading_l_2[],atr_reading[]; double padding,close; //+------------------------------------------------------------------+ //| Library | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> CTrade Trade; TradeInfo *TradeInfoHandler; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our technical indicators bb_handler = iBands(Symbol(),TF_1,BB_PERIOD,BB_SHIFT,BB_SD_1,PRICE_CLOSE); bb_handler_2 = iBands(Symbol(),TF_1,BB_PERIOD,BB_SHIFT,BB_SD_2,PRICE_CLOSE); atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); TradeInfoHandler = new TradeInfo(Symbol(),TF_1); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicators we are no longer using IndicatorRelease(bb_handler); IndicatorRelease(bb_handler_2); IndicatorRelease(atr_handler); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Keep track of time static datetime timestamp; datetime current_time = iTime(Symbol(),TF_1,0); if(timestamp != current_time) { //--- Update indicator readings CopyBuffer(bb_handler,0,0,1,bb_reading_m); CopyBuffer(bb_handler,1,0,1,bb_reading_u); CopyBuffer(bb_handler,2,0,1,bb_reading_l); CopyBuffer(bb_handler_2,0,0,1,bb_reading_m_2); CopyBuffer(bb_handler_2,1,0,1,bb_reading_u_2); CopyBuffer(bb_handler_2,2,0,1,bb_reading_l_2); CopyBuffer(atr_handler,0,0,1,atr_reading); //--- Get updated market readings close = iClose(Symbol(),TF_1,0); padding = atr_reading[0] * 2; //--- If we have no open positions if(PositionsTotal() == 0) { if(close > bb_reading_u[0]) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),TradeInfoHandler.GetAsk()+padding,TradeInfoHandler.GetAsk()-padding); else if(close < bb_reading_l[0]) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),TradeInfoHandler.GetBid()-padding,TradeInfoHandler.GetBid()+padding); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Undefine system definitions | //+------------------------------------------------------------------+ #undef BB_PERIOD #undef BB_SD_1 #undef BB_SD_2 #undef BB_SHIFT #undef ATR_PADDING #undef ATR_PERIOD #undef TF_1 //+------------------------------------------------------------------+
次に、このアプリケーションの5年間のバックテストにおけるパフォーマンスを分析します。総純利益は−169ドルであり、決して印象的ではありません。シャープレシオは−0.53、利益を出した取引の割合は46%で50%を下回っています。要するに、このアプリケーションは偶然よりも低いパフォーマンスで、一貫して収益を上げられないことが分かります。

図4:古典的取引戦略が示す詳細統計の可視化
古典的戦略のエクイティカーブを見ると、時間とともに強まる持続的な下落傾向が確認できます。

図5:古典的取引戦略の時間経過による負の傾向
改訂版取引戦略の概要
改良点はシンプルです。単一のボリンジャーバンドシステムではなく、2つのバンドセットを使用します。内側のバンドは標準偏差を小さく設定し、外側のバンドは標準偏差を大きく設定します。内側のバンドが突破された場合はトレンド追随のシグナルと解釈し、外側のバンド(2標準偏差)が突破された場合は平均回帰のシグナルと解釈します。具体的には、内側バンドの上限が突破された場合は買い(トレンドフォロー)、外側バンドの上限が突破された場合は売り(平均回帰)をおこないます。下部バンドについても同様の逆ルールが適用されます。
この戦略を実現するには、エントリールールを変更するだけで十分であり、コードのほとんどはそのまま使用可能です。

図6:2つ目のフィルターとしてよりタイトな標準偏差のボリンジャーバンドを追加するが、期間は固定のままである
MetaTrader 5ターミナルを使用すると、この改良版戦略を視覚的に確認できます。なお、最初に適用したインジケーターを削除したり、新しいチャートに変更したりする必要はありません。これまで通りのチャート上に、ボリンジャーバンドの2つ目のインスタンスを適用するだけで構いません。

図7:古典的ボリンジャーバンド戦略を再構想した改良版の可視化
改良版戦略の実装
これで、ダブルボリンジャーバンド戦略の改良部分を実装する準備が整いました。 //--- If we have no open positions if(PositionsTotal() == 0) { //--- Is the close price above the highest band? Buy if(close > bb_reading_u[0]) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),TradeInfoHandler.GetBid()-padding,TradeInfoHandler.GetBid()+padding); //--- Is the close price below the lowest band? Sell else if(close < bb_reading_l[0]) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),TradeInfoHandler.GetAsk()+padding,TradeInfoHandler.GetAsk()-padding); //--- Is the close price above the mid-high band? Sell else if((close < bb_reading_u[0]) && (close > bb_reading_l[0]) && (close > bb_reading_u_2[0])) Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),TradeInfoHandler.GetAsk()+padding,TradeInfoHandler.GetAsk()-padding); //--- Is the close price below the mid-low band? Buy else if((close < bb_reading_u[0]) && (close > bb_reading_l[0]) && (close < bb_reading_l_2[0])) Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),TradeInfoHandler.GetBid()-padding,TradeInfoHandler.GetBid()+padding); }
バックテストは、2020年1月から2025年3月までの同じ5年間の期間で実施します。

図8:改良版ダブルボリンジャーバンド戦略のテスト
バックテストでは、実際のティックデータに基づき、各ポジション取得時にランダムな遅延設定を使用して、市場のレイテンシーや予測不能性をより忠実に再現します。

図9:実際の市場条件を信頼性高く再現するためにランダム遅延設定を選択する
改良版戦略の詳細統計を確認すると、全体的に明確な変化が見られます。総純利益はプラスに転じ、古典的システムと比べて絶対値でほぼ倍増しています。期待利益も上昇し、プロフィットファクターは改善、利益を出した取引の割合もより魅力的な56%に増加しています。通常、戦略を改良すると取引回数が減少する一方で収益性が向上することがあります。しかし今回の場合、取引総数と純利益の両方が増加しています。これは、改良版が古典的システムでは捉えられなかった追加のシグナルを見つけ出していることを示唆しています。

図10:改良版戦略により、取引アプリケーションのパフォーマンス統計が大幅に改善した
最後に、改良版戦略のエクイティカーブを見ると、以前の負の傾向は完全に改善され、プラス方向に転換しています。新しいエクイティカーブは、古いカーブでは到達不可能だった高値に達しており、その成長は持続的です。

図11:改良版戦略によって生成されたエクイティカーブは最終的に収益性を示す
結論
記事を読み終えると、読者は、自分が理解していると思っていた古典的な取引戦略でさえも、まだ気づいていない可能性を秘めていることに気付くでしょう。
MQL5 APIを活用することで、これらの戦略を再構想することが可能です。明確に定義されたメソッドや各種ユーティリティを使えば、APIの規模が固定されているにもかかわらず、幅広い取引アプリケーションを構築できます。実際、MQL5 APIは毎週のように新しいアップデートが追加され、MQL5フレームワークは常に進化しています。
しかし、コアユーティリティだけでも、古典的戦略が持つ盲点を補完できる新しいアプリケーションを継続的に作り出すことが可能です。MQL5 APIは、広範かつ動的なレベルのユーティリティを提供しており、市場の状態変化をより意識したアプリケーションを構築するために必要な機能を備えています。これは、静的で二値的な視点に偏りがちだった古典的戦略とは異なるアプローチです。
| ファイル名 | ファイルの説明 |
|---|---|
| DBB.mq5 | 改良版のダブルボリンジャーバンド戦略で、バックテスト結果を改善するために実装したもの |
| DBB_Benchmark.mq5 | 古典的ボリンジャーバンド戦略の実装版 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19418
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5での取引戦略の自動化(第36回):リテストとインパルスモデルによる需給取引
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5取引ツール(第9回):EA向けスクロール可能ガイド付き初回実行ユーザー設定ウィザードの開発
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索