MQL5における取引戦略の自動化(第45回):逆フェアバリューギャップ(IFVG)
はじめに
前回の記事(第44回)では、MetaQuotes Language 5 (MQL5)でChange of Character (CHoCH)検出システムを開発しました。このシステムはバーをスキャンし、トレンド判定のためにスイングハイおよびスイングローを識別し、ラベル付けするものでした。また、反転を示すブレイクシグナルに基づいて取引をトリガーしました。このシステムはバー単位およびティックモードの両方に対応し、アイコン、ラベル、ブレイクライン、動的フォントを用いて可視化されていました。第45回では、逆フェアバリューギャップ(IFVG)システムを開発します。
このシステムは、直近のバーにおいて強気または弱気のフェアバリューギャップ(FVG)を検出し、最小ギャップサイズフィルタを適用します。その後、価格の相互作用に基づいて状態をnormal、mitigated、invertedとして追跡します。ミティゲーションは遠側ブレイク時に発生し、再エントリー時にはリトレースとして扱われ、内側から遠側を超えてクローズした場合にはインバージョンとして扱われます。このシステムは重複ゾーンを無視しつつ、追跡するFVGの数を制限します。また、各FVGに対して1回、制限付き、または無制限の取引実行モードをサポートします。強気IFVGでは買い、弱気IFVGでは売りエントリーをおこないます。ゾーンは色付きの矩形として表示され、状態や取引ラベル、ミティゲーションアイコンと共に可視化されます。本記事では以下のトピックを扱います。
最終的に、状態追跡、適応的な可視化、設定可能なモードを備えたIFVG検出および取引戦略をMQL5で実装できるようになります。それでは始めましょう。
逆フェアバリューギャップ(IFVG)フレームワークの理解
フェアバリューギャップ(FVG)とは、ローソク足間における価格の不均衡またはギャップを表すプライスアクションの概念です。これは、買い圧力または売り圧力によって未充填の領域(空白)が形成された状態を指し、一般的に強気FVG(後続ローソク足の安値が前ローソク足の高値より上)または弱気FVG(後続ローソク足の高値が前ローソク足の安値より下)として観測されます。これらは、価格が後に埋め戻す可能性のある領域であり、サポート/レジスタンスゾーンとして機能します。逆フェアバリューギャップ(IFVG)は、ミティゲーションされたFVG(価格が遠側をブレイクした状態)がその後リトレースされ、さらにその内部からの価格が遠側を超えてクローズすることで反転が発生する現象です。これはトレンド反転シグナルとして機能します。すなわち、ミティゲーションされた強気FVGが弱気へ反転する場合(再エントリー後に価格が安値を下回ってクローズ)、またはミティゲーションされた弱気FVGが強気へ反転する場合(高値を上抜けてクローズ)です。状態は、normal(初期ギャップ)→ mitigated(遠側ブレイク)→ retraced(ミティゲーション後の再侵入)→ inverted(リトレース後に内部から遠側をブレイクしてクローズ)のように進行を追跡します。この中でinverted状態が主要な売買シグナルとなります。
たとえば、ミティゲーションされた弱気FVG(元は下方向のギャップ)においては、価格が再びゾーン内に戻った後に高値を上抜けてクローズすると強気IFVGとなり、買いエントリーをおこないます。この場合、ストップロスは安値の下、テイクプロフィットは固定ポイントに設定されます。逆に、ミティゲーションされた強気FVG(元は上方向のギャップ)では、価格が再侵入後、安値を下回ってクローズした場合に弱気IFVGが成立し、売りエントリーをおこないます。以下に想定される各セットアップの例を示します。

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

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)を追記します。このテキストをObjectSetStringとOBJPROP_TEXTを用いてラベルに適用します。最後にDrawMitIcon関数を作成し、ミティゲーションイベントの視覚的マーカーを配置します。アイコン名はFVG名に_MitIconを付加して生成し、OBJ_ARROWを用いてミティゲーション時刻と適切な価格位置(上方向ギャップでは安値、下方向ギャップでは高値)に配置します。矢印コードは251に設定し、色は青に設定します。またisUpに応じてアンカーを上または下に設定し、チャートを再描画します。矢印コードは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)まで逆方向にループ処理をおこないます。各バーについて、iLow、iHigh、iClose関数を用いて安値、高値、終値を取得します。まだミティゲーションが発生していない場合、遠側ブレイクをチェックします。強気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が検出された場合、バー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の場合は終了ログを出力します。コンパイルすると、次の結果が得られます。

この可視化から分かる通り、起動時にすべての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を呼び出して直近のバーから新しいギャップをスキャンします。次のような結果が得られます。

初期検出が完了したため、次はセットアップの更新処理に進みます。以下のロジックを使用します。
//+------------------------------------------------------------------+ //| 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を用い、ObjectGetDoubleのOBJPROP_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状態が価格変動に同期され、正確なインバージョンシグナルを取得しつつ、欠損オブジェクトも適切に処理できます。この関数を呼び出した際の結果は以下のようになります。

このように、価格が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価格をSymbolInfoDoubleでSYMBOL_ASKを用いて取得し、桁数に正規化してAskに格納します。同様にBidもSYMBOL_BIDで取得してBidに格納します。その後fvgs配列をループし、各エントリについて新しいシグナルがない場合、またはミティゲーション時間が0の場合はスキップします。また取引モードも確認し、TradeOnceの場合は既にトレード回数が1以上であれば、LimitedTradesの場合はmaxTradesPerFVG以上であれば、そのシグナルをリセットして次へ進みます。
有効なシグナルについては、FVGの高値と安値をMathMinとMathMaxおよび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が一致するポジションに対して、PositionGetDoubleとPOSITION_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を呼び出し、迅速な調整をおこないます。コンパイルすると、次の結果が得られます。
この可視化から、逆フェアバリューギャップの検出、取引実行、および管理がすべて機能しており、目的が達成されていることが確認できます。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストによって、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
本記事ではMQL5において逆フェアバリューギャップ(IFVG)システムを開発しました。このシステムは、直近のバーから最小ギャップサイズのフィルタを用いて強気および弱気のフェアバリューギャップ(FVGs)を検出し、価格の動きに基づいて状態を通常、ミティゲーション、インバージョンとして追跡します。また、重複を無視しつつ追跡数を制限し、初期化時には過去のFVGを読み込み、リアルタイム更新および期限切れのクリーンアップにも対応しています。このシステムは、各セットアップごとに1回、制限付き、または無制限の取引をサポートし、強気のIFVGでは買い、弱気のIFVGでは売りを固定のトレードレベルで実行します。さらに、ポジション制限、トレーリングストップ機能を備え、状態やトレード情報を表示するラベルやミティゲーションアイコン付きのカラーレクタングルとして視覚化します。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
この逆フェアバリューギャップ戦略により、状態追跡とインバージョンシグナルを活用したギャップの不均衡トレードが可能になります。今後の取引におけるさらなる最適化に向けた基盤としてご活用ください。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20361
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5入門(第30回):MQL5のAPIとWebRequest関数の習得(IV)
プロップファームチャレンジをクリアするための自動リスク管理
取引戦略の開発:出来高制限アプローチの使用
MQL5における純粋なRSA暗号化の実装
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索