MQL5における取引戦略の自動化(第46回):Liquidity Sweep on Break of Structure (BoS)
はじめに
前回の記事(第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)スイープでは売りエントリーをおこないます。ストップレベルは動的に設定され、最大取引数制限や反対ポジションのクローズも実装されます。また、アイコン、ラベル、矩形、点線、矢印および動的フォントを用いて可視化をおこないます。本記事では以下のトピックを扱います。
本記事を終える頃には、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戦略を選択しましたが、インバランスなど、他の設定に切り替えることもできます。以下に想定されるビジュアル表示の例を示します。

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で取得し、タイプはObjectGetInteger(OBJPROP_TYPEを使用)で取得します。OBJ_TEXTオブジェクトの場合には、ObjectSetIntegerとOBJPROP_FONTSIZEを用いてフォントサイズを更新し、最後にチャートを再描画します。
次にDeleteObjectsByPrefix関数を定義します。この関数は、指定された接頭辞に一致するすべてのチャートオブジェクトを削除し、チャート上の不要な要素をクリーンアップするために使用されます。メインチャート上のすべてのオブジェクト(すべてのタイプ)を取得し、逆順にループ処理をおこないます。各オブジェクトについて名前を取得し、StringFindが0を返すことで指定した接頭辞で始まるかを確認し、一致した場合はObjectDeleteで削除します。 さらにCountOpenTrades関数を作成し、プログラムに属する現在の保有ポジション数をカウントします。countを0で初期化し、PositionsTotalを用いて全ポジションを逆順にループします。各ポジションはPositionGetTicketでチケットを取得し、PositionSelectByTicketで選択します。選択後、PositionGetStringとPOSITION_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」の矢印を描画し、トレンドをリセットします。失敗した場合は、失敗を記録します。それが完了したら、ティック関数の関数を呼び出すだけで、次の結果が得られます。
可視化結果から、スイープ検出、取引実行、管理が正しく機能していることが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストによって、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
本記事では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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
Codexパイプライン:PythonからMQL5へ ― FXI ETFを対象とした複数四半期の指標分析
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
Adaptive Smart Money Architecture (ASMA):SMCロジックと市場センチメントを統合した動的戦略切替システム
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
添付されたファイルはmt5で動作しません。
アランさん、新年明けましておめでとうございます。記事をありがとうございます。あなたのステップに従い、ただコピーペーストするのではなく、すべてをタイプアウトしながら、私のマルチタイムフレームBOS EAをコード化する方法を理解するのに役立っています。しかし、コードの「スイングとBOSを検出する」セクションでエラーに遭遇しました。コンパイルすると、コードの「FindBreakTime」行で「宣言されていない識別子」エラーが発生します。SymbolInfoSessionsTrade "のようなものを使用するよう求められているようですが、"FindBreakTime "を使用してどのように動作させたのでしょうか?
ありがとうございました。
アランさん、新年明けましておめでとうございます。記事をありがとうございます。あなたのステップに従い、ただコピーペーストするのではなく、すべてをタイプアウトしながら進めていくことで、私のマルチタイムフレームBOS EAをコード化する方法を理解するのに役立っています。しかし、コードの「スイングとBOSを検出する」セクションでエラーに遭遇しました。コンパイルすると、コードの「FindBreakTime」行で「宣言されていない識別子」エラーが発生します。SymbolInfoSessionsTrade "のようなものを使用するよう求められているようですが、"FindBreakTime "を使用してどのように動作させたのでしょうか?
ありがとうございました。