//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "..\\Utils\\MetricSaver.mqh" //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| Destructor: Systematic cleanup on terminal shutdown | //+------------------------------------------------------------------+ CFVGScanner::~CFVGScanner(void) { ClearOldObjects(); } //+------------------------------------------------------------------+ //| 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); } } } } //+------------------------------------------------------------------+ //| 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; } //--- Global Pointer to the encapsulated scanner object CFVGScanner *g_fvg_engine; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { CMetricsSave::Start(FILE_CODE7); 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; } CMetricsSave::Destroy(); } //+------------------------------------------------------------------+ //| 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(); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+