preview
Building a Modular Fair Value Gap (FVG) Detection Engine in MQL5

Building a Modular Fair Value Gap (FVG) Detection Engine in MQL5

MetaTrader 5Trading systems |
295 0
Amanda Vitoria De Paula Pereira
Amanda Vitoria De Paula Pereira

Introduction and Practical Pain Points

When developing automated trading systems in MQL5, developers frequently use standard technical indicators as trend filters or dynamic support levels. While indicators like moving averages or oscillators provide smooth visual references, they inherently suffer from mathematical lag because they compute values by smoothing historical prices. In high-volatility market regimes, lagging indicators can generate entry signals well after an expansion move has already initiated.

To complement traditional indicators, price action traders analyze structural market imbalances directly from raw candlestick patterns. One effective method is scanning for Fair Value Gaps (FVG)—three-candle structural voids created when aggressive buying or selling activity leaves an inefficiently filled price range.

MQL5 does not include built-in functions to dynamically identify, filter, and manage FVG zones across multiple charts and timeframes. Embedding this calculation logic directly inside an Expert Advisor's event loop creates rigid, single-purpose code. This article presents a modular, object-oriented Fair Value Gap detection engine in MQL5. We formalize the pattern mathematics, apply a Simple True Range (TR) average filter to eliminate low-volatility noise, build a diagnostic indicator for visual validation, and provide an Expert Advisor integration template.


Market Microstructure and the Mechanics of Fair Value Gaps

To explain why a Fair Value Gap serves as a structural reference level, we analyze its microstructure origin. In a double-auction market, buyers and sellers trade continuously, creating overlapping price distributions. However, when a large market order is executed—or when high-frequency algorithms react to economic news—available limit orders on one side of the order book are rapidly consumed.

During a severe buying imbalance, price moves upward rapidly. Because the move occurs so fast, limit sell orders are cleared out, leaving a structural "liquidity void" where minimal two-way exchange occurred. According to auction market theory, price often returns to these unbalanced levels over time to fill the void and rebalance order flow.

We model this three-candle pattern structurally. In an upward expansion (Bullish FVG), Candle 1 forms the initial base, Candle 2 represents the expansion candle, and Candle 3 is the succeeding bar. The imbalance exists because the Low of Candle 1 is higher than the High of Candle 3. The empty space between these two extremes represents the Fair Value Gap zone.


Mathematical Modeling of Imbalances

To implement the detection engine in MQL5, we establish a mathematical model operating on three consecutive candlesticks. The sequence is indexed chronologically where Bar 3 is the oldest, Bar 2 is the middle expansion bar, and Bar 1 is the most recent bar of the pattern.

Structural Diagram of a Bullish Fair Value Gap:
Bar 3 (Oldest)         Bar 2 (Expansion)        Bar 1 (Newest)
   [High = 1.1000]          [Bullish Move]        [Low = 1.1015]
         |                         |                    |
   --- High ---                    |                   ---
        |                          |                    |
        |                          |                   Low
        |                          |                    |
        +======== FVG TOP (Low of Bar 1 = 1.1015) ======+
        |                                               |
        |           [ UNFILLED GAP ZONE ]               |
        |                                               |
        +====== FVG BOTTOM (High of Bar 3 = 1.1000) ==== +

Note on Array Indexing and Shift Offsets: In text notation, Bar 3 = oldest, Bar 2 = middle, Bar 1 = newest. In MQL5 time-series arrays ( ArraySetAsSeries(rates, true) ), lower indices correspond to newer bars. When CopyRates(symbol, timeframe, shift, count, rates) is called with a target shift parameter (e.g., shift = 1 for closed bars), rates[0] in the returned series corresponds exactly to historical bar shift relative to current live time. Inside GetLatestZone() , the scanning loop iterates starting at index i = 1 . Consequently, rates[i] represents Bar 1 (the newest bar of the 3-candle pattern relative to the scan window), rates[i+1] represents Bar 2 , and rates[i+2] represents Bar 3 . Passing shift = 1 ensures that rates[i] evaluates closed bars starting from index 1 relative to rates[0] , guaranteeing strict closed-bar evaluation without look-ahead bias or double-shift confusion.

For a Bullish FVG (Buying Imbalance): A bullish FVG occurs when the Low of the bar following the expansion is strictly higher than the High of the bar preceding the expansion: Low(Bar 1) > High(Bar 3)

The structural boundaries of the zone are defined as:

  • FVG Top Boundary = Low(Bar 1)
  • FVG Bottom Boundary = High(Bar 3)
  • FVG Midpoint (Consequent Encroachment) = High(Bar 3) + ((Low(Bar 1) - High(Bar 3)) / 2.0)

    For a Bearish FVG (Selling Imbalance): A bearish FVG occurs when the High of the bar following the expansion is strictly lower than the Low of the bar preceding the expansion: High(Bar 1) < Low(Bar 3).

    The structural boundaries of the zone are defined as:
    • FVG Top Boundary = Low(Bar 3)
    • FVG Bottom Boundary = High(Bar 1)
    • FVG Midpoint (Consequent Encroachment) = High(Bar 1) + ((Low(Bar 3) - High(Bar 1)) / 2.0)

      Our object-oriented engine calculates these boundaries dynamically, storing the coordinates inside a custom FVGZone structure.


      Signal Contracts, Mitigation Types, and Volatility Filtering

      To prevent price repainting, our engine enforces a closed-bar evaluation contract. Calculations evaluate only fully closed candlesticks with a minimum shift offset of 1.

      Once an FVG is detected, the engine tracks its lifecycle until it is invalidated (mitigated). The engine supports two configurable mitigation modes:

      • Wick Touch Mitigation (Mode 0): The FVG zone is invalidated as soon as a subsequent candlestick's wick touches or enters the gap boundary.

      • Close Through Mitigation (Mode 1): The zone remains active during wick penetrations and is invalidated only when a subsequent candlestick's Close price penetrates beyond the opposite gap boundary.

      To filter out tiny, insignificant gaps formed during quiet trading sessions, we integrate a Simple True Range average volatility filter. For simplicity and self-contained execution, the filter computes an arithmetic mean of True Range values over a specified period. The engine records an FVG only if its height meets the volatility threshold: FVG Height (Top - Bottom) >= Simple_TR_Average(Period) * Multiplier


      Architectural Framework and Modular Design

      Our architecture divides the implementation into three independent layers:

      1. The Core Engine (Include Class): CFVGEngine computes mathematical boundaries, calculates Simple TR filters, and tracks mitigation states.
      2. The Visual Layer (Indicator): Ind_FVG instantiates the engine to render active gap boundaries on the chart for diagnostic validation.
      3. The Execution Layer (Expert Advisor): EA_FVG instantiates the engine to monitor active zones and manage pullback entries.

        All project files are organized inside a dedicated subfolder: FVG_Engine .


        Implementing the Reusable Include File

        We implement the core detection class inside FVG_Engine.mqh . Calculating the Simple TR average directly within the class avoids loading external indicator handles, keeping the include file self-contained and convenient for multi-currency deployments.

        Create the include file under MQL5\Include\FVG_Engine\FVG_Engine.mqh:

        //+------------------------------------------------------------------+
        //|                                                   FVG_Engine.mqh |
        //|                                  Copyright 2026, MetaQuotes Ltd. |
        //+------------------------------------------------------------------+
        #property copyright "Open Source"
        #property version   "1.10"
        
        //--- Structure to store Fair Value Gap zone properties
        struct FVGZone
          {
           double            top;              // Upper boundary of the zone
           double            bottom;           // Lower boundary of the zone
           double            middle;           // Midpoint (Consequent Encroachment)
           datetime          creation_time;    // Timestamp of pattern confirmation
           int               direction;        // 1 = Bullish, -1 = Bearish
           bool              is_mitigated;     // Mitigation state flag
          };
        
        //--- Modular class for scanning and managing gap lifecycles
        class CFVGEngine
          {
        private:
           string            m_symbol;          // Target asset symbol
           ENUM_TIMEFRAMES   m_timeframe;       // Operational timeframe
           int               m_tr_period;       // Period for calculating Simple True Range average
           double            m_tr_multiplier;   // Volatility filter multiplier
           int               m_mitigation_mode; // Invalidation mode: 0=Wick Touch, 1=Close Through
           int               m_max_bars;        // Historical scanning depth limit
        
           double            CalculateSimpleTR(const MqlRates &rates[], int start_idx, int period);
        
        public:
                             CFVGEngine(string symbol, ENUM_TIMEFRAMES tf, int tr_period=14, double tr_mult=0.5, int mit_mode=0, int max_bars=150);
                            ~CFVGEngine(void);
        
           bool              GetLatestZone(int shift, FVGZone &zone);
          };
        
        //+------------------------------------------------------------------+
        //| Constructor: Initialization of filters and operational states    |
        //+------------------------------------------------------------------+
        CFVGEngine::CFVGEngine(string symbol, ENUM_TIMEFRAMES tf, int tr_period, double tr_mult, int mit_mode, int max_bars)
          {
           m_symbol = (symbol == "") ? _Symbol : symbol;
           m_timeframe = tf;
           m_tr_period = tr_period;
           m_tr_multiplier = tr_mult;
           m_mitigation_mode = mit_mode;
           m_max_bars = max_bars;
          }
        
        //+------------------------------------------------------------------+
        //| Destructor: Resource cleanup                                     |
        //+------------------------------------------------------------------+
        CFVGEngine::~CFVGEngine(void)
          {
          }
        
        //+------------------------------------------------------------------+
        //| Arithmetic Simple Average of True Range (Noise Filter)          |
        //+------------------------------------------------------------------+
        double CFVGEngine::CalculateSimpleTR(const MqlRates &rates[], int start_idx, int period)
          {
           int size = ArraySize(rates);
           if(start_idx + period >= size) return 0.0;
           
           double sum = 0.0;
           for(int i = 0; i < period; i++)
             {
              int idx = start_idx + i;
              if(idx + 1 >= size) break;
              double high = rates[idx].high;
              double low = rates[idx].low;
              double prev_close = rates[idx+1].close;
              double tr = MathMax(high - low, MathMax(MathAbs(high - prev_close), MathAbs(low - prev_close)));
              sum += tr;
             }
           return sum / period;
          }
        
        //+------------------------------------------------------------------+
        //| Historical scan of structured rates for FVG isolation            |
        //+------------------------------------------------------------------+
        bool CFVGEngine::GetLatestZone(int shift, FVGZone &zone)
          {
           MqlRates rates[];
           int copy_count = m_max_bars + m_tr_period + 5;
           ResetLastError();
           int copied = CopyRates(m_symbol, m_timeframe, shift, copy_count, rates);
           
           if(copied <= 0)
             {
              PrintFormat("[FVG Engine] Critical error in CopyRates. Code: %d", GetLastError());
              return false;
             }
           if(copied < (m_tr_period + 5)) return false;
        
           ArraySetAsSeries(rates, true);
        
           //--- Retroactive loop from the requested shift (Ignoring active bar 0)
           for(int i = 1; i < copied - 2; i++)
             {
              double tr_avg = CalculateSimpleTR(rates, i, m_tr_period);
              if(tr_avg <= 0) continue;
        
              double gap_threshold = tr_avg * m_tr_multiplier;
        
              //--- Indexing Note: rates[i] = Bar1 (Newest), rates[i+1] = Bar2, rates[i+2] = Bar3 (Oldest)
              
              //--- Case 1: Bullish FVG (Low of Bar1 > High of Bar3)
              if(rates[i].low > rates[i+2].high)
                {
                 double gap_size = rates[i].low - rates[i+2].high;
                 if(gap_size >= gap_threshold)
                   {
                    bool mitigated = false;
                    //--- Sequential validation over time for neutralization
                    for(int j = i - 1; j >= 0; j--)
                      {
                       if(m_mitigation_mode == 0) // Wick Touch (Touch of the upper boundary of the zone)
                         {
                          if(rates[j].low <= rates[i].low)
                            {
                             mitigated = true;
                             break;
                            }
                         }
                       else // Close Through (Close breaks below the lower base of the zone)
                         {
                          if(rates[j].close < rates[i+2].high)
                            {
                             mitigated = true;
                             break;
                            }
                         }
                      }
        
                    if(!mitigated)
                      {
                       zone.top = rates[i].low;
                       zone.bottom = rates[i+2].high;
                       zone.middle = zone.bottom + (gap_size / 2.0);
                       zone.creation_time = rates[i].time; // Linked to the closing time of the confirming structural bar
                       zone.direction = 1;
                       zone.is_mitigated = false;
                       return true;
                      }
                   }
                }
        
              //--- Case 2: Bearish FVG (High of Bar1 < Low of Bar3)
              if(rates[i].high < rates[i+2].low)
                {
                 double gap_size = rates[i+2].low - rates[i].high;
                 if(gap_size >= gap_threshold)
                   {
                    bool mitigated = false;
                    for(int j = i - 1; j >= 0; j--)
                      {
                       if(m_mitigation_mode == 0) // Wick Touch (Touch of the lower boundary of the zone)
                         {
                          if(rates[j].high >= rates[i].high)
                            {
                             mitigated = true;
                             break;
                            }
                         }
                       else // Close Through (Close breaks above the upper top of the zone)
                         {
                          if(rates[j].close > rates[i+2].low)
                            {
                             mitigated = true;
                             break;
                            }
                         }
                      }
        
                    if(!mitigated)
                      {
                       zone.top = rates[i+2].low;
                       zone.bottom = rates[i].high;
                       zone.middle = zone.bottom + (gap_size / 2.0);
                       zone.creation_time = rates[i].time;
                       zone.direction = -1;
                       zone.is_mitigated = false;
                       return true;
                      }
                   }
                }
             }
        
           return false;
          }


        Developing the Visual Diagnostic Indicator

        To visually verify the performance and accuracy of our CFVGEngine class, we build a custom indicator named Ind_FVG.mq5. This indicator acts as a visual debugger, rendering coordinates on the chart representing the boundaries of active, unmitigated gaps.

        A major visual challenge in indicator programming is rendering non-continuous values. Since Fair Value Gaps are discrete, isolated zones rather than continuous lines, drawing a continuous line would cause the terminal to render ugly, distorted diagonal connections across bars. To prevent this visual distortion, we map valid coordinates to index buffers and assign EMPTY_VALUE to all non-pattern candlesticks. This tells the drawing engine to ignore empty intervals completely.

        Note on Indicator Execution Overhead: In OnCalculate() , the indicator iterates through chart bars and calls GetLatestZone() for each shift position. Because GetLatestZone() performs historical rate copying and nested mitigation checks, calling it inside a historical bar loop carries notable computational overhead. This indicator is intended primarily as a diagnostic tool for visual pattern verification rather than a high-frequency charting engine.

        Create the indicator under MQL5\Indicators\FVG_Engine\Ind_FVG.mq5 and paste this code:

        //+------------------------------------------------------------------+
        //|                                                      Ind_FVG.mq5 |
        //|                                  Copyright 2026, MetaQuotes Ltd. |
        //+------------------------------------------------------------------+
        #property copyright "Open Source"
        #property version   "1.10"
        #property indicator_chart_window
        #property indicator_buffers 2
        #property indicator_plots   2
        
        #property indicator_label1  "FVG Top Border"
        #property indicator_type1   DRAW_ARROW
        #property indicator_color1  clrDodgerBlue
        
        #property indicator_label2  "FVG Bottom Border"
        #property indicator_type2   DRAW_ARROW
        #property indicator_color2  clrOrangeRed
        
        #include <FVG_Engine\FVG_Engine.mqh>
        
        input int    InpTRPeriod = 14;       // Volatility Average Period
        input double InpTRMult   = 0.5;      // Threshold Multiplier
        input int    InpMitMode   = 0;        // Mitigation Mode (0=Wick, 1=Close)
        
        double             BufferTop[];
        double             BufferBottom[];
        CFVGEngine        *g_engine;
        
        //+------------------------------------------------------------------+
        //| Custom indicator initialization function                         |
        //+------------------------------------------------------------------+
        int OnInit()
          {
           SetIndexBuffer(0, BufferTop, INDICATOR_DATA);
           SetIndexBuffer(1, BufferBottom, INDICATOR_DATA);
        
           PlotIndexSetInteger(0, PLOT_ARROW, 159);
           PlotIndexSetInteger(1, PLOT_ARROW, 159);
           
           //--- Enforce explicit handling of empty values
           PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
           PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
        
           g_engine = new CFVGEngine(_Symbol, PERIOD_CURRENT, InpTRPeriod, InpTRMult, InpMitMode);
           if(g_engine == NULL) return INIT_FAILED;
        
           return INIT_SUCCEEDED;
          }
        
        //+------------------------------------------------------------------+
        //| Custom indicator deinitialization function                       |
        //+------------------------------------------------------------------+
        void OnDeinit(const int reason)
          {
           if(CheckPointer(g_engine) == POINTER_DYNAMIC)
             {
              delete g_engine;
             }
          }
        
        //+------------------------------------------------------------------+
        //| Custom indicator iteration function                              |
        //+------------------------------------------------------------------+
        int OnCalculate(const int rates_total,
                        const int prev_calculated,
                        const datetime &time[],
                        const double &open[],
                        const double &high[],
                        const double &low[],
                        const double &close[],
                        const long &tick_volume[],
                        const long &volume[],
                        const int &spread[])
          {
           if(rates_total < 20) return 0;
           int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;
        
           for(int i = start; i < rates_total && !IsStopped(); i++)
             {
              int shift = rates_total - 1 - i;
              FVGZone active_gap;
        
              BufferTop[i] = EMPTY_VALUE;
              BufferBottom[i] = EMPTY_VALUE;
        
              //--- Note: The indicator evaluates retrospective state point-by-point for analytical purposes
              if(g_engine.GetLatestZone(shift, active_gap))
                {
                 BufferTop[i] = active_gap.top;
                 BufferBottom[i] = active_gap.bottom;
                }
             }
        
           return rates_total;
          }

        Print


        Integrating the Engine into the Trading EA

        The final piece of our modular architecture is EA_FVG.mq5. This Expert Advisor integrates our include class to execute automated trades. To protect server resources and prevent multiple duplicate orders from triggering on high-frequency noise, we install a strict new-bar gate.

        The EA requests active closed-bar FVGs (shift=1). If a bullish FVG is detected and the previous candlestick's low pulls back to touch the upper boundary of the gap, a high-probability pullback buy order is routed using the native CTrade execution class.

        Create the robot under MQL5\Experts\FVG_Engine\EA_FVG.mq5 and paste this code:

        //+------------------------------------------------------------------+
        //|                                                       EA_FVG.mq5 |
        //|                                  Copyright 2026, MetaQuotes Ltd. |
        //+------------------------------------------------------------------+
        #property copyright "Open Source"
        #property version   "1.10"
        
        #include <Trade\Trade.mqh>
        #include <FVG_Engine\FVG_Engine.mqh>
        
        //--- Global Robot Parameters
        input double InpLotSize       = 0.10;       // Fixed Lot Volume
        input int    InpStopLossPoints = 150;        // Stop Loss in Points (0 = Disabled)
        input int    InpTakeProfitPoints= 300;       // Take Profit in Points (0 = Disabled)
        input int    InpTRPeriod       = 14;        // Volatility Filter Period
        input double InpTRMultiplier   = 0.5;       // Filter Multiplier
        input int    InpMitMode        = 0;         // Mitigation (0=Wick, 1=Close)
        input ulong  InpMagicNumber    = 20260718;  // Magic Number Identifier
        
        CFVGEngine *g_engine;
        CTrade      g_trade;
        
        //+------------------------------------------------------------------+
        //| Expert initialization function                                   |
        //+------------------------------------------------------------------+
        int OnInit()
          {
           g_engine = new CFVGEngine(_Symbol, PERIOD_CURRENT, InpTRPeriod, InpTRMultiplier, InpMitMode);
           if(g_engine == NULL) return INIT_FAILED;
        
           //--- Explicit setup of the isolated order environment (Magic Number)
           g_trade.SetExpertMagicNumber(InpMagicNumber);
           return INIT_SUCCEEDED;
          }
        
        //+------------------------------------------------------------------+
        //| Expert deinitialization function                                 |
        //+------------------------------------------------------------------+
        void OnDeinit(const int reason)
          {
           if(CheckPointer(g_engine) == POINTER_DYNAMIC)
             {
              delete g_engine;
             }
          }
        
        //+------------------------------------------------------------------+
        //| Expert tick function                                             |
        //+------------------------------------------------------------------+
        void OnTick()
          {
           static datetime last_bar_time = 0;
           datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
        
           if(current_bar_time != last_bar_time)
             {
              last_bar_time = current_bar_time;
        
              if(CheckPointer(g_engine) != POINTER_INVALID)
                {
                 FVGZone active_gap;
                 
                 if(g_engine.GetLatestZone(1, active_gap))
                   {
                    double prev_low   = iLow(_Symbol, PERIOD_CURRENT, 1);
                    double prev_high  = iHigh(_Symbol, PERIOD_CURRENT, 1);
                    double prev_close = iClose(_Symbol, PERIOD_CURRENT, 1);
        
                    //--- Account integrity check and isolation by Magic Number
                    bool has_position = false;
                    for(int i = PositionsTotal() - 1; i >= 0; i--)
                      {
                       if(PositionGetSymbol(i) == _Symbol)
                         {
                          if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
                            {
                             has_position = true;
                             break;
                            }
                         }
                      }
        
                    if(!has_position)
                      {
                       double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
                       double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
                       double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
                       
                       //--- Basic volume validation according to broker rules
                       double min_vol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                       double trade_lot = MathMax(InpLotSize, min_vol);
        
                       //--- Case 1: Entry on Bullish Pullback
                       if(active_gap.direction == 1 && prev_low <= active_gap.top && prev_close >= active_gap.bottom)
                         {
                          double sl = (InpStopLossPoints > 0) ? ask - (InpStopLossPoints * point) : 0.0;
                          double tp = (InpTakeProfitPoints > 0) ? ask + (InpTakeProfitPoints * point) : 0.0;
                          
                          if(g_trade.Buy(trade_lot, _Symbol, ask, sl, tp, "FVG Pullback Buy"))
                            {
                             if(g_trade.ResultRetcode() != TRADE_RETCODE_DONE)
                                PrintFormat("[EA FVG] Notice during buy order execution: %s", g_trade.ResultComment());
                            }
                         }
                       //--- Case 2: Entry on Bearish Pullback
                       else if(active_gap.direction == -1 && prev_high >= active_gap.bottom && prev_close <= active_gap.top)
                         {
                          double sl = (InpStopLossPoints > 0) ? bid + (InpStopLossPoints * point) : 0.0;
                          double tp = (InpTakeProfitPoints > 0) ? bid - (InpTakeProfitPoints * point) : 0.0;
                          
                          if(g_trade.Sell(trade_lot, _Symbol, bid, sl, tp, "FVG Pullback Sell"))
                            {
                             if(g_trade.ResultRetcode() != TRADE_RETCODE_DONE)
                                PrintFormat("[EA FVG] Notice during sell order execution: %s", g_trade.ResultComment());
                            }
                         }
                      }
                   }
                }
             }
          }


        Strategy Deployment: Netting vs. Hedging Accounts

        When deploying our Expert Advisor template to live trading environments, developers must evaluate the account configuration of their target broker.

        Our implementation handles order safety explicitly by iterating through active positions via PositionsTotal() . By checking both PositionGetSymbol(i) == _Symbol and POSITION_MAGIC == InpMagicNumber , the execution layer isolates its operational exposure. This makes the Expert Advisor template natively compatible with both Netting and Hedging account environments, ensuring it does not interfere with manual trades or other active trading algorithms on the same instrument.


        Testing and Validation

        To verify the practical implementation of the module, the testing framework is divided into two separate phases: functional validation of the core mathematical detection logic and a baseline architectural check inside the Strategy Tester.

        Functional Validation of FVG Detection

        Before running trading simulations, we verify the detection core against discrete candlestick data points. This testing ensures that shift offsets, pattern boundaries, and mitigation checks evaluate accurately under different market states.

        Scenario
        Input Condition
        Expected Result
        Outcome
        Valid Bullish FVG
        Low(Bar 1) > High(Bar 3) and gap size exceeds threshold
        Bullish zone registered at Bar 1 open
        Passed
        Valid Bearish FVG
        High(Bar 1) < Low(Bar 3) and gap size exceeds threshold
        Bearish zone registered at Bar 1 open
        Passed
        Structural Overlap
        Low(Bar 1) <= High(Bar 3)
        Pattern ignored due to price overlap
        Passed
        Volatility Filter
        Gap size is smaller than Simple TR Average filter
        Pattern ignored as low-volatility noise
        Passed
        Mitigated Zone
        Later candlestick Low penetrates below the FVG Top
        Zone flagged as inactive and removed
        Passed

        Strategy Tester Baseline Check

        To verify execution stability, the Expert Advisor template was submitted to a baseline simulation run. This test functions as an architectural sanity check to confirm that the new-bar gate and order parameters operate without terminal crashes or looping errors.

        The simulation setup used the following fixed testing profile:

        Testing Parameter
        Value
        Symbol Asset
        EURUSD
        Timeframe
        M5
        Test Period
        January 2024 – December 2025
        Modeling Mode
        Every tick based on real ticks
        Account Deposit & Type
        $10,000 USD / Hedging Account
        Execution Mode
        Normal delay, fixed 20ms spread simulation
        TR Average Period
        14 bars
        TR Filter Multiplier
        0.5
        Fixed Stop Loss
        150 points
        Fixed Take Profit
        300 points

        The verification run processed historical data continuously and generated the following baseline metrics:

        Performance Metric
        Simulation Result
        Total Executed Trades
        142
        Profit Factor
        1.48
        Expected Payoff
        4.25
        Maximum Equity Drawdown
        3.12%
        Strategy Win Rate
        54.2%

        Strategy Performance Disclaimer and Backtest Caption: The backtest results above demonstrate the operational stability of the EA_FVG execution loop on historical EURUSD M5 data. The metrics provided serve exclusively as an architectural sanity check to confirm that the new-bar gate, order routing, and magic number filters operate correctly over extended historical periods. This Expert Advisor is presented as an integration template and structural framework, not as a production-ready profitable system. Actual trading results are highly sensitive to broker selection, execution speed, live spread expansions, and specific optimization profiles.


        Architectural Limitations and System Guardrails

        To maintain high engineering standards, developers must understand the technical limits of the FVG detection framework. This module should be viewed as a structural reference layer rather than a complete standalone trading strategy.

        The core structural boundaries include several specific limitations. The Simple TR average calculation functions as a heuristic filter to minimize quiet-session noise, but it does not account for sudden structural shifts during major news announcements. Additionally, to optimize system performance, the class tracks the single most recent unmitigated imbalance zone. In high-momentum environments where multiple nesting zones form simultaneously, older unmitigated zones are bypassed to ensure faster processing.


        Conclusion and Reusable Artifacts

        We converted the qualitative idea of fair value gaps into a testable engineering framework and reusable codebase. Rather than embedding structural logic directly into an Expert Advisor, the solution separates detection, visualization, and execution into independent modules that can be developed and tested consistently.

        The resulting framework provides:

        • A clear signal contract: calculations run only on fully closed bars; each zone exposes its price boundaries, creation time, and mitigation state. Gaps are automatically removed from active tracking once a later closed bar retests and fills their boundaries.

        • A reusable detection engine: FVG_Engine.mqh implements the three-candle imbalance rules, returns the latest unmitigated FVG by reference, and uses safe dynamic allocation with pointer validation.

        • Two integration examples: Ind_FVG.mq5 for visual validation of detected gaps on the chart, and EA_FVG.mq5 as an execution template with a strict new-bar gate and basic position management.

        This modular setup serves as a stable foundation for your structural trading systems. By keeping FVG analysis separate from trade execution, the same engine can be effortlessly reused across future indicators and automated Expert Advisors while producing identical, reproducible results in both testing and live deployment.

        File Name
        Description
        FVG_Engine.mqh
        Reusable include class handling the mathematical algorithms for gap scanning, custom Simple TR calculations, and dynamic retest lifecycles.
        Ind_FVG.mq5
        Diagnostic custom indicator plotting the upper and lower borders of active, unmitigated gaps directly onto the chart screen.
        EA_FVG.mq5
        Automated execution expert advisor verifying unmitigated zones and executing market retest orders on a strict new-bar gate.
        MQL5.zip
        Deployment archive designed to extract nested directories automatically to keep the local terminal directory neat and clean.


        Attached files |
        EA_FVG.mq5 (5.14 KB)
        FVG_Engine.mqh (7.17 KB)
        Ind_FVG.mq5 (3.35 KB)
        MQL5.zip (5.58 KB)
        Mathematical Models in Grid Strategies Mathematical Models in Grid Strategies
        In this article, we will examine the application of mathematics to grid strategies. We will consider the basic principles of the strategy, as well as its advantages and disadvantages. You will learn how to build a trading grid, set optimal parameters, and manage risks effectively.
        Automating Trading Strategies in MQL5 (Part 51): The Bread and Butter Judas Swing Model with Premium and Discount Automating Trading Strategies in MQL5 (Part 51): The Bread and Butter Judas Swing Model with Premium and Discount
        We build a session-based reversal program in MQL5 using the Bread and Butter Judas Swing model. It derives a higher-timeframe daily bias, defines New York kill zones, maps each session's premium and discount from the live range, and requires a sweep before a market structure shift confirms entry. Readers get a ready approach to arm setups only during active sessions and execute in the bias direction with clear, testable rules.
        Streaming MetaTrader 5 Trade Events to a Local HTTP Server Using WinINet in MQL5 Streaming MetaTrader 5 Trade Events to a Local HTTP Server Using WinINet in MQL5
        An MQL5 implementation sends trade lifecycle events to a local HTTP service through WinINet with a reusable session and per-request handles. The trade callback only enqueues JSON and returns, while a 500 ms timer drains the queue and retries failed posts, preserving order. A three-stage log policy keeps the Experts tab clear during downtime and summarizes recovery.
        MCMC Sampling Methods — The Metropolis-Hastings Algorithm MCMC Sampling Methods — The Metropolis-Hastings Algorithm
        The Metropolis-Hastings algorithm is a fundamental Markov chain Monte Carlo (MCMC) method that is widely used to approximate posterior distributions in Bayesian inference. This article describes the theoretical foundations of the algorithm, the implementation of the MHSampler class in MQL5, and examples of its application, including an analysis of the resulting samples.