preview
Building an Object-Oriented FVG Scanner in MQL5

Building an Object-Oriented FVG Scanner in MQL5

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

Building an Object-Oriented FVG Scanner in MQL5

The transition from retail indicator-based trading to institutional algorithmic modeling requires a fundamental shift in how a developer interprets historical data. Traditional oscillators and moving averages use smoothing formulas. This introduces mathematical lag. Institutional algorithms operate on raw market microstructure. They analyze liquidity voids and structural imbalances created by high-frequency trading.

One of the most critical footprints of institutional order flow is the Fair Value Gap, commonly referred to as an FVG or a liquidity void. Automating the detection of these imbalances provides algorithmic systems with highly precise, unlagged zones of interest for entry and exit routing. This article describes how to build an object-oriented Fair Value Gap scanner in MQL5. We define the three-candle geometry, load data with CopyRates, manage chart objects safely, and add mitigation checks to detect when gaps are filled.


The Microstructure and Mathematical Geometry of Imbalances

Before translating the concept into MQL5 syntax, we must rigidly define the geometry of a Fair Value Gap to prevent false positives. A Fair Value Gap is strictly a three-candle formation that exposes a temporary lack of liquidity on one side of the market order book. In a bullish macroeconomic environment, a bullish FVG occurs when the high of the first candle fails to overlap with the low of the third candle, leaving the entire body of the massive second candle completely unchecked by opposing sellers. Define the indices as follows: 1 = most recently closed candle, 2 = displacement candle, 3 = origin candle. A bullish imbalance is the range between the high of index 3 and the low of index 1.

Conversely, a bearish liquidity void occurs during a violent downside displacement. In this scenario, the low of the origin candle (index 3) remains significantly higher than the high of the most recently closed candle (index 1). The unmitigated space between these two specific price points represents a severe lack of buying liquidity. Institutional algorithms target these coordinates because price often returns to the void to fill orders, and then continues the main trend. Hardcoding this precise three-candle comparative logic is the absolute foundation of our scanning architecture.

Print



Fractal Liquidity and Multi-Timeframe Alignment

Financial markets exhibit a strictly fractal nature, meaning that the micro-structural imbalances detected on a one-minute chart are mathematically and functionally identical to those forming on a weekly timeframe. However, programming an algorithmic system to trade an intraday Fair Value Gap in absolute isolation is a severe statistical flaw. Institutional models overlay a multi-timeframe matrix, demanding that a micro bullish FVG only be validated if it forms within the premium discount zone of a higher-timeframe order block.

This fractal alignment acts as a primary directional filter, aggressively reducing the win-rate degradation caused by market noise. Developers building upon our core FVG scanner class are strongly encouraged to instantiate two separate objects—one reading the hourly baseline and another targeting the five-minute execution chart—ensuring that the local geometric imbalance is heavily supported by the macroeconomic tide before any graphical rendering is authorized on the terminal screen.


Object-Oriented Encapsulation of Price Action

A scanner that evaluates history and draws many chart objects can cause memory issues if implemented linearly. To ensure institutional-grade stability, we must encapsulate the entire detection and rendering pipeline within a dedicated MQL5 class. This approach avoids global variables and allows multiple scanners on different timeframes without shared state. We declare private variables to track the maximum number of historical bars to scan, the specific color configurations for bullish and bearish voids, and a critical string prefix to identify and isolate our specific graphical objects from other manual drawings on the user's chart.

//+------------------------------------------------------------------+
//| Class: CFVGScanner                                               |
//| Purpose: Detects, draws, and manages Fair Value Gaps             |
//+------------------------------------------------------------------+
class CFVGScanner
  {
private:
   string            m_symbol;            // Target symbol for the scanner
   ENUM_TIMEFRAMES   m_timeframe;         // Target timeframe
   int               m_history_bars;      // Depth of historical scan
   string            m_obj_prefix;        // Prefix for graphical objects
   
   color             m_color_bullish;     // Color for bullish imbalances
   color             m_color_bearish;     // Color for bearish imbalances

   void              DrawRectangle(string name, datetime time1, double price1, datetime time2, double price2, color clr);
   void              ClearOldObjects(void);

public:
                     CFVGScanner(string symbol, ENUM_TIMEFRAMES tf, int depth);
                    ~CFVGScanner(void);
                    
   void              ScanAndDraw(void);
   void              SetColors(color bull, color bear);
  };

//+------------------------------------------------------------------+
//| Constructor: Initializes the environment and routing variables   |
//+------------------------------------------------------------------+
CFVGScanner::CFVGScanner(string symbol, ENUM_TIMEFRAMES tf, int depth)
  {
   m_symbol = (symbol == "") ? _Symbol : symbol;
   m_timeframe = tf;
   m_history_bars = (depth < 10) ? 10 : depth;
   m_obj_prefix = "FVG_" + m_symbol + "_" + EnumToString(m_timeframe) + "_";
   
   m_color_bullish = clrDarkGreen;
   m_color_bearish = clrMaroon;
  }



Dynamic Array Synchronization via CopyRates

To evaluate the structural geometry of the market, the algorithm must pull raw Open, High, Low, and Close (OHLC) data directly from the broker's historical server cache. MQL5 provides the highly optimized CopyRates function for this exact operation, populating an array of MqlRates structures. Because we are analyzing a three-candle sequence, we must extract the data dynamically on every new bar formation. We utilize the ArraySetAsSeries function to rigorously format the extracted array as a chronological time series. ArraySetAsSeries reverses the internal indexing so that index zero always permanently represents the current, actively fluctuating, unfinished candle, while index one represents the last fully confirmed historical block.

A crucial element of algorithmic safety involves validating the return value of the CopyRates request. If the terminal is not synchronized or the server disconnects, CopyRates may return fewer bars than requested or a negative error code. If you proceed, the code may access invalid array elements and crash due to an out-of-range error. Our engine intercepts this integer and silently aborts the scan, waiting for the terminal to successfully synchronize the missing data packets.


The Scanning Engine and Mitigation Logic

The core scanning loop iterates backwards through the securely populated MqlRates array. For every confirmed candle, the engine evaluates the relationship between index i (the origin candle), index i-1 (the displacement candle), and index i-2 (the confirmation candle). If the high of the origin candle is strictly lower than the low of the confirmation candle, a bullish liquidity void is mathematically confirmed. However, detecting the gap is only the first logical phase. Financial markets are dynamic, and these gaps are frequently mitigated or filled by subsequent price action hours or days later.

To address this, the algorithm executes a secondary look-ahead loop over subsequent candles immediately after detecting a valid imbalance. The nested loop checks later candles. It marks a bullish gap as mitigated if a low touches the gap, and a bearish gap if a high touches it. If the price action has successfully retraced and touched the origin coordinates, the gap is classified as mitigated and is mathematically disqualified from being rendered on the chart. This rigorous filtration ensures that the algorithmic system only reacts to fresh, active institutional imbalances rather than exhausted historical data.

//+------------------------------------------------------------------+
//| Core Engine: Extracts data, evaluates geometry, and filters voids|
//+------------------------------------------------------------------+
void CFVGScanner::ScanAndDraw(void)
  {
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   
   if(CopyRates(m_symbol, m_timeframe, 0, m_history_bars, rates) < m_history_bars)
     {
      PrintFormat("Warning: Failed to synchronize %s history for FVG scanner.", m_symbol);
      return;
     }
     
   ClearOldObjects();
   
   for(int i = m_history_bars - 1; i >= 3; i--)
     {
      double origin_high = rates[i].high;
      double origin_low  = rates[i].low;
      
      double confirm_high = rates[i-2].high;
      double confirm_low  = rates[i-2].low;
      
      bool is_bullish_fvg = (origin_high < confirm_low);
      bool is_bearish_fvg = (origin_low > confirm_high);
      
      if(is_bullish_fvg)
        {
         bool is_mitigated = false;
         for(int j = i - 3; j >= 1; j--)
           {
            if(rates[j].low <= origin_high) 
              { is_mitigated = true; break; }
           }
           
         if(!is_mitigated)
           {
            string name = m_obj_prefix + "BULL_" + IntegerToString(i);
            DrawRectangle(name, rates[i].time, confirm_low, rates[1].time, origin_high, m_color_bullish);
           }
        }
        
      if(is_bearish_fvg)
        {
         bool is_mitigated = false;
         for(int j = i - 3; j >= 1; j--)
           {
            if(rates[j].high >= origin_low) 
              { is_mitigated = true; break; }
           }
           
         if(!is_mitigated)
           {
            string name = m_obj_prefix + "BEAR_" + IntegerToString(i);
            DrawRectangle(name, rates[i].time, origin_low, rates[1].time, confirm_high, m_color_bearish);
           }
        }
     }
  }



Computational Complexity and Array Traversal Efficiency

When dealing with massive arrays containing thousands of historical MqlRates structures, algorithmic latency becomes a critical performance bottleneck. Inefficiently programmed scanners often deploy brute-force nested loops that evaluate the entire historical chart from start to finish on every tick, exhibiting an exponential Big O complexity of O(N²). This catastrophic design choice causes the terminal to completely freeze during initialization and destroys the frame rate of the user interface.

Our scanning architecture deliberately utilizes a mathematically bounded, single backward-iterating primary loop, maintaining a strictly linear O(N) execution profile. Furthermore, the nested mitigation loop breaks and terminates its execution the exact millisecond a mitigation wick is confirmed, actively preventing redundant CPU cycles. This high-performance memory traversal ensures that the engine can process decades of tick data seamlessly without bottlenecking the trade execution router.


Volume-Backed Displacement and Institutional Validation

Detecting a geometric gap in price action is fundamentally meaningless if the displacement was caused by low-liquidity retail panic rather than true institutional sponsorship. A genuine Fair Value Gap requires massive volume injection during the creation of the displacement candle. Developers must conceptually integrate volume analysis alongside the geometric detection to prevent the algorithm from buying into false breakouts. If the second candle in our three-bar sequence forms a massive body but registers abysmal tick volume from the broker feed, the overarching algorithm should theoretically flag this zone as a low-probability trap.

While our geometric class maps the exact coordinates of the void, advanced quantitative models will overlay the native iVolume function to ensure the displacement candle holds a volume metric significantly higher than the rolling average of the previous twenty periods. This cross-validation filters out algorithmic noise generated during illiquid market sessions and isolates authentic macroeconomic order blocks.


Algorithmic Execution Mechanics: Limit Orders vs. Market Confirmation

Once the scanner accurately maps an unmitigated liquidity void on the terminal chart, the execution engine must decide how to use this data for execution decisions. Novice algorithmic models often place static limit orders exactly at the proximal edge of the Fair Value Gap. This aggressive approach guarantees a rapid entry but exposes the trading account to severe drawdowns if the institutional flow intends to sweep the entire gap violently rather than bounce cleanly off its surface.

A vastly superior architectural design utilizes the FVG strictly as a designated zone of interest rather than a hardcoded execution trigger. The system waits for the price action to penetrate the graphical rectangle and then actively monitors lower-timeframe microstructures for a confirmed reversal signature, such as a moving average crossover or a local break of structure, before firing a market order. This multi-layered execution protocol drastically reduces the overall trade frequency but exponentially increases the mathematical expectancy of the portfolio by tightening the stop-loss parameters precisely around the point of confirmed mitigation.


Bid-Ask Spread Dynamics and Front-Running Phenomena

A theoretical liquidity void mapped perfectly on a charting canvas does not guarantee an immediate or precise order fill due to the mechanical nature of the bid-ask spread and the documented phenomena of institutional front-running. Major liquidity providers frequently defend their positions by placing massive limit order walls just a few fractional pips ahead of the actual geometric gap boundary to ensure execution before the retail crowd.

If an algorithm strictly demands a pixel-perfect touch of the origin candle's extreme price point before authorizing an entry, it will systematically miss highly profitable macroeconomic reversals. Professional developers address this slippage asymmetry by injecting a fractional spread buffer directly into the graphical coordinates, artificially expanding the zone of interest by one or two points. This programmatic tolerance absorbs latency delays and guarantees that the Expert Advisor participates in the institutional mitigation wave even if the market price falls infinitesimally short of absolute structural equilibrium.


Memory Management for Graphical Rectangles

Rendering visual elements directly onto the MetaTrader 5 chart canvas requires the strict manipulation of the ObjectCreate and ObjectSet parameters. A Fair Value Gap is best represented by the OBJ_RECTANGLE class, which requires two distinct time coordinates and two distinct price coordinates to form a bounding box. We project the rectangle from the time of the origin candle forward into the future, stopping at the time of the most recently closed candle to visually drag the zone across the screen. To maintain a professional aesthetic, we explicitly disable the background fill and configure the bounding lines to remain immutable against accidental user clicks.

When creating chart objects programmatically, memory and object lifecycle management are critical. An algorithm running on a one-minute timeframe will execute the scanning loop over a thousand times per day. If old rectangles are not deleted, the terminal may accumulate thousands of objects. This can exhaust resources and freeze the terminal (and possibly the OS). We implement a dedicated destructor and a pre-scan cleanup method. By utilizing the ObjectsDeleteAll function coupled with our unique string prefix, the engine surgically identifies and deletes only its own historical rectangles, leaving the user's manual trendlines completely untouched.

//+------------------------------------------------------------------+
//| Renders the institutional imbalance zone on the chart canvas     |
//+------------------------------------------------------------------+
void CFVGScanner::DrawRectangle(string name, datetime time1, double price1, datetime time2, double price2, color clr)
  {
   if(ObjectFind(0, name) < 0)
     {
      ObjectCreate(0, name, OBJ_RECTANGLE, 0, time1, price1, time2, price2);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_BACK, false);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
   else
     {
      ObjectSetInteger(0, name, OBJPROP_TIME, 1, time2);
     }
  }

//+------------------------------------------------------------------+
//| Prevents memory leaks by purging outdated graphical objects      |
//+------------------------------------------------------------------+
void CFVGScanner::ClearOldObjects(void)
  {
   ObjectsDeleteAll(0, m_obj_prefix);
  }

//+------------------------------------------------------------------+
//| Public method to allow developers to customize the visual output |
//+------------------------------------------------------------------+
void CFVGScanner::SetColors(color bull, color bear)
  {
   m_color_bullish = bull;
   m_color_bearish = bear;
  }



The Institutional Bridge and Final Architecture

To transition this advanced mathematical library into a fully operational trading system, we construct an Expert Advisor that instantiates the CFVGScanner class. A fundamental rule of high-performance quantitative development is protecting the central processing unit from redundant calculations. Market structure geometries, by their very definition, can only physically alter their state when a candle actively closes on the server. Running a heavy array traversal loop on every single incoming market tick is a severe architectural blunder that will overwhelm the Strategy Tester environment and degrade live performance.

We avoid redundant scans by running ScanAndDraw only when a new bar starts. By perpetually tracking the opening time of the current active candle, the algorithm mathematically locks the execution gate, allowing the heavy scanning method to fire exclusively once per timeframe cycle. The development of this automated scanner serves as a crucial bridge between retail indicator programming and institutional price action modeling in MQL5. Implementing strict synchronization protocols with the CopyRates function guarantees that the algorithm reacts exclusively to verified historical data, while aggressive memory management techniques protect the terminal from systemic crashes, leaving the developer with a highly optimized framework capable of integrating Smart Money Concepts seamlessly into any automated portfolio.

//+------------------------------------------------------------------+
//|                                              EA_FVG_Scanner.mq5  |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//+------------------------------------------------------------------+
#property copyright "Open Source"
#property version   "1.00"

//--- Input Parameters for the Scanner Engine
input int InpHistoryDepth = 100; // Historical Bars to Evaluate

//--- Global Pointer to the encapsulated scanner object
CFVGScanner *g_fvg_engine;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_fvg_engine = new CFVGScanner(_Symbol, PERIOD_CURRENT, InpHistoryDepth);
   g_fvg_engine.SetColors(clrLimeGreen, clrRed);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(CheckPointer(g_fvg_engine) == POINTER_DYNAMIC)
     {
      delete g_fvg_engine;
     }
  }

//+------------------------------------------------------------------+
//| Expert tick function with strict CPU optimization                |
//+------------------------------------------------------------------+
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_fvg_engine) != POINTER_INVALID)
        {
         g_fvg_engine.ScanAndDraw();
        }
     }
  }
//+------------------------------------------------------------------+


Conclusion

Following the stated requirements, the article produces a compact, extendable implementation that bridges concept to production. The delivered artifacts and guarantees include an object-oriented CFVGScanner class that encapsulates detection, drawing, and lifecycle management with configurable symbol, timeframe, and history depth. It provides deterministic FVG geometry utilizing a strict three-candle comparison for bullish and bearish voids.

The architecture ensures robust data handling through CopyRates and ArraySetAsSeries, featuring validation and a graceful abort when history is insufficient to prevent out-of-range access. Furthermore, a mitigation filter uses a nested look-ahead loop to reject gaps that subsequent price action has already touched, ensuring only active, unmitigated zones are presented. Safe chart rendering is achieved by naming objects with a unique prefix and selectively deleting them to avoid contaminating user drawings and prevent resource leaks. Finally, CPU protection is guaranteed as the EA integration triggers the ScanAndDraw method only on new bar formation rather than on every tick.

The scanner is intentionally designed as a zone-of-interest engine rather than an execution module. Recommended next steps involve overlaying a volume-validation check by comparing the displacement candle volume to a rolling average, and wiring a separate execution layer that uses lower-timeframe confirmation before placing market orders. Because the architecture is completely modular, these extensions can be seamlessly added without changing the core synchronization, filtration, and object-management guarantees.

File Name
Description
EA_FVG_Scanner.mq5
Complete source code integrating the FVG Class and the CPU optimization loop.
Attached files |
EA_FVG_Scanner.mq5 (7.32 KB)
Engineering Trading Discipline into Code (Part 5): Account-Level Risk Enforcement in MQL5 Engineering Trading Discipline into Code (Part 5): Account-Level Risk Enforcement in MQL5
We introduce an MQL5 discipline engine that enforces risk consistently at the account level. It continuously scans positions from any source, validates SL/TP, equity-based exposure, and target R:R, and automatically corrects deviations by setting levels or adjusting volume. The result is uniform risk structure across manual and EA trades, supported by on-chart feedback and mode-based control.
How to implement AutoARIMA forecasting in MQL5 How to implement AutoARIMA forecasting in MQL5
This article presents an MQL5 implementation of AutoARIMA that builds ARIMA models without manual tuning. It estimates d via a variance-based heuristic, fits ARMA(p,q) by gradient optimization with Adam, and selects p and q using AICc. The code returns a one-step-ahead price forecast by differencing, model estimation, and integration back to price level, ready to call on a Close series.
From Novice to Expert: Creating an MTF CRT Overlay Indicator in MQL5 From Novice to Expert: Creating an MTF CRT Overlay Indicator in MQL5
Higher-timeframe CRT ranges are informative, yet traders often execute on lower timeframes without that context. We implement an MQL5 indicator that reads higher-timeframe OHLC, projects the full candle range, body, and wicks onto the active lower-timeframe chart, and marks entries, stops, and targets. This improves situational awareness and removes the need to switch windows.
Graph Theory: Heuristic Search Algorithm (A-Star) Applied in Trading Graph Theory: Heuristic Search Algorithm (A-Star) Applied in Trading
The article applies the A* heuristic to market structure by modeling validated swing highs and lows as graph nodes and weighting edges with ATR‑normalized distance, spread, and noise penalties. The engine searches the most efficient route to infer trade direction and targets, then filters signals by directional ratio, total path cost, and opposing swings. It anchors TP to the final node and SL to prior structure, with on‑chart visualization and configurable inputs.