English Deutsch
preview
MQL5における取引戦略の自動化(第46回):Liquidity Sweep on Break of Structure (BoS)

MQL5における取引戦略の自動化(第46回):Liquidity Sweep on Break of Structure (BoS)

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

はじめに

前回の記事(第45回)では、MetaQuotes Language 5 (MQL5)を用いてInverse Fair Value Gap (IFVG)システムを開発しました。このシステムでは、最小サイズフィルタリング付きのギャップ検出、状態管理(normal/mitigated/inverted)、重複の無視、固定ストップレベルによるインバージョン取引、取引モード、トレーリングストップ、さらに矩形、ラベル、アイコンによる可視化を実装しました。第46回では、Liquidity Sweep on Break of Structure (BoS)システムを構築します。

本システムは、定義された期間に基づいてスイングを検出し、スイングを用いてBoSを識別するためにHH(上昇トレンドの場合)およびLL(下降トレンドの場合)としてラベル付けをおこないます。また、価格がスイングをヒゲでブレイクした後にその内側へクローズする動きを流動性スイープとして検出します。さらに、上昇BoSにおけるSell Side Liquidity (SSL)スイープでは買いエントリーをおこない、下降BoSにおけるBuy Side Liquidity (BSL)スイープでは売りエントリーをおこないます。ストップレベルは動的に設定され、最大取引数制限や反対ポジションのクローズも実装されます。また、アイコン、ラベル、矩形、点線、矢印および動的フォントを用いて可視化をおこないます。本記事では以下のトピックを扱います。

  1. Liquidity Sweep on Break of Structure (BoS)戦略の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

本記事を終える頃には、BoSベースの流動性スイープ(Liquidity Sweep)をトレードするための、可視化機能およびリスク管理を備えた実用的なMQL5戦略を完成させることになります。それでは始めましょう。


Liquidity Sweep on Break of Structure (BoS)戦略の理解

Liquidity Sweep on Break of Structure (BoS)は、スイングポイントによる構造判定と、そのポイントを超えた後に反転を引き起こす流動性スイープの検出を組み合わせ、リバーサル前に流動性を吸収するプライスアクション戦略です。まず、周囲のバーをスキャンし、左および右の隣接バーより高いスイングハイ、または低いスイングローを検出します。これらを過去のスイングと比較してラベル付けし、高値についてはHH (higher high)またはLH (lower high)、安値についてはHL (higher low)またはLL (lower low)として分類します。BoSは、上昇トレンドにおけるHH、または下降トレンドにおけるLLで発生し、構造のブレイク(トレンド継続または転換のシグナル)を示します。一方でスイープは、価格がスイングをヒゲで一時的にブレイクするものの(上昇トレンドではSSL=Sell Side Liquidityを下抜け、下降トレンドではBSL=Buy Side Liquidityを上抜け)、その後バーがスイング内部にクローズすることで成立します。これは、実際の方向性の動きの前にストップを巻き込む「ストップ狩り」の形成を示唆します。

本アプローチでは、入力された期間に基づいてスイングを検出し、HH/HL/LH/LLとしてラベル付けをおこなうことでBoSのトレンド構造を定義します。その後、BoS発生時において、スイングをヒゲで一時的にブレイクしつつも終値がスイング内に戻る条件を満たすスイープを検出します。エントリーは、上昇BoSにおけるSSLスイープでは買い、下降BoSにおけるBSLスイープでは売りとして実行します。これに加えて、動的な取引レベル設定、最大保有ポジション数の制限、反対ポジションのクローズ制御を組み込みます。さらに可視化として、スイングにはアイコンおよびラベル、スイープには矩形、BoSブレイクには点線、エントリーには矢印を表示し、加えてスケール変化に応じた動的フォント表示もおこないます。流動性スイープはさまざまな条件設定で検出可能です。ここでは、そのシンプルさからBreak of Structure戦略を選択しましたが、インバランスなど、他の設定に切り替えることもできます。以下に想定されるビジュアル表示の例を示します。

LIQUIDITY SWEEP ON BOSフレームワーク


MQL5での実装

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

//+------------------------------------------------------------------+
//|                                       BOS Liquidity Sweep EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "EA GENERAL SETTINGS"
input int    SwingLength       = 5;                               // Swing Length in Bars (left/right check)
input double LotSize           = 0.01;                            // Fixed lot size
input double SL_Buffer_Pips    = 10.0;                            // SL buffer in pips below/above sweep
input double RiskRewardRatio   = 2.0;                             // Take profit multiplier (e.g., 2:1 RR)
input int    MaxTrades         = 1;                               // Max open trades
input long   MagicNumber       = 12345;                           // Unique magic number
input group "VISUALIZATION SETTINGS"
input color  clr_Bullish       = clrBlue;                         // Bullish Color (HH/HL)
input color  clr_Bearish       = clrRed;                          // Bearish Color (LL/LH)
input color  clr_SSL_Rect      = clrLightBlue;                    // SSL Sweep Rectangle Color
input color  clr_BSL_Rect      = clrLightCoral;                   // BSL Sweep Rectangle Color
input color  clr_SSL_Line      = clrBlue;                         // SSL Sweep Line Color
input color  clr_BSL_Line      = clrRed;                          // BSL Sweep Line Color
input color  clr_BullBOS       = clrGreen;                        // Bullish BOS Line Color
input color  clr_BearBOS       = clrMaroon;                       // Bearish BOS Line Color
input int    LineWidth         = 2;                               // Line Width
input bool   PrintLogs         = true;                            // Print Statements

//+------------------------------------------------------------------+
//| Global Variables Continued                                       |
//+------------------------------------------------------------------+
static double   current_swing_high = -1.0, current_swing_low = -1.0; //--- Current swing high and low
static datetime swing_high_time = 0, swing_low_time = 0;          //--- Swing high and low times
int    MarketTrend = 0;                                           //--- Market trend (1: Bullish BOS, -1: Bearish BOS, 0: Neutral)
int    OpenTrades = 0;                                            //--- Open trades count
int    current_font_size = 10;                                    //--- Current font size
int    object_code = 174;                                         //--- Wingdings arrow code for swings
int    buy_arrow_code = 233;                                      //--- Wingdings up arrow for buy
int    sell_arrow_code = 234;                                     //--- Wingdings down arrow for sell
string ObjPrefix = "BOSLiqSweep_";                                //--- Object prefix

実装はまず、Tradeライブラリを「#include <Trade/Trade.mqh>」でインクルードするところから開始します。これにより、注文およびポジション管理のためのCTradeクラスが利用可能になります。取引操作を扱うために、CTradeのグローバルインスタンスとしてobj_Tradeを宣言します。入力パラメータはEA GENERAL SETTINGSとしてプロパティダイアログにまとめられています。SwingLengthは左右のスイング判定に使用するバー数、LotSizeは固定ロットサイズ、SL_Buffer_Pipsはスイープ高値/安値の上下に対するストップロス用バッファ、RiskRewardRatioはテイクプロフィットの倍率、MaxTradesは同時保有ポジション数の上限、そしてMagicNumberは取引識別のためのマジックナンバーとして使用されます。VISUALIZATION SETTINGSでは、HH/HL用のclr_Bullish(青)、LL/LH用のclr_Bearish(赤)、SSL矩形用のclr_SSL_Rect(ライトブルー)、BSL矩形用のclr_BSL_Rect(ライトコーラル)、SSLライン用のclr_SSL_Line(青)、BSLライン用のclr_BSL_Line(赤)、強気BoS用のclr_BullBOS(緑)、弱気BoS用のclr_BearBOS(マルーン)、ラインの太さを指定するLineWidth、ログ出力のオンとオフを切り替えるPrintLogsが設定されています。

入力パラメータ

その後、さらにいくつかのグローバル変数を定義します。まず、直近のスイングを追跡するためのstatic current_swing_highおよびstatic current_swing_lowをそれぞれ-1.0で初期化し、対応するタイムスタンプ管理用としてstatic swing_high_timeおよびstatic swing_low_timeを用意します。市場トレンドを表す変数としてMarketTrendを定義し、強気BoSを1、弱気BoSを-1、中立を0とします。さらに現在のポジション数をカウントするOpenTrades、動的テキスト表示用のcurrent_font_sizeを10に設定します。スイング描画用のobject_codeはWingdingsの174、買いシグナル矢印のbuy_arrow_codeは233、売りシグナル矢印のsell_arrow_codeは234とし、オブジェクト命名用の接頭辞としてObjPrefixを「BOSLiqSweep_」に設定します。これらの準備が整ったら、初期化イベントハンドラで処理を開始します。初期化時には、既存の描画オブジェクトを削除し、チャート上の不要な描画オブジェクトを除去するようにします。その後、いくつかのヘルパー関数を先に定義します。

//+------------------------------------------------------------------+
//| Update font sizes                                                |
//+------------------------------------------------------------------+
void UpdateFontSizes() {
   long scale = 0;                                                //--- Init scale
   if (ChartGetInteger(0, CHART_SCALE, 0, scale)) {               //--- Get scale
      current_font_size = (int)(7 + scale * 0.7);                 //--- Calculate font size
      if (current_font_size < 6) current_font_size = 6;           //--- Set minimum font size
      if (current_font_size > 15) current_font_size = 15;         //--- Set maximum font size
      for (int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--) {    //--- Iterate objects reverse
         string name = ObjectName(0, i, -1, -1);                  //--- Get object name
         long type = ObjectGetInteger(0, name, OBJPROP_TYPE);     //--- Get object type
         if (type == OBJ_TEXT) {                                  //--- Check text type
            ObjectSetInteger(0, name, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
         }
      }
      ChartRedraw(0);                                             //--- Redraw chart
   }
}

//+------------------------------------------------------------------+
//| Delete objects by prefix                                         |
//+------------------------------------------------------------------+
void DeleteObjectsByPrefix(string prefix) {
   int total = ObjectsTotal(0, 0, -1);                            //--- Get total objects
   for (int i = total - 1; i >= 0; i--) {                         //--- Iterate reverse
      string name = ObjectName(0, i, 0, -1);                      //--- Get name
      if (StringFind(name, prefix) == 0) {                        //--- Check prefix
         ObjectDelete(0, name);                                   //--- Delete object
      }
   }
}

//+------------------------------------------------------------------+
//| Count open trades                                                |
//+------------------------------------------------------------------+
int CountOpenTrades() {
   int count = 0;                                                 //--- Init count
   for (int i = PositionsTotal() - 1; i >= 0; i--) {              //--- Iterate reverse
      ulong ticket = PositionGetTicket(i);                        //--- Get ticket
      if (PositionSelectByTicket(ticket)) {                       //--- Select position
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { //--- Check symbol and magic
            count++;                                              //--- Increment count
         }
      }
   }
   return count;                                                  //--- Return count
}

ここでは、UpdateFontSizes関数を実装します。この関数は、チャートのズームレベルに応じてテキストオブジェクトのフォントサイズを動的に調整し、可読性を確保するためのものです。まずscaleを0で初期化し、ChartGetIntegerを用いてCHART_SCALEからチャートのスケール値を取得します。取得に成功した場合、current_font_sizeを7にスケール値の70%を加えた値として計算し、その結果を6から15の範囲に制限します。その後、チャート上のすべてのオブジェクトをObjectsTotalで逆順にループ処理し、-1を指定することですべてのウィンドウとタイプを対象とします。各オブジェクト名はObjectNameで取得し、タイプはObjectGetIntegerOBJPROP_TYPEを使用)で取得します。OBJ_TEXTオブジェクトの場合には、ObjectSetIntegerOBJPROP_FONTSIZEを用いてフォントサイズを更新し、最後にチャートを再描画します。

次にDeleteObjectsByPrefix関数を定義します。この関数は、指定された接頭辞に一致するすべてのチャートオブジェクトを削除し、チャート上の不要な要素をクリーンアップするために使用されます。メインチャート上のすべてのオブジェクト(すべてのタイプ)を取得し、逆順にループ処理をおこないます。各オブジェクトについて名前を取得し、StringFindが0を返すことで指定した接頭辞で始まるかを確認し、一致した場合はObjectDeleteで削除します。 さらにCountOpenTrades関数を作成し、プログラムに属する現在の保有ポジション数をカウントします。countを0で初期化し、PositionsTotalを用いて全ポジションを逆順にループします。各ポジションはPositionGetTicketでチケットを取得し、PositionSelectByTicketで選択します。選択後、PositionGetStringPOSITION_SYMBOLにより銘柄を確認し、さらにPositionGetIntegerとPOSITION_MAGICによりマジックナンバーを照合します。一致する場合のみcountをインクリメントし、最終的にその値を返します。これに加えて可視化のためのさらなるヘルパー関数を定義していきます。

//+------------------------------------------------------------------+
//| Draw swing point with label                                      |
//+------------------------------------------------------------------+
void DrawSwingPoint(string objName, datetime time, double price, int arrCode, color clr, int direction, string label) {
   UpdateFontSizes();                                             //--- Update font sizes
   objName = ObjPrefix + label + TimeToString(time);              //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      string iconName = objName + "_icon";                        //--- Icon name
      ObjectCreate(0, iconName, OBJ_TEXT, 0, time, price);        //--- Create icon
      ObjectSetString(0, iconName, OBJPROP_FONT, "Wingdings");    //--- Set font
      ObjectSetInteger(0, iconName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      ObjectSetString(0, iconName, OBJPROP_TEXT, CharToString((uchar)arrCode)); //--- Set text
      ObjectSetInteger(0, iconName, OBJPROP_COLOR, clr);          //--- Set color
      ObjectSetInteger(0, iconName, OBJPROP_ANCHOR, ANCHOR_RIGHT); //--- Set anchor
      string txtName = objName + "_txt";                          //--- Text name
      ObjectCreate(0, txtName, OBJ_TEXT, 0, time, price);         //--- Create text
      ObjectSetString(0, txtName, OBJPROP_FONT, "Arial");         //--- Set font
      ObjectSetInteger(0, txtName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_LEFT);  //--- Set anchor
      ObjectSetString(0, txtName, OBJPROP_TEXT, label);           //--- Set text
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw sweep rectangle (no text)                                   |
//+------------------------------------------------------------------+
void DrawSweepRectangle(string objName, datetime time, double level, double extremum, color clr, bool is_ssl) {
   UpdateFontSizes();                                             //--- Update font sizes
   objName = ObjPrefix + objName + TimeToString(time, TIME_SECONDS); //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      double top = MathMax(level, extremum);                      //--- Calc top
      double bottom = MathMin(level, extremum);                   //--- Calc bottom
      datetime end_time = time + PeriodSeconds(_Period);          //--- Calc end time
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time, top, end_time, bottom); //--- Create rectangle
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, objName, OBJPROP_BACK, true);           //--- Set back
      ObjectSetInteger(0, objName, OBJPROP_FILL, true);           //--- Set fill
      ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_SOLID);   //--- Set style
      // No text inside rectangle to reduce clutter
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw horizontal dashed break level                               |
//+------------------------------------------------------------------+
void DrawBreakLevel(string objName, datetime time1, double price, datetime time2, double price2, color clr, int direction, string label) {
   UpdateFontSizes();                                             //--- Update font sizes
   objName = ObjPrefix + objName + label + TimeToString(time2, TIME_SECONDS); //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      ObjectCreate(0, objName, OBJ_TREND, 0, time1, price, time2, price); //--- Create trend line
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, objName, OBJPROP_WIDTH, LineWidth);     //--- Set width
      ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH);    //--- Set style
      ObjectSetInteger(0, objName, OBJPROP_RAY_RIGHT, false);     //--- Set no ray right
      string txt = label + " Sweep";                              //--- Set text
      string txtName = objName + "_txt";                          //--- Text name
      ObjectCreate(0, txtName, OBJ_TEXT, 0, time2, price);        //--- Create text
      ObjectSetInteger(0, txtName, OBJPROP_COLOR, clr);           //--- Set color
      ObjectSetInteger(0, txtName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      if (direction > 0) {                                        //--- Check positive
         ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_RIGHT_UPPER); //--- Set anchor
         ObjectSetString(0, txtName, OBJPROP_TEXT, " " + txt);    //--- Set text
      } else {                                                    //--- Negative
         ObjectSetInteger(0, txtName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER); //--- Set anchor
         ObjectSetString(0, txtName, OBJPROP_TEXT, " " + txt);    //--- Set text
      }
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw entry arrow with Wingdings                                  |
//+------------------------------------------------------------------+
void DrawEntryArrow(datetime time, double price, bool is_buy) {
   UpdateFontSizes();                                             //--- Update font sizes
   string objName = ObjPrefix + "Entry_" + TimeToString(time, TIME_SECONDS); //--- Set obj name
   if (ObjectFind(0, objName) < 0) {                              //--- Check no object
      int arrCode = is_buy ? buy_arrow_code : sell_arrow_code;    //--- Set arrow code
      color arrow_color = is_buy ? clrBlue : clrRed;              //--- Set color
      ObjectCreate(0, objName, OBJ_TEXT, 0, time, price);         //--- Create text
      ObjectSetString(0, objName, OBJPROP_FONT, "Wingdings");     //--- Set font
      ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
      ObjectSetString(0, objName, OBJPROP_TEXT, CharToString((uchar)arrCode)); //--- Set text
      ObjectSetInteger(0, objName, OBJPROP_COLOR, arrow_color);   //--- Set color
      ObjectSetInteger(0, objName, OBJPROP_ANCHOR, is_buy ? ANCHOR_UPPER : ANCHOR_LOWER); //--- Set anchor
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

まずDrawSwingPoint関数を定義します。この関数は、検出されたスイングポイントをアイコンおよびラベルとしてチャート上に可視化するためのものです。最初にUpdateFontSizesを呼び出し、現在のフォントサイズ設定を更新します。次にObjPrefixとラベルおよび時間文字列を結合して一意のオブジェクト名を生成します。ObjectFindより同名オブジェクトが存在しない場合のみ処理を続行し、まずWingdings,アイコンとしてOBJ_TEXTオブジェクトを作成します。名前には接尾辞として「_icon」を付与し、時刻と価格を座標として配置します。フォントはWingdings、サイズはcurrent_font_size、表示文字はarrCodeからCharToStringで取得した値、色はclr、アンカーは右寄せに設定します。次にテキストラベルを作成します。接尾辞「_txt」を付けたOBJ_TEXTとして追加し、フォントはArial、色とサイズはアイコンと同一、アンカーは左寄せに設定します。表示テキストはlabelとします。最後にChartRedrawを呼び出し、チャートを再描画します。

続いてDrawSweepRectangle関数を実装します。この関数はスイープ領域を強調表示するための塗りつぶし矩形を描画しますが、内部テキストは表示せず、チャートの視認性を維持します。まずUpdateFontSizesを呼び出し、ObjPrefixと時間秒文字列を用いてオブジェクト名を生成します。ObjectFindで存在確認後、未存在の場合のみ作成処理をおこないます。矩形の上端はlevelとextremumの最大値、下端は最小値として計算し、終了時刻はtimeに1バー分の期間を加算して設定します。OBJ_RECTANGLEを用いてこの時間・価格範囲を描画し、色はclr、バック描画と塗りつぶしはtrue、スタイルはsolidとします。その後チャートを再描画します。次にDrawBreakLevel関数を定義します。この関数はBoSブレイクレベルを水平の点線およびテキストで示すためのものです。UpdateFontSizesを呼び出し、ObjPrefix、label、time2文字列(秒)を組み合わせてオブジェクト名を生成します。存在しない場合のみ処理をおこない、OBJ_TRENDを用いてtime1とtime2の同一価格上に水平線を描画します。色はclr、線幅はLineWidth、スタイルは破線、右方向の延長は無効に設定します。テキストはOBJ_TEXTとして、time2および対応する価格に配置し、オブジェクト名には接尾辞「_txt」を付与します。色はclr、フォントサイズcurrent_font_sizeに設定し、方向が正の場合は右上、負の場合は右下をアンカーとして指定します。

最後にDrawEntryArrow関数を定義します。この関数は取引エントリー位置を示すWingdings矢印を配置します。UpdateFontSizesを呼び出し、ObjPrefix、「Entry_」およびtime文字列(秒)を結合して一意の名前を生成します。未存在の場合のみ作成をおこない、買いの場合はbuy_arrow_code、売りの場合はsell_arrow_codeを選択します。色は買いが青、売りが赤となります。OBJ_TEXTとして時刻と価格位置に配置し、フォントはWingdings、サイズはcurrent_font_size、表示文字はarrCodeから変換した文字、色を設定し、買いは上方向、売りは下方向にアンカーを設定します。最後にチャートを再描画します。これで初期化処理に進み、入力変数のクリーンアップおよび検証をおこなう準備が整います。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(MagicNumber);                   //--- Set magic number for trade object
   if (SwingLength < 1 || LotSize <= 0 || SL_Buffer_Pips < 0 || RiskRewardRatio < 1.0 || MaxTrades < 1) { //--- Check invalid inputs
      Print("Invalid input parameters.");                         //--- Log invalid parameters
      return(INIT_PARAMETERS_INCORRECT);                          //--- Return incorrect parameters
   }
   DeleteObjectsByPrefix(ObjPrefix);                              //--- Delete objects by prefix
   UpdateFontSizes();                                             //--- Update font sizes
   Print("EA Initialized Successfully.");                         //--- Log initialization success
   return(INIT_SUCCEEDED);                                        //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   DeleteObjectsByPrefix(ObjPrefix);                              //--- Delete objects by prefix
   Print("EA Deinitialized.");                                    //--- Log deinitialization
}

OnInitOnInitイベントハンドラでは、プログラムがチャートにアタッチされた際またはロードされた際に実行される初期化処理をおこないます。まずMagicNumberをobj_Tradeに設定し、SetExpertMagicNumberを用いて本EAの取引を識別可能な状態にします。次に主要な入力パラメータの検証をおこないます。SwingLengthが1未満、LotSizeが0以下、SL_Buffer_Pipsが負の値、RiskRewardRatioが1.0未満、MaxTradesが1未満である場合は、Printを用いてログに「Invalid input parameters」と出力し、INIT_PARAMETERS_INCORRECTを返して初期化を中止します。すべての入力が有効な場合は、DeleteObjectsByPrefixを呼び出して既存のチャート上の描画オブジェクトを削除し、UpdateFontSizesを実行して初期フォントサイズを設定します。その後ログに「EA Initialized Successfully」と出力し、INIT_SUCCEEDEDを返します。OnDeinitイベントハンドラは、プログラムがチャートから削除された場合またはターミナルが終了する際に実行されます。この処理ではDeleteObjectsByPrefixを呼び出し、指定接頭辞に一致するすべてのチャートオブジェクトを削除することで、視覚要素を残さずクリーンな終了状態を保証します。これで初期化と初期化解除処理が整いました。次はOnTickイベントハンドラ内でスイング検出およびブレイク検出の主要ロジックへ進みます。

//+------------------------------------------------------------------+
//| Detect swings and BOS                                            |
//+------------------------------------------------------------------+
void DetectSwingsAndBOS() {
   int curr_bar = SwingLength;                                    //--- Set current bar
   bool isSwingHigh = true, isSwingLow = true;                    //--- Init swing flags
   for (int j = 1; j <= SwingLength; j++) {                       //--- Iterate length
      int right_index = curr_bar - j;                             //--- Calc right index (newer)
      int left_index = curr_bar + j;                              //--- Calc left index (older)
      if (iHigh(_Symbol, _Period, curr_bar) <= iHigh(_Symbol, _Period, right_index) || iHigh(_Symbol, _Period, curr_bar) < iHigh(_Symbol, _Period, left_index)) { //--- Check not high
         isSwingHigh = false;                                     //--- Set not high
      }
      if (iLow(_Symbol, _Period, curr_bar) >= iLow(_Symbol, _Period, right_index) || iLow(_Symbol, _Period, curr_bar) > iLow(_Symbol, _Period, left_index)) { //--- Check not low
         isSwingLow = false;                                      //--- Set not low
      }
   }
   if (isSwingHigh) {                                             //--- Check swing high
      double new_high = iHigh(_Symbol, _Period, curr_bar);        //--- Get new high
      string label = "H";                                         //--- Init label
      color clr = clr_Bullish;                                    //--- Set color
      if (current_swing_high > 0) {                               //--- Check existing high
         if (new_high > current_swing_high) {                     //--- Check higher
            label = "HH";                                         //--- Set HH
            MarketTrend = 1;                                      //--- Set bullish trend
            if (PrintLogs) Print("Bullish BOS Detected");         //--- Log bullish BOS
            datetime break_time = FindBreakTime(swing_high_time, current_swing_high, true); //--- Find break time
            if (break_time > 0) DrawBreakLevel("Bull_BOS_", swing_high_time, current_swing_high, break_time, current_swing_high, clr_BullBOS, -1, "Bullish BOS"); //--- Draw BOS
         } else {                                                 //--- Lower
            label = "LH";                                         //--- Set LH
            clr = clr_Bearish;                                    //--- Set bearish color
         }
      }
      if (PrintLogs) Print("SWING HIGH @ BAR INDEX ", curr_bar, " of High: ", new_high, " Label: ", label); //--- Log high
      DrawSwingPoint(TimeToString(iTime(_Symbol, _Period, curr_bar)), iTime(_Symbol, _Period, curr_bar), new_high, object_code, clr, -1, label); //--- Draw high point
      current_swing_high = new_high;                              //--- Update high
      swing_high_time = iTime(_Symbol, _Period, curr_bar);        //--- Update high time
   }
   if (isSwingLow) {                                              //--- Check swing low
      double new_low = iLow(_Symbol, _Period, curr_bar);          //--- Get new low
      string label = "L";                                         //--- Init label
      color clr = clr_Bearish;                                    //--- Set color
      if (current_swing_low > 0) {                                //--- Check existing low
         if (new_low < current_swing_low) {                       //--- Check lower
            label = "LL";                                         //--- Set LL
            MarketTrend = -1;                                     //--- Set bearish trend
            if (PrintLogs) Print("Bearish BOS Detected");         //--- Log bearish BOS
            datetime break_time = FindBreakTime(swing_low_time, current_swing_low, false); //--- Find break time
            if (break_time > 0) DrawBreakLevel("Bear_BOS_", swing_low_time, current_swing_low, break_time, current_swing_low, clr_BearBOS, 1, "Bearish BOS"); //--- Draw BOS
         } else {                                                 //--- Higher
            label = "HL";                                         //--- Set HL
            clr = clr_Bullish;                                    //--- Set bullish color
         }
      }
      if (PrintLogs) Print("SWING LOW @ BAR INDEX ", curr_bar, " of Low: ", new_low, " Label: ", label); //--- Log low
      DrawSwingPoint(TimeToString(iTime(_Symbol, _Period, curr_bar)), iTime(_Symbol, _Period, curr_bar), new_low, object_code, clr, 1, label); //--- Draw low point
      current_swing_low = new_low;                                //--- Update low
      swing_low_time = iTime(_Symbol, _Period, curr_bar);         //--- Update low time
   }
}

//+------------------------------------------------------------------+
//| Find break candle time (based on close)                          |
//+------------------------------------------------------------------+
datetime FindBreakTime(datetime prev_time, double prev_level, bool is_high_break) {
   int prev_shift = iBarShift(_Symbol, _Period, prev_time);       //--- Get prev shift
   if (prev_shift < 0) return 0;                                  //--- Return invalid
   for (int i = prev_shift - 1; i >= 0; i--) {                    //--- Iterate reverse
      if (is_high_break) {                                        //--- Check high break
         if (iClose(_Symbol, _Period, i) > prev_level) return iTime(_Symbol, _Period, i); //--- Return time if break
      } else {                                                    //--- Low break
         if (iClose(_Symbol, _Period, i) < prev_level) return iTime(_Symbol, _Period, i); //--- Return time if break
      }
   }
   return 0;                                                      //--- Return no break
}

検出ロジックを格納するために、DetectSwingsAndBOS関数を定義します。この関数は、新しいバーごとにスイングポイントの識別とストラクチャーブレイクの検出をおこない、トレンド方向および可視化を更新します。まずcurr_barをSwingLengthに設定し、スキャン対象のバーを決定します。次にisSwingHighおよびisSwingLowをtrueで初期化します。その後、jを1からSwingLengthまでループし、各ステップでright_indexをcurr_bar - j(より新しいバー)、left_indexをcurr_bar + j(より古いバー)として計算します。そして、現在バーの高値が左右の高値の両方より厳密に高いかを確認し、いずれかの条件を満たさない場合はisSwingHighをfalseに設定します。同様に安値についても、左右両方より厳密に低くない場合はisSwingLowをfalseに設定します。

isSwingHighがtrueのままの場合、新しいスイングハイnew_highを取得し、初期ラベルをH、色をclr_Bullishとします。過去のcurrent_swing_highが存在する場合は比較をおこないます。新しい高値が過去より高い場合、ラベルをHHに設定し、MarketTrendを1(強気)に更新します。PrintLogsがtrueの場合にはログに「Bullish BOS Detected」と出力します。さらにFindBreakTime関数を用いてswing_high_time、current_swing_high、および高値ブレイクフラグtrueを引数としてブレイク時刻を特定します。もし有効な値が返る場合は、DrawBreakLevelを呼び出し、接頭辞「Bull_BOS_」、時刻、レベル、clr_BullBOS、方向-1、テキスト「Bullish BOS」を渡して描画します。

一方、新しいスイングが過去より低い場合はLHとして扱い、色をclr_Bearishに設定します。この場合もPrintLogsが有効であればスイング情報をログ出力し、DrawSwingPointを呼び出して時刻文字列、時刻、価格、object_code、色、方向-1、ラベルを描画します。その後current_swing_highおよびswing_high_timeを更新します。スイングローについても同様のロジックをミラー処理として適用します。FindBreakTime関数は、過去のスイングレベルを突破した最初のバーを特定するために使用します。まずiBarShiftを用いてprev_timeに対応するバー位置prev_shiftを取得し、無効な場合は0を返します。その後prev_shift - 1から0まで逆方向にループし、高値ブレイクの場合は終値がprev_levelを超えたとき、そのバーの時刻をiTimeで返します。安値ブレイクの場合は終値がprev_levelを下回ったときに同様に時刻を返します。どちらにも該当しない場合は0を返します。この関数を用いることで、バーごとの検出処理を以下のように呼び出して実行します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static bool isNewBar = false;                                  //--- New bar flag
   int currBars = iBars(_Symbol, _Period);                        //--- Get current bars
   static int prevBars = currBars;                                //--- Previous bars
   if (prevBars == currBars) {                                    //--- Check same bars
      isNewBar = false;                                           //--- Set not new bar
   } else if (prevBars != currBars) {                             //--- Check new bars
      isNewBar = true;                                            //--- Set new bar
      prevBars = currBars;                                        //--- Update previous bars
   }
   if (!isNewBar) return;                                         //--- Return if not new bar
   OpenTrades = CountOpenTrades();                                //--- Count open trades
   if (OpenTrades >= MaxTrades) return;                           //--- Return if max trades reached
   DetectSwingsAndBOS();                                          //--- Detect swings and BOS
}

ここではOnTickイベントハンドラを実装します。このハンドラはすべての価格ティックで実行され、コアロジックの管理をおこないます。まずstatic isNewBarフラグとprevBarsを使用してバーの変化を検出します。iBarsを用いてcurrBarsに現在のバー数を取得し、prevBarsと比較します。バー数が変化していない場合はisNewBarをfalseとし、変化している場合はtrueとし、その後prevBarsを更新します。新しいバーでない場合は早期リターンします。新しいバーが形成された場合、まずCountOpenTradesを呼び出してOpenTradesを更新します。OpenTradesがMaxTrades以上であれば、新規エントリーを防ぐためにここで処理を終了します。その後DetectSwingsAndBOSを呼び出し、スイング検出およびストラクチャーブレイクの判定を実行します。コンパイルすると、次の結果が得られます。

弱気スイープのセットアップ:

弱気スイープのセットアップ

強気スイープのセットアップ:

強気スイープのセットアップ

検出が完了したため、次は流動性スイープを用いた取引ロジックを実装します。この処理はモジュール化のため、専用の関数として分離して定義します。

//+------------------------------------------------------------------+
//| Detect and trade sweep on BOS                                    |
//+------------------------------------------------------------------+
void DetectAndTradeSweepOnBOS() {
   if (MarketTrend == 0) return;                                  //--- Return if neutral
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);            //--- Get bid
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);            //--- Get ask
   // Bullish BOS + SSL Sweep for Buy
   if (MarketTrend == 1 && current_swing_low > 0.0 && iLow(_Symbol, _Period, 1) < current_swing_low && iClose(_Symbol, _Period, 1) > current_swing_low && iClose(_Symbol, _Period, 1) > iOpen(_Symbol, _Period, 1)) { //--- Check bullish sweep
      if (PrintLogs) Print("Bullish BOS + SSL Sweep Detected");   //--- Log sweep
      double sweep_low = iLow(_Symbol, _Period, 1);               //--- Get sweep low
      datetime sweep_time = iTime(_Symbol, _Period, 1);           //--- Get sweep time
      DrawSweepRectangle("SSL_Rect_", sweep_time, current_swing_low, sweep_low, clr_SSL_Rect, true); //--- Draw SSL rect
      DrawBreakLevel("SSL_Line_", swing_low_time, current_swing_low, sweep_time, current_swing_low, clr_SSL_Line, 1, "SSL"); //--- Draw SSL line
      CloseOpposite(true);                                        //--- Close opposite
      double sl = NormalizeDouble(sweep_low - SL_Buffer_Pips * _Point, _Digits); //--- Calc SL
      double entry = ask;                                         //--- Set entry
      double risk = entry - sl;                                   //--- Calc risk
      double tp = NormalizeDouble(entry + risk * RiskRewardRatio, _Digits); //--- Calc TP
      obj_Trade.Buy(LotSize, _Symbol, entry, sl, tp, "BOS SSL Buy"); //--- Open buy
      if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) {      //--- Check success
         DrawEntryArrow(sweep_time, iLow(_Symbol,_Period, 1), true);                 //--- Draw buy arrow
         MarketTrend = 0;                                         //--- Reset trend
      } else Print("Buy order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Log failure
   }
   // Bearish BOS + BSL Sweep for Sell
   if (MarketTrend == -1 && current_swing_high > 0.0 && iHigh(_Symbol, _Period, 1) > current_swing_high && iClose(_Symbol, _Period, 1) < current_swing_high && iClose(_Symbol, _Period, 1) < iOpen(_Symbol, _Period, 1)) { //--- Check bearish sweep
      if (PrintLogs) Print("Bearish BOS + BSL Sweep Detected");   //--- Log sweep
      double sweep_high = iHigh(_Symbol, _Period, 1);             //--- Get sweep high
      datetime sweep_time = iTime(_Symbol, _Period, 1);           //--- Get sweep time
      DrawSweepRectangle("BSL_Rect_", sweep_time, current_swing_high, sweep_high, clr_BSL_Rect, false); //--- Draw BSL rect
      DrawBreakLevel("BSL_Line_", swing_high_time, current_swing_high, sweep_time, current_swing_high, clr_BSL_Line, -1, "BSL"); //--- Draw BSL line
      CloseOpposite(false);                                       //--- Close opposite
      double sl = NormalizeDouble(sweep_high + SL_Buffer_Pips * _Point, _Digits); //--- Calc SL
      double entry = bid;                                         //--- Set entry
      double risk = sl - entry;                                   //--- Calc risk
      double tp = NormalizeDouble(entry - risk * RiskRewardRatio, _Digits); //--- Calc TP
      obj_Trade.Sell(LotSize, _Symbol, entry, sl, tp, "BOS BSL Sell"); //--- Open sell
      if (obj_Trade.ResultRetcode() == TRADE_RETCODE_DONE) {      //--- Check success
         DrawEntryArrow(sweep_time, iHigh(_Symbol,_Period,1), false);                //--- Draw sell arrow
         MarketTrend = 0;                                         //--- Reset trend
      } else Print("Sell order failed: ", obj_Trade.ResultRetcodeDescription()); //--- Log failure
   }
}

ここではDetectAndTradeSweepOnBOS関数を定義します。この関数は、BoSの後に発生する流動性スイープを検出し、それに応じて取引を実行し、同時に可視化および既存ポジションの管理をおこないます。まずMarketTrendが0の場合は早期リターンします。これは中立状態であり、アクティブなBoSが存在しないことを意味します。次にSymbolInfoDoubleを用いてSYMBOL_BIDおよびSYMBOL_ASKから現在のBidおよびAsk価格を取得します。MarketTrendが1(強気BoS)であり、かつcurrent_swing_lowが0.0より大きい場合、SSLスイープの検出をおこないます。具体的には、直前バー(shift 1)の安値iLowがcurrent_swing_lowを下抜けしている一方で、そのバーの終値iClose(1)がスイングレベル上および始値iOpen(1)より上でクローズしている場合、ショートポジションを巻き込んだ強気ローソク足としてスイープ成立と判定します。スイープが検出された場合、PrintLogsがtrueであればログに「Bullish BOS + SSL Sweep Detected」と出力します。さらにスイープ安値および時刻をiLowとiTime (shift 1)から取得し、DrawSweepRectangleを呼び出して「SSL_Rect_」接頭辞、時刻、current_swing_low、スイープ安値、clr_SSL_Rect、およびSSLフラグtrueを渡して矩形を描画します。

次に弱気BOS (MarketTrend == -1)について同様の処理をおこないます。current_swing_highが有効な場合、直前バーの高値がcurrent_swing_highを上抜けしたにもかかわらず、終値がその下かつ始値よりも下でクローズしている場合をBSLスイープとして判定します。検出された場合、ログに「Bearish BOS + BSL Sweep Detected」と出力し、スイープ高値と時刻を記録します。その後DrawSweepRectangleをBSL_Rect_およびclr_BSL_Rectで呼び出し、さらにDrawBreakLevelをBSL_Line_、clr_BSL_Line、方向-1、ラベル「BSL」として実行します。その後CloseOppositeを呼び出して買いポジションをクローズし、取引管理をおこないます。ストップロスはスイープ高値の上にバッファを加えた位置に設定し、エントリーはBid価格とします。リスクはエントリーとストップロスの差として計算し、テイクプロフィットはそのリスクにRiskRewardRatioを掛けて設定します。その後obj_Trade.Sellを用いて売り注文を実行し、コメントとして「BOS BSL Sell」を付与します。成功した場合は、売りを示す「false」の矢印を描画し、トレンドをリセットします。失敗した場合は、失敗を記録します。それが完了したら、ティック関数の関数を呼び出すだけで、次の結果が得られます。

LIQUIDITY SWEEP ON BOSテストGIF

可視化結果から、スイープ検出、取引実行、管理が正しく機能していることが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

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

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事ではMQL5におけるLiquidity Sweep on Break of Structure (BoS)システムを構築しました。本システムは、入力された期間に基づいてスイングを検出し、スイングポイントをラベル付けすることでトレンドを定義します。さらに、スイングをヒゲでブレイクした後に終値がスイング内へ戻る動きをスイープとして検出します。本システムは、上昇BoSにおけるSell Side Liquidity (SSL)スイープで買いをおこない、下降BoSにおけるBuy Side Liquidity (BSL)スイープで売りをおこないます。加えて、動的な取引レベル設定、最大保有ポジション数の制限、反対ポジションのクローズ機能を備えています。可視化としては、スイングにアイコン、ブレイクに点線、スイープに塗りつぶし矩形、エントリーに矢印を表示し、さらにスケーリング変化に応じた動的フォントサイズ調整もおこないます。

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

このLiquidity Sweep on Break of Structure戦略を用いることで、BoS後に発生する操作的なヒゲ(流動性スイープ)を検出することが可能になります。これにより、反転系セットアップの取引に対応でき、さらなる最適化へ向けた準備が整います。取引をお楽しみください。

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

添付されたファイル |
Liquidity_Sweep.mq5 (55.03 KB)
最後のコメント | ディスカッションに移動 (5)
jumah55
jumah55 | 14 12月 2025 において 15:01
添付ファイルはmt5で動作しません。
Allan Munene Mutiiria
Allan Munene Mutiiria | 15 12月 2025 において 19:41
jumah55 #:
添付されたファイルはmt5で動作しません。
エラーや警告、またはどのように動作しないのでしょうか?
ersan yurdakul
ersan yurdakul | 20 12月 2025 において 20:56
そのコードでリペイントしているのか?
Emile Munro
Emile Munro | 2 1月 2026 において 10:14

アランさん、新年明けましておめでとうございます。記事をありがとうございます。あなたのステップに従い、ただコピーペーストするのではなく、すべてをタイプアウトしながら、私のマルチタイムフレームBOS EAをコード化する方法を理解するのに役立っています。しかし、コードの「スイングとBOSを検出する」セクションでエラーに遭遇しました。コンパイルすると、コードの「FindBreakTime」行で「宣言されていない識別子」エラーが発生します。SymbolInfoSessionsTrade "のようなものを使用するよう求められているようですが、"FindBreakTime "を使用してどのように動作させたのでしょうか?

ありがとうございました。

Emile Munro
Emile Munro | 2 1月 2026 において 13:13
Emile Munro #:

アランさん、新年明けましておめでとうございます。記事をありがとうございます。あなたのステップに従い、ただコピーペーストするのではなく、すべてをタイプアウトしながら進めていくことで、私のマルチタイムフレームBOS EAをコード化する方法を理解するのに役立っています。しかし、コードの「スイングとBOSを検出する」セクションでエラーに遭遇しました。コンパイルすると、コードの「FindBreakTime」行で「宣言されていない識別子」エラーが発生します。SymbolInfoSessionsTrade "のようなものを使用するよう求められているようですが、"FindBreakTime "を使用してどのように動作させたのでしょうか?

ありがとうございました。

わかりました、ありがとうございます。
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
Codexパイプライン:PythonからMQL5へ ― FXI ETFを対象とした複数四半期の指標分析 Codexパイプライン:PythonからMQL5へ ― FXI ETFを対象とした複数四半期の指標分析
MetaTraderを本来のFX取引という「コンフォートゾーン」の外でどのように活用できるかという検討を継続し、FXI ETFという別の取引可能資産に着目します。前回の記事では、指標の選定にとどまらず、指標間のパターンの組み合わせにまで踏み込み、やや過度に複雑化した側面がありました。本記事では一歩引いて、指標選定そのものに焦点を当てます。最終的には、十分な価格履歴データが存在する場合に、さまざまな資産に対して適切な指標を推奨できるパイプラインの構築を目指します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
Adaptive Smart Money Architecture (ASMA):SMCロジックと市場センチメントを統合した動的戦略切替システム Adaptive Smart Money Architecture (ASMA):SMCロジックと市場センチメントを統合した動的戦略切替システム
Adaptive Smart Money Architecture (ASMA)の構築方法について解説します。ASMAは、Smart Money Concept(Order Block、Break of Structure、Fair Value Gap)とリアルタイムの市場センチメントを統合し、現在の市場状況に応じて最適な取引戦略を自動的に選択するインテリジェントなエキスパートアドバイザー(EA)です。