preview
MQL5 Trading Tools (Part 26): Integrating Frequency Binning, Entropy, and Chi-Square in Visual Analyzer

MQL5 Trading Tools (Part 26): Integrating Frequency Binning, Entropy, and Chi-Square in Visual Analyzer

MetaTrader 5Trading systems |
259 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introduction

You have a multi-distribution graphing tool, but it models synthetic samples rather than actual market data, so you cannot see how closing prices distribute across levels, quantify how predictable the current market structure is, or test whether prices cluster or are uniformly distributed. This article is for MetaQuotes Language 5 (MQL5) developers and algorithmic traders looking to build a frequency analysis tool that operates directly on live price data for actionable statistical insights.

In our previous article (Part 25), we expanded the graphing tool to support seventeen statistical distributions with interactive cycling via a header switch icon. In Part 26, we build a frequency analysis tool that bins closing prices into a histogram, computes Shannon entropy, and runs a chi-square goodness-of-fit test. The tool also includes an auto-scrolling log panel, per-bar/per-tick modes, and supersampled rendering. We will cover the following topics:

  1. Understanding the Frequency Binning, Entropy, and Chi-Square FrameworkБ/фЮ
  2. Implementation in MQL5
  3. Backtesting
  4. Conclusion

By the end, you’ll have a comprehensive MQL5 frequency analysis tool, ready for customization—let’s dive in!


Understanding the Frequency Binning, Entropy, and Chi-Square Framework

Frequency binning divides a price range into equal-width intervals and counts how many closes fall into each bin. Dense bins indicate acceptance zones, while sparse bins indicate impulsive transitions. Shannon entropy quantifies how evenly spread the counts are across bins: a perfectly uniform distribution produces maximum entropy, while a histogram dominated by one or two bins produces low entropy, signaling strong clustering and potentially tradeable structure. The chi-square test formalizes this by comparing observed bin counts against the expected counts under a uniform distribution, producing a test statistic that rises as clustering intensifies, with degrees of freedom equal to the number of bins minus one.

In the market, use the frequency histogram to identify the highest-density bin as the current value area — price returning to that bin after a deviation is a mean-reversion candidate. Monitor entropy across sessions: a drop in entropy after a range period signals emerging structure and a potential directional breakout setup. Use the chi-square statistic as a filter — high values confirm non-random clustering worth trading, while values near zero suggest a random walk where frequency-based strategies lose edge.

We load recent closing prices and bin them into user-defined intervals. We then compute relative frequencies, entropy (−∑p·log p), and the chi-square statistic. Results are displayed on a canvas with a statistics panel and a supersampled auto-scrolling log panel (per-bar or per-tick). In a nutshell, here is what we intend to achieve.

FREQUENCY ANALYSIS FRAMEWORK GIF


Implementation in MQL5

We begin the implementation by defining the enumerations, inputs, structures, and global variables that the frequency analysis system requires.

Defining Enumerations, Inputs, Structures, and Global Variables

To support typed logging, selectable computation modes, configurable analysis parameters, and a scrollable log panel, we define two enumerations for log entry types and computation modes, add input groups for frequency settings and log appearance, declare two structures for bin data and log entries, and initialize all global variables covering statistical metrics, scroll state, and supersampling constants.

//+------------------------------------------------------------------+
//|                  Canvas Graphing PART 5 - Frequency Analysis.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict

enum ENUM_LOG_TYPE
  {
   LOG_INFO,          // Info
   LOG_FREQUENCY,     // Frequency
   LOG_STATISTICAL,   // Statistical
   LOG_VECTOR_MATRIX, // Vector Matrix
   LOG_WARNING,       // Warning
   LOG_SUCCESS        // Success
  };

enum ENUM_COMPUTE_MODE
  {
   PER_BAR,  // Per Bar
   PER_TICK  // Per Tick
  };

input group "=== FREQUENCY ANALYSIS SETTINGS ==="
input int               frequencyBins             = 10;      // Frequency Analysis Bins
input int               analysisWindowSize         = 100;     // Analysis Window Size (bars)
input bool              enableChiSquareTest        = true;    // Enable Chi-Square Test
input bool              enableEntropyCalculation   = true;    // Enable Entropy Calculation
input bool              enableCorrelationAnalysis  = true;    // Enable Correlation Analysis
input int               logUpdateIntervalTicks     = 5;       // Log Update Interval (ticks)
input ENUM_COMPUTE_MODE computeMode                = PER_BAR; // Compute Mode (per bar or per tick)

input group "=== LOG PANEL COLORS ==="
input color             logBackgroundColor       = clrBlack;       // Log Background Color
input double            logBackgroundOpacity     = 0.91;           // Log Background Opacity (0-1)
input color             logFrequencyTextColor    = clrLime;        // Frequency Log Color
input color             logStatisticalTextColor  = clrCyan;        // Statistical Log Color
input color             logVectorMatrixTextColor = clrYellow;      // Vector/Matrix Log Color
input color             logWarningTextColor      = clrOrange;      // Warning Log Color
input color             logSuccessTextColor      = clrSpringGreen; // Success Log Color
input color             logInfoTextColor         = clrWhite;       // Info Log Color

input group "=== LOG SCROLL SETTINGS ==="
input bool              showLogScrollButtons           = false; // Show Log Up/Down Buttons
input int               logTriangleCornerRadius        = 1;     // Log Triangle Round Radius
input double            logTriangleBaseWidthPercent    = 65.0;  // Log Triangle Base Width Percent (of button size)
input double            logTriangleHeightPercent       = 70.0;  // Log Triangle Height Percent (of base width)


//+------------------------------------------------------------------+
//| Structures                                                       |
//+------------------------------------------------------------------+
struct FrequencyBinStruct
  {
   double rangeMinimum; // Minimum value of this bin's price range
   double rangeMaximum; // Maximum value of this bin's price range
   int    count;        // Number of data points that fall in this bin
   double frequency;    // Relative frequency (count / total samples)
  };

struct LogEntryStruct
  {
   string        message;   // Text content of the log entry
   ENUM_LOG_TYPE type;      // Log type controlling the display color
   datetime      timestamp; // Time the entry was recorded
  };

//+------------------------------------------------------------------+
//| Global Variables - Frequency Analysis                            |
//+------------------------------------------------------------------+
FrequencyBinStruct frequencyBinsTable[]; // Array of frequency bin descriptors
double             priceDataArray[];     // Array of close prices used for analysis
int                tickUpdateCounter = 0; // Counter of ticks since the last analysis update

double currentMeanValue          = 0.0; // Sample mean of the price data
double currentStandardDeviation  = 0.0; // Sample standard deviation of the price data
double currentSkewnessValue      = 0.0; // Sample skewness of the price data
double currentModeValue          = 0.0; // Modal bin midpoint value
int    modeFrequencyCount        = 0;   // Count of data points in the modal bin
double chiSquareTestStatistic    = 0.0; // Computed chi-square uniformity test statistic
double shannonEntropyValue       = 0.0; // Computed Shannon entropy of the frequency distribution
double correlationCoefficient    = 0.0; // Computed lag-1 autocorrelation coefficient

double dataMinimum             = 0.0;   // Minimum price value in the current window
double dataMaximum             = 0.0;   // Maximum price value in the current window
double maximumFrequency        = 0.0;   // Highest relative frequency across all bins
bool   dataLoadedSuccessfully  = false; // Flag indicating data was loaded without error

double sampleMeanValue                    = 0.0; // Advanced: sample mean
double sampleStandardDeviation            = 0.0; // Advanced: sample standard deviation
double sampleSkewnessValue                = 0.0; // Advanced: sample skewness
double sampleKurtosisValue                = 0.0; // Advanced: sample excess kurtosis
double percentile25Value                  = 0.0; // 25th percentile (Q1)
double percentile50Value                  = 0.0; // 50th percentile (median)
double percentile75Value                  = 0.0; // 75th percentile (Q3)
double confidenceInterval95LowerBound     = 0.0; // Lower bound of 95% confidence interval
double confidenceInterval95UpperBound     = 0.0; // Upper bound of 95% confidence interval
double confidenceInterval99LowerBound     = 0.0; // Lower bound of 99% confidence interval
double confidenceInterval99UpperBound     = 0.0; // Upper bound of 99% confidence interval

LogEntryStruct logEntriesArray[];    // Array storing all log entries
int  maximumLogEntries    = 100;     // Maximum number of retained log entries
int  logScrollPosition    = 0;       // Current scroll offset of the log panel
int  maximumLogScroll     = 0;       // Maximum scroll offset of the log panel
bool isHoveringOverLogPanel    = false; // Flag for mouse hovering over the log panel
bool isDraggingLogScrollbar    = false; // Flag for active scrollbar drag

//+------------------------------------------------------------------+
//| Log Scroll Constants and Globals                                 |
//+------------------------------------------------------------------+
const int supersamplingFactor       = 4;  // Supersampling multiplier for high-res log rendering
const int logScrollSmoothFactor     = 10; // Pixel multiplier applied to each scroll step
const int logScrollbarFullWidth     = 16; // Full scrollbar track width in pixels
const int logScrollbarThinWidth     = 2;  // Thin (collapsed) scrollbar width in pixels
const int logTrackWidth             = 16; // Width of the scrollbar track area
const int logScrollbarMargin        = 5;  // Inner margin around the scrollbar slider
const int logButtonSize             = 16; // Pixel size of up/down scroll buttons

color logTrackColor                    = C'45,45,45';   // Scrollbar track background color
color logButtonBackgroundColor         = C'60,60,60';   // Scroll button background color
color logButtonBackgroundHoverColor    = C'70,70,70';   // Scroll button hover background color
color logArrowColor                    = C'150,150,150'; // Arrow glyph normal color
color logArrowDisabledColor            = C'80,80,80';   // Arrow glyph color when scroll is at limit
color logArrowHoverColor               = C'100,100,100'; // Arrow glyph hover color
color logSliderBackgroundColor         = C'80,80,80';   // Scrollbar slider normal color
color logSliderBackgroundHoverColor    = C'100,100,100'; // Scrollbar slider hover color

int  logScrollCurrentPosition          = 0;     // Current scroll position in high-res pixels
int  logScrollMaximumPosition          = 0;     // Maximum scroll position in high-res pixels
int  logSliderHeight                   = 0;     // Computed height of the scrollbar slider in display pixels
bool isMovingLogSlider                 = false; // Flag for active slider drag
int  logMouseDownPositionYOnSlider     = 0;     // Local Y of mouse at slider drag start
int  logMouseDownDeltaYOnSlider        = 0;     // Slider top Y at drag start
int  logTotalContentHeight             = 0;     // Total scrollable content height in pixels
int  logVisibleContentHeight           = 0;     // Visible content height in pixels
bool isLogScrollbarVisible             = false; // Flag indicating the scrollbar is currently shown
bool isMouseInLogContentBody           = false; // Flag for mouse inside the log text area
bool previousMouseInLogContentBody     = false; // Previous frame's log body hover state
bool isLogScrollUpButtonHovered        = false; // Flag for mouse hovering over the up scroll button
bool isLogScrollDownButtonHovered      = false; // Flag for mouse hovering over the down scroll button
bool isLogScrollSliderHovered          = false; // Flag for mouse hovering over the slider thumb
bool isLogScrollAreaHovered            = false; // Flag for mouse hovering over the scrollbar track area

First, we define "ENUM_LOG_TYPE" to categorize log messages ("LOG_INFO", "LOG_FREQUENCY", "LOG_STATISTICAL", "LOG_VECTOR_MATRIX", "LOG_WARNING", "LOG_SUCCESS"). This enables color-coded logging. Next, we create the "ENUM_COMPUTE_MODE" enumeration with "PER_BAR" for bar-based updates and "PER_TICK" for tick-level, controlling analysis frequency. We add input groups for frequency settings like "frequencyBins" for histogram divisions, "analysisWindowSize" for data lookback, toggles for chi-square, entropy, and correlation, "logUpdateIntervalTicks" for tick mode pacing, and "computeMode" selecting the enum defaulting to "PER_BAR".

For log colors, we provide inputs to customize backgrounds and text per type, such as "logFrequencyTextColor" to lime, ensuring visual distinction. Scroll settings include toggles like "showLogScrollButtons" and parameters for triangle shapes in buttons, like "logTriangleCornerRadius". We define the "FrequencyBinStruct" structure to hold bin ranges, counts, and frequencies for histogram data.

The "LogEntryStruct" structure stores the message, the type from the enum, and the timestamp for each log entry. Globals include "frequencyBinsTable" array of the bin struct, "priceDataArray" for raw prices, "tickUpdateCounter" starting at 0 for timing. Statistical globals like "currentMeanValue" to 0.0 for mean, "currentStandardDeviation" for std dev, "currentSkewnessValue", "currentModeValue" with "modeFrequencyCount", "chiSquareTestStatistic", "shannonEntropyValue", "correlationCoefficient".

Advanced stats like "sampleMeanValue", "percentile25Value", and confidence bounds are all set to 0.0. Log system uses "logEntriesArray" of the entry struct, "maximumLogEntries" to 100, "logScrollPosition" to 0, "maximumLogScroll" to 0, hover and drag flags to false. Scroll constants define dimensions like "supersamplingFactor" to 4 for anti-aliasing, widths, colors for track, buttons, arrows, slider, with hovers and disabled states. Globals track positions, heights, and hovers for the interactive scrollbar. With the globals in place, we now define the log entry management function that feeds the scrollable log panel.

Managing the Log Entry Queue

To keep the log bounded in memory while always showing the most recent entries, we maintain a fixed-size array that shifts existing entries left when capacity is reached, appends the new entry at the end, and optionally flags a pending auto-scroll to the bottom so the next render pass brings the latest message into view without an immediate redraw.

//+------------------------------------------------------------------+
//| Add log entry                                                    |
//+------------------------------------------------------------------+
bool pendingAutoScrollToBottom = false; // Flag to scroll to the bottom on the next render

void AddLogEntry(string entryMessage, ENUM_LOG_TYPE entryType)
  {
   int currentSize = ArraySize(logEntriesArray);

   //--- If the log is full, evict the oldest entry by shifting the array
   if (currentSize >= maximumLogEntries)
     {
      for (int i = 0; i < currentSize - 1; i++)
         logEntriesArray[i] = logEntriesArray[i + 1];
      currentSize = maximumLogEntries - 1;
     }

   //--- Append the new entry at the end of the array
   ArrayResize(logEntriesArray, currentSize + 1);
   logEntriesArray[currentSize].message   = entryMessage;
   logEntriesArray[currentSize].type      = entryType;
   logEntriesArray[currentSize].timestamp = TimeCurrent();

   //--- Request an auto-scroll to the bottom on the next render if enabled
   if (enableLogAutoScroll)
      pendingAutoScrollToBottom = true;
  }

We define the "AddLogEntry" function to append a new log message to the "logEntriesArray" array, maintaining a capped size for efficiency. We first check the current array size with ArraySize, and if it meets or exceeds "maximumLogEntries", shift elements left by one to discard the oldest, reducing the size to make room. Then, we resize the array by one with ArrayResize, set the new entry's message, type from "ENUM_LOG_TYPE", and timestamp via the TimeCurrent function. If "enableLogAutoScroll" is true, we flag "pendingAutoScrollToBottom" to trigger scrolling in rendering, ensuring recent logs are visible without immediate redraw. This will help manage a FIFO queue for logs, preventing unbounded growth while supporting auto-scroll for user convenience. With the log queue ready, we now define the core function that bins the price data and computes relative frequencies.

Performing Frequency Binning on Price Data

This function fetches recent close prices into an array, determines the data range, divides it into equal-width bins, counts how many prices fall into each bin, computes relative frequencies, finds the maximum frequency for plot scaling, and identifies the mode bin, preparing all data structures that the histogram plot and statistical functions will consume.

//+------------------------------------------------------------------+
//| Perform frequency analysis                                       |
//+------------------------------------------------------------------+
void PerformFrequencyAnalysis()
  {
   //--- Collect close prices for the configured analysis window
   int dataPointsCount = MathMin(analysisWindowSize, Bars(_Symbol, PERIOD_CURRENT));
   ArrayResize(priceDataArray, dataPointsCount);
   for (int i = 0; i < dataPointsCount; i++)
      priceDataArray[i] = iClose(_Symbol, PERIOD_CURRENT, i);

   //--- Require at least two data points
   if (dataPointsCount < 2) return;

   //--- Find the price range across the window
   dataMinimum = priceDataArray[0];
   dataMaximum = priceDataArray[0];
   for (int i = 1; i < dataPointsCount; i++)
     {
      if (priceDataArray[i] < dataMinimum) dataMinimum = priceDataArray[i];
      if (priceDataArray[i] > dataMaximum) dataMaximum = priceDataArray[i];
     }

   //--- Guard against a zero-range window (all closes identical)
   double dataRange = dataMaximum - dataMinimum;
   if (dataRange == 0) dataRange = _Point;

   //--- Build and initialize all frequency bins
   ArrayResize(frequencyBinsTable, frequencyBins);
   double binWidth = dataRange / frequencyBins;
   for (int i = 0; i < frequencyBins; i++)
     {
      frequencyBinsTable[i].rangeMinimum = dataMinimum + i * binWidth;
      frequencyBinsTable[i].rangeMaximum = dataMinimum + (i + 1) * binWidth;
      frequencyBinsTable[i].count        = 0;
      frequencyBinsTable[i].frequency    = 0.0;
     }

   //--- Assign each data point to its bin using a linear scan
   for (int i = 0; i < dataPointsCount; i++)
      for (int j = 0; j < frequencyBins; j++)
         if (priceDataArray[i] >= frequencyBinsTable[j].rangeMinimum &&
             priceDataArray[i] <  frequencyBinsTable[j].rangeMaximum)
           {
            frequencyBinsTable[j].count++;
            break;
           }

   //--- Include the maximum value in the last bin to handle the closed right boundary
   if (priceDataArray[0] == dataMaximum)
      frequencyBinsTable[frequencyBins - 1].count++;

   //--- Compute relative frequency for each bin
   for (int i = 0; i < frequencyBins; i++)
      frequencyBinsTable[i].frequency = (double)frequencyBinsTable[i].count / dataPointsCount;

   //--- Find the maximum frequency for Y-axis scaling
   maximumFrequency = 0.0;
   for (int i = 0; i < frequencyBins; i++)
      if (frequencyBinsTable[i].frequency > maximumFrequency)
         maximumFrequency = frequencyBinsTable[i].frequency;

   //--- Identify the modal bin
   modeFrequencyCount = 0;
   int modeBinIndex   = 0;
   for (int i = 0; i < frequencyBins; i++)
      if (frequencyBinsTable[i].count > modeFrequencyCount)
        {
         modeFrequencyCount = frequencyBinsTable[i].count;
         modeBinIndex       = i;
        }
   //--- Set the mode value to the modal bin's midpoint
   currentModeValue = (frequencyBinsTable[modeBinIndex].rangeMinimum +
                       frequencyBinsTable[modeBinIndex].rangeMaximum) / 2.0;

   dataLoadedSuccessfully = true;
  }

Here, we define the "PerformFrequencyAnalysis" function to conduct the core binning and frequency computation on price data. We first determine the number of data points as the minimum of "analysisWindowSize" and available bars via Bars, resize "priceDataArray" with ArrayResize, and populate it by looping to fetch close prices using iClose from the current symbol and timeframe, starting from recent bars. If fewer than two points, we exit early. We find the data range by initializing "dataMinimum" and "dataMaximum" to the first value, then looping to update mins/maxes, adjusting zero range to "_Point" for minimal spread. To set up bins, we resize "frequencyBinsTable" to "frequencyBins", compute "binWidth" as range divided by bins, and loop to assign each bin's "rangeMinimum" and "rangeMaximum" as sequential intervals, resetting count and frequency to zero.

We count frequencies by nested loops: for each price, check against bin ranges and increment the matching bin's count, breaking on match. For edge cases where prices equal max, we increment the last bin. Next, we calculate relative frequencies by dividing each count by the total points. We find "maximumFrequency" by looping to get the highest frequency for plot scaling. For mode, we reset "modeFrequencyCount" and index, loop to find the bin with the max count, and set "currentModeValue" as its midpoint. Finally, flag "dataLoadedSuccessfully" true, completing the analysis, ready for visualization and statistics. With the bins populated, we now compute the statistical measures that accompany the histogram.

Calculating Basic Statistics, Chi-Square, Shannon Entropy, and Autocorrelation

Four dedicated functions cover the statistical layer: basic statistics derives the mean, standard deviation, and skewness directly from the price array; the chi-square test compares observed bin counts against a uniform expectation to quantify clustering; Shannon entropy sums negative frequency-log-frequency terms across bins to measure predictability; and lag-1 autocorrelation computes the ratio of consecutive-price covariance to variance to detect serial dependence.

//+------------------------------------------------------------------+
//| Calculate basic statistics                                       |
//+------------------------------------------------------------------+
void CalculateBasicStatistics()
  {
   int sampleSize = ArraySize(priceDataArray);
   if (sampleSize < 2) return;

   //--- Compute the arithmetic mean
   double sumTotal = 0.0;
   for (int i = 0; i < sampleSize; i++)
      sumTotal += priceDataArray[i];
   currentMeanValue = sumTotal / sampleSize;

   //--- Compute the sample standard deviation
   double sumSquaredDifferences = 0.0;
   for (int i = 0; i < sampleSize; i++)
     {
      double difference = priceDataArray[i] - currentMeanValue;
      sumSquaredDifferences += difference * difference;
     }
   currentStandardDeviation = MathSqrt(sumSquaredDifferences / (sampleSize - 1));

   //--- Compute the skewness only when standard deviation is nonzero
   if (currentStandardDeviation > 0)
     {
      double sumCubedDifferences = 0.0;
      for (int i = 0; i < sampleSize; i++)
        {
         double normalizedValue = (priceDataArray[i] - currentMeanValue) / currentStandardDeviation;
         sumCubedDifferences += normalizedValue * normalizedValue * normalizedValue;
        }
      currentSkewnessValue = (sampleSize / ((sampleSize - 1.0) * (sampleSize - 2.0))) * sumCubedDifferences;
     }
  }

//+------------------------------------------------------------------+
//| Calculate chi-square test                                        |
//+------------------------------------------------------------------+
void CalculateChiSquareTest()
  {
   if (!enableChiSquareTest) return;

   int sampleSize = ArraySize(priceDataArray);
   if (sampleSize < frequencyBins) return;

   //--- Under uniform distribution, each bin expects the same count
   double expectedFrequency    = (double)sampleSize / frequencyBins;
   chiSquareTestStatistic = 0.0;

   //--- Accumulate chi-square contributions from each bin
   for (int i = 0; i < frequencyBins; i++)
     {
      double difference = frequencyBinsTable[i].count - expectedFrequency;
      chiSquareTestStatistic += (difference * difference) / expectedFrequency;
     }
  }

//+------------------------------------------------------------------+
//| Calculate Shannon entropy                                        |
//+------------------------------------------------------------------+
void CalculateShannonEntropy()
  {
   if (!enableEntropyCalculation) return;

   shannonEntropyValue = 0.0;

   //--- Sum -p * log(p) over all non-empty bins
   for (int i = 0; i < frequencyBins; i++)
      if (frequencyBinsTable[i].frequency > 0)
         shannonEntropyValue -= frequencyBinsTable[i].frequency *
                                MathLog(frequencyBinsTable[i].frequency);
  }

//+------------------------------------------------------------------+
//| Calculate auto-correlation                                       |
//+------------------------------------------------------------------+
void CalculateAutoCorrelation()
  {
   if (!enableCorrelationAnalysis) return;

   int sampleSize = ArraySize(priceDataArray);
   if (sampleSize < 3) return;

   //--- Compute variance and lag-1 covariance using the sample mean
   double meanValue    = currentMeanValue;
   double varianceSum  = 0.0;
   double covarianceSum = 0.0;

   for (int i = 0; i < sampleSize; i++)
      varianceSum += (priceDataArray[i] - meanValue) * (priceDataArray[i] - meanValue);

   for (int i = 0; i < sampleSize - 1; i++)
      covarianceSum += (priceDataArray[i] - meanValue) * (priceDataArray[i + 1] - meanValue);

   //--- Divide covariance by variance to obtain the lag-1 autocorrelation
   if (varianceSum > 0)
      correlationCoefficient = covarianceSum / varianceSum;
  }

Here, we define the "CalculateBasicStatistics" function to derive core metrics from "priceDataArray". We first check if the size is at least 2, exiting early if not, then compute the mean by summing values and dividing by count, followed by standard deviation via sum of squared differences from mean divided by n-1 under MathSqrt, and skewness as adjusted sum of cubed normalized differences, providing foundational stats without advanced tools. Next, we implement the "CalculateChiSquareTest" function for goodness-of-fit, returning early if "enableChiSquareTest" is false or the sample is small. We set expected frequency as sample size over bins, reset "chiSquareTestStatistic", and loop to add (observed - expected)^2 / expected per bin, quantifying deviation from uniform.

For information measure, we create the "CalculateShannonEntropy" function, skipping if "enableEntropyCalculation" off, resetting "shannonEntropyValue", and accumulating negative frequency * log(frequency) for positive frequencies with "MathLog", yielding entropy in nats to assess data predictability. Finally, we define the "CalculateAutoCorrelation" function for lag-1 dependency, exiting if "enableCorrelationAnalysis" is false or small sample. We compute variance sum as squared deviations from mean, covariance as product of consecutive deviations, and set "correlationCoefficient" as covariance over variance if positive, detecting serial correlation in prices. With the statistical measures computed, we now render the frequency histogram on the main canvas.

Drawing the Frequency Histogram Plot

The histogram plot maps each bin's relative frequency to a bar height scaled against the maximum frequency, draws thickened X and Y axes with optimal tick marks and formatted labels, and writes fixed axis titles for price bins and relative frequency, producing a complete visual representation of the price distribution over the analysis window.

//+------------------------------------------------------------------+
//| Draw frequency histogram plot                                    |
//+------------------------------------------------------------------+
void DrawFrequencyHistogramPlot()
  {
   if (!dataLoadedSuccessfully) return;

   //--- Define the outer and inner (padded) plot area boundaries
   int plotAreaLeftEdge   = 60;
   int plotAreaRightEdge  = currentCanvasWidth  - 40;
   int plotAreaTopEdge    = headerBarHeight + 10;
   int plotAreaBottomEdge = currentCanvasHeight  - 50;

   int drawAreaLeftEdge   = plotAreaLeftEdge   + plotAreaPaddingPixels;
   int drawAreaRightEdge  = plotAreaRightEdge  - plotAreaPaddingPixels;
   int drawAreaTopEdge    = plotAreaTopEdge    + plotAreaPaddingPixels;
   int drawAreaBottomEdge = plotAreaBottomEdge - plotAreaPaddingPixels;

   int plotWidth  = drawAreaRightEdge - drawAreaLeftEdge;
   int plotHeight = drawAreaBottomEdge - drawAreaTopEdge;

   if (plotWidth <= 0 || plotHeight <= 0) return;

   //--- Compute axis ranges, guarding against degenerate zero values
   double xAxisRange = dataMaximum - dataMinimum;
   double yAxisRange = maximumFrequency;
   if (xAxisRange == 0) xAxisRange = 1;
   if (yAxisRange == 0) yAxisRange = 1;

   //--- Draw X and Y axes with a two-pixel stroke for visibility
   uint argbAxisColor = ColorToARGB(clrBlack, 255);
   for (int thickness = 0; thickness < 2; thickness++)
      mainDistributionCanvas.Line(plotAreaLeftEdge - thickness, plotAreaTopEdge,
                                  plotAreaLeftEdge - thickness, plotAreaBottomEdge, argbAxisColor);
   for (int thickness = 0; thickness < 2; thickness++)
      mainDistributionCanvas.Line(plotAreaLeftEdge, plotAreaBottomEdge + thickness,
                                  plotAreaRightEdge, plotAreaBottomEdge + thickness, argbAxisColor);

   //--- Draw Y-axis tick marks and labels
   mainDistributionCanvas.FontSet("Arial", axisLabelFontSize);
   uint argbTickLabelColor = ColorToARGB(clrBlack, 255);

   double yAxisTickValues[];
   int    yAxisTickCount = CalculateOptimalAxisTicks(0, yAxisRange, plotHeight, yAxisTickValues);
   for (int i = 0; i < yAxisTickCount; i++)
     {
      double yTickValue = yAxisTickValues[i];
      if (yTickValue < 0 || yTickValue > yAxisRange) continue;

      int yPosition = drawAreaBottomEdge - (int)((yTickValue / yAxisRange) * plotHeight);
      mainDistributionCanvas.Line(plotAreaLeftEdge - 5, yPosition,
                                  plotAreaLeftEdge,     yPosition, argbAxisColor);
      mainDistributionCanvas.TextOut(plotAreaLeftEdge - 8,
                                     yPosition - axisLabelFontSize / 2,
                                     FormatAxisTickLabel(yTickValue, yAxisRange),
                                     argbTickLabelColor, TA_RIGHT);
     }

   //--- Draw X-axis tick marks and labels
   double xAxisTickValues[];
   int    xAxisTickCount = CalculateOptimalAxisTicks(dataMinimum, dataMaximum, plotWidth, xAxisTickValues);
   for (int i = 0; i < xAxisTickCount; i++)
     {
      double xTickValue = xAxisTickValues[i];
      if (xTickValue < dataMinimum || xTickValue > dataMaximum) continue;

      int xPosition = drawAreaLeftEdge + (int)((xTickValue - dataMinimum) / xAxisRange * plotWidth);
      mainDistributionCanvas.Line(xPosition, plotAreaBottomEdge,
                                  xPosition, plotAreaBottomEdge + 5, argbAxisColor);
      mainDistributionCanvas.TextOut(xPosition, plotAreaBottomEdge + 7,
                                     FormatAxisTickLabel(xTickValue, xAxisRange),
                                     argbTickLabelColor, TA_CENTER);
     }

   //--- Draw histogram bars centered on each bin midpoint
   uint   argbHistogramColor = ColorToARGB(histogramBarColor, 255);
   double totalBarGaps       = (frequencyBins - 1) * histogramBarGapPixels;
   double barWidth           = (plotWidth - totalBarGaps) / frequencyBins;
   if (barWidth < 1) barWidth = 1; // Enforce a minimum bar width of one pixel

   for (int i = 0; i < frequencyBins; i++)
     {
      double binMidpoint     = (frequencyBinsTable[i].rangeMinimum + frequencyBinsTable[i].rangeMaximum) / 2.0;
      int    barLeftPosition = drawAreaLeftEdge +
                               (int)((binMidpoint - dataMinimum) / xAxisRange * plotWidth - barWidth / 2);
      int    barRightPosition = barLeftPosition + (int)barWidth - 1;
      int    barHeight        = (int)(frequencyBinsTable[i].frequency / yAxisRange * plotHeight);
      int    barTopPosition   = drawAreaBottomEdge - barHeight;

      if (barRightPosition >= barLeftPosition)
         mainDistributionCanvas.FillRectangle(barLeftPosition, barTopPosition,
                                              barRightPosition, drawAreaBottomEdge,
                                              argbHistogramColor);
     }

   //--- Draw X and Y axis labels
   mainDistributionCanvas.FontSet("Arial Bold", labelFontSize);
   uint argbAxisLabelColor = ColorToARGB(clrBlack, 255);

   mainDistributionCanvas.TextOut(currentCanvasWidth / 2, currentCanvasHeight - 20,
                                  "Price Bins", argbAxisLabelColor, TA_CENTER);

   //--- Rotate 90° to draw the Y axis label vertically
   mainDistributionCanvas.FontAngleSet(900);
   mainDistributionCanvas.TextOut(12, currentCanvasHeight / 2,
                                  "Relative Frequency", argbAxisLabelColor, TA_CENTER);
   mainDistributionCanvas.FontAngleSet(0);
  }

We define the "DrawFrequencyHistogramPlot" function to visualize the frequency bins as a histogram on the main canvas, returning early if "dataLoadedSuccessfully" is false to avoid errors. We set plot area edges with fixed margins, adjust draw boundaries by "plotAreaPaddingPixels", compute width and height, and exit if invalid. After determining X range from "dataMaximum" - "dataMinimum" and Y from "maximumFrequency" with minimum safeguards, we convert black to ARGB for axes and draw thickened Y/X lines via loops with the "Line" method. To label axes, we set the font with "FontSet" and prepare tick ARGB. For Y, we compute optimal ticks via "CalculateOptimalAxisTicks" from 0 to Y range, loop to position, draw short lines with "Line", format labels using "FormatAxisTickLabel", and place right-aligned via the TextOut method. Similarly, for X from "dataMinimum" to "dataMaximum", drawing downward ticks and centered labels.

For the histogram, we convert "histogramBarColor" to ARGB, calculate bar width accounting for "histogramBarGapPixels", and loop over bins: use midpoint for centering, position bars, scale height to Y range, and fill with FillRectangle if valid. Finally, we bolded the font, set axis labels as "Price Bins" for X centered at the bottom and "Relative Frequency" for Y rotated 90 degrees with FontAngleSet and centered left, resetting the angle to 0, completing the plot for frequency insights. With the histogram in place, we now build the log panel, which renders timestamped entries with color-coded types, a supersampled background, and an interactive scrollbar that expands on hover and collapses when idle.

Rendering the Log Panel with Supersampled Scrollbar

The log panel renders at four times the display resolution and downsamples the result for anti-aliased text and smooth rounded scrollbar caps. It draws a color-coded entry list clipped to the visible area, adjusts the scroll position when auto-scroll is pending, and renders the scrollbar as either a full-width interactive track with up and down arrow buttons and a rounded pill-shaped slider when hovered, or a narrow centered strip when idle.

//+------------------------------------------------------------------+
//| Get log entry text color                                         |
//+------------------------------------------------------------------+
color GetLogEntryTextColor(ENUM_LOG_TYPE entryType)
  {
   //--- Return the configured display color for each log category
   switch (entryType)
     {
      case LOG_FREQUENCY:    return logFrequencyTextColor;
      case LOG_STATISTICAL:  return logStatisticalTextColor;
      case LOG_VECTOR_MATRIX: return logVectorMatrixTextColor;
      case LOG_WARNING:      return logWarningTextColor;
      case LOG_SUCCESS:      return logSuccessTextColor;
      default:               return logInfoTextColor;
     }
  }

//+------------------------------------------------------------------+
//| Render log panel visualization                                   |
//+------------------------------------------------------------------+
void RenderLogPanelVisualization()
  {
   //--- Erase the high-resolution canvas before drawing
   logPanelHighResolutionCanvas.Erase(0);

   int highResolutionWidth  = currentCanvasWidth   * supersamplingFactor;
   int highResolutionHeight = currentLogPanelHeight * supersamplingFactor;

   //--- Fill the log background with the configured semi-transparent color
   uint argbBackgroundColor = ColorToARGB(logBackgroundColor,
                                          (uchar)(255 * logBackgroundOpacity));
   logPanelHighResolutionCanvas.FillRectangle(0, 0,
      highResolutionWidth - 1, highResolutionHeight - 1, argbBackgroundColor);

   //--- Draw the log panel border if enabled
   if (showBorderFrame)
     {
      uint argbBorderColor = ColorToARGB(masterThemeColor, 255);
      logPanelHighResolutionCanvas.Rectangle(0, 0,
         highResolutionWidth - 1, highResolutionHeight - 1, argbBorderColor);
      logPanelHighResolutionCanvas.Rectangle(
         supersamplingFactor, supersamplingFactor,
         highResolutionWidth  - supersamplingFactor - 1,
         highResolutionHeight - supersamplingFactor - 1, argbBorderColor);
     }

   //--- Draw the panel title
   logPanelHighResolutionCanvas.FontSet("Arial Bold", titleFontSize * supersamplingFactor);
   uint argbHeaderTextColor = ColorToARGB(clrWhite, 255);
   logPanelHighResolutionCanvas.TextOut(highResolutionWidth / 2,
      5 * supersamplingFactor,
      "FREQUENCY ANALYSIS LOG", argbHeaderTextColor, TA_CENTER);

   //--- Switch to the monospaced log font
   logPanelHighResolutionCanvas.FontSet("Courier New", logFontSize * supersamplingFactor);

   //--- Compute layout metrics for the scrollable log area
   int logLineHeightHighRes     = logPanelHighResolutionCanvas.TextHeight("A") + 4 * supersamplingFactor;
   int logStartPositionYHighRes = 35 * supersamplingFactor;
   int logVisibleHeightHighRes  = highResolutionHeight - logStartPositionYHighRes - 5 * supersamplingFactor;
   int logTotalHeightHighRes    = ArraySize(logEntriesArray) * logLineHeightHighRes;

   //--- Compute the maximum scroll position and honor a pending scroll-to-bottom request
   logScrollMaximumPosition = MathMax(0, logTotalHeightHighRes - logVisibleHeightHighRes);
   if (pendingAutoScrollToBottom)
     {
      logScrollCurrentPosition  = logScrollMaximumPosition;
      pendingAutoScrollToBottom = false;
     }
   logScrollCurrentPosition = MathMin(logScrollCurrentPosition, logScrollMaximumPosition);

   isLogScrollbarVisible = logScrollMaximumPosition > 0;

   //--- Determine which log entries are currently visible
   int scrollOffsetHighRes = logScrollCurrentPosition;
   int firstEntryIndex     = scrollOffsetHighRes / logLineHeightHighRes;
   int lastEntryIndex      = (scrollOffsetHighRes + logVisibleHeightHighRes) / logLineHeightHighRes + 1;
   lastEntryIndex  = MathMin(lastEntryIndex,  ArraySize(logEntriesArray) - 1);
   firstEntryIndex = MathMax(firstEntryIndex, 0);

   //--- Render each visible log entry
   for (int i = firstEntryIndex; i <= lastEntryIndex; i++)
     {
      color entryTextColor     = GetLogEntryTextColor(logEntriesArray[i].type);
      uint  argbEntryTextColor = ColorToARGB(entryTextColor, 255);
      string timestampString   = TimeToString(logEntriesArray[i].timestamp, TIME_SECONDS);
      string fullEntryMessage  = StringFormat("[%s] %s", timestampString, logEntriesArray[i].message);

      int yPositionHighRes = logStartPositionYHighRes + i * logLineHeightHighRes - scrollOffsetHighRes;

      //--- Skip entries that are fully outside the visible area
      if (yPositionHighRes + logLineHeightHighRes < logStartPositionYHighRes ||
          yPositionHighRes > logStartPositionYHighRes + logVisibleHeightHighRes) continue;

      logPanelHighResolutionCanvas.TextOut(5 * supersamplingFactor,
                                           yPositionHighRes,
                                           fullEntryMessage, argbEntryTextColor, TA_LEFT);
     }

   //--- Render the scrollbar only when content overflows
   if (isLogScrollbarVisible)
     {
      int logScrollbarPositionXHighRes = highResolutionWidth - (logTrackWidth * supersamplingFactor);
      int logScrollbarPositionYHighRes = 0;
      int logScrollbarHeightHighRes    = highResolutionHeight;

      //--- Fill the scrollbar track background
      uint argbTrackColor = ColorToARGB(logTrackColor, 255);
      logPanelHighResolutionCanvas.FillRectangle(
         logScrollbarPositionXHighRes, logScrollbarPositionYHighRes,
         logScrollbarPositionXHighRes + (logTrackWidth * supersamplingFactor) - 1,
         logScrollbarPositionYHighRes + logScrollbarHeightHighRes - 1, argbTrackColor);

      int logScrollAreaHeightHighRes = logScrollbarHeightHighRes - 2 * (logButtonSize * supersamplingFactor);

      //--- Compute the display-space slider height and position
      int logVisibleHeightDisplay  = currentLogPanelHeight - 25 - 5;
      int logTotalHeightDisplay    = ArraySize(logEntriesArray) * (logFontSize + 4);
      int logScrollAreaHeightDisplay = currentLogPanelHeight - 2 * logButtonSize;
      logSliderHeight = CalculateLogSliderHeight(logVisibleHeightDisplay, logTotalHeightDisplay,
                                                  logScrollAreaHeightDisplay, 20);

      int logSliderPositionYHighRes = logScrollbarPositionYHighRes +
                                      (logButtonSize * supersamplingFactor) +
                                      (int)(((double)logScrollCurrentPosition / logScrollMaximumPosition) *
                                            (logScrollAreaHeightHighRes - (logSliderHeight * supersamplingFactor)));

      if (isLogScrollAreaHovered)
        {
         //--- Draw full-width buttons and slider when the scrollbar is hovered
         color upButtonBg   = showLogScrollButtons
                              ? (isLogScrollUpButtonHovered
                                 ? logButtonBackgroundHoverColor : logButtonBackgroundColor)
                              : logTrackColor;
         logPanelHighResolutionCanvas.FillRectangle(
            logScrollbarPositionXHighRes, logScrollbarPositionYHighRes,
            logScrollbarPositionXHighRes + (logTrackWidth * supersamplingFactor) - 1,
            logScrollbarPositionYHighRes + (logButtonSize * supersamplingFactor) - 1,
            ColorToARGB(upButtonBg, 255));

         //--- Draw up arrow glyph
         color upArrowColor = (logScrollCurrentPosition == 0)
                              ? logArrowDisabledColor
                              : (isLogScrollUpButtonHovered ? logArrowHoverColor : logArrowColor);
         int   arrowPositionX  = logScrollbarPositionXHighRes + (logTrackWidth * supersamplingFactor) / 2;
         double baseWidth      = logButtonSize * (logTriangleBaseWidthPercent / 100.0) * supersamplingFactor;
         int   triangleHeight  = (int)(baseWidth * (logTriangleHeightPercent / 100.0));
         int   arrowPositionY  = logScrollbarPositionYHighRes +
                                 ((logButtonSize * supersamplingFactor) - triangleHeight) / 2;
         DrawRoundedTriangleArrow(logPanelHighResolutionCanvas,
                                  arrowPositionX, arrowPositionY,
                                  (int)baseWidth, triangleHeight, true,
                                  ColorToARGB(upArrowColor, 255));

         //--- Draw down button background
         int   downPositionYHighRes = logScrollbarPositionYHighRes + logScrollbarHeightHighRes -
                                      (logButtonSize * supersamplingFactor);
         color downButtonBg = showLogScrollButtons
                              ? (isLogScrollDownButtonHovered
                                 ? logButtonBackgroundHoverColor : logButtonBackgroundColor)
                              : logTrackColor;
         logPanelHighResolutionCanvas.FillRectangle(
            logScrollbarPositionXHighRes, downPositionYHighRes,
            logScrollbarPositionXHighRes + (logTrackWidth * supersamplingFactor) - 1,
            downPositionYHighRes + (logButtonSize * supersamplingFactor) - 1,
            ColorToARGB(downButtonBg, 255));

         //--- Draw down arrow glyph
         color downArrowColor = (logScrollCurrentPosition >= logScrollMaximumPosition)
                                ? logArrowDisabledColor
                                : (isLogScrollDownButtonHovered ? logArrowHoverColor : logArrowColor);
         int downArrowPositionX = logScrollbarPositionXHighRes + (logTrackWidth * supersamplingFactor) / 2;
         int downArrowPositionY = downPositionYHighRes +
                                  ((logButtonSize * supersamplingFactor) - triangleHeight) / 2;
         DrawRoundedTriangleArrow(logPanelHighResolutionCanvas,
                                  downArrowPositionX, downArrowPositionY,
                                  (int)baseWidth, triangleHeight, false,
                                  ColorToARGB(downArrowColor, 255));

         //--- Draw the full-width pill-shaped slider thumb
         int   sliderPositionXHighRes = logScrollbarPositionXHighRes + (logScrollbarMargin * supersamplingFactor);
         int   sliderWidthHighRes     = (logTrackWidth * supersamplingFactor) - 2 * (logScrollbarMargin * supersamplingFactor);
         int   capRadius              = sliderWidthHighRes / 2;
         color sliderBgColor = (isLogScrollSliderHovered || isMovingLogSlider)
                               ? logSliderBackgroundHoverColor : logSliderBackgroundColor;
         uint  argbSliderColor = ColorToARGB(sliderBgColor, 255);

         //--- Draw top cap, middle fill, and bottom cap of the slider
         logPanelHighResolutionCanvas.Arc(sliderPositionXHighRes + capRadius,
            logSliderPositionYHighRes + capRadius,
            capRadius, capRadius,
            ConvertDegreesToRadians(180), ConvertDegreesToRadians(90), argbSliderColor);
         logPanelHighResolutionCanvas.FillCircle(sliderPositionXHighRes + capRadius,
            logSliderPositionYHighRes + capRadius, capRadius, argbSliderColor);
         logPanelHighResolutionCanvas.Arc(sliderPositionXHighRes + capRadius,
            logSliderPositionYHighRes + (logSliderHeight * supersamplingFactor) - capRadius,
            capRadius, capRadius,
            ConvertDegreesToRadians(90), ConvertDegreesToRadians(90), argbSliderColor);
         logPanelHighResolutionCanvas.FillCircle(sliderPositionXHighRes + capRadius,
            logSliderPositionYHighRes + (logSliderHeight * supersamplingFactor) - capRadius,
            capRadius, argbSliderColor);
         logPanelHighResolutionCanvas.FillRectangle(
            sliderPositionXHighRes,
            logSliderPositionYHighRes + capRadius,
            sliderPositionXHighRes + sliderWidthHighRes,
            logSliderPositionYHighRes + (logSliderHeight * supersamplingFactor) - capRadius,
            argbSliderColor);
        }
      else
        {
         //--- Draw the thin (collapsed) slider when the scrollbar is not hovered
         int  thinWidthHighRes     = logScrollbarThinWidth * supersamplingFactor;
         int  thinPositionXHighRes = logScrollbarPositionXHighRes +
                                     ((logTrackWidth * supersamplingFactor) - thinWidthHighRes) / 2;
         int  capRadius            = thinWidthHighRes / 2;
         uint argbSliderColor      = ColorToARGB(logSliderBackgroundColor, 255);

         logPanelHighResolutionCanvas.Arc(thinPositionXHighRes + capRadius,
            logSliderPositionYHighRes + capRadius,
            capRadius, capRadius,
            ConvertDegreesToRadians(180), ConvertDegreesToRadians(90), argbSliderColor);
         logPanelHighResolutionCanvas.FillCircle(thinPositionXHighRes + capRadius,
            logSliderPositionYHighRes + capRadius, capRadius, argbSliderColor);
         logPanelHighResolutionCanvas.Arc(thinPositionXHighRes + capRadius,
            logSliderPositionYHighRes + (logSliderHeight * supersamplingFactor) - capRadius,
            capRadius, capRadius,
            ConvertDegreesToRadians(90), ConvertDegreesToRadians(90), argbSliderColor);
         logPanelHighResolutionCanvas.FillCircle(thinPositionXHighRes + capRadius,
            logSliderPositionYHighRes + (logSliderHeight * supersamplingFactor) - capRadius,
            capRadius, argbSliderColor);
         logPanelHighResolutionCanvas.FillRectangle(
            thinPositionXHighRes,
            logSliderPositionYHighRes + capRadius,
            thinPositionXHighRes + thinWidthHighRes,
            logSliderPositionYHighRes + (logSliderHeight * supersamplingFactor) - capRadius,
            argbSliderColor);
        }
     }

   //--- Downsample the high-res canvas to the display canvas and flush it
   DownsampleCanvasImage(logPanelCanvas, logPanelHighResolutionCanvas);
   logPanelCanvas.Update();
  }

First, we define the "GetLogEntryTextColor" helper function to retrieve the appropriate color for a log entry based on its type from "ENUM_LOG_TYPE". We use a switch statement to map each type to the corresponding input color, such as "logFrequencyTextColor" for frequency logs or "logSuccessTextColor" for success, defaulting to "logInfoTextColor" for others, ensuring color-coded differentiation in the display.

The log panel is rendered in "RenderLogPanelVisualization". The function clears the high-resolution canvas, draws the background and entries, updates the scrollbar state, downsamples the image, and flushes the result. We set a bold font for the title "FREQUENCY ANALYSIS LOG" centered with "TextOut", then switch to monospace for logs. We calculate line height from text height plus padding, visible area below title, total content from entry count times height, and max scroll as excess height. If "pendingAutoScrollToBottom" (set on new entries if auto-scroll enabled), jump to bottom and reset flag; clamp current position.

For visible scrollbar (if content overflows), we draw the track fill, and if hovered, add up/down buttons as rectangles with backgrounds (hover variants), arrows via "DrawRoundedTriangleArrow" in colors (disabled if at limits, hover adjusted), and a rounded slider with arcs via Arc and FillCircle plus middle fill, using colors for normal/hover/drag states. If not hovered, draw a thinner slider similarly. Finally, we downsample the high-res to the main log canvas with "DownsampleCanvasImage" for anti-aliased output and call "Update" to display, providing a smooth, interactive log view. With all rendering and analysis functions defined, we now wire everything together in the initialization event handler.

//+------------------------------------------------------------------+
//| Initialize expert advisor                                        |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Restore canvas geometry to the configured initial values
   currentCanvasPositionX = initialCanvasPositionX;
   currentCanvasPositionY = initialCanvasPositionY;
   currentCanvasWidth     = initialCanvasWidth;
   currentCanvasHeight    = initialCanvasHeight;
   currentLogPanelHeight  = logPanelHeight;

   //--- Create the main distribution histogram canvas
   if (!mainDistributionCanvas.CreateBitmapLabel(0, 0, canvasObjectName,
       currentCanvasPositionX, currentCanvasPositionY,
       currentCanvasWidth, currentCanvasHeight,
       COLOR_FORMAT_ARGB_NORMALIZE))
     {
      Print("ERROR: Failed to create distribution canvas");
      return INIT_FAILED;
     }

   //--- Create the log panel canvas positioned below the main canvas
   int logPanelPositionY = currentCanvasPositionY + currentCanvasHeight + panelGap;
   if (!logPanelCanvas.CreateBitmapLabel(0, 0, logCanvasObjectName,
       currentCanvasPositionX, logPanelPositionY,
       currentCanvasWidth, currentLogPanelHeight,
       COLOR_FORMAT_ARGB_NORMALIZE))
     {
      Print("ERROR: Failed to create log panel canvas");
      return INIT_FAILED;
     }

   //--- Create the high-resolution canvas used for supersampled log rendering
   if (!logPanelHighResolutionCanvas.Create(logCanvasHighResolutionName,
       currentCanvasWidth   * supersamplingFactor,
       currentLogPanelHeight * supersamplingFactor,
       COLOR_FORMAT_ARGB_NORMALIZE))
     {
      Print("ERROR: Failed to create log panel high-res canvas");
      return INIT_FAILED;
     }

   //--- Initialize the log system and record startup messages
   ArrayResize(logEntriesArray, 0);
   AddLogEntry("=== FREQUENCY ANALYSIS SYSTEM INITIALIZED ===", LOG_SUCCESS);
   AddLogEntry(StringFormat("Window Size: %d bars | Bins: %d",
               analysisWindowSize, frequencyBins), LOG_INFO);

   //--- Run the initial analysis pipeline
   PerformFrequencyAnalysis();
   CalculateBasicStatistics();
   CalculateChiSquareTest();
   CalculateShannonEntropy();
   CalculateAutoCorrelation();
   CalculateAdvancedStatistics();

   AddLogEntry("Initial frequency analysis completed", LOG_SUCCESS);

   //--- Render both panels and enable mouse interaction
   RenderDistributionVisualization();
   RenderLogPanelVisualization();
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE,  true);
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);
   ChartRedraw();

   return INIT_SUCCEEDED;
  }

In the OnInit event handler, we assign input values to globals like "currentCanvasPositionX" from "initialCanvasPositionX" for canvas placement and size, including "currentLogPanelHeight" from "logPanelHeight", just like we did with the previous version. We create the main distribution canvas with CreateBitmapLabel using normalized ARGB, checking for failure to print error, and return INIT_FAILED. Similarly, compute log panel Y as canvas Y plus height plus "panelGap", create the log canvas, and a high-res version scaled by "supersamplingFactor" for anti-aliased rendering, handling errors. To initialize logs, we resize "logEntriesArray" to zero, add a success entry for system initialization, and another info entry formatting window size and bins with the StringFormat function.

We perform initial frequency analysis via "PerformFrequencyAnalysis", compute basics with "CalculateBasicStatistics", chi-square if enabled with "CalculateChiSquareTest", entropy with "CalculateShannonEntropy", correlation with "CalculateAutoCorrelation", and advanced stats with "CalculateAdvancedStatistics", then log completion as success. We render the distribution with "RenderDistributionVisualization" and the log panel with "RenderLogPanelVisualization". Finally, enable mouse move and wheel events with ChartSetInteger, redraw the chart via "ChartRedraw", and return INIT_SUCCEEDED to confirm ready state. With initialization complete, we now define the tick handler that drives real-time updates.

Driving Real-Time Analysis in the Tick Event Handler

The tick handler increments a counter and evaluates whether to run the analysis pipeline based on the selected computation mode: in per-bar mode it triggers on a new bar open, in per-tick mode it triggers every configured number of ticks. When triggered, it runs the full analysis suite, logs the key results for mode, mean, standard deviation, skewness, chi-square, entropy, and autocorrelation, then re-renders both canvases.

//+------------------------------------------------------------------+
//| Handle tick events                                               |
//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBarOpenTime = 0;
   datetime currentBarOpenTime = iTime(_Symbol, PERIOD_CURRENT, 0);

   tickUpdateCounter++;

   //--- Decide whether to run the analysis based on the configured compute mode
   bool performAnalysis = false;
   if (computeMode == PER_BAR)
     {
      //--- Trigger once per new bar
      if (currentBarOpenTime > lastBarOpenTime)
        {
         performAnalysis  = true;
         lastBarOpenTime  = currentBarOpenTime;
        }
     }
   else if (computeMode == PER_TICK)
     {
      //--- Trigger every N ticks according to the configured interval
      if (tickUpdateCounter >= logUpdateIntervalTicks)
        {
         performAnalysis      = true;
         tickUpdateCounter    = 0;
        }
     }

   if (performAnalysis)
     {
      AddLogEntry("--- ANALYSIS UPDATE ---", LOG_INFO);

      //--- Run the full analysis pipeline
      PerformFrequencyAnalysis();
      CalculateBasicStatistics();
      CalculateChiSquareTest();
      CalculateShannonEntropy();
      CalculateAutoCorrelation();
      CalculateAdvancedStatistics();

      //--- Log frequency results
      AddLogEntry(StringFormat("FREQ: Mode=%.5f (count=%d, %.2f%%)",
                  currentModeValue, modeFrequencyCount,
                  (double)modeFrequencyCount / ArraySize(priceDataArray) * 100),
                  LOG_FREQUENCY);

      //--- Log statistical summary
      AddLogEntry(StringFormat("STAT: Mean=%.5f | StdDev=%.5f | Skew=%.3f",
                  currentMeanValue, currentStandardDeviation, currentSkewnessValue),
                  LOG_STATISTICAL);

      //--- Log chi-square result if enabled
      if (enableChiSquareTest)
         AddLogEntry(StringFormat("CHI²: χ²=%.4f (df=%d)",
                     chiSquareTestStatistic, frequencyBins - 1),
                     LOG_STATISTICAL);

      //--- Log entropy result if enabled
      if (enableEntropyCalculation)
         AddLogEntry(StringFormat("ENTROPY: H=%.4f bits", shannonEntropyValue),
                     LOG_VECTOR_MATRIX);

      //--- Log autocorrelation result if enabled
      if (enableCorrelationAnalysis)
         AddLogEntry(StringFormat("CORR: ρ(lag-1)=%.4f", correlationCoefficient),
                     LOG_VECTOR_MATRIX);

      //--- Refresh both panels
      RenderDistributionVisualization();
      RenderLogPanelVisualization();
      ChartRedraw();
     }
  }

In the OnTick event handler, we increment "tickUpdateCounter" for tracking, then set a "performAnalysis" flag based on "computeMode": if "PER_BAR", check for a new bar by comparing timestamps and update if true; if "PER_TICK", analyze when counter reaches "logUpdateIntervalTicks" and reset it. If flagged, we add an info log entry for update, execute the full analysis suite—"PerformFrequencyAnalysis" for binning, "CalculateBasicStatistics" for mean/std/skew, "CalculateChiSquareTest" if enabled, "CalculateShannonEntropy" if on, "CalculateAutoCorrelation" if active, and "CalculateAdvancedStatistics" for percentiles/CI—then log key results like mode (with StringFormat for frequency percent), mean/std/skew as statistical, conditional chi-square with DF, entropy as vector-matrix, and correlation lag-1.

Finally, we re-render visualizations for distribution and log panels, redraw the chart with ChartRedraw, ensuring timely insights per mode without overload. In the de-initialization, we will need to include our new canvases for removal by calling the respective functions as follows.

//+------------------------------------------------------------------+
//| Deinitialize expert advisor                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- Release all canvas bitmap objects from the chart
   mainDistributionCanvas.Destroy();
   logPanelCanvas.Destroy();
   logPanelHighResolutionCanvas.Destroy();
   ChartRedraw();
  }

Upon compilation, we get the following outcome.

FREQUENCY ANALYSIS ON BINS

With the histogram, statistics panel, and scrollable log panel all rendering correctly, the implementation is complete. What remains is testing the system, covered in the next section.


Backtesting

We did the testing, and below is the compiled visualization in a single Graphics Interchange Format (GIF) image.

BACKTEST GIF

During testing, the frequency histogram updated correctly on each new bar in per-bar mode and on the configured tick interval in per-tick mode, Shannon entropy and chi-square values changed meaningfully across different market conditions, and the log panel auto-scrolled to the latest entry without layout disruption when the entry count exceeded the visible area.


Conclusion

In conclusion, we have developed a frequency analysis tool in MQL5 that bins closing prices into histograms, computes Shannon entropy to quantify market predictability, and applies chi-square tests to detect non-random price clustering. The implementation covered equal-width bin construction, relative frequency computation, basic and advanced statistics, a supersampled auto-scrolling log panel with a rounded interactive scrollbar, and per-bar or per-tick update modes. After reading this article, you will be able to:

  • Read the frequency histogram to identify the highest-density bin as the current value area, treating price returns to that bin from a deviation as mean-reversion candidates
  • Monitor Shannon entropy across sessions to detect drops that signal emerging directional structure, distinguishing range conditions worth fading from breakout conditions worth following
  • Use the chi-square statistic as a pre-trade filter, entering frequency-based setups only when the value is high enough to confirm that clustering is statistically significant rather than random

That concludes the article.

Using the MQL5 Economic Calendar for News Filter (Part 3): Surviving Terminal Restarts During News Window Using the MQL5 Economic Calendar for News Filter (Part 3): Surviving Terminal Restarts During News Window
The article introduces a restart-safe storage model for news-time stop removal. Suspension state and original SL/TP per position are written to terminal global variables, reconstructed on OnInit, and cleaned after restoration. This lets the EA resume an active suspension window after recompiles or restarts and restore stops only when the news window ends.
Neuro-Structural Trading Engine — NSTE (Part II): Jardine's Gate Six-Gate Quantum Filter Neuro-Structural Trading Engine — NSTE (Part II): Jardine's Gate Six-Gate Quantum Filter
This article introduces Jardine's Gate, a six-gate orthogonal signal filter for MetaTrader 5 that validates LSTM predictions across entropy, expert interference, confidence, regime-adjusted probability, trend direction, and consecutive-loss kill switch dimensions. Out of 43,200 raw signals per month, only 127 pass all six gates. Readers get the complete QuantumEdgeFilter MQL5 class, threshold calibration logic, and gate performance analytics.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Trend Criteria. Conclusion Trend Criteria. Conclusion
In this article, we will consider the specifics of applying some trend criteria in practice. We will also try to develop several new criteria. The focus will be on the efficiency of applying these criteria to market data analysis and trading.