preview
Building a Viewport SnR Volume Profile Indicator in MQL5

Building a Viewport SnR Volume Profile Indicator in MQL5

MetaTrader 5Examples |
2 158 0
Chukwubuikem Okeke
Chukwubuikem Okeke

Introduction

Support and Resistance (SnR) detection is often one of the first practical challenges traders face when moving from discretionary chart analysis to algorithmic development in MQL5—and it was no different for me. After experimenting with several approaches, I was able to successfully detect and plot SnR levels using both close-price and wick-based approaches. At the time, this felt like a major milestone: the market structure was finally being translated into code.

However, that success quickly revealed a deeper and often overlooked problem—abundance. The chart became crowded with SnR levels, all technically valid, yet offering no clear guidance on which ones truly mattered. MetaTrader 5 provides no native mechanism to differentiate strong, well-defended SnR levels from weak or incidental ones. This results in visual noise: too many levels, with little or no insight.

What’s actually missing is not just another SnR detector, but a way to quantitatively express the strength of each SnR level within the current viewport.

In this article, we bridge that gap by building a viewport-based SnR volume profile indicator in MQL5. It is context-aware and fully adaptive. The indicator responds dynamically to chart interactions. It recalculates on scroll, uses fewer bins when you zoom in, and uses more bins when you zoom out to capture broader structure.By tying the profile resolution to the visible chart range, the indicator reflects the trader's current focus. It presents SnR strength in a visually intuitive and quantitatively meaningful way.

By the end of this article, you will understand how to:

  • Implement viewport awareness using MQL5.
  • Programmatically detect dynamic SnR levels in MQL5.
  • Construct a volume profile within those levels using tick or real volume.
  • Identify high-probability reaction levels inside SnR levels.
  • Plot and update these levels efficiently in real time on MetaTrader 5.


Program Overview

This project presents the design and implementation of an adaptive, viewport-aware Support and Resistance (SnR) Volume Profile indicator for MetaTrader 5 using MQL5. Rather than treating all detected SnR levels as equally important, the project introduces a quantitative framework for evaluating SnR strength based on volume participation within the visible chart range.

The indicator continuously analyzes the current viewport, dynamically detecting SnR zones and constructing localized volume profiles whose resolution automatically adjusts to chart zoom and scroll events. By compressing or expanding profile bins in response to chart context, the system maintains clarity, relevance, and performance—regardless of whether the trader is analyzing micro price action or broader market structure.

At its core, the project transforms raw structural levels into probability-weighted reaction zones. This helps distinguish incidental levels from well-defended areas backed by meaningful volume. The result is a context-sensitive SnR visualization that bridges structural price analysis with volume-based insight, offering a practical and extensible foundation for advanced algorithmic trading tools in MQL5.

Below is a visual model of the indicator described in this project:

Viewport SnR Volume Profile Indicator

Fig. 1. Project Overview

Design Workflow

This section outlines the end-to-end system workflow. We break down the process into its core stages: viewport detection, support and resistance identification, adaptive bin construction, volume accumulation within SnR zones, and efficient rendering of the resulting volume profile (boxes) on the chart.

Viewport Detection

The indicator continuously monitors the visible chart range to determine the active time and price boundaries. All subsequent computations are constrained strictly to this viewport, ensuring that analysis remains context-aware and responsive to chart scroll and zoom events.

Viewport Detection

Fig. 2. Viewport Detection

Support and Resistance (SnR) Identification

Within the detected viewport, support and resistance zones are derived using price-based rules (wick or body logic). These zones are recalculated dynamically as the viewport changes, allowing SnR structures to adapt in real time to the trader’s current focus.

Support and Resistance (SnR) Identification

Fig. 3. SnR Identification

Adaptive Bin Construction

The vertical price range—defined by the highest and lowest price levels within the detected viewport—is subdivided into a variable number of price bins (or buckets). The number of bins is adjusted dynamically based on chart zoom level, compressing into fewer bins for narrow views and expanding into more bins for broader views, ensuring consistent analytical resolution and visual clarity across all chart contexts.

Bin Construction

Fig. 4. Bin Construction

Volume Accumulation Within SnR Zones

Tick or real volume is aggregated into the corresponding price bins as price traverses the identified support and resistance zones. These SnR levels are highlighted in Figure 3. This accumulation process quantifies market participation at each level, transforming raw structural zones into measurable, volume-weighted strength profiles.

Volume Profiles Rendering

Finally, the accumulated volume data is mapped to horizontal profile bars and rendered directly on the chart. Profile widths remain uniform across all levels, while the horizontal extent to the right represents the relative strength of each SnR level within its corresponding price range. Rendering is optimized to update only when necessary, preserving performance while ensuring the displayed profiles always reflect the current viewport state.


MQL5 Implementation

This section focuses on the practical implementation of the system workflow in MQL5. Each component is translated into concrete implementation details, covering chart event handling, data structures, computational logic, and rendering routines used to build the adaptive viewport-based SnR volume profile indicator.

Before diving into the individual workflow components, we first establish the structural foundation of the indicator. The core code layout is presented, including preprocessor directives, custom enumerations, data structures, input parameters, global state variables, and the standard MQL5 event handlers (OnInit, OnDeinit, OnCalculate, and OnChartEvent).

Together, these elements define the indicator’s configuration, runtime state, and execution flow.

//+------------------------------------------------------------------+
//|                                  Viewport SnR Volume Profile.mq5 |
//|                                             © 2026, ChukwuBuikem |
//|                             https://www.mql5.com/en/users/bikeen |
//+------------------------------------------------------------------+
#property copyright "© 2026, ChukwuBuikem"
#property link      "https://www.mql5.com/en/users/bikeen"

#define _PROG_NAME "Viewport SnR Volume Profile"

#property version   "1.00"
#property description _PROG_NAME
#property indicator_chart_window
#property indicator_plots 0

#define _SNR_PROFILE _PROG_NAME + "Profile"
#define _SNR_POC _PROG_NAME + "PoC"

#include <ChartObjects\ChartObjectsShapes.mqh>
#include <ChartObjects\ChartObjectsLines.mqh>

//--- CUSTOM ENUMERATION
enum ENUM_SNR_MODE
  {
   SNR_MODE_WICK, //WICK DETECTION
   SNR_MODE_BODY  //BODY DETECTION
  };
//--- DATA STRUCTURE
struct st_priceVolume
  {
   //---
   double            price;
   long              volume;
   //--- CONSTRUCTOR
                     st_priceVolume(): price(EMPTY_VALUE), volume(0) {}
  };
//--- INDICATOR SETTINGS
input group "+== SnR Settings ==+"
input ENUM_SNR_MODE modeSNR = SNR_MODE_WICK;//SnR Detection Mode
input int rightLeftBars = 4;                //Pivot Strength (bars on each side)

//--- GLOBAL STATE VARIABLES
CChartObjectRectangle rect;
CChartObjectHLine hLine;

//+------------------------------------------------------------------+
//|               Initialization Function                            |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                Deinitialization Function                         |
//+------------------------------------------------------------------+
void OnDeinit(const int32_t reason)
  {
//---

  }
//+------------------------------------------------------------------+
//|                Indicator iteration function                      |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---

   return(rates_total);
  }
//+------------------------------------------------------------------+
//|                     OnChartEvent Function                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id, const long& lparam, const double& dparam, const string& sparam)
  {
//---

  }

Explanation:


  • Preprocessor Directives
    The indicator begins with a set of preprocessor directives that define its compile-time configuration and dependencies.

    1.  #property directives specify indicator metadata and runtime behavior, informing MetaTrader 5 how the indicator should be initialized and displayed on the chart.
    2. #define macros declare compile-time constants used to centralize fixed parameters and improve code readability.
    3. #include statements import required MQL5 headers and libraries, enabling access to external classes, utilities, and shared functionality.

         Together, these directives establish the foundational context in which the indicator operates.

  • Custom Enumeration
    This custom enumeration defines the SnR detection mode, allowing the indicator to switch between wick-based and body-based price analysis to aid accurate identification of support and resistance levels.
  • Data Structure
    The st_priceVolume structure encapsulates a price–volume pair, providing a lightweight container for associating aggregated volume with specific price levels during SnR volume profile construction.
  • Input Settings
    The following input settings define the user-configurable parameters for support and resistance detection, allowing selection of the SnR detection mode (wick- or body-based) and control over pivot strength via the number of bars evaluated on each side.
  • Global State Variables
    At runtime, these global state variables maintain references to chart objects used for rendering graphical elements, such as rectangles and horizontal lines, directly on the price chart.
  • Standard Event Handlers
    The execution flow of the indicator is managed through the standard MQL5 event functions, namely OnInit(), OnDeinit(), OnCalculate(), and OnChartEvent(). These handlers enable real-time responsiveness, with OnChartEvent() specifically capturing CHARTEVENT_CHART_CHANGE, allowing the system to react dynamically to chart interactions such as zooming, scrolling, and viewport updates.
Helper Functions

This section introduces a set of helper functions that encapsulate the indicator’s core responsibilities. These utilities handle viewport detection, support and resistance identification, volume normalization, chart object rendering, and the central orchestration function where the full system workflow is assembled.

  • Viewport Detection
    We begin by defining helper functions that extract the current chart viewport properties. The first function determines the visible time range by calculating the start and end timestamps of the active viewport while also returning the number of candlesticks currently in view. Building on this information, a second function derives the number of price bins from the chart’s zoom level by mapping visible bars to bin count, with a minimum fallback applied to maintain analytical stability at extreme zoom levels.
//+------------------------------------------------------------------+
//|                    Viewport Detection                            |
//+------------------------------------------------------------------+
int getViewportValues(datetime &start, datetime &end)
  {
//---
   int first = (int) ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   int visibleBars = (int) ChartGetInteger(0, CHART_VISIBLE_BARS);

   int last = first - visibleBars + 2;
   if(last < 0)
      last = 0;

   visibleBars = visibleBars - 2;
   start = iTime(_Symbol, PERIOD_CURRENT, last);
   end = iTime(_Symbol, PERIOD_CURRENT, first);

   return visibleBars;
  }
//+------------------------------------------------------------------+
//|               ADAPTIVE BIN CONSTRUCTION FROM ZOOM                |
//+------------------------------------------------------------------+
int getBinsFromZoom()
  {
//---
   int visibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   int bins = (int)(visibleBars / 4);

   return MathMax(30, bins);
  }

  • Support and Resistance (SnR) Identification
    At this stage, the indicator implements a price-action–driven support and resistance detection mechanism using either candle bodies or wicks, as selected by the user. Helper functions abstract the price source for support and resistance evaluation, enabling a consistent detection logic across both modes. Pivot-based validation is then applied by comparing each candidate bar against a configurable number of surrounding bars to confirm structural significance. Detection is performed strictly within the active viewport, where validated support and resistance levels are captured alongside their corresponding tick or real volume, forming the foundational input for subsequent volume profiling.
//+------------------------------------------------------------------+
//|          Body / Wick based Detection For Support                 |
//+------------------------------------------------------------------+
double sPrice(const int barIndex, const MqlRates &rates[])
  {
   return(modeSNR == SNR_MODE_BODY) ? rates[barIndex].close
         : rates[barIndex].low;
  }
//+------------------------------------------------------------------+
//|       Body / Wick based Detection For Resistance                 |
//+------------------------------------------------------------------+
double rPrice(const int barIndex, const MqlRates &rates[])
  {
   return(modeSNR == SNR_MODE_BODY) ? rates[barIndex].close
         : rates[barIndex].high;
  }
//+------------------------------------------------------------------+
//|                 Support Identification                           |
//+------------------------------------------------------------------+
bool isSupport(const int barIndex, const MqlRates &rates[])
  {
//---
   int totalBars = ArraySize(rates);
//--- Index boundary validation
   if(barIndex < rightLeftBars)
      return false;
   if(barIndex >= totalBars - rightLeftBars)
      return false;

   for(int w = 1; w <= rightLeftBars && (barIndex - w) >= 1; w++)
     {
      if(barIndex - w < 1)
         return false;
      //--- Look right
      if(sPrice(barIndex, rates) > sPrice(barIndex - w, rates))
         return false;
      //--- Look left
      if(sPrice(barIndex, rates) > sPrice(barIndex + w, rates))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//|                  Resistance Identification                       |
//+------------------------------------------------------------------+
bool isResistance(const int barIndex, const MqlRates &rates[])
  {
//---
   int totalBars = ArraySize(rates);
//--- Index boundary validation
   if(barIndex < rightLeftBars)
      return false;
   if(barIndex >= totalBars - rightLeftBars)
      return false;

   for(int w = 1; w <= rightLeftBars && (barIndex - w) >= 1; w++)
     {
      //--- Look right
      if(rPrice(barIndex, rates) < rPrice(barIndex - w, rates))
         return false;
      //--- Look left
      if(rPrice(barIndex, rates) < rPrice(barIndex + w, rates))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//|                 Viewport Support Detection                       |
//+------------------------------------------------------------------+
void getSupportLevels(st_priceVolume &supp[])
  {
//--- OBTAIN VIEWPORT VALUES
   datetime start, end;
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   getViewportValues(start, end);
//--- COPY VIEWPORT BARS DATA
   if(CopyRates(_Symbol, PERIOD_CURRENT, start, end, rates) > 0)
     {
      bool useRealVol = (rates[0].real_volume > 0);
      //--- DETECT SNR WITHIN VIEWPORT
      for(int w = rightLeftBars; w < ArraySize(rates) - rightLeftBars; w++)
        {
         if(isSupport(w, rates))
           {
            if(ArrayResize(supp, ArraySize(supp) + 1))
              {
               supp[ArraySize(supp) - 1].price = sPrice(w, rates);
               supp[ArraySize(supp) - 1].volume = (useRealVol) ? rates[w].real_volume : rates[w].tick_volume;
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//|                 VIEWPORT RESISTANCE DETECTION                    |
//+------------------------------------------------------------------+
void getResistanceLevels(st_priceVolume &res[])
  {
//--- OBTAIN VIEWPORT VALUES
   datetime start, end;
   MqlRates rates[];
   ArraySetAsSeries(rates, true);
   getViewportValues(start, end);
//--- COPY VIEWPORT BARS DATA
   if(CopyRates(_Symbol, PERIOD_CURRENT, start, end, rates) > 0)
     {
      bool useRealVol = (rates[0].real_volume > 0);
      //--- DETECT SNR WITHIN VIEWPORT
      for(int w = rightLeftBars; w < ArraySize(rates) - rightLeftBars; w++)
        {
         if(isResistance(w, rates))
           {
            if(ArrayResize(res, ArraySize(res) + 1))
              {
               res[ArraySize(res) - 1].price = rPrice(w, rates);
               res[ArraySize(res) - 1].volume = (useRealVol) ? rates[w].real_volume : rates[w].tick_volume;
              }
           }
        }
     }
  }
  • Volume Normalization
    To ensure consistent visual scaling, a min–max normalization function is defined for the aggregated volume values. By mapping raw volume into a bounded range between 0 and 1 relative to the minimum and maximum observed volumes, this step enables proportional comparison of SnR strength and drives uniform profile rendering across varying market conditions.
//+------------------------------------------------------------------+
//|                   VOLUME NORMALIZATION                           |
//+------------------------------------------------------------------+
double normalizeMinMax(const long value, const long maxVol, const long minVol)
  {
//---
   if(maxVol <= minVol)
      return 0;

   return (double)(value - minVol) / (maxVol - minVol);
  }
  • Chart Object Rendering
    For graphical output, dedicated helper functions are used to create and manage chart objects through the globally instantiated CChartObjectRectangle and CChartObjectHLine classes. These routines leverage the shared object instances to efficiently render volume profile blocks and key horizontal levels on the chart, while enforcing consistent styling, non-interactive behavior, and hidden state to ensure clean visualization and minimal user interference.
//+------------------------------------------------------------------+
//|                   RECTANGLE CREATION                             |
//+------------------------------------------------------------------+
void createRectangle(const string objName, const datetime time1, const double price1,
                     const datetime time2, const double price2, const color clr)
  {
//---
   if(rect.Create(0, objName, 0, time1, price1, time2, price2))
     {
      rect.Color(clr);
      rect.Tooltip("\n");
      rect.Background(true);
      rect.Selectable(false);
      rect.SetInteger(OBJPROP_HIDDEN, true);
      rect.Fill(true);
      return;
     }
  }
//+------------------------------------------------------------------+
//|                 HORIZONTAL (POC) LINE CREATION                   |
//+------------------------------------------------------------------+
void createHLine(const string objName, const double price,
                 const color clr, const string tooltip = "SnR POC")
  {
//---
   if(hLine.Create(0, objName, 0, price))
     {
      hLine.Color(clr);
      hLine.Width(2);
      hLine.Tooltip(tooltip);
      hLine.Selectable(false);
      hLine.SetInteger(OBJPROP_HIDDEN, true);
      return;
     }
  }
  • Central Orchestration

    The calcSnRVolumeProfile() function serves as the execution core of the indicator, coordinating all subsystems into a single, coherent workflow. Each stage transforms raw chart data into a fully rendered, adaptive SnR volume profile.

//+------------------------------------------------------------------+
//|                    PUTTING IT ALL TOGETHER                       |
//+------------------------------------------------------------------+
void calcSnRVolumeProfile()
  {
//--- CLEAR CHART
   ObjectsDeleteAll(0, _PROG_NAME);
   ChartRedraw();
//--- VIEWPORT CONTEXT INITIALIZATION
   datetime start, end;
   int count = getViewportValues(start, end);
//--- SNR DETECTION WITHIN VIEWPORT
   st_priceVolume support[], resistance[];
   ArraySetAsSeries(support, true);
   ArraySetAsSeries(resistance, true);
   getSupportLevels(support);
   getResistanceLevels(resistance);
//--- PRICE RANGE EXTRACTION
   int firstBar = iBarShift(_Symbol, PERIOD_CURRENT, start);
   int highestIndex = iHighest(_Symbol, PERIOD_CURRENT, (modeSNR == SNR_MODE_BODY)
                               ? MODE_CLOSE : MODE_HIGH, count, firstBar);
   int lowestIndex = iLowest(_Symbol, PERIOD_CURRENT, (modeSNR == SNR_MODE_BODY)
                             ? MODE_CLOSE : MODE_LOW, count, firstBar);

   double rangeHigh = iHigh(_Symbol, PERIOD_CURRENT, highestIndex);
   double rangeLow = iLow(_Symbol, PERIOD_CURRENT, lowestIndex);
//--- ADAPTIVE BIN CONSTRUCTION
   int numberOfBins = getBinsFromZoom();
   double step = (rangeHigh - rangeLow) / numberOfBins;
   st_priceVolume bins[];
   ArrayResize(bins, numberOfBins);
   double binHigh = EMPTY_VALUE,  binLow = EMPTY_VALUE;
//--- SNR VOLUME ACCUMULATION
   for(int w = 0; w < numberOfBins; w++)
     {
      binLow = rangeLow + step * w;
      binHigh = binLow + step;
      //--- SUPPORT
      for(int s = 0; s < ArraySize(support); s++)
        {
         if(support[s].price >= binLow && support[s].price <= binHigh)
           {
            bins[w].volume += support[s].volume;
            bins[w].price = support[s].price;
           }
        }
      //--- RESISTANCE
      for(int r = 0; r < ArraySize(resistance); r++)
        {
         if(resistance[r].price >= binLow && resistance[r].price <= binHigh)
           {
            bins[w].volume += resistance[r].volume;
            bins[w].price = resistance[r].price;
           }
        }
      //--- VOLUME FALLBACK
      if(bins[w].volume < 1)
         bins[w].volume = 0;
     }
//--- VOLUME EXTREMES
   long maxVol = 0, minVol = LONG_MAX;
   for(int b = 0; b < ArraySize(bins); b++)
     {
      if(bins[b].volume > maxVol)
         maxVol = bins[b].volume;

      if(bins[b].volume < minVol)
         minVol = bins[b].volume;
     }
//--- PROFILE SCALING
   int extendBars = 0;
   int maxProfileLength = (getBinsFromZoom() / 2);
   int minProfileLength = (int)MathRound(maxProfileLength * 0.1);
   minProfileLength = MathMax(2, minProfileLength);// CLAMP
   color clr = clrNONE;
   double dominance = EMPTY_VALUE;
//--- PROFILE RENDERING AND POC IDENTIFICATION (LAST RECORDED)
   bool pocDrawn = false;
   for(int p = 0; p < ArraySize(bins); p++)
     {
      binLow = rangeLow + step * p;
      binHigh = binLow + step;

      extendBars = minProfileLength + (int)MathRound(normalizeMinMax(bins[p].volume, maxVol, minVol)
                   * (maxProfileLength - minProfileLength));
      dominance = (double)extendBars / maxProfileLength;
      clr = (dominance >= 0.8) ? clrBlue :
            (dominance >= 0.6) ? clrPurple :
            (dominance >= 0.4) ? clrLime :
            (dominance >= 0.2) ? clrTeal :
            (dominance >= 0.15) ? clrBlueViolet :
            clrGray;

      createRectangle(_SNR_PROFILE + (string)p, end, binHigh, end + (PeriodSeconds()*extendBars), binLow, clr);
      if(bins[p].volume == maxVol && !pocDrawn)
        {
         //--- DRAW MAXIMUM VOLUME'S POC : ONCE
         createHLine(_SNR_POC + (string)p, bins[p].price, clr);
         pocDrawn = true;
        }
     }
     //--- CHART UPDATE
   ChartRedraw();
  }

Explanation:

The following breakdown clarifies each stage of this function in detail, outlining how the individual workflows interact to produce the final SnR volume profile.

  • Viewport Context Initialization
    The process begins by clearing previously rendered chart objects associated with the indicator and forcing a redraw to ensure a clean state. The active viewport is then detected, yielding the visible time range and the number of bars currently in view, which defines the analytical context for all subsequent computations.

  • Viewport-Based SnR Detection
    Support and resistance levels are identified strictly within the detected viewport. Using the previously defined wick- or body-based logic, validated SnR price points are collected along with their corresponding tick or real volume, forming the raw structural inputs for the volume profile.

  • Price Range Extraction
    The highest and lowest price levels within the viewport are determined based on the selected SnR mode. These bounds establish the vertical price range that will later be discretized into bins for volume aggregation.

  • Adaptive Bin Construction
    The detected price range is subdivided into a dynamically determined number of bins derived from the current chart zoom level. Each bin represents a discrete price bucket used to aggregate volume, allowing the profile resolution to scale naturally with viewport depth.

  • SnR Volume Accumulation
    Support and resistance volumes are iteratively assigned to their corresponding price bins based on price inclusion. This step consolidates structural SnR information into volume-weighted bins, with fallback handling applied to empty or insignificant bins.

  • Volume Extremes and Profile Scaling
    Minimum and maximum bin volumes are identified to establish normalization bounds. These values are used to compute adaptive profile lengths, ensuring that stronger SnR levels visually extend further while weaker levels remain proportionally constrained.
  • Profile Rendering and POC Identification

    Each bin is rendered as a horizontal profile rectangle with uniform width and variable length, where the horizontal extent reflects relative SnR strength within the price range. Color encoding is applied based on dominance thresholds, and the Point of Control (POC)—the bin with the highest volume—is highlighted once using a horizontal line.

The function concludes with a chart redraw, ensuring that all rendered elements accurately reflect the current viewport, zoom level, and underlying volume distribution. This orchestration layer effectively binds viewport awareness, structural analysis, volume profiling, and graphical rendering into a single adaptive execution pipeline.

Updating Standard Event Handlers

With the helper functions fully defined, only minimal updates are required in the standard MQL5 event handlers. These handlers act as lightweight triggers, delegating execution to the central orchestration function rather than containing core logic themselves. The OnDeinit function handles cleanup by removing all indicator-related chart objects, ensuring a clean chart state on removal. The OnCalculate function invokes the volume profile recalculation only when a new candle arrives, avoiding unnecessary computation and preserving performance. The OnChartEvent handler listens for CHARTEVENT_CHART_CHANGE and triggers a recalculation whenever the chart state changes, such as during zooming, scrolling, or initial indicator loading.

//+------------------------------------------------------------------+
//|                Deinitialization Function                         |
//+------------------------------------------------------------------+
void OnDeinit(const int32_t reason)
  {
//---
   ObjectsDeleteAll(0, _PROG_NAME);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//|                Indicator iteration function                      |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//---
   if(prev_calculated > 0 && prev_calculated != rates_total)
     {
      calcSnRVolumeProfile();
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+
//|                     OnChartEvent Function                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id, const long& lparam, const double& dparam, const string& sparam)
  {
//---
   if(id == CHARTEVENT_CHART_CHANGE)
     {
      calcSnRVolumeProfile();
     }
  }

Note: Profile rendering is not explicitly stated in OnInit because CHARTEVENT_CHART_CHANGE is fired automatically when the indicator is launched on a chart, causing the volume profiles to be drawn immediately without duplicating logic.


Indicator Testing

Once the indicator compiles successfully without errors, the GIF below demonstrates how it renders and updates the volume profiles correctly within the viewport under normal chart interactions such as scrolling and zooming.

Indicator Testing


Conclusion

We implemented a practical, viewport-centered SnR Volume Profile in MQL5 that converts raw structural levels into quantitatively ranked reaction zones. Key deliverables and behaviors are:

  • Flexible SnR Detection—configurable wick-based or body-based level detection with adjustable pivot strength for precise control over structural sensitivity.
  • Efficient Bin Construction—adaptive bin counts tied to visible bars.
  • Normalized volume profiling—horizontal profile rectangles (uniform height, variable horizontal extent), a single POC horizontal line, and underlying per-bin dominance scores (normalized volume).
  • Efficient Update—volume profile updates on CHARTEVENT CHART CHANGE (scroll,zoom) and on new-bar arrival (OnCalculate), ensuring the analysis always reflects the visible context.

        By anchoring level strength to viewport volume participation (dominance and the POC) and making the pipeline event-driven and adaptive, this framework turns an overloaded SnR field into a focused, actionable layer—ready to be extended into alerts, EA filters, or analytics pipelines.

        Training a nonlinear U-Transformer on the residuals of a linear autoregressive model Training a nonlinear U-Transformer on the residuals of a linear autoregressive model
        The article presents an innovative hybrid system for forecasting exchange rates that combines a linear autoregressive model with a U-Transformer architecture for residual analysis. The system automatically switches between signal sources depending on their quality and includes complete trading logic with averaging/pyramiding strategies. The key advantage of this approach is that the neural network is trained on the residuals of the linear model, which simplifies the task and reduces the risk of overfitting. The implementation is done entirely in MQL5 and is ready for use in real trading with automatic adaptation to changing market conditions.
        Feature Engineering for ML (Part 9): Structural Break Tests in Python Feature Engineering for ML (Part 9): Structural Break Tests in Python
        We present a production‑ready implementation of AFML Chapter 17 structural break tests. The module includes Chu-Stinchcombe-White (one-/two-sided), Chow-type DFC, SADF across six models (linear, quadratic, sm poly 1, sm poly 2, sm exp, sm power), plus QADF (q, v) and CADF (q), returning bar-indexed scalar features. We address the book snippets' scaling issues and argument‑order pitfall, and show how a fixed lookback (L=504) bounds SADF cost to O(L²) per bar for regime detection.
        Features of Experts Advisors Features of Experts Advisors
        Creation of expert advisors in the MetaTrader trading system has a number of features.
        Neural Networks in Trading: Time Series Forecasting Using Adaptive Modal Decomposition (ACEFormer) Neural Networks in Trading: Time Series Forecasting Using Adaptive Modal Decomposition (ACEFormer)
        We invite you to explore the ACEFormer architecture — a modern solution that combines the effectiveness of probabilistic attention with adaptive time series decomposition. This article will be useful for those seeking a balance between computational performance and forecast accuracy in financial markets.