English Deutsch
preview
MQL5における取引戦略の自動化(第45回):逆フェアバリューギャップ(IFVG)

MQL5における取引戦略の自動化(第45回):逆フェアバリューギャップ(IFVG)

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

はじめに

前回の記事(第44回)では、MetaQuotes Language 5 (MQL5)でChange of Character (CHoCH)検出システムを開発しました。このシステムはバーをスキャンし、トレンド判定のためにスイングハイおよびスイングローを識別し、ラベル付けするものでした。また、反転を示すブレイクシグナルに基づいて取引をトリガーしました。このシステムはバー単位およびティックモードの両方に対応し、アイコン、ラベル、ブレイクライン、動的フォントを用いて可視化されていました。第45回では、逆フェアバリューギャップ(IFVG)システムを開発します。

このシステムは、直近のバーにおいて強気または弱気のフェアバリューギャップ(FVG)を検出し、最小ギャップサイズフィルタを適用します。その後、価格の相互作用に基づいて状態をnormal、mitigated、invertedとして追跡します。ミティゲーションは遠側ブレイク時に発生し、再エントリー時にはリトレースとして扱われ、内側から遠側を超えてクローズした場合にはインバージョンとして扱われます。このシステムは重複ゾーンを無視しつつ、追跡するFVGの数を制限します。また、各FVGに対して1回、制限付き、または無制限の取引実行モードをサポートします。強気IFVGでは買い、弱気IFVGでは売りエントリーをおこないます。ゾーンは色付きの矩形として表示され、状態や取引ラベル、ミティゲーションアイコンと共に可視化されます。本記事では以下のトピックを扱います。

  1. 逆フェアバリューギャップ(IFVG)フレームワークの理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的に、状態追跡、適応的な可視化、設定可能なモードを備えたIFVG検出および取引戦略をMQL5で実装できるようになります。それでは始めましょう。


逆フェアバリューギャップ(IFVG)フレームワークの理解

フェアバリューギャップ(FVG)とは、ローソク足間における価格の不均衡またはギャップを表すプライスアクションの概念です。これは、買い圧力または売り圧力によって未充填の領域(空白)が形成された状態を指し、一般的に強気FVG(後続ローソク足の安値が前ローソク足の高値より上)または弱気FVG(後続ローソク足の高値が前ローソク足の安値より下)として観測されます。これらは、価格が後に埋め戻す可能性のある領域であり、サポート/レジスタンスゾーンとして機能します。逆フェアバリューギャップ(IFVG)は、ミティゲーションされたFVG(価格が遠側をブレイクした状態)がその後リトレースされ、さらにその内部からの価格が遠側を超えてクローズすることで反転が発生する現象です。これはトレンド反転シグナルとして機能します。すなわち、ミティゲーションされた強気FVGが弱気へ反転する場合(再エントリー後に価格が安値を下回ってクローズ)、またはミティゲーションされた弱気FVGが強気へ反転する場合(高値を上抜けてクローズ)です。状態は、normal(初期ギャップ)→ mitigated(遠側ブレイク)→ retraced(ミティゲーション後の再侵入)→ inverted(リトレース後に内部から遠側をブレイクしてクローズ)のように進行を追跡します。この中でinverted状態が主要な売買シグナルとなります。

たとえば、ミティゲーションされた弱気FVG(元は下方向のギャップ)においては、価格が再びゾーン内に戻った後に高値を上抜けてクローズすると強気IFVGとなり、買いエントリーをおこないます。この場合、ストップロスは安値の下、テイクプロフィットは固定ポイントに設定されます。逆に、ミティゲーションされた強気FVG(元は上方向のギャップ)では、価格が再侵入後、安値を下回ってクローズした場合に弱気IFVGが成立し、売りエントリーをおこないます。以下に想定される各セットアップの例を示します。

逆フェアバリューギャップ(IFVG)設定

本システムの計画は、最小ギャップフィルタを用いて最近のバーからFVGを検出し、初期化時に過去のFVGを読み込み、価格との相互作用に基づいて状態(normal/mitigated/inverted)を追跡・更新し、重複を無視しつつ追跡FVGを制限し、期限切れのFVGをクリーンアップし、反転を取引対象として扱い、強気IFVGでは買い、弱気IFVGでは売りをおこない、固定の取引レベル、トレードモードおよびFVGごとの取引回数制御、トレーリングストップを適用し、状態や取引ラベルおよびミティゲーションアイコン付きの色付き矩形として可視化することです。以下に想定されるビジュアル表示の例を示します。

IFVGSフレームワーク


MQL5での実装

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

//+------------------------------------------------------------------+
//|                                                  FVG Inverse.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
#define FVG_Prefix "IFVG REC "                                    //--- FVG prefix
// Normal FVGs
#define CLR_UP clrGreen                                           // Green for normal up (Bullish FVG)
#define CLR_DOWN clrRed                                           // Red for normal down (Bearish FVG)
// Mitigated FVGs
#define CLR_MIT_UP clrPurple                                      // Purple for mitigated up (Mitigated Bullish FVG)
#define CLR_MIT_DOWN clrOrange                                    // Orange for mitigated down (Mitigated Bearish FVG)
// Inverted FVGs
#define CLR_INV_UP clrRed                                         // Red for inverted up (Bearish IFVG)
#define CLR_INV_DOWN clrGreen                                     // Green for inverted down (Bullish IFVG)

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum TradeMode {                                                  // Define trade mode enum
   TradeOnce,                                                     // Trade Once
   LimitedTrades,                                                 // Limited Trades
   UnlimitedTrades                                                // Unlimited Trades
};

enum FVGState {                                                   // Define FVG state enum
   Normal,                                                        // Normal
   Mitigated,                                                     // Mitigated
   Inverted                                                       // Inverted
};

enum TrailingTypeEnum {                                           // Define enum for trailing stop types
   Trailing_None   = 0,                                           // None
   Trailing_Points = 2                                            // By Points
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "EA GENERAL SETTINGS"
input double inpLot             = 0.01;                           // Lotsize
input int    sl_pts             = 300;                            // Stop Loss Points
input int    tp_pts             = 300;                            // Take Profit Points
input int    minPts             = 100;                            // Minimum Gap Size in Points
input int    FVG_Rec_Ext_Bars   = 30;                             // FVG Extension Bars
input bool   prt                = true;                           // Print Statements
input long   magic_number       = 123456789;                      // Magic Number
input bool   ignoreOverlaps     = true;                           // Ignore new FVGs that overlap existing ones
input TradeMode tradeMode       = TradeOnce;                      // Mode for trading FVGs
input int    maxTradesPerFVG    = 2;                              // Maximum trades per FVG for LimitedTrades
input int    maxFVGs            = 50;                             // Maximum FVGs to track in array
input TrailingTypeEnum TrailingType = Trailing_None;              // Trailing Stop Type
input double Trailing_Stop_Pips = 30.0;                           // Trailing Stop in Pips (for Points type)
input double Min_Profit_To_Trail_Pips = 50.0;                     // Min Profit to Start Trailing in Pips

実装はまず、Tradeライブラリを「#include <Trade/Trade.mqh>」でインクルードするところから開始します。これにより、注文およびポジション管理のためのCTradeクラスが利用可能になります。すべての取引操作を扱うために、CTradeのグローバルインスタンスとしてobj_Tradeを宣言します。また、FVG矩形オブジェクトの命名に使用するため、FVG_Prefixという文字列定数をIFVG RECとして定義します。続いて、異なるFVG状態および方向に対応する色の定数を定義します。CLR_UPは通常の強気FVG用の緑、CLR_DOWNは通常の弱気FVG用の赤、CLR_MIT_UPはミティゲーションされた強気FVG用の紫、CLR_MIT_DOWNはミティゲーションされた弱気FVG用のオレンジ、CLR_INV_UPは反転した弱気FVG(元の上方向ギャップ)用の赤、CLR_INV_DOWNは反転した強気FVG用の緑として設定します。これらの色は必要に応じて変更可能であり、白背景チャート用として使用した任意の配色です。

その後、設定用に3つの列挙型を定義します。TradeMode列挙型は、FVGごとに1回のトレードに制限するTradeOnce、FVGごとにユーザー定義の最大回数を許可するLimitedTrades、制限なしのUnlimitedTradesを提供します。これは複数回のエントリーをおこなう場合に重要です。FVGState列挙型は、初期ギャップを示すNormal、遠側ブレイク後のMitigated、反転シグナル時のInvertedを定義します。TrailingTypeEnumは、トレーリングを無効化するTrailing_Noneと、ポイントベースのトレーリングストップを使用するTrailing_Pointsを提供します。

入力パラメータはEA GENERAL SETTINGSとしてプロパティダイアログにまとめられています。ここにはロットサイズを指定するinpLot、ストップロスとテイクプロフィット距離をポイントで指定するsl_ptsとtp_pts、FVGとして認識するための最小ギャップサイズminPts、FVG矩形を右方向へ延長するバー数FVG_Rec_Ext_Bars、ログ出力を制御するprt、取引識別用magic_number、既存FVGと重複する新規FVGをスキップするignoreOverlaps(視覚的明瞭性のため重要と考えていますが任意です)、取引制御用enumを使用するtradeMode、制限モード時の最大トレード数maxTradesPerFVG、追跡するFVG数の上限maxFVGs、トレーリング方式を指定するTrailingType、トレーリング距離Trailing_Stop_Pips、トレーリング開始条件となる利益閾値Min_Profit_To_Trail_Pipsが含まれます。これらの入力を定義した後、セットアップ管理のための構造体およびヘルパー関数を定義していきます。

//+------------------------------------------------------------------+
//| Structure for FVG zone information                               |
//+------------------------------------------------------------------+
struct FVGZone {                                                  // Define FVG zone structure
   string   name;                                                 //--- Zone name
   datetime startTime;                                            //--- Start time
   datetime origEndTime;                                          //--- Original end time
   datetime mitTime;                                              //--- Mitigation time
   bool     signal;                                               //--- Signal flag
   bool     inverted;                                             //--- Inverted flag
   bool     mit;                                                  //--- Mitigated flag
   bool     ret;                                                  //--- Retraced flag
   bool     origUp;                                               //--- Original up flag
   int      tradeCount;                                           //--- Trade count
   FVGState state;                                                //--- State
   bool     newSignal;                                            //--- New signal flag
};
FVGZone fvgs[];                                                   //--- FVG zones array

//+------------------------------------------------------------------+
//| Get color based on state and direction                           |
//+------------------------------------------------------------------+
color GetFVGColor(bool isUp, FVGState currentState) {
   if (currentState == Normal) return isUp ? CLR_UP : CLR_DOWN;   //--- Return normal color
   if (currentState == Mitigated) return isUp ? CLR_MIT_UP : CLR_MIT_DOWN; //--- Return mitigated color
   if (currentState == Inverted) return isUp ? CLR_INV_UP : CLR_INV_DOWN; //--- Return inverted color
   return clrNONE;                                                //--- Return none
}

//+------------------------------------------------------------------+
//| Print FVGs for debugging                                         |
//+------------------------------------------------------------------+
void PrintFVGs() {
   if (!prt) return;                                              //--- Return if no print
   Print("Current FVGs count: ", ArraySize(fvgs));                //--- Print count
   for (int i = 0; i < ArraySize(fvgs); i++) {                    //--- Iterate FVGs
      Print("FVG ", i, ": ", fvgs[i].name, " state=", EnumToString(fvgs[i].state), " mit=", fvgs[i].mit, " ret=", fvgs[i].ret, " inverted=", fvgs[i].inverted, " tradeCount=", fvgs[i].tradeCount, " newSignal=", fvgs[i].newSignal, " endTime=", TimeToString(fvgs[i].origEndTime)); //--- Print details
   }
}

まず、検出された各フェアバリューギャップに関するすべての情報を保持するためにFVGZone構造体を定義します。この構造体には、オブジェクト識別子としての文字列name、ギャップの初期区間を表すdatetimes型のstartTimeおよびorigEndTime、ミティゲーションが発生した時刻を示すmitTimeが含まれます。また、インバージョントリガーを示すsignal、インバージョン状態を示すinverted、ミティゲーション状態を示すmit、リトレース状態を示すretといったブールフラグも含まれます。さらに、元が強気FVGであったかを示すorigUp、当該FVG上でのトレード回数を追跡する整数tradeCount、状態を保持するstate列挙型、そして新規インバージョンシグナルを示すnewSignalフラグも保持します。次に、すべてのアクティブなFVGを効率的に管理するため、FVGZone型のグローバル配列fvgs[]を宣言します。これにより、複数のギャップをそれぞれの状態とともに追跡することが可能になります。

続いてGetFVGColor関数を実装し、FVG矩形の表示色を決定します。この関数は方向を示すisUpと現在の状態currentStateに基づいて色を返します。状態がNormalの場合、上方向であればCLR_UP(緑)、下方向であればCLR_DOWN(赤)を返します。Mitigated状態ではCLR_MIT_UP(紫)またはCLR_MIT_DOWN(オレンジ)を返します。Inverted状態ではCLR_INV_UP(赤)またはCLR_INV_DOWN(緑)を返します。それ以外の場合は未設定の値を返します。また、デバッグ用としてPrintFVGs関数を作成します。この関数はprtがfalseの場合は即座に終了し、trueの場合のみ処理を実行します。まずArraySizeから現在のFVG数を出力し、その後すべてのエントリをループ処理して、インデックス、name、EnumToStringによるstate、mitやretやinvertedなどのフラグ、tradeCount、新規シグナルフラグ、およびTimeToStringを用いた終了時刻などの詳細情報を出力します。これにより、次は矩形およびラベルのビジュアル機能の定義へ進むことができます。

//+------------------------------------------------------------------+
//| Create Rectangle                                                 |
//+------------------------------------------------------------------+
void CreateRec(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   ObjectCreate(0, objName, OBJ_RECTANGLE, 0, time1, price1, time2, price2); //--- Create rectangle
   ObjectSetInteger(0, objName, OBJPROP_FILL, true);              //--- Set fill
   ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);              //--- Set color
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);             //--- Set foreground
   datetime midTime = time1 + (time2 - time1) / 2;                //--- Calc mid time
   double midPrice = (price1 + price2) / 2;                       //--- Calc mid price
   CreateLabel(objName, midTime, midPrice);                       //--- Create label
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Update Rectangle                                                 |
//+------------------------------------------------------------------+
void UpdateRec(string objName, datetime time1, double price1, datetime time2, double price2, color clr) {
   if (ObjectFind(0, objName) >= 0) {                             //--- Check exists
      ObjectSetInteger(0, objName, OBJPROP_TIME, 0, time1);       //--- Set time1
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, price1);      //--- Set price1
      ObjectSetInteger(0, objName, OBJPROP_TIME, 1, time2);       //--- Set time2
      ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, price2);      //--- Set price2
      ObjectSetInteger(0, objName, OBJPROP_COLOR, clr);           //--- Set color
      datetime midTime = time1 + (time2 - time1) / 2;             //--- Calc mid time
      double midPrice = (price1 + price2) / 2;                    //--- Calc mid price
      UpdateLabel(objName, midTime, midPrice);                    //--- Update label
      ChartRedraw(0);                                             //--- Redraw chart
   }
}

//+------------------------------------------------------------------+
//| Create label                                                     |
//+------------------------------------------------------------------+
void CreateLabel(string zoneName, datetime time, double price) {
   string lblName = zoneName + "_Label";                          //--- Label name
   ObjectCreate(0, lblName, OBJ_TEXT, 0, time, price);            //--- Create text
   ObjectSetInteger(0, lblName, OBJPROP_ANCHOR, ANCHOR_CENTER);   //--- Set anchor
   ObjectSetInteger(0, lblName, OBJPROP_COLOR, clrBlack);         //--- Set color
   UpdateLabelText(lblName, zoneName);                            //--- Update text
}

//+------------------------------------------------------------------+
//| Update label position                                            |
//+------------------------------------------------------------------+
void UpdateLabel(string zoneName, datetime time, double price) {
   string lblName = zoneName + "_Label";                          //--- Label name
   if (ObjectFind(0, lblName) >= 0) {                             //--- Check exists
      ObjectSetInteger(0, lblName, OBJPROP_TIME, 0, time);        //--- Set time
      ObjectSetDouble(0, lblName, OBJPROP_PRICE, 0, price);       //--- Set price
      UpdateLabelText(lblName, zoneName);                         //--- Update text
   }
}

//+------------------------------------------------------------------+
//| Update label text                                                |
//+------------------------------------------------------------------+
void UpdateLabelText(string lblName, string zoneName) {
   string text = "";                                              //--- Init text
   int tradeCnt = 0;                                              //--- Init count
   FVGState state = Normal;                                       //--- Init state
   bool origUp = false;                                           //--- Init orig up
   for (int idx = 0; idx < ArraySize(fvgs); idx++) {              //--- Iterate FVGs
      if (fvgs[idx].name == zoneName) {                           //--- Check match
         tradeCnt = fvgs[idx].tradeCount;                         //--- Get count
         state = fvgs[idx].state;                                 //--- Get state
         origUp = fvgs[idx].origUp;                               //--- Get orig up
         break;                                                   //--- Break loop
      }
   }
   if (state == Normal) {                                         //--- Check normal
      text = origUp ? "Bullish FVG" : "Bearish FVG";              //--- Set text
   } else if (state == Mitigated) {                               //--- Check mitigated
      text = origUp ? "Mitigated Bullish FVG" : "Mitigated Bearish FVG"; //--- Set text
   } else if (state == Inverted) {                                //--- Check inverted
      text = origUp ? "Bearish Inversed FVG" : "Bullish Inversed FVG"; //--- Set text
   }
   if (tradeCnt > 0) {                                            //--- Check traded
      text += " (Traded " + IntegerToString(tradeCnt) + " times)"; //--- Add traded
   }
   ObjectSetString(0, lblName, OBJPROP_TEXT, text);               //--- Set text
}

//+------------------------------------------------------------------+
//| Draw mitigation icon                                              |
//+------------------------------------------------------------------+
void DrawMitIcon(string fvgNAME, datetime mitTime, double fvgHigh, double fvgLow, bool isUp) {
   string iconName = fvgNAME + "_MitIcon";                        //--- Icon name
   double iconPrice = isUp ? fvgLow : fvgHigh;                    //--- Icon price
   ObjectCreate(0, iconName, OBJ_ARROW, 0, mitTime, iconPrice);   //--- Create arrow
   ObjectSetInteger(0, iconName, OBJPROP_ARROWCODE, 251);         //--- Set code
   ObjectSetInteger(0, iconName, OBJPROP_COLOR, clrBlue);         //--- Set color
   ObjectSetInteger(0, iconName, OBJPROP_ANCHOR, isUp ? ANCHOR_TOP : ANCHOR_BOTTOM); //--- Set anchor
   ChartRedraw(0);                                                //--- Redraw chart
}

可視化のために、まずCreateRec関数を定義し、チャート上にFVGゾーンを表す新しい矩形を描画します。矩形オブジェクトはObjectCreateを用いてOBJ_RECTANGLEとして生成し、time1とprice1からtime2とprice2までの範囲で拡張されます。塗りつぶしはOBJPROP_FILL をtrueに設定して有効化し、指定された色をOBJPROP_COLORで適用します。さらにOBJPROP_BACKをfalseに設定して前景に表示されるようにし、矩形の中央時刻をtime1に期間の半分を加算することで算出し、中央価格をprice1とprice2の平均として計算します。その後CreateLabelを呼び出して中央に説明ラベルを追加し、最後にChartRedraw関数でチャートを再描画します。次にUpdateRec関数を実装し、状態や色が変化した際に既存のFVG矩形を更新します。ObjectFindによってオブジェクトが存在する場合のみ処理をおこない、OBJPROP_TIMEおよびOBJPROP_PRICEを使用して両端の座標を更新します。新しい色を適用し、中央時刻と中央価格を先ほどと同様に再計算し、UpdateLabelを呼び出してテキストを再配置および更新します。その後チャートを再描画します。

続いてCreateLabel関数を作成し、FVG矩形内部にテキストラベルを追加します。ラベル名はゾーン名に_Labelを付加して生成し、ObjectCreateにより指定された時刻と価格位置にOBJ_TEXTオブジェクトとして作成します。アンカーはOBJPROP_ANCHORを用いて中央に設定し、色は黒に設定します。その後UpdateLabelTextを呼び出して初期の説明テキストを設定します。次にUpdateLabel関数を定義し、矩形が調整された際に既存ラベルを移動させます。ラベルが存在する場合のみ位置を更新し、その後UpdateLabelTextを呼び出して現在の状態に基づき内容を更新します。続いてUpdateLabelText関数を実装し、ラベルの文字列を動的に構築して設定します。まず空のテキストを初期化し、その後fvgs配列をループして一致するゾーンを名前で検索し、トレード回数、状態、元の上方向フラグを取得します。

状態に応じてテキストを設定します。Normalの場合はBullish FVGまたはBearish FVG、Mitigatedの場合はMitigated Bullish FVGまたはMitigated Bearish FVG、Invertedの場合はBearish Inversed FVGまたはBullish Inversed FVGとなります。さらに取引回数が0より大きい場合は(Traded X times)を追記します。このテキストをObjectSetStringOBJPROP_TEXTを用いてラベルに適用します。最後にDrawMitIcon関数を作成し、ミティゲーションイベントの視覚的マーカーを配置します。アイコン名はFVG名に_MitIconを付加して生成し、OBJ_ARROWを用いてミティゲーション時刻と適切な価格位置(上方向ギャップでは安値、下方向ギャップでは高値)に配置します。矢印コードは251に設定し、色は青に設定します。またisUpに応じてアンカーを上または下に設定し、チャートを再描画します。矢印コードはMQL5のWingdingsフォントに基づき、必要に応じて変更可能です。。

MQL5 WINGDINGS

これらの関数が揃ったことで、チャート上に既存バーから初期セットアップのマーキングを生成できるようになります。これにより、プログラム初期化時点でインジケータの構造を視覚的に確認することが可能になります。このロジックは専用関数としてまとめて実装します。

//+------------------------------------------------------------------+
//| Process historical mitigation, retracement, signal for an FVG    |
//+------------------------------------------------------------------+
void ProcessHistoricalState(int idx) {
   string fvgNAME = fvgs[idx].name;                               //--- Get name
   datetime timeSTART = fvgs[idx].startTime;                      //--- Get start time
   datetime endTime = fvgs[idx].origEndTime;                      //--- Get end time
   double fvgLow = MathMin(ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 1)); //--- Calc low
   double fvgHigh = MathMax(ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgNAME, OBJPROP_PRICE, 1)); //--- Calc high
   int fvgBar = iBarShift(_Symbol, _Period, timeSTART);           //--- Get bar
   if (fvgBar < 0) return;                                        //--- Return invalid
   bool isMit = false, isRet = false, isSig = false;              //--- Init flags
   datetime mitTime = 0;                                          //--- Init mit time
   int mitK = -1, sigK = -1;                                      //--- Init indices
   for (int k = fvgBar - 1; k >= 0; k--) {                        //--- Iterate bars
      double barLow = iLow(_Symbol, _Period, k);                  //--- Get bar low
      double barHigh = iHigh(_Symbol, _Period, k);                //--- Get bar high
      double barClose = iClose(_Symbol, _Period, k);              //--- Get bar close
      if (!isMit) {                                               //--- Check not mit
         bool breakFar = (fvgs[idx].origUp && barLow < fvgLow) || (!fvgs[idx].origUp && barHigh > fvgHigh); //--- Check break far
         if (breakFar) {                                          //--- Break far
            isMit = true;                                         //--- Set mit
            mitK = k;                                             //--- Set mit k
            mitTime = iTime(_Symbol, _Period, k);                 //--- Set mit time
            if (prt) Print("Historical Mitigated: ", fvgNAME, " at bar ", k, " time=", TimeToString(mitTime)); //--- Log mitigated
         }
      }
      if (isMit && !isRet) {                                      //--- Check mit and not ret
         bool inside = (barHigh > fvgLow && barLow < fvgHigh);    //--- Check inside
         if (inside) {                                            //--- Inside
            isRet = true;                                         //--- Set ret
            if (prt) Print("Historical Retraced: ", fvgNAME, " at bar ", k); //--- Log retraced
         }
      }
      if (isMit && isRet && !isSig) {                             //--- Check mit ret not sig
         bool signal = (fvgs[idx].origUp && barClose < fvgLow) || (!fvgs[idx].origUp && barClose > fvgHigh); //--- Check signal
         if (signal) {                                            //--- Signal
            if (k + 1 < iBars(_Symbol, _Period)) {                //--- Check prev bar
               double prevClose = iClose(_Symbol, _Period, k + 1); //--- Get prev close
               bool prevInside = (prevClose > fvgLow && prevClose < fvgHigh); //--- Check prev inside
               if (prevInside) {                                  //--- Prev inside
                  isSig = true;                                   //--- Set sig
                  sigK = k;                                       //--- Set sig k
                  if (prt) Print("Historical Signal/Inverted: ", fvgNAME, " at bar ", k, " time=", TimeToString(iTime(_Symbol, _Period, k))); //--- Log signal
               }
            }
         }
      }
   }
   fvgs[idx].mit = isMit;                                         //--- Set mit
   fvgs[idx].ret = isRet;                                         //--- Set ret
   fvgs[idx].inverted = isSig;                                    //--- Set inverted
   fvgs[idx].signal = isSig;                                      //--- Set signal
   fvgs[idx].mitTime = mitTime;                                   //--- Set mit time
   fvgs[idx].state = isSig ? Inverted : (isMit ? Mitigated : Normal); //--- Set state
   fvgs[idx].newSignal = false;                                   //--- Set no new signal
   color currentClr = GetFVGColor(fvgs[idx].origUp, fvgs[idx].state); //--- Get color
   UpdateRec(fvgs[idx].name, fvgs[idx].startTime, fvgLow, fvgs[idx].origEndTime, fvgHigh, currentClr); //--- Update rec
   if (mitTime > 0) DrawMitIcon(fvgs[idx].name, mitTime, fvgHigh, fvgLow, fvgs[idx].origUp); //--- Draw mit icon
}

ここではProcessHistoricalState関数を定義し、初期化時に特定のFVGに対して、そのギャップ発生以降の過去バーを対象にミティゲーション、リトレース、およびインバージョンの有無を解析し、状態を更新します。まず、指定されたインデックスの構造体からFVGのname、startTime、およびorigEndTimeを取得します。その後、ObjectGetDoubleを用いてOBJPROP_PRICEから矩形オブジェクトの価格情報を取得し、MathMinおよびMathMaxを用いてFVGの下限と上限を算出します。続いてiBarShiftを用いてstartTimeに対応するバーインデックスを取得し、無効な場合は即座に処理を終了します。次に、ミティゲーション、リトレース、およびシグナル用のフラグをすべてfalseで初期化し、ミティゲーション時刻およびバーインデックスを-1に設定します。その後、FVG発生バーの直後から最新バー(0)まで逆方向にループ処理をおこないます。各バーについて、iLowiHighiClose関数を用いて安値、高値、終値を取得します。まだミティゲーションが発生していない場合、遠側ブレイクをチェックします。強気FVG(元が上方向)の場合はバーの安値がFVGの安値を下回っているかを確認し、弱気FVG(元が下方向)の場合はバーの高値がFVGの高値を上回っているかを確認します。条件を満たした場合、ミティゲーションフラグをtrueに設定し、バーインデックスとiTimeによる時刻を記録し、prtがtrueの場合はログ出力をおこないます。

ミティゲーション済みかつ未リトレースの場合、そのバーがFVGゾーンと重なるかどうかを確認します。具体的には高値が下限を上回り、かつ安値が上限を下回る場合を重複とみなし、リトレースフラグをtrueに設定し、必要に応じてログを出力します。さらにミティゲーション済みかつリトレース済みであり、まだシグナルが発生していない場合、インバージョンを検出します。強気FVGではクローズがFVGの下限を下回った場合、弱気FVGではクローズが上限を上回った場合にインバージョンとします。その際、直前バーのクローズがFVG内部(下限より上かつ上限より下)にあることを確認し、内部からのブレイクアウトであることを検証します。条件成立時にはシグナルフラグをtrueにし、バーインデックスを記録し、ログ出力をおこないます。

その後、構造体の各フィールドを更新します。mitigation、retracement、inverted、signalにそれぞれのフラグ値を設定し、ミティゲーション時刻を記録します。stateはシグナルがある場合はInverted、ミティゲーションのみの場合はMitigated、それ以外はNormalに設定し、newSignalはfalseにリセットします。続いてGetFVGColor関数を用いて現在の状態と元の方向に基づいた色を取得し、UpdateRec関数を呼び出して矩形の座標および色を更新します。ミティゲーションが発生している場合はDrawMitIcon関数を用いてミティゲーションアイコンを描画します。この際、位置はミティゲーション時刻と適切なエッジ価格(強気FVGでは安値、弱気FVGでは高値)に配置されます。この関数により、初期化時に過去データを用いたFVG状態の完全な再構築が可能となり、初期セットアップの描画に利用できるようになります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(magic_number);                  //--- Set magic number
   ObjectsDeleteAll(0, FVG_Prefix);                               //--- Delete FVG objects
   ArrayResize(fvgs, 0);                                          //--- Reset array
   if (prt) Print("Initializing: Deleted all existing FVG objects and reset array."); //--- Log init
   int visibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars
   if (prt) Print("Total Visible Bars On Chart = ", visibleBars); //--- Log visible bars
   // Detect historical FVGs from older to newer
   for (int i = visibleBars - 3; i >= 0; i--) {                   //--- Iterate bars
      double low0 = iLow(_Symbol, _Period, i);                    //--- Get low0
      double high2 = iHigh(_Symbol, _Period, i + 2);              //--- Get high2
      double gap_L0_H2 = NormalizeDouble((low0 - high2) / _Point, _Digits); //--- Calc gap L0 H2
      double high0 = iHigh(_Symbol, _Period, i);                  //--- Get high0
      double low2 = iLow(_Symbol, _Period, i + 2);                //--- Get low2
      double gap_H0_L2 = NormalizeDouble((low2 - high0) / _Point, _Digits); //--- Calc gap H0 L2
      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;           //--- Check up FVG
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;         //--- Check down FVG
      if (FVG_UP || FVG_DOWN) {                                   //--- Check FVG
         datetime time1 = iTime(_Symbol, _Period, i + 1);         //--- Get time1
         double price1 = FVG_UP ? high2 : high0;                  //--- Set price1
         double price2 = FVG_UP ? low0 : low2;                    //--- Set price2
         double newLow = MathMin(price1, price2);                 //--- Calc new low
         double newHigh = MathMax(price1, price2);                //--- Calc new high
         bool overlaps = false;                                   //--- Init overlaps
         if (ignoreOverlaps) {                                    //--- Check ignore overlaps
            for (int ex = 0; ex < ArraySize(fvgs); ex++) {        //--- Iterate existing
               double exLow = ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 0); //--- Get ex low
               double exHigh = ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 1); //--- Get ex high
               exLow = MathMin(exLow, exHigh);                    //--- Min ex low
               exHigh = MathMax(exLow, exHigh);                   //--- Max ex high
               if (MathMax(newLow, exLow) < MathMin(newHigh, exHigh)) { //--- Check overlap
                  overlaps = true;                                //--- Set overlaps
                  if (prt) Print("Historical: Skipping overlapping FVG at ", TimeToString(time1)); //--- Log skip
                  break;                                          //--- Break loop
               }
            }
         }
         if (overlaps) continue;                                  //--- Continue if overlaps
         string fvgNAME = FVG_Prefix + "(" + TimeToString(time1) + ")"; //--- FVG name
         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;               //--- Set color
         CreateRec(fvgNAME, time1, price1, time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars, price2, fvgClr); //--- Create rec
         int size = ArraySize(fvgs);                              //--- Get size
         if (size >= maxFVGs) {                                   //--- Check max
            if (prt) Print("Historical: Max FVGs reached, removing oldest."); //--- Log max
            ArrayRemove(fvgs, 0, 1);                              //--- Remove oldest
            PrintFVGs();                                          //--- Print FVGs
         }
         ArrayResize(fvgs, size + 1);                             //--- Resize array
         fvgs[size].name = fvgNAME;                               //--- Set name
         fvgs[size].startTime = time1;                            //--- Set start time
         fvgs[size].origEndTime = time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars; //--- Set end time
         fvgs[size].mitTime = 0;                                  //--- Set mit time
         fvgs[size].signal = false;                               //--- Set signal
         fvgs[size].inverted = false;                             //--- Set inverted
         fvgs[size].mit = false;                                  //--- Set mit
         fvgs[size].ret = false;                                  //--- Set ret
         fvgs[size].origUp = FVG_UP;                              //--- Set orig up
         fvgs[size].tradeCount = 0;                               //--- Set trade count
         fvgs[size].state = Normal;                               //--- Set state
         fvgs[size].newSignal = false;                            //--- Set new signal
         if (prt) Print("Historical FVG created: ", fvgNAME, " origUp=", FVG_UP, " endTime=", TimeToString(fvgs[size].origEndTime)); //--- Log created
         PrintFVGs();                                             //--- Print FVGs
      }
   }
   // Process historical states
   for (int j = 0; j < ArraySize(fvgs); j++) {                    //--- Iterate FVGs
      ProcessHistoricalState(j);                                  //--- Process state
   }
   PrintFVGs();                                                   //--- Print FVGs
   return(INIT_SUCCEEDED);                                        //--- Return success
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   for (int i = 0; i < ArraySize(fvgs); i++) {                    //--- Iterate FVGs
      ObjectDelete(0, fvgs[i].name);                              //--- Delete name
      ObjectDelete(0, fvgs[i].name + "_Label");                   //--- Delete label
      ObjectDelete(0, fvgs[i].name + "_MitIcon");                 //--- Delete mit icon
   }
   ArrayResize(fvgs, 0);                                          //--- Reset array
   ChartRedraw(0);                                                //--- Redraw chart
   if (prt) Print("Deinit: Deleted all FVG objects and reset array."); //--- Log deinit
}

OnInitイベントハンドラでは、プログラム開始時に最初にmagic_number入力値をobj_TradeへSetExpertMagicNumberを用いて設定し、取引識別をおこないます。次に、既存のFVG矩形をすべて削除するためにObjectsDeleteAllを現在のチャートとFVG_Prefixを指定して実行し、fvgs配列をArrayResizeで0にリセットします。prtがtrueの場合は初期化ログを出力します。その後、ChartGetIntegerを用いてチャート上の表示バー数をCHART_VISIBLE_BARSとして取得し、visibleBarsに格納し、必要に応じてログ出力します。

履歴FVGを古い順から新しい順に検出するため、visibleBars - 3から0までループ処理をおこないます。各バーiに対して、iとi+2のバーを比較することでギャップを計算し、low[i]とhigh[i+2]の比較からgap_L0_H2を、high[i]とlow[i+2]の比較からgap_H0_L2を算出します。low0がhigh2より上にあり、かつギャップがminPtsを超える場合はFVG_UP(強気ギャップ)、low2がhigh0より下にあり、かつギャップがminPtsを超える場合はFVG_DOWN(弱気ギャップ)として識別します。FVG検出には最低3本の完全なバーが必要であり、これは極めて重要な前提条件です。この点は一度で完全に理解する必要があります。視覚的な説明として以下に示します。

FVGセットアップの要件

続いて、いずれかのタイプのFVGが検出された場合、バーi+1の時刻をtime1として取得し、価格を適切に設定します。その後MathMinおよびMathMaxを用いてギャップの高値と安値を計算します。ignoreOverlapsがtrueの場合、既存のfvgs全体を走査し、ObjectGetDoubleで各FVGの価格を取得して範囲比較をおこない、重複がある場合はoverlapsをtrueとし、prtがtrueであればスキップログを出力して次のループへ進みます。重複がない場合、FVG名をFVG_PrefixにTimeToString(time1)を連結して生成し、方向に応じた色を選択します。その後CreateRecを呼び出して矩形を描画し、FVG_Rec_Ext_Bars分だけ右方向へ拡張します。fvgsのサイズがmaxFVGsに到達している場合は最も古い要素をArrayRemoveで削除し、必要に応じてログ出力をおこないます。その後fvgs配列を1増やして新規要素を追加し、name、time、フラグを初期化します。origUpのみFVG_UPの値を設定し、tradeCountは0、stateはNormal、newSignalはfalseに設定します。作成完了時にはログを出力し、PrintFVGsを呼び出します。重複FVGは見た目の観点では好ましくありませんが、売買ロジックには影響しません。

重複するセットアップのサンプル

ご覧の通り、重複したインスタンスは視覚的には望ましくありません。その後、検出されたすべての履歴FVGに対してfvgs配列をループし、それぞれにProcessHistoricalStateを適用して過去の価格アクションに基づく初期状態を設定します。その後再度PrintFVGsを呼び出し、INIT_SUCCEEDEDを返します。最後にOnDeinitではfvgsをループし、各矩形、ラベル(_Label付き)、およびミティゲーションアイコン(_MitIcon)をObjectDeleteで削除し、fvgs配列を0にリサイズし、チャートを再描画し、prtがtrueの場合は終了ログを出力します。コンパイルすると、次の結果が得られます。

IFVG初期履歴読み込み設定

この可視化から分かる通り、起動時にすべてのFVGをスキャンし、マッピングし、更新しています。次に必要なのは、新規バーに対する継続的な検出とセットアップ更新です。それでは検出ロジックに進みます。

//+------------------------------------------------------------------+
//| Detect new FVGs on recent bars                                   |
//+------------------------------------------------------------------+
void DetectFVGs() {
   for (int i = 3; i >= 1; i--) {                                 //--- Iterate recent bars
      double low0 = iLow(_Symbol, _Period, i);                    //--- Get low0
      double high2 = iHigh(_Symbol, _Period, i + 2);              //--- Get high2
      double gap_L0_H2 = NormalizeDouble((low0 - high2) / _Point, _Digits); //--- Calc gap L0 H2
      double high0 = iHigh(_Symbol, _Period, i);                  //--- Get high0
      double low2 = iLow(_Symbol, _Period, i + 2);                //--- Get low2
      double gap_H0_L2 = NormalizeDouble((low2 - high0) / _Point, _Digits); //--- Calc gap H0 L2
      bool FVG_UP = low0 > high2 && gap_L0_H2 > minPts;           //--- Check up FVG
      bool FVG_DOWN = low2 > high0 && gap_H0_L2 > minPts;         //--- Check down FVG
      if (FVG_UP || FVG_DOWN) {                                   //--- Check FVG
         datetime time1 = iTime(_Symbol, _Period, i + 1);         //--- Get time1
         double price1 = FVG_UP ? high2 : high0;                  //--- Set price1
         double price2 = FVG_UP ? low0 : low2;                    //--- Set price2
         double newLow = MathMin(price1, price2);                 //--- Calc new low
         double newHigh = MathMax(price1, price2);                //--- Calc new high
         bool overlaps = false;                                   //--- Init overlaps
         if (ignoreOverlaps) {                                    //--- Check ignore overlaps
            for (int ex = 0; ex < ArraySize(fvgs); ex++) {        //--- Iterate existing
               double exLow = MathMin(ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 1)); //--- Calc ex low
               double exHigh = MathMax(ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[ex].name, OBJPROP_PRICE, 1)); //--- Calc ex high
               if (MathMax(newLow, exLow) < MathMin(newHigh, exHigh)) { //--- Check overlap
                  overlaps = true;                                //--- Set overlaps
                  if (prt) Print("Detect: Skipping overlapping FVG at ", TimeToString(time1)); //--- Log skip
                  break;                                          //--- Break loop
               }
            }
         }
         if (overlaps) continue;                                  //--- Continue if overlaps
         string fvgNAME = FVG_Prefix + "(" + TimeToString(time1) + ")"; //--- FVG name
         if (ObjectFind(0, fvgNAME) >= 0) continue;               //--- Skip duplicate
         color fvgClr = FVG_UP ? CLR_UP : CLR_DOWN;               //--- Set color
         datetime endTime = time1 + PeriodSeconds(_Period) * FVG_Rec_Ext_Bars; //--- Calc end time
         CreateRec(fvgNAME, time1, price1, endTime, price2, fvgClr); //--- Create rec
         int size = ArraySize(fvgs);                              //--- Get size
         if (size >= maxFVGs) {                                   //--- Check max
            if (prt) Print("Detect: Max FVGs reached, removing oldest."); //--- Log max
            ArrayRemove(fvgs, 0, 1);                              //--- Remove oldest
            PrintFVGs();                                          //--- Print FVGs
         }
         ArrayResize(fvgs, size + 1);                             //--- Resize array
         fvgs[size].name = fvgNAME;                               //--- Set name
         fvgs[size].startTime = time1;                            //--- Set start time
         fvgs[size].origEndTime = endTime;                        //--- Set end time
         fvgs[size].mitTime = 0;                                  //--- Set mit time
         fvgs[size].signal = false;                               //--- Set signal
         fvgs[size].inverted = false;                             //--- Set inverted
         fvgs[size].mit = false;                                  //--- Set mit
         fvgs[size].ret = false;                                  //--- Set ret
         fvgs[size].origUp = FVG_UP;                              //--- Set orig up
         fvgs[size].tradeCount = 0;                               //--- Set trade count
         fvgs[size].state = Normal;                               //--- Set state
         fvgs[size].newSignal = false;                            //--- Set new signal
         if (prt) Print("New FVG added to storage: ", fvgNAME, " origUp=", FVG_UP, " endTime=", TimeToString(endTime)); //--- Log added
         PrintFVGs();                                             //--- Print FVGs
      }
   }
}

DetectFVGs関数を定義し、新しいバーごとに直近のバーをスキャンして新規のフェアバリューギャップを検出し、条件を満たす場合にトラッキングシステムへ追加します。ループはshift 3から1までを対象とし、直近の確定済みバーをチェックします(ここは既に説明済みです)。各iに対して、iLowを用いてlow0を取得し、i+2のhighをhigh2として取得し、これらから正規化されたギャップをpoints単位でgap_L0_H2として計算します。同様にiのhighをhigh0、i+2のlowをlow2として取得し、gap_H0_L2を算出します。low0がhigh2より大きく、かつギャップがminPtsを超える場合はFVG_UP(強気ギャップ)と判定し、low2がhigh0より小さく、かつギャップがminPtsを超える場合はFVG_DOWN(弱気ギャップ)と判定します。

いずれかが検出された場合、i+1の時刻をtime1として取得し、price1を上方向の場合はhigh2、下方向の場合はhigh0に設定し、price2をそれぞれlow0またはlow2に設定します。その後ギャップの上限および下限を計算します。ignoreOverlapsがtrueの場合、既存のfvgs全体を走査し、ObjectGetDoubleで各FVGの価格範囲を取得し、MathMin/MathMaxを用いてレンジを算出します。新規ギャップが既存のいずれかと重複する場合(最大の下限が最小の上限より小さい場合)、overlapsをtrueとし、prtがtrueであればスキップログを出力して次へ進みます。重複がなく、かつ同名オブジェクトがObjectFindで存在しない場合、FVG名をFVG_PrefixにTimeToString(time1)を連結して生成し、方向に応じて色(上方向は緑、下方向は赤)を選択します。その後endTimeをtime1にPeriodSeconds(_Period) * FVG_Rec_Ext_Barsを加算して計算し、CreateRecを呼び出して矩形を描画し、右方向へ拡張します。続いてfvgs配列を管理します。サイズがmaxFVGsに到達している場合は最も古い要素をArrayRemoveで削除し、必要に応じてログを出力します。その後ArrayResizeで配列を1つ拡張し、新規エントリを追加します。各フィールドにはname、time情報、ミティゲーション時刻を0、すべてのフラグをfalseに初期化し、origUpのみFVG_UPの値を設定します。tradeCountは0、stateはNormal、newSignalはfalseに設定し、追加時にはログを出力し、PrintFVGsを呼び出します。これにより、有効で重複のない新規FVGのみを検出し、可視性のために右方向へ拡張された状態で保存することができます。この関数はティックイベントハンドラ内で呼び出すことで、主要な処理を担うことができます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime lastBarTime = 0;                               //--- Last bar time
   datetime curBarTime = iTime(_Symbol, _Period, 0);              //--- Current bar time
   bool newBar = (curBarTime != lastBarTime);                     //--- Check new bar
   if (!newBar) return;                                           //--- Return if not new
   lastBarTime = curBarTime;                                      //--- Update last time
   DetectFVGs();                                                  //--- Detect FVGs
}

OnTickイベントハンドラでは、すべての価格ティックごとにリアルタイム更新を処理するため、static変数lastBarTimeを使用して直前のバーの始値時刻を追跡します。現在のバーの時刻はiTimeのシフト0でcurBarTimeとして取得し、これらが異なる場合にnewBarをtrueとし、新しいバーが形成されたことを検出します。新しいバーでない場合は、不要な処理を避けるために早期リターンします。新しいバーが形成された場合のみlastBarTimeをcurBarTimeに更新し、その後DetectFVGsを呼び出して直近のバーから新しいギャップをスキャンします。次のような結果が得られます。

初期ONTICK IFVGS検出

初期検出が完了したため、次はセットアップの更新処理に進みます。以下のロジックを使用します。

//+------------------------------------------------------------------+
//| Update states for all FVGs                                       |
//+------------------------------------------------------------------+
void UpdateFVGs() {
   double prevClose = iClose(_Symbol, _Period, 1);                //--- Get prev close
   double prevLow = iLow(_Symbol, _Period, 1);                    //--- Get prev low
   double prevHigh = iHigh(_Symbol, _Period, 1);                  //--- Get prev high
   double bar2Close = iClose(_Symbol, _Period, 2);                //--- Get bar2 close
   datetime curBarTime = iTime(_Symbol, _Period, 1);              //--- Get prev bar time
   bool removed = false;                                          //--- Init removed
   for (int j = ArraySize(fvgs) - 1; j >= 0; j--) {               //--- Iterate reverse
      if (ObjectFind(0, fvgs[j].name) < 0) {                      //--- Check no object
         if (prt) Print("Update: Removed non-existent FVG from storage: ", fvgs[j].name); //--- Log removed
         ArrayRemove(fvgs, j, 1);                                 //--- Remove from array
         removed = true;                                          //--- Set removed
         continue;                                                //--- Continue
      }
      double fvgLow = MathMin(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc low
      double fvgHigh = MathMax(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc high
      if (!fvgs[j].mit) {                                         //--- Check not mit
         bool breakFar = (fvgs[j].origUp && prevLow < fvgLow) || (!fvgs[j].origUp && prevHigh > fvgHigh); //--- Check break far
         if (breakFar) {                                          //--- Break far
            fvgs[j].mit = true;                                   //--- Set mit
            fvgs[j].mitTime = curBarTime;                         //--- Set mit time
            fvgs[j].state = Mitigated;                            //--- Set state
            if (prt) Print("Mitigated FVG: ", fvgs[j].name, " at time=", TimeToString(curBarTime)); //--- Log mitigated
            color mitClr = GetFVGColor(fvgs[j].origUp, fvgs[j].state); //--- Get color
            UpdateRec(fvgs[j].name, fvgs[j].startTime, fvgLow, fvgs[j].origEndTime, fvgHigh, mitClr); //--- Update rec
            DrawMitIcon(fvgs[j].name, curBarTime, fvgHigh, fvgLow, fvgs[j].origUp); //--- Draw icon
         }
      }
      if (fvgs[j].mit && !fvgs[j].ret) {                          //--- Check mit not ret
         bool inside = (prevHigh > fvgLow && prevLow < fvgHigh);  //--- Check inside
         if (inside) {                                            //--- Inside
            fvgs[j].ret = true;                                   //--- Set ret
            if (prt) Print("Retraced into FVG: ", fvgs[j].name);  //--- Log retraced
         }
      }
      if (fvgs[j].mit && fvgs[j].ret) {                           //--- Check mit ret
         bool signal = (fvgs[j].origUp && prevClose < fvgLow) || (!fvgs[j].origUp && prevClose > fvgHigh); //--- Check signal
         bool prevInside = (bar2Close > fvgLow && bar2Close < fvgHigh); //--- Check prev inside
         if (signal && curBarTime != fvgs[j].mitTime && prevInside) { //--- Check signal conditions
            fvgs[j].newSignal = true;                             //--- Set new signal
            if (!fvgs[j].inverted) {                              //--- Check not inverted
               fvgs[j].inverted = true;                           //--- Set inverted
               fvgs[j].state = Inverted;                          //--- Set state
               if (prt) Print("Signal/Inverted FVG: ", fvgs[j].name, " at time=", TimeToString(curBarTime)); //--- Log signal
               color sigClr = GetFVGColor(fvgs[j].origUp, fvgs[j].state); //--- Get color
               UpdateRec(fvgs[j].name, fvgs[j].startTime, fvgLow, fvgs[j].origEndTime, fvgHigh, sigClr); //--- Update rec
            }
         }
      }
   }
   if (removed) PrintFVGs();                                      //--- Print if removed
}

ここでは、UpdateFVGs関数を定義し、トラッキングしているすべてのフェアバリューギャップの状態を各新規バーごとに更新します。これにより、直前のバーのデータを用いて、ミティゲーション、リトレースメント、インバージョンをリアルタイムで検出します。まず、前のバーの終値をiCloseのシフト1でprevCloseとして取得し、その安値をiLowでprevLowとして取得、高値をiHighでprevHighとして取得します。また、その1つ前のバーの終値をiCloseのシフト2でbar2Closeとして取得し、さらに前のバーの時刻をiTimeのシフト1でcurBarTimeとして取得します。removedフラグをfalseで初期化し、その後fvgs配列を後ろからループし、安全に要素削除ができるようにします。各FVG(インデックスj)について、まずレクタングルオブジェクトが存在しない場合(ObjectFindが負値を返す場合)、prtがtrueなら削除ログを出力し、ArrayRemoveで要素を削除してremovedをtrueに設定し、次の要素へ進みます。オブジェクトが存在する場合は、MathMinとMathMaxを用い、ObjectGetDoubleOBJPROP_PRICE(アンカー0と1)から現在のFVGの高値と安値を算出します。

まだミティゲーションされていない場合、まず遠い側のブレイクを確認します。元が上方向のギャップの場合はprevLowがFVGの下限を下回るか、下方向のギャップの場合はprevHighがFVGの上限を上回るかをチェックします。この条件が成立した場合、ミティゲーションフラグをtrueに設定し、mitTimeをcurBarTimeに設定し、stateをMitigatedに変更し、prtがtrueならログを出力します。その後GetFVGColorで新しい色を取得し、UpdateRecでレクタングルを更新し、DrawMitIconを使用してcurBarTimeと遠いエッジ(下方向は高値、上方向は安値)にミティゲーションアイコンを描画します。ミティゲーション済みでまだリトレースされていない場合、前のバーがFVG内に重なっているか(高値が上限以上かつ安値が下限以下か)を確認し、該当すればリトレースフラグをtrueにしログを出力します。

ミティゲーションとリトレースの両方が成立している場合はインバージョンを検出します。上方向ギャップの場合はprevCloseがFVGの下限を下回る場合、下方向ギャップの場合はprevCloseがFVGの上限を上回る場合を条件とします。さらにミティゲーションバー自体ではないこと、およびbar2CloseがFVG内部(上限と下限の間)にあることを確認し、内部からの反転を保証します。newSignalをtrueに設定し、まだinvertedでない場合はinvertedをtrue、stateをInvertedに更新し、ログを出力し、インバージョンカラーを取得してレクタングルを更新します。最後に、削除が発生していた場合はPrintFVGsを呼び出してデバッグ出力をおこないます。これにより、すべてのFVG状態が価格変動に同期され、正確なインバージョンシグナルを取得しつつ、欠損オブジェクトも適切に処理できます。この関数を呼び出した際の結果は以下のようになります。

更新されたIFVGセットアップGIF

このように、価格がFVGに反応するたびにセットアップが更新されることが確認できます。残る作業はインバージョンセットアップのトレード処理であり、これでシステムは完成となります。以下に、そのロジックをモジュール化するために関数として実装した内容を示します。

//+------------------------------------------------------------------+
//| Trade on FVGs with signals                                       |
//+------------------------------------------------------------------+
void TradeOnFVGs() {
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Get ask
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Get bid
   for (int j = 0; j < ArraySize(fvgs); j++) {                    //--- Iterate FVGs
      if (!fvgs[j].newSignal || fvgs[j].mitTime == 0) continue;   //--- Skip no signal or no mit
      if (tradeMode == TradeOnce && fvgs[j].tradeCount >= 1) {    //--- Check once and traded
         fvgs[j].newSignal = false;                               //--- Reset signal
         continue;                                                //--- Continue
      }
      if (tradeMode == LimitedTrades && fvgs[j].tradeCount >= maxTradesPerFVG) { //--- Check limited and max
         fvgs[j].newSignal = false;                               //--- Reset signal
         continue;                                                //--- Continue
      }
      double fvgLow = MathMin(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc low
      double fvgHigh = MathMax(ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 0), ObjectGetDouble(0, fvgs[j].name, OBJPROP_PRICE, 1)); //--- Calc high
      if (!fvgs[j].origUp) {                                      //--- Check orig down: Bullish IFVG, Buy
         if (prt) Print("BULLISH IFVG TRADE SIGNAL For ", fvgs[j].name, " at ", Bid); //--- Log buy signal
         double SL_Buy = NormalizeDouble(fvgLow - sl_pts * _Point, _Digits); //--- Calc buy SL
         double TP_Buy = NormalizeDouble(Ask + tp_pts * _Point, _Digits); //--- Calc buy TP
         obj_Trade.Buy(inpLot, _Symbol, Ask, SL_Buy, TP_Buy, "IFVG Buy"); //--- Open buy
      } else {                                                    //--- Orig up: Bearish IFVG, Sell
         if (prt) Print("BEARISH IFVG TRADE SIGNAL For ", fvgs[j].name, " at ", Ask); //--- Log sell signal
         double SL_Sell = NormalizeDouble(fvgHigh + sl_pts * _Point, _Digits); //--- Calc sell SL
         double TP_Sell = NormalizeDouble(Bid - tp_pts * _Point, _Digits); //--- Calc sell TP
         obj_Trade.Sell(inpLot, _Symbol, Bid, SL_Sell, TP_Sell, "IFVG Sell"); //--- Open sell
      }
      fvgs[j].tradeCount++;                                       //--- Increment count
      fvgs[j].newSignal = false;                                  //--- Reset signal
      fvgs[j].ret = false;                                        //--- Reset ret
      if (prt) Print("Trade executed on ", fvgs[j].name, ", tradeCount now=", fvgs[j].tradeCount); //--- Log executed
      double midPrice = (fvgLow + fvgHigh) / 2;                   //--- Calc mid price
      datetime midTime = fvgs[j].startTime + (fvgs[j].origEndTime - fvgs[j].startTime) / 2; //--- Calc mid time
      UpdateLabel(fvgs[j].name, midTime, midPrice);               //--- Update label
   }
}

//+------------------------------------------------------------------+
//| Cleanup expired FVGs from array (keep on chart)                  |
//+------------------------------------------------------------------+
void CleanupExpiredFVGs(datetime curBarTime) {
   bool removed = false;                                          //--- Init removed
   for (int j = ArraySize(fvgs) - 1; j >= 0; j--) {               //--- Iterate reverse
      if (curBarTime > fvgs[j].origEndTime) {                     //--- Check expired
         if (prt) Print("Expired FVG removed from storage (kept on chart): ", fvgs[j].name, " endTime=", TimeToString(fvgs[j].origEndTime)); //--- Log expired
         ArrayRemove(fvgs, j, 1);                                 //--- Remove from array
         removed = true;                                          //--- Set removed
      }
   }
   if (removed) PrintFVGs();                                      //--- Print if removed
}

まず、TradeOnFVGs関数を定義し、FVGから生成された新しいインバージョンシグナルに基づいて取引を実行し、設定された取引モードと制限を遵守します。まず現在のAsk価格をSymbolInfoDoubleSYMBOL_ASKを用いて取得し、桁数に正規化してAskに格納します。同様にBidもSYMBOL_BIDで取得してBidに格納します。その後fvgs配列をループし、各エントリについて新しいシグナルがない場合、またはミティゲーション時間が0の場合はスキップします。また取引モードも確認し、TradeOnceの場合は既にトレード回数が1以上であれば、LimitedTradesの場合はmaxTradesPerFVG以上であれば、そのシグナルをリセットして次へ進みます。

有効なシグナルについては、FVGの高値と安値をMathMinMathMaxおよびObjectGetDoubleを用いて算出します。元の方向が上方向でない場合(つまり弱気FVGが強気へ反転している場合)、買いシグナルとして処理します。prtが有効であればログを出力し、ストップロスをlowの下にsl_pts * _Pointを引いた位置に設定し、テイクプロフィットをAskの上にtp_pts * _Pointを加えた位置に設定します。その後obj_Trade.Buyを用いてinpLot、シンボル、Ask価格、計算したSL/TP、コメント「IFVG Buy」で買い注文を実行します。逆に、元が上方向のFVGが反転した場合は売りロジックを同様に適用します。取引実行後はtradeCountを増加させ、新しいシグナルとリトレースメントフラグをリセットし、prtが有効な場合は現在のトレード回数とともにログを出力します。また中間価格と時間を計算し、UpdateLabelを呼び出してラベル位置をチャート上で更新します。

最後にCleanupExpiredFVGs関数を実装し、古くなったFVGをトラッキング配列から削除しますが、チャート上の可視オブジェクトは保持します。この関数は前のバーの時刻を引数として呼び出されます。removedフラグをfalseで初期化し、fvgs配列を後ろからループします。指定された時刻が元の終了時刻を超えている場合、そのFVGを期限切れとしてログに出力し、ArrayRemoveで削除し、removedをtrueに設定します。削除が発生した場合はPrintFVGsを呼び出してデバッグをおこないます。この処理は、必要なセットアップのみをメモリ上に保持するために重要です。これらを実行すると以下の結果が得られます。

取引執行

現在、生成されたシグナルに基づいてトレードを実行できるため、次のステップとしてトレーリングストップによるポジション管理をおこないます。そのためのロジックを以下に実装します。

//+------------------------------------------------------------------+
//| Apply Points Trailing Stop                                       |
//+------------------------------------------------------------------+
void ApplyPointsTrailing() {
   double point = _Point;                                            //--- Get point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) {                 //--- Iterate positions reverse
      if (PositionGetTicket(i) > 0) {                                //--- Check valid ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { //--- Check symbol and magic
            double sl = PositionGetDouble(POSITION_SL);              //--- Get SL
            double tp = PositionGetDouble(POSITION_TP);              //--- Get TP
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            ulong ticket = PositionGetInteger(POSITION_TICKET);      //--- Get ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            }
         }
      }
   }
}

//--- Call the function in the tick event handler per tick
if (TrailingType == Trailing_Points && PositionsTotal() > 0) { //--- Check trailing
   ApplyPointsTrailing();                                      //--- Apply trailing
}

ここでは、ApplyPointsTrailing関数を定義し、ポイントベースのトレーリングストップを実装します。これは選択された場合に、価格が有利な方向へ動いた際にストップロスをリアルタイムで調整するものです。銘柄の1ポイント値を_Pointでpointに代入します。その後、ポジション変更時のインデックス問題を避けるため、PositionsTotalを用いてすべてのオープンポジションを後ろからループします。各ポジションについて、PositionGetTicketでチケットの有効性を確認します。対象銘柄かつmagic_numberが一致するポジションに対して、PositionGetDoublePOSITION_SLでストップロス、POSITION_TPでテイクプロフィット、POSITION_PRICE_OPENでエントリー価格、POSITION_TICKETでチケット番号を取得します。買いポジション(POSITION_TYPE_BUY)の場合、現在のBidからTrailing_Stop_Pips * pointを引いた値を新しいストップロスとして計算し、桁数に正規化します。この値が既存のストップロスよりも有利(よりタイト)であり、かつ未実現利益がMin_Profit_To_Trail_Pips * pointを上回っている場合にのみ、obj_Trade.PositionModifyを使用してストップロスを更新し、テイクプロフィットは変更しません。売りポジションについても同様に処理し、現在のAskにトレーリング距離を加えた値を新しいストップロスとし、それがより有利であり、かつ利益条件を満たしている場合にのみ更新をおこないます。

OnTick内では、TrailingTypeがTrailing_Pointsに設定されており、かつPositionsTotalでポジションが存在する場合、毎ティックごとにApplyPointsTrailingを呼び出し、迅速な調整をおこないます。コンパイルすると、次の結果が得られます。

IFVG最終テストGIF

この可視化から、逆フェアバリューギャップの検出、取引実行、および管理がすべて機能しており、目的が達成されていることが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

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

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事ではMQL5において逆フェアバリューギャップ(IFVG)システムを開発しました。このシステムは、直近のバーから最小ギャップサイズのフィルタを用いて強気および弱気のフェアバリューギャップ(FVGs)を検出し、価格の動きに基づいて状態を通常、ミティゲーション、インバージョンとして追跡します。また、重複を無視しつつ追跡数を制限し、初期化時には過去のFVGを読み込み、リアルタイム更新および期限切れのクリーンアップにも対応しています。このシステムは、各セットアップごとに1回、制限付き、または無制限の取引をサポートし、強気のIFVGでは買い、弱気のIFVGでは売りを固定のトレードレベルで実行します。さらに、ポジション制限、トレーリングストップ機能を備え、状態やトレード情報を表示するラベルやミティゲーションアイコン付きのカラーレクタングルとして視覚化します。

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

この逆フェアバリューギャップ戦略により、状態追跡とインバージョンシグナルを活用したギャップの不均衡トレードが可能になります。今後の取引におけるさらなる最適化に向けた基盤としてご活用ください。取引をお楽しみください。 

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

添付されたファイル |
FVG_Inverse.mq5 (81.62 KB)
MQL5入門(第30回):MQL5のAPIとWebRequest関数の習得(IV) MQL5入門(第30回):MQL5のAPIとWebRequest関数の習得(IV)
APIレスポンスから取得したローソク足データの抽出、変換、整理を、MQL5環境において簡潔におこなうためのステップごとのチュートリアルを紹介します。本ガイドは、コーディングスキルを向上させたい初心者の方や、市場データを効率的に管理するための堅牢な手法を構築したい方に最適です。
プロップファームチャレンジをクリアするための自動リスク管理 プロップファームチャレンジをクリアするための自動リスク管理
本記事では、GOLD向けのプロップファーム用エキスパートアドバイザー(EA)の設計について解説します。このEAは、ブレイクアウトフィルター、マルチタイムフレーム分析、堅牢なリスク管理、そして厳格なドローダウン制御を特徴としています。ルール違反を回避し、ボラティリティの高い市場環境下でも安定した取引実行を維持することで、トレーダーがプロップファームのチャレンジをクリアするのを支援します。
取引戦略の開発:出来高制限アプローチの使用 取引戦略の開発:出来高制限アプローチの使用
テクニカル分析の世界では、価格がしばしば中心的な役割を果たします。トレーダーはサポートやレジスタンス、パターンを綿密に描きますが、多くの場合、これらの動きを駆動する重要な力である「出来高」を見落としています。本記事では、新しい出来高分析のアプローチであるVolume Boundaryインジケーターについて解説します。この指標は、バタフライ曲線やトリプルサイン曲線といった高度な平滑化関数を用いることで変換をおこない、より明確な解釈と体系的な取引戦略の構築を可能にします。
MQL5における純粋なRSA暗号化の実装 MQL5における純粋なRSA暗号化の実装
MQL5には組み込みの非対称暗号が存在しないため、HTTPのような安全でないチャネルでのデータ交換は困難です。本記事では、PKCS#1 v1.5パディングを用いた純粋なMQL5実装のRSAを紹介し、外部ライブラリを使用せずにAESのセッションキーや小規模なデータブロックを安全に送信できる方法を解説します。このアプローチにより、標準HTTP上でも、アプリケーションレベルでHTTPSに近い安全性を実現できるだけでなく、MQL5アプリケーションにおける安全な通信の重要なギャップを埋めることができます。