English
preview
MQL5での取引戦略の自動化(第35回):ブレーカーブロック取引システムの作成

MQL5での取引戦略の自動化(第35回):ブレーカーブロック取引システムの作成

MetaTrader 5トレーディング |
21 1
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第34回)では、MetaQuotes Language 5 (MQL5)を用いてトレンドラインブレイクアウトシステムを開発しました。このシステムでは、スイングポイントを使用してサポートおよびレジスタンスのトレンドラインを特定し、R²(決定係数)による適合度で検証した上で、ブレイクアウト取引を実行し、チャート上で動的に可視化しました。本記事(第35回)では、ブレーカーブロック取引システムを作成します。本システムは、レンジ相場を検出し、スイングポイントでブレーカーブロックを検証した上で、リテスト取引を実行できるカスタマイズ可能なリスクパラメータと視覚的フィードバックを備えています。本記事では以下のトピックを扱います。

  1. ブレーカーブロック戦略フレームワークを理解する
  2. MQL5での実装
  3. バックテスト
  4. 結論

この記事を読み終える頃には、ブレーカーブロックのリテスト取引をおこなうMQL5戦略が動作する状態で完成し、カスタマイズも可能になっています。それでは、さっそく始めましょう。


ブレーカーブロック戦略フレームワークを理解する

ブレーカーブロック戦略とは、価格が狭いレンジ内で推移するレンジ相場を特定し、その後のブレイクアウトとインパルスムーブ(勢いのある値動き)を捉える取引戦略です。このとき形成されるオーダーブロックが無効化されると、ブレーカーブロックとして機能し、リテスト取引の候補となります。戦略の基本コンセプトは、価格が大きく動いた後にこれらのブロックに戻ってくるタイミングを捉え、ブレイクアウト方向への取引を、定義されたストップロスとテイクプロフィットとともに実行することです。さらに、チャート上で視覚的に確認できる要素を組み合わせて分析精度を高めます。以下に、代表的なブレーカーブロックの種類を示します。

弱気ブレーカーブロック

弱気ブレーカーブロック

強気ブレーカーブロック

強気ブレーカーブロック

戦略では、まず指定した本数のバーを分析してレンジ相場を検出し、価格がレンジを抜けたタイミングでブレイクアウトを特定します。その後、乗数(マルチプライヤー)ベースの閾値を用いてインパルスムーブを確認します。さらに、スイングポイントを用いたブレーカーブロックの検証ロジックを実装し、リテスト時にカスタマイズ可能なパラメータで取引を実行します。ブロックは動的なラベルや矢印で可視化し、ブレーカーブロックの発生と取引機会を特定するシステムを構築します。簡単に言うと、下図のように目標を視覚的に表現できます。

ブレーカーブロックフレームワーク


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                                    Breaker Block Strategy EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

#include <Trade/Trade.mqh>                         //--- Include Trade library for position management
CTrade obj_Trade;                                  //--- Instantiate trade object for order operations

実装は、まず「#include <Trade/Trade.mqh>」を使用して取引ライブラリを読み込むことから始めます。このライブラリには、売買などの取引操作を管理するための組み込み関数が用意されています。次に、CTradeクラスを用いてobj_Tradeという取引オブジェクトを初期化します。これにより、エキスパートアドバイザー(EA)は売買注文をプログラムから自動的に実行できるようになります。この設定により、手動での操作を必要とせずに効率的な注文実行が可能となります。その後、ユーザーがユーザインタフェース(UI)から動作を変更・制御できるよう、外部入力パラメータを定義します。

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input double tradeLotSize = 0.01;                  // Trade size for each position in lots
input bool   enableTrading = true;                 // Toggle to enable or disable automated trading
input bool   enableTrailingStop = true;            // Toggle to enable or disable trailing stop
input double trailingStopPoints = 30;              // Distance in points for trailing stop adjustment
input double minProfitToTrail = 50;                // Minimum profit in points before trailing starts
input int    uniqueMagicNumber = 12345;            // Unique identifier for EA trades
input int    consolidationBars = 7;                // Number of bars to check for consolidation range
input double maxConsolidationSpread = 50;          // Maximum allowed spread in points for consolidation
input int    barsToWaitAfterBreakout = 3;          // Bars to wait after breakout before impulse check
input double impulseMultiplier = 1.0;              // Multiplier for detecting impulsive price moves
input double stopLossDistance = 1500;              // Stop loss distance in points from entry
input double takeProfitDistance = 1500;            // Take profit distance in points from entry
input double moveAwayDistance = 50;                // Distance in points for price to move away post-invalidation
input color  bullishColor = clrGreen;              // Base color for bullish order/breaker blocks
input color  bearishColor = clrRed;                // Base color for bearish order/breaker blocks
input color  labelTextColor = clrBlack;            // Color for text labels on blocks
input bool   enableSwingValidation = true;         // Enable validation of swing points for block invalidation
input bool   showSwingPoints = true;               // Show swing point labels if validation enabled
input color  swingLabelColor = clrWhite;           // Color for swing point labels
input int    swingFontSize = 10;                   // Font size for swing point labels

ここでは、プログラムの動作を設定するための入力パラメータを定義します。tradeLotSizeはポジションサイズを指定するもので、enableTradingとenableTrailingStopはそれぞれ売買の実行とトレーリングストップの有効・無効を制御します。さらに、trailingStopPointsとminProfitToTrailを使用することで、トレーリングストップの挙動をより細かく調整できます。uniqueMagicNumberは、EAが発注したポジションを識別するための一意の識別子です。レンジの検出にはconsolidationBarsとmaxConsolidationSpreadを用い、ブレイクアウトの判定にはbarsToWaitAfterBreakoutとimpulseMultiplierを使って価格の勢いとタイミングを評価します。それ以外の入力項目は直感的に理解しやすい内容です。理解を容易にするためにコメントを追加しました。最後に、システム全体の制御に必要なグローバル変数を定義していきます。

//+------------------------------------------------------------------+
//| Structure for price and index                                    |
//+------------------------------------------------------------------+
struct PriceAndIndex {                             //--- Define structure for price and index data
   double price;                                   //--- Store price value (high or low)
   int    index;                                   //--- Store bar index of price
};

//+------------------------------------------------------------------+
//| Global variables for market state tracking                       |
//+------------------------------------------------------------------+
PriceAndIndex rangeHighestHigh = {0, 0};           //--- Track highest high in consolidation range
PriceAndIndex rangeLowestLow = {0, 0};             //--- Track lowest low in consolidation range
bool   isBreakoutDetected = false;                 //--- Flag for breakout detection
double lastImpulseLow = 0.0;                       //--- Store low price after breakout for impulse
double lastImpulseHigh = 0.0;                      //--- Store high price after breakout for impulse
int    breakoutBarNumber = -1;                     //--- Store bar index of breakout
datetime breakoutTimestamp = 0;                    //--- Store timestamp of breakout
string   blockNames[];                             //--- Store names of block objects
datetime blockEndTimes[];                          //--- Store end times of blocks
bool     invalidatedStatus[];                      //--- Track invalidation status of blocks
string   blockTypes[];                             //--- Track block types (OB/BB, bullish/bearish)
bool     movedAwayStatus[];                        //--- Track if price moved away after invalidation
bool     retestedStatus[];                         //--- Track if block was retested
string   blockLabels[];                            //--- Store label object names for blocks
datetime creationTimes[];                          //--- Store creation times of blocks
datetime invalidationTimes[];                      //--- Store invalidation times of blocks
double   invalidationSwings[];                     //--- Store swing high/low at invalidation
bool     isBullishImpulse = false;                 //--- Flag for bullish impulsive move
bool     isBearishImpulse = false;                 //--- Flag for bearish impulsive move
#define OB_Prefix "OB REC "                        //--- Define prefix for order block names

ここでは、システムのデータ構造と状態管理を確立します。まず、PriceAndIndexという構造体を定義し、価格値(高値または安値)と対応するバーのインデックスを保持できるようにします。これにより、レンジ相場の境界を正確に追跡できます。次に、グローバル変数を初期化します。rangeHighestHighとrangeLowestLowはPriceAndIndexのインスタンスとして、レンジ内の最高値と最安値を保持します。isBreakoutDetectedはブレイクアウトの発生を示すフラグとしてfalseで初期化し、lastImpulseLowとlastImpulseHighはブレイクアウト後のインパルスムーブの確認用に0.0で初期化します。ブレイクアウトのタイミングを追跡するため、breakoutBarNumberは-1、breakoutTimestampは0で設定します。さらに、配列としてblockNames、blockEndTimes、invalidatedStatus、blockTypes、movedAwayStatus、retestedStatus、blockLabels、creationTimes、invalidationTimes、invalidationSwingsを用意し、ブロックオブジェクトの管理、期限切れや無効化の状態、種類(オーダーブロックまたはブレーカーブロック、ブルリッシュまたはベアリッシュ)、リテスト状況、関連するスイングポイントを扱います。

最後に、オーダーブロックオブジェクトを一貫した名前で管理するために、OB_Prefixを「OB REC」として定義します。次に、無効化されたオーダーブロックの色を暗くするヘルパー関数や、イベントハンドラを扱うための関数を定義していきます。

//+------------------------------------------------------------------+
//| Darken color by factor                                           |
//+------------------------------------------------------------------+
color DarkenColor(color colorValue, double factor = 0.8) {
   int red = int((colorValue & 0xFF) * factor);          //--- Extract and darken red component
   int green = int(((colorValue >> 8) & 0xFF) * factor); //--- Extract and darken green component
   int blue = int(((colorValue >> 16) & 0xFF) * factor); //--- Extract and darken blue component
   return (color)(red | (green << 8) | (blue << 16));    //--- Combine components into color
}

//+------------------------------------------------------------------+
//| Price data accessors                                             |
//+------------------------------------------------------------------+
double high(int index) {
   return iHigh(_Symbol, _Period, index);         //--- Return high price for specified index
}
double low(int index) {
   return iLow(_Symbol, _Period, index);          //--- Return low price for specified index
}
double close(int index) {
   return iClose(_Symbol, _Period, index);        //--- Return close price for specified index
}
datetime time(int index) {
   return iTime(_Symbol, _Period, index);         //--- Return time for specified index
}


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(uniqueMagicNumber); //--- Set magic number for trade identification
   return(INIT_SUCCEEDED);                            //--- Return initialization success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   ObjectsDeleteAll(0, OB_Prefix);                //--- Remove all objects with OB prefix
   ChartRedraw(0);                                //--- Redraw chart to clear objects
}

ここからは、システムのユーティリティおよびライフサイクル管理用の関数を実装していきます。まず、DarkenColor関数を作成します。この関数はカラー値と任意の係数(デフォルトは0.8)を受け取り、ビット演算を使って赤、緑、青の成分を抽出します(colorValue & 0xFF、(colorValue >> 8) & 0xFF、(colorValue >> 16) & 0xFF)。抽出した各成分を係数で暗くし、再びビットシフトとOR演算で組み合わせる(red | (green << 8) | (blue << 16))ことで、無効化されたブロックを視覚的に区別するための暗色を返します。

次に、アクセサ関数としてhigh、low、close、timeを作成します。これらは、現在のシンボルと時間足に対して指定したバーインデックスの高値(iHigh)、安値(iLow)、終値(iClose)、およびタイムスタンプ(iTime)を返し、価格データの取得を簡素化します。その後、OnInit関数内では、obj_Tradeに対してSetExpertMagicNumberを呼び出し、uniqueMagicNumberで取引にタグを付けて識別できるようにし、初期化が成功した場合はINIT_SUCCEEDEDを返します。OnDeinit関数では、ObjectsDeleteAllを用いてOB_Prefixで作成されたすべてのチャートオブジェクトを削除し、ChartRedrawを呼び出してチャートを更新することで、リソースをクリーンに解放します。これで、次はメインのOnTickイベントハンドラに進み、システムの主要な制御ロジックを実装する準備が整いました。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static bool isNewBar = false;                  //--- Track new bar status
   int currentBarCount = iBars(_Symbol, _Period); //--- Get current bar count
   static int previousBarCount = currentBarCount; //--- Store previous bar count
   if (previousBarCount == currentBarCount) {     //--- Check if no new bar
      isNewBar = false;                           //--- Set no new bar
   } else {                                       //--- New bar detected
      isNewBar = true;                            //--- Set new bar flag
      previousBarCount = currentBarCount;         //--- Update previous bar count
   }
   if (!isNewBar) return;                         //--- Exit if not new bar
   int startBarIndex = 1;                         //--- Set start index for analysis
   int chartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get chart zoom scale
   int dynamicFontSize = 8 + (chartScale * 2);    //--- Calculate dynamic font size
   if (!isBreakoutDetected) {                     //--- Check if no breakout detected
      if (rangeHighestHigh.price == 0 && rangeLowestLow.price == 0) { //--- Check if range not set
         bool isConsolidated = true;              //--- Assume consolidation
         for (int i = startBarIndex; i < startBarIndex + consolidationBars - 1; i++) { //--- Iterate consolidation bars
            if (MathAbs(high(i) - high(i + 1)) > maxConsolidationSpread * Point() || MathAbs(low(i) - low(i + 1)) > maxConsolidationSpread * Point()) { //--- Check spread
               isConsolidated = false;            //--- Mark as not consolidated
               break;                             //--- Exit loop
            }
         }
         if (isConsolidated) {                    //--- Confirm consolidation
            rangeHighestHigh.price = high(startBarIndex); //--- Set initial high
            rangeHighestHigh.index = startBarIndex; //--- Set high index
            for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { //--- Find highest high
               if (high(i) > rangeHighestHigh.price) { //--- Check higher high
                  rangeHighestHigh.price = high(i); //--- Update highest high
                  rangeHighestHigh.index = i;    //--- Update high index
               }
            }
            rangeLowestLow.price = low(startBarIndex); //--- Set initial low
            rangeLowestLow.index = startBarIndex; //--- Set low index
            for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { //--- Find lowest low
               if (low(i) < rangeLowestLow.price) { //--- Check lower low
                  rangeLowestLow.price = low(i);  //--- Update lowest low
                  rangeLowestLow.index = i;       //--- Update low index
               }
            }
            Print("Consolidation range established: Highest High = ", rangeHighestHigh.price, " at index ", rangeHighestHigh.index, " and Lowest Low = ", rangeLowestLow.price, " at index ", rangeLowestLow.index); //--- Log range
         }
      } else {                                    //--- Range already set
         double currentHigh = high(1);            //--- Get current high
         double currentLow = low(1);              //--- Get current low
         if (currentHigh <= rangeHighestHigh.price && currentLow >= rangeLowestLow.price) { //--- Check within range
            Print("Range extended: High = ", currentHigh, ", Low = ", currentLow); //--- Log range extension
         } else {                                 //--- Outside range
            Print("No extension: Bar outside range."); //--- Log no extension
         }
      }
   }
}

まず、レンジ相場を検出するロジックを定義します。OnTick関数内では、iBarsで取得した現在のバー数と静的変数previousBarCountを比較することで新しいバーを追跡します。新しいバーが検出された場合はisNewBarをtrueに設定し、previousBarCountを更新します。新しいバーがなければisNewBarをfalseにして処理を終了します。その後、ChartGetIntegerを用いてチャートのズームスケール(CHART_SCALE)を取得し、ラベルのサイズを動的に調整するためにdynamicFontSizeを8にスケールの2倍を加えた値として計算します。ブレイクアウトが検出されていない場合(isBreakoutDetectedがfalse)かつレンジが設定されていない場合(rangeHighestHigh.priceおよびrangeLowestLow.priceが0)、レンジ相場をチェックします。具体的には、startBarIndexを1としてconsolidationBars本分のバーをループし、隣接するバーの高値・安値の差が「maxConsolidationSpread * Point()」以内であることをMathAbsで確認します。条件を超える場合はisConsolidatedをfalseに設定します。

レンジ相場が成立した場合、rangeHighestHigh.priceとrangeLowestLow.priceにstartBarIndexの高値と安値を設定し、その後consolidationBars本分のバーをループして、最も高い高値と最も低い安値およびそのインデックスを更新します。レンジはPrint関数でログに記録されます。既存のレンジがある場合は、現在のバーの高値・安値(high(1)、low(1))がrangeHighestHigh.priceとrangeLowestLow.priceの範囲内にあるかを確認し、範囲内であれば拡張として、範囲外であれば拡張なしとしてログに記録します。この時点で、これらの価格情報を用いてオーダーブロックの検出、可視化、管理をおこなう準備が整います。ブレーカーブロックとして利用する前に、まずオーダーブロックを無効化する必要があるためです。

if (rangeHighestHigh.price > 0 && rangeLowestLow.price > 0) { //--- Check if range defined
   double currentClosePrice = close(1);        //--- Get current close price
   if (currentClosePrice > rangeHighestHigh.price) { //--- Check upward breakout
      Print("Upward breakout at ", currentClosePrice, " > ", rangeHighestHigh.price); //--- Log breakout
      isBreakoutDetected = true;               //--- Set breakout flag
   } else if (currentClosePrice < rangeLowestLow.price) { //--- Check downward breakout
      Print("Downward breakout at ", currentClosePrice, " < ", rangeLowestLow.price); //--- Log breakout
      isBreakoutDetected = true;               //--- Set breakout flag
   }
}
if (isBreakoutDetected) {                      //--- Process breakout
   Print("Breakout detected. Resetting for the next range."); //--- Log reset
   breakoutBarNumber = 1;                      //--- Set breakout bar index
   breakoutTimestamp = TimeCurrent();          //--- Set breakout timestamp
   lastImpulseHigh = rangeHighestHigh.price;   //--- Store high for impulse check
   lastImpulseLow = rangeLowestLow.price;      //--- Store low for impulse check
   isBreakoutDetected = false;                 //--- Reset breakout flag
   rangeHighestHigh.price = 0;                 //--- Clear highest high
   rangeHighestHigh.index = 0;                 //--- Clear high index
   rangeLowestLow.price = 0;                   //--- Clear lowest low
   rangeLowestLow.index = 0;                   //--- Clear low index
}
if (breakoutBarNumber >= 0 && TimeCurrent() > breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds()) { //--- Check impulse window
   double impulseRange = lastImpulseHigh - lastImpulseLow; //--- Calculate impulse range
   double impulseThresholdPrice = impulseRange * impulseMultiplier; //--- Calculate impulse threshold
   isBullishImpulse = false;                   //--- Reset bullish impulse flag
   isBearishImpulse = false;                   //--- Reset bearish impulse flag
   for (int i = 1; i <= barsToWaitAfterBreakout; i++) { //--- Check bars for impulse
      double closePrice = close(i);            //--- Get close price
      if (closePrice >= lastImpulseHigh + impulseThresholdPrice) { //--- Check bullish impulse
         isBullishImpulse = true;              //--- Set bullish impulse flag
         Print("Impulsive upward move: ", closePrice, " >= ", lastImpulseHigh + impulseThresholdPrice); //--- Log bullish impulse
         break;                                //--- Exit loop
      } else if (closePrice <= lastImpulseLow - impulseThresholdPrice) { //--- Check bearish impulse
         isBearishImpulse = true;              //--- Set bearish impulse flag
         Print("Impulsive downward move: ", closePrice, " <= ", lastImpulseLow - impulseThresholdPrice); //--- Log bearish impulse
         break;                                //--- Exit loop
      }
   }
   if (!isBullishImpulse && !isBearishImpulse) { //--- Check no impulse
      Print("No impulsive movement detected."); //--- Log no impulse
   }
   bool isOrderBlockValid = isBearishImpulse || isBullishImpulse; //--- Validate order block
   if (isOrderBlockValid) {                    //--- Process valid order block
      datetime blockStartTime = iTime(_Symbol, _Period, consolidationBars + barsToWaitAfterBreakout + 1); //--- Set block start time
      double blockTopPrice = lastImpulseHigh;  //--- Set block top price
      int visibleBarsOnChart = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars
      datetime blockEndTime = blockStartTime + (visibleBarsOnChart / 1) * PeriodSeconds(); //--- Set block end time
      double blockBottomPrice = lastImpulseLow; //--- Set block bottom price
      string blockName = OB_Prefix + "(" + TimeToString(blockStartTime) + ")"; //--- Generate block name
      color blockColor = isBullishImpulse ? bullishColor : bearishColor; //--- Set block color
      string blockLabel = isBullishImpulse ? "Bullish Order Block" : "Bearish Order Block"; //--- Set block label
      string blockType = isBullishImpulse ? "OB-bullish" : "OB-bearish"; //--- Set block type
      if (ObjectFind(0, blockName) < 0) {      //--- Check if block exists
         ObjectCreate(0, blockName, OBJ_RECTANGLE, 0, blockStartTime, blockTopPrice, blockEndTime, blockBottomPrice); //--- Create block rectangle
         ObjectSetInteger(0, blockName, OBJPROP_TIME, 0, blockStartTime); //--- Set start time
         ObjectSetDouble(0, blockName, OBJPROP_PRICE, 0, blockTopPrice); //--- Set top price
         ObjectSetInteger(0, blockName, OBJPROP_TIME, 1, blockEndTime); //--- Set end time
         ObjectSetDouble(0, blockName, OBJPROP_PRICE, 1, blockBottomPrice); //--- Set bottom price
         ObjectSetInteger(0, blockName, OBJPROP_FILL, true); //--- Enable fill
         ObjectSetInteger(0, blockName, OBJPROP_COLOR, blockColor); //--- Set block color
         ObjectSetInteger(0, blockName, OBJPROP_BACK, false); //--- Set to foreground
         datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate label time
         double labelPrice = (blockTopPrice + blockBottomPrice) / 2; //--- Calculate label price
         string labelObjectName = blockName + " Label"; //--- Generate label name
         ObjectCreate(0, labelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create label
         ObjectSetString(0, labelObjectName, OBJPROP_TEXT, blockLabel); //--- Set label text
         ObjectSetInteger(0, labelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color
         ObjectSetInteger(0, labelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size
         ObjectSetInteger(0, labelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor
         ChartRedraw(0);                       //--- Redraw chart
         ArrayResize(blockNames, ArraySize(blockNames) + 1); //--- Resize block names array
         blockNames[ArraySize(blockNames) - 1] = blockName; //--- Add block name
         ArrayResize(blockEndTimes, ArraySize(blockEndTimes) + 1); //--- Resize end times array
         blockEndTimes[ArraySize(blockEndTimes) - 1] = blockEndTime; //--- Add end time
         ArrayResize(invalidatedStatus, ArraySize(invalidatedStatus) + 1); //--- Resize invalidated status
         invalidatedStatus[ArraySize(invalidatedStatus) - 1] = false; //--- Set invalidated status
         ArrayResize(blockTypes, ArraySize(blockTypes) + 1); //--- Resize block types array
         blockTypes[ArraySize(blockTypes) - 1] = blockType; //--- Add block type
         ArrayResize(movedAwayStatus, ArraySize(movedAwayStatus) + 1); //--- Resize moved away status
         movedAwayStatus[ArraySize(movedAwayStatus) - 1] = false; //--- Set moved away status
         ArrayResize(retestedStatus, ArraySize(retestedStatus) + 1); //--- Resize retested status
         retestedStatus[ArraySize(retestedStatus) - 1] = false; //--- Set retested status
         ArrayResize(blockLabels, ArraySize(blockLabels) + 1); //--- Resize block labels array
         blockLabels[ArraySize(blockLabels) - 1] = labelObjectName; //--- Add label name
         ArrayResize(creationTimes, ArraySize(creationTimes) + 1); //--- Resize creation times
         creationTimes[ArraySize(creationTimes) - 1] = time(1); //--- Set creation time
         ArrayResize(invalidationTimes, ArraySize(invalidationTimes) + 1); //--- Resize invalidation times
         invalidationTimes[ArraySize(invalidationTimes) - 1] = 0; //--- Set invalidation time
         ArrayResize(invalidationSwings, ArraySize(invalidationSwings) + 1); //--- Resize invalidation swings
         invalidationSwings[ArraySize(invalidationSwings) - 1] = 0.0; //--- Set invalidation swing
         Print("Order Block created: ", blockName); //--- Log block creation
      }
   }
   breakoutBarNumber = -1;                     //--- Reset breakout bar
   breakoutTimestamp = 0;                      //--- Reset breakout timestamp
   lastImpulseHigh = 0;                        //--- Reset impulse high
   lastImpulseLow = 0;                         //--- Reset impulse low
   isBullishImpulse = false;                   //--- Reset bullish impulse
   isBearishImpulse = false;                   //--- Reset bearish impulse
}

ここでは、ブレイクアウトの検出とオーダーブロック作成のロジックを実装します。まず、レンジ相場が定義されているかを確認します(rangeHighestHigh.priceおよびrangeLowestLow.priceが0より大きい場合)。次に、現在のバーの終値(close(1))を取得し、これがrangeHighestHigh.priceを上回っていれば上方向のブレイクアウトとしてログに記録し、isBreakoutDetectedをtrueに設定します。逆に、rangeLowestLow.priceを下回っていれば下方向のブレイクアウトとしてログに記録し、同様にフラグを立てます。isBreakoutDetectedがtrueの場合、ブレイクアウトをログに記録し、breakoutBarNumberを1、breakoutTimestampをTimeCurrentに設定します。さらに、lastImpulseHighとlastImpulseLowを保存し、レンジ変数とブレイクアウトフラグをリセットします。

その後、breakoutBarNumberが0以上でかつ現在の時間が「breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds」を超えている場合、インパルスレンジ(lastImpulseHigh - lastImpulseLow)および閾値(impulseRange * impulseMultiplier)を計算します。そして、barsToWaitAfterBreakout本のバーの中で、終値が「lastImpulseHigh + impulseThresholdPrice」を超えていればisBullishImpulse、「lastImpulseLow - impulseThresholdPrice」を下回っていればisBearishImpulseとして判定します。

インパルスが検出されなければログに記録し、ブルリッシュまたはベアリッシュのインパルスが有効であれば、ObjectCreate (OBJ_RECTANGLE)を用いてオーダーブロックを作成します。開始時間にはiTime、上下の価格にはlastImpulseHighとlastImpulseLowを設定し、終了時間はChartGetInteger(CHART_VISIBLE_BARS)で算出します。プロパティとしてOBJPROP_FILLやOBJPROP_COLOR(bullishColorまたはbearishColor)を設定し、中央にラベルをOBJ_TEXTで配置します。さらに、配列blockNames、blockEndTimes、invalidatedStatus、blockTypes、movedAwayStatus、retestedStatus、blockLabels、creationTimes、invalidationSwingsを更新します。最後に、ブレイクアウト関連の変数をリセットします。これにより、ブレイクアウトを検出しオーダーブロックを可視化するシステムが構築されます。コンパイルすると、次の結果が得られます。

初期オーダーブロック検出

これで初期オーダーブロックを検出できるようになったため、次に、該当する価格ブレイクアウトが発生した際にそれらを無効化としてマークし、プライスアクションのスイングポイントで無効化を確認するロジックを定義します。無効化されたブロックは、視覚的に区別するために色を暗くします。

for (int j = ArraySize(blockNames) - 1; j >= 0; j--) { //--- Iterate through blocks
   string currentBlockName = blockNames[j];    //--- Get current block name
   bool doesBlockExist = false;                //--- Initialize block existence flag
   double blockHigh = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 0); //--- Get block high
   double blockLow = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 1); //--- Get block low
   datetime blockStartTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 0); //--- Get block start
   datetime blockEndTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 1); //--- Get block end
   color blockCurrentColor = (color)ObjectGetInteger(0, currentBlockName, OBJPROP_COLOR); //--- Get block color
   if (time(1) < blockEndTime) {               //--- Check if block still valid
      doesBlockExist = true;                   //--- Set block exists
   }
   if (StringFind(blockTypes[j], "OB-") == 0 && !invalidatedStatus[j]) { //--- Check valid order block
      bool invalidated = false;                //--- Initialize invalidation flag
      string newBlockType = "";                //--- Initialize new block type
      color invalidatedColor = clrNONE;        //--- Initialize invalidated color
      string newLabel = "";                    //--- Initialize new label
      bool isForBullishBB = false;             //--- Initialize bullish breaker block flag
      double breakPrice = 0.0;                 //--- Initialize break price
      int arrowCode = 0;                       //--- Initialize arrow code
      int anchor = 0;                          //--- Initialize anchor
      if (blockTypes[j] == "OB-bearish" && close(1) > blockHigh) { //--- Check bearish block invalidation
         isForBullishBB = true;                //--- Set bullish breaker block
         breakPrice = blockHigh;               //--- Set break price
         arrowCode = 233;                      //--- Set upward arrow
         anchor = ANCHOR_BOTTOM;               //--- Set bottom anchor
         newBlockType = "Invalidated-bearish"; //--- Set invalidated type
         invalidatedColor = DarkenColor(bearishColor); //--- Darken bearish color
         newLabel = "Invalidated Bearish Order Block"; //--- Set invalidated label
      } else if (blockTypes[j] == "OB-bullish" && close(1) < blockLow) { //--- Check bullish block invalidation
         isForBullishBB = false;               //--- Set bearish breaker block
         breakPrice = blockLow;                //--- Set break price
         arrowCode = 234;                      //--- Set downward arrow
         anchor = ANCHOR_TOP;                  //--- Set top anchor
         newBlockType = "Invalidated-bullish"; //--- Set invalidated type
         invalidatedColor = DarkenColor(bullishColor); //--- Darken bullish color
         newLabel = "Invalidated Bullish Order Block"; //--- Set invalidated label
      } else {                                 //--- No invalidation
         continue;                             //--- Skip to next block
      }
      bool validSwingForInvalidation = true;   //--- Assume valid swing
      int swingShift = -1;                     //--- Initialize swing shift
      double swingPrice = 0.0;                 //--- Initialize swing price
      if (enableSwingValidation) {             //--- Check swing validation
         int creationShift = iBarShift(_Symbol, _Period, creationTimes[j], false); //--- Get creation bar shift
         if (creationShift > 1) {              //--- Ensure enough bars
            double extreme = isForBullishBB ? blockLow : blockHigh; //--- Set extreme price
            bool isBearishOB = isForBullishBB; //--- Set bearish OB flag
            if (isBearishOB) {                 //--- Handle bearish OB
               double minLow = extreme;        //--- Initialize minimum low
               for (int k = creationShift - 1; k > 1; k--) { //--- Find lower low
                  if (low(k) < minLow) {       //--- Check lower low
                     minLow = low(k);          //--- Update minimum low
                     swingShift = k;           //--- Update swing shift
                  }
               }
               validSwingForInvalidation = minLow < extreme; //--- Validate swing
               swingPrice = minLow;            //--- Set swing price
            } else {                           //--- Handle bullish OB
               double maxHigh = extreme;       //--- Initialize maximum high
               for (int k = creationShift - 1; k > 1; k--) { //--- Find higher high
                  if (high(k) > maxHigh) {     //--- Check higher high
                     maxHigh = high(k);        //--- Update maximum high
                     swingShift = k;           //--- Update swing shift
                  }
               }
               validSwingForInvalidation = maxHigh > extreme; //--- Validate swing
               swingPrice = maxHigh;           //--- Set swing price
            }
         } else {                              //--- Insufficient bars
            validSwingForInvalidation = false; //--- Invalidate swing
         }
      }
      if (validSwingForInvalidation) {        //--- Confirm swing validation
         invalidated = true;                  //--- Set invalidated flag
      }
      if (invalidated) {                      //--- Process invalidation
         ObjectSetInteger(0, currentBlockName, OBJPROP_COLOR, invalidatedColor); //--- Update block color
         ObjectDelete(0, blockLabels[j]);     //--- Delete old label
         datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate new label time
         double labelPrice = (blockHigh + blockLow) / 2; //--- Calculate new label price
         string newLabelObjectName = currentBlockName + " Label"; //--- Generate new label name
         ObjectCreate(0, newLabelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create new label
         ObjectSetString(0, newLabelObjectName, OBJPROP_TEXT, newLabel); //--- Set label text
         ObjectSetInteger(0, newLabelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color
         ObjectSetInteger(0, newLabelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size
         ObjectSetInteger(0, newLabelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor
         string arrowName = currentBlockName + "_break_arrow"; //--- Generate arrow name
         if (ObjectFind(0, arrowName) < 0) {  //--- Check if arrow exists
            ObjectCreate(0, arrowName, OBJ_ARROW, 0, time(1), breakPrice); //--- Create break arrow
            ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode); //--- Set arrow code
            ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, anchor); //--- Set arrow anchor
            ObjectSetInteger(0, arrowName, OBJPROP_COLOR, invalidatedColor); //--- Set arrow color
         }
         if (enableSwingValidation && showSwingPoints && swingShift > 0) { //--- Check swing point display
            string swingLabelName = currentBlockName + "_invalid_swing"; //--- Generate swing label name
            if (ObjectFind(0, swingLabelName) < 0) { //--- Check if swing label exists
               datetime swingTime = time(swingShift); //--- Get swing time
               ObjectCreate(0, swingLabelName, OBJ_TEXT, 0, swingTime, swingPrice); //--- Create swing label
               string swingText = isForBullishBB ? "LL" : "HH"; //--- Set swing text
               ObjectSetString(0, swingLabelName, OBJPROP_TEXT, swingText); //--- Set swing label text
               ObjectSetInteger(0, swingLabelName, OBJPROP_COLOR, swingLabelColor); //--- Set swing label color
               ObjectSetInteger(0, swingLabelName, OBJPROP_FONTSIZE, swingFontSize); //--- Set swing label font size
               ObjectSetInteger(0, swingLabelName, OBJPROP_ANCHOR, isForBullishBB ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); //--- Set swing label anchor
            }
         }
         ChartRedraw(0);                       //--- Redraw chart
         invalidatedStatus[j] = true;          //--- Set invalidated status
         blockTypes[j] = newBlockType;         //--- Update block type
         movedAwayStatus[j] = false;           //--- Reset moved away status
         retestedStatus[j] = false;            //--- Reset retested status
         blockLabels[j] = newLabelObjectName;  //--- Update label name
         invalidationTimes[j] = time(1);       //--- Set invalidation time
         invalidationSwings[j] = isForBullishBB ? high(1) : low(1); //--- Set invalidation swing
         Print("Order Block invalidated: ", currentBlockName); //--- Log invalidation
      }
   }
   if (!doesBlockExist) {                     //--- Check if block expired
      ArrayRemove(blockNames, j, 1);          //--- Remove block name
      ArrayRemove(blockEndTimes, j, 1);       //--- Remove end time
      ArrayRemove(invalidatedStatus, j, 1);   //--- Remove invalidated status
      ArrayRemove(blockTypes, j, 1);          //--- Remove block type
      ArrayRemove(movedAwayStatus, j, 1);     //--- Remove moved away status
      ArrayRemove(retestedStatus, j, 1);      //--- Remove retested status
      ArrayRemove(blockLabels, j, 1);         //--- Remove label name
      ArrayRemove(creationTimes, j, 1);       //--- Remove creation time
      ArrayRemove(invalidationTimes, j, 1);   //--- Remove invalidation time
      ArrayRemove(invalidationSwings, j, 1);  //--- Remove invalidation swing
      Print("Removed expired block at index ", j); //--- Log block removal
   }
}

オーダーブロックの管理および無効化のロジックを実装するために、まずblockNames配列を逆順にループし、各ブロックを処理します。ブロックの高値と安値はObjectGetDoubleで、開始時間と終了時間はObjectGetIntegerで取得し、現在のバーの時間(time(1))が終了時間より前であれば、そのブロックは存在すると判断します。有効なオーダーブロック(タイプがOB-で始まり、かつ無効化されていないもの)については、無効化条件を確認します。具体的には、OB-bearishの場合、終値(close(1))がブロックの高値を上回ると、上方向のブレーカーブロックを作成します。この際、ObjectCreate (OBJ_ARROW)で上向き矢印(コード233)を描画し、DarkenColorで色を暗くして区別します。同様に、OB-bullishの場合は終値がブロックの安値を下回ると、下方向のブレーカーブロックを作成し、下向き矢印(コード234)で表示します。MQL5ではWingdingsフォントの膨大なコードリストが用意されているため、必要に応じて任意の記号を選択して表示に利用することができます。

MQL5 WINGDINGS

enableSwingValidationがtrueの場合、ブロックをスイングポイントで検証します。弱気ブロックでは安値更新を、強気ブロックでは高値更新をiBarShiftおよびlowやhighを用いて確認し、有効であればObjectSetIntegerObjectCreate (OBJ_TEXT)でブロックの色やラベルを更新します。さらに、showSwingPointsが有効な場合は、スイングポイントにラベル(「LL」または「HH」)をObjectCreateで追加し、該当する時間と価格に表示します。ブロックが無効化された場合は、invalidatedStatus、blockTypes、invalidationTimesなどのブロック状態を更新し、無効化をログに記録するとともに、リテスト状態をリセットします。ブロックが存在しない場合は、ArrayRemoveを使用してinvalidatedStatusなどの状態を格納している配列から削除し、削除をログに記録します。その後、ChartRedrawを呼び出してチャートを再描画します。コンパイル後は、この処理により以下のような結果が得られるはずです。

無効なオーダーブロック

これでオーダーブロック無効化の第2ステップが完了したため、次のステップに進みます。ここでは、無効化されたオーダーブロックを追跡し、価格がそれらをリテストするのを待ち、リテストが確認されたタイミングでブレーカーブロックとしてマークします。

for (int j = ArraySize(blockNames) - 1; j >= 0; j--) { //--- Iterate invalidated blocks
   if (StringFind(blockTypes[j], "Invalidated-") != 0) continue; //--- Skip non-invalidated
   string currentBlockName = blockNames[j];    //--- Get current block name
   double blockHigh = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 0); //--- Get block high
   double blockLow = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 1); //--- Get block low
   datetime blockStartTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 0); //--- Get block start
   datetime blockEndTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 1); //--- Get block end
   bool isForBullishBB = (blockTypes[j] == "Invalidated-bearish"); //--- Check for bullish breaker block
   datetime currentBarTime = time(1);          //--- Get current bar time
   if (currentBarTime <= invalidationTimes[j]) continue; //--- Skip if same or earlier bar
   if (!movedAwayStatus[j]) {                  //--- Check if not moved away
      if (isForBullishBB && close(1) > blockHigh + moveAwayDistance * _Point) { //--- Check bullish move away
         movedAwayStatus[j] = true;           //--- Set moved away
         Print("Moved away for bullish BB setup: ", currentBlockName); //--- Log move away
      } else if (!isForBullishBB && close(1) < blockLow - moveAwayDistance * _Point) { //--- Check bearish move away
         movedAwayStatus[j] = true;           //--- Set moved away
         Print("Moved away for bearish BB setup: ", currentBlockName); //--- Log move away
      }
   }
   if (movedAwayStatus[j] && !retestedStatus[j]) { //--- Check for retest
      bool retestCondition = false;            //--- Initialize retest condition
      if (isForBullishBB && low(1) <= blockHigh && close(1) > blockHigh) { //--- Check bullish retest
         retestCondition = true;               //--- Set retest condition
      } else if (!isForBullishBB && high(1) >= blockLow && close(1) < blockLow) { //--- Check bearish retest
         retestCondition = true;               //--- Set retest condition
      }
      bool validSwingForRetest = true;         //--- Assume valid swing
      int swingShift = -1;                     //--- Initialize swing shift
      double swingPrice = 0.0;                 //--- Initialize swing price
      if (enableSwingValidation && retestCondition) { //--- Check swing validation
         int invalidShift = iBarShift(_Symbol, _Period, invalidationTimes[j], false); //--- Get invalidation shift
         if (invalidShift > 1) {               //--- Ensure enough bars
            double extreme = invalidationSwings[j]; //--- Get invalidation swing
            if (isForBullishBB) {              //--- Handle bullish breaker block
               double maxHigh = extreme;       //--- Initialize maximum high
               for (int k = invalidShift - 1; k > 1; k--) { //--- Find higher high
                  if (high(k) > maxHigh) {     //--- Check higher high
                     maxHigh = high(k);        //--- Update maximum high
                     swingShift = k;           //--- Update swing shift
                  }
               }
               validSwingForRetest = maxHigh > extreme; //--- Validate swing
               swingPrice = maxHigh;           //--- Set swing price
            } else {                           //--- Handle bearish breaker block
               double minLow = extreme;        //--- Initialize minimum low
               for (int k = invalidShift - 1; k > 1; k--) { //--- Find lower low
                  if (low(k) < minLow) {       //--- Check lower low
                     minLow = low(k);          //--- Update minimum low
                     swingShift = k;           //--- Update swing shift
                  }
               }
               validSwingForRetest = minLow < extreme; //--- Validate swing
               swingPrice = minLow;            //--- Set swing price
            }
         } else {                              //--- Insufficient bars
            validSwingForRetest = false;       //--- Invalidate swing
         }
      }
   }
}

無効化されたブレーカーブロックのリテスト検出ロジックを実装するために、まずblockNames配列を逆順にループし、blockTypesに「Invalidated-」が含まれるブロックを処理します。各ブロックについて、高値と安値はObjectGetDoubleで、開始と終了の時刻はObjectGetIntegerで取得します。ブロックが強気ブレーカーブロック(Invalidated-bearish)かどうかを判定します。現在のバーの時間(time(1))がinvalidationTimesより前であれば、そのブロックはスキップされます。まだ「移動済み」となっていないブロック(movedAwayStatusがfalse)については、終値(close(1))が強気の場合は「blockHigh + moveAwayDistance *_Point」を超え、弱気の場合は「blockLow - moveAwayDistance * _Point」を下回るかをチェックし、条件を満たせばmovedAwayStatusをtrueに設定します。

移動済みだがリテストされていないブロック(retestedStatusがfalse)については、low(1)がblockHighに到達し、かつclose(1)がその上で終値を付けた場合を強気のリテストと判定します。一方、high(1)がblockLowに到達し、かつclose(1)がその下で終値を付けた場合を弱気のリテストと判定します。さらに、enableSwingValidationが有効で、リテスト条件を満たす場合には、iBarShiftを使用して無効化バーを取得し、強気の場合は高値更新、弱気の場合はその後の安値更新を確認することでスイングを検証します。これにより、リテストの有効性(validSwingForRetest)とスイング価格(swingPrice)を設定し、価格変動後に有効なリテストの機会を特定するシステムを構築します。この仕組みにより、リテストの追跡、ブロックのブレーカーブロックへのマーキング、色の変更による視覚的区別、さらにポジションのオープンまでおこなうことが可能になります。そのためのロジックを以下に実装します。

if (retestCondition && validSwingForRetest) { //--- Confirm retest and swing
   if (enableTrading) {                  //--- Check trading enabled
      double entryPrice = 0.0;           //--- Initialize entry price
      double stopLossPrice = 0.0;        //--- Initialize stop loss
      double takeProfitPrice = 0.0;      //--- Initialize take profit
      if (isForBullishBB) {              //--- Handle bullish trade
         entryPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Set entry at ask
         stopLossPrice = entryPrice - stopLossDistance * _Point; //--- Set stop loss
         takeProfitPrice = entryPrice + takeProfitDistance * _Point; //--- Set take profit
         obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); //--- Execute buy trade
         Print("Buy trade on bullish BB retest: ", currentBlockName); //--- Log buy trade
      } else {                           //--- Handle bearish trade
         entryPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Set entry at bid
         stopLossPrice = entryPrice + stopLossDistance * _Point; //--- Set stop loss
         takeProfitPrice = entryPrice - takeProfitDistance * _Point; //--- Set take profit
         obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); //--- Execute sell trade
         Print("Sell trade on bearish BB retest: ", currentBlockName); //--- Log sell trade
      }
   }
   color bbColor = isForBullishBB ? clrBlueViolet : clrOrange; //--- Set breaker block color
   ObjectSetInteger(0, currentBlockName, OBJPROP_COLOR, bbColor); //--- Update block color
   ObjectDelete(0, blockLabels[j]);     //--- Delete old label
   string newLabel = isForBullishBB ? "Bullish Breaker Block" : "Bearish Breaker Block"; //--- Set new label
   datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate label time
   double labelPrice = (blockHigh + blockLow) / 2; //--- Calculate label price
   string newLabelObjectName = currentBlockName + " Label"; //--- Generate new label name
   ObjectCreate(0, newLabelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create new label
   ObjectSetString(0, newLabelObjectName, OBJPROP_TEXT, newLabel); //--- Set label text
   ObjectSetInteger(0, newLabelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color
   ObjectSetInteger(0, newLabelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size
   ObjectSetInteger(0, newLabelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor
   if (enableSwingValidation && showSwingPoints && swingShift > 0) { //--- Check swing point display
      string swingLabelName = currentBlockName + "_retest_swing"; //--- Generate swing label name
      if (ObjectFind(0, swingLabelName) < 0) { //--- Check if swing label exists
         datetime swingTime = time(swingShift); //--- Get swing time
         ObjectCreate(0, swingLabelName, OBJ_TEXT, 0, swingTime, swingPrice); //--- Create swing label
         string swingText = isForBullishBB ? "HH" : "LL"; //--- Set swing text
         ObjectSetString(0, swingLabelName, OBJPROP_TEXT, swingText); //--- Set swing label text
         ObjectSetInteger(0, swingLabelName, OBJPROP_COLOR, swingLabelColor); //--- Set swing label color
         ObjectSetInteger(0, swingLabelName, OBJPROP_FONTSIZE, swingFontSize); //--- Set swing label font size
         ObjectSetInteger(0, swingLabelName, OBJPROP_ANCHOR, isForBullishBB ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); //--- Set swing label anchor
      }
   }
   ChartRedraw(0);                       //--- Redraw chart
   blockTypes[j] = isForBullishBB ? "BB-bullish" : "BB-bearish"; //--- Update block type
   retestedStatus[j] = true;             //--- Set retested status
   blockLabels[j] = newLabelObjectName;  //--- Update label name
   Print("Converted to ", newLabel, ": ", currentBlockName); //--- Log conversion
}

最後に、リテストされたブレーカーブロックに対する取引と可視化のロジックを実装します。リテストが確認された場合(retestConditionとvalidSwingForRetestがtrue)かつ取引が有効な場合(enableTrading)、取引を実行します。強気のブレーカーブロック(isForBullishBB)の場合、SymbolInfoDoubleを使ってエントリーをAsk価格に設定し、ストップロスを「エントリー価格から「stopLossDistance *_Point」下、テイクプロフィットをエントリー価格から「takeProfitDistance * _Point」上に計算し、obj_Trade.Buyで買いを実行します。弱気の場合はBid価格を使用し、ストップロスを上に、テイクプロフィットを下に設定してobj_Trade.Sellで売りを実行し、それに応じてログを記録します。

その後、ブロックの見た目を更新します。強気の場合は色をclrBlueViolet、弱気の場合はclrOrangeにObjectSetIntegerで設定し、古いラベルをObjectDeleteで削除、新しいOBJ_TEXTラベル(「Bullish Breaker Block」または「Bearish Breaker Block」)をブロックの中央にObjectCreateで作成し、labelTextColorとdynamicFontSizeを適用します。さらに、enableSwingValidationとshowSwingPointsがtrueで、かつ有効なswingShiftがある場合、スイングラベル(強気は「HH」、弱気は「LL」)をスイングの時間と価格にObjectCreateで追加し、swingLabelColorとswingFontSizeを適用します。最後に、blockTypesを「BB-bullish」または「BB-bearish」に更新し、retestedStatusをtrueに設定、blockLabelsを更新、変換をログに記録し、チャートを再描画します。コンパイル後の結果は以下の通りです。

確認済みブレーカーブロック

画像から、ブレーカーブロックを検出して可視化し、取引できることが確認できます。次におこなうべきは、利益を最大化するためのトレーリングストップロジックを追加することです。これには関数を定義してコードをモジュール化します。

//+------------------------------------------------------------------+
//| Apply trailing stop to open positions                            |
//+------------------------------------------------------------------+
void applyTrailingStop(double trailingPoints, CTrade &trade_object, int magicNo = 0) {
   double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point, _Digits); //--- Calculate buy stop loss
   double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point, _Digits); //--- Calculate sell stop loss
   for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through open positions
      ulong ticket = PositionGetTicket(i);         //--- Get position ticket
      if (ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)) { //--- Verify position
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buyStopLoss > PositionGetDouble(POSITION_PRICE_OPEN) && (buyStopLoss > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { //--- Check buy trailing
            trade_object.PositionModify(ticket, buyStopLoss, PositionGetDouble(POSITION_TP)); //--- Update buy stop loss
         } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellStopLoss < PositionGetDouble(POSITION_PRICE_OPEN) && (sellStopLoss < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { //--- Check sell trailing
            trade_object.PositionModify(ticket, sellStopLoss, PositionGetDouble(POSITION_TP)); //--- Update sell stop loss
         }
      }
   }
}

//--- Call the function on every tick

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   if (enableTrailingStop) {                      //--- Check if trailing stop enabled
      applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber); //--- Apply trailing stop
   }

   //---

}

ここでは、トレーリングストップ機能を実装し、それをイベント駆動ロジックに統合します。まず、applyTrailingStop関数を開発します。この関数では、買いポジション用のストップロスを現在のBid価格(SymbolInfoDoubleSYMBOL_BID)から「trailingPoints *_Point」を差し引いた値として計算し、売りポジション用のストップロスを現在のAsk価格(SYMBOL_ASK)に「trailingPoints * _Point」を加えた値として計算します。いずれも、銘柄の桁数に合わせてNormalizeDoubleにより正規化します。次に、PositionsTotalを使用して保有中のポジションを後ろから順に反復処理し、PositionGetTicketによって各ポジションのチケット番号を取得します。その後、PositionGetStringおよびPositionGetString 関数を用いて、対象の銘柄と「magicNo」(0でない場合)に一致しているかを確認します。

買いポジション(POSITION_TYPE_BUY)の場合は、「buyStopLoss」が建値(PositionGetDouble(POSITION_PRICE_OPEN))よりも上にあり、かつ現在のストップロスより高い、またはストップロスが未設定であることを確認したうえで、trade_object.PositionModifyを使用して更新します。売りポジションの場合は、sellStopLossが建値よりも下にあり、かつ現在のストップロスより低い、または未設定であることを確認し、同様に更新します。最後に、OnTick関数内でenableTrailingStopがtrueであるかを確認し、毎ティックごとにtrailingStopPoints、obj_Trade、uniqueMagicNumberを引数としてapplyTrailingStopを呼び出し、保有中のポジションを管理します。コンパイルすると、次の結果が得られます。

トレーリングストップが有効

画像から分かるように、価格が有利な方向へ動いた場合、トレーリングストップは完全に有効化されます。以下は、弱気および強気のブレーカーブロックの両方に対する統合テストです。

統合ブレーカーブロックGIF

可視化からわかるように、プログラムはすべてのエントリー条件を正確に特定・検証し、条件が満たされた場合にはそれぞれのパラメーターでポジションをオープンしています。これで、狙い通りの動作が実現できていることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

徹底的なバックテストによって、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事では、MQL5を用いて、レンジ(保ち合い)を識別し、スイングポイントによってブレーカーブロックを検証し、カスタマイズ可能なリスクパラメータおよびトレーリングストップを用いてリテスト取引を実行するブレーカーブロック取引システムを構築しました。本システムは、動的なラベルや矢印を用いてオーダーブロックおよびブレーカーブロックを可視化し、取引判断の明確性を高めています。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

このブレーカーブロック戦略により、価格のリテスト局面を捉えるための準備が整い、取引の旅路においてさらなる改良を加えていくことが可能となります。取引をお楽しみください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19638

添付されたファイル |
最後のコメント | ディスカッションに移動 (1)
hiteshdaoya
hiteshdaoya | 27 11月 2025 において 08:06
こんにちは、

共有ファイルを追加してみましたが、描画されないと 言うか、チャート上で何も起こらないか、取引が行われません。

よろしくお願いします。
初心者からエキスパートへ:MQL5を使用したバックエンド操作モニター 初心者からエキスパートへ:MQL5を使用したバックエンド操作モニター
取引システムの内部動作を意識せずに、既製のソリューションをそのまま利用することは一見すると安心に思えますが、開発者にとっては必ずしもそうとは限りません。いずれアップデートや動作不良、あるいは予期しないエラーが発生し、その原因がどこにあるのかを正確に突き止め、迅速に診断して解決する必要に迫られます。本記事では、取引用エキスパートアドバイザー(EA)の裏側で通常どのような操作がおこなわれているのかを明らかにするとともに、MQL5を用いてバックエンド操作を表示し、記録するための専用カスタムクラスを開発します。これにより、開発者およびトレーダーの双方が、エラーの特定、挙動の監視、EAごとの診断情報に迅速にアクセスできるようになります。
プライスアクション分析ツールキットの開発(第42回):ボタンロジックと統計レベルを用いたインタラクティブチャートの検証 プライスアクション分析ツールキットの開発(第42回):ボタンロジックと統計レベルを用いたインタラクティブチャートの検証
市場においてスピードと精度が重要である以上、分析ツールも市場と同じくらい賢くある必要があります。本記事では、ボタン操作に基づくエキスパートアドバイザー(EA)を紹介します。これは、価格データを瞬時に意味のある統計レベルに変換するインタラクティブなシステムです。ワンクリックで平均値、偏差、パーセンタイルなどを計算して表示し、複雑な分析をチャート上の明確なシグナルに変換します。価格が反発、押し戻し、または突破する可能性の高いゾーンをハイライトすることで、分析をより迅速かつ実用的にします。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5での取引戦略の自動化(第34回):R²適合度を用いたトレンドラインブレイクアウトシステム MQL5での取引戦略の自動化(第34回):R²適合度を用いたトレンドラインブレイクアウトシステム
本記事では、スイングポイントを用いてサポートおよびレジスタンスのトレンドラインを特定し、R²(決定係数)による適合度と角度制約で検証することで、ブレイクアウト取引を自動化するMQL5によるトレンドラインブレイクアウトシステムを構築します。本システムでは、指定したルックバック期間内のスイングハイとスイングローを検出し、一定数以上のタッチポイントを持つトレンドラインを生成します。その後、R²指標および角度制約を用いてトレンドラインの信頼性を評価し、取引に使用可能かを判定します。