English
preview
Торговые инструменты MQL5 (Часть 25): Расширяем поддержку нескольких распределений с интерактивным переключением

Торговые инструменты MQL5 (Часть 25): Расширяем поддержку нескольких распределений с интерактивным переключением

MetaTrader 5Торговые системы |
41 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

У вас есть инструмент для построения графиков биномиального распределения. Однако, ограничивая анализ одной моделью, вы не сможете сравнивать соответствия между распределениями, изучать поведение хвостов в семействах или переключаться между дискретной и непрерывной моделями без перестройки инструмента с нуля. Эта статья предназначена для разработчиков MetaQuotes Language 5 (MQL5) и алгоритмических трейдеров, стремящихся создать универсальный инструмент визуализации нескольких распределений с интерактивным переключением для более широкого анализа вероятностных моделей.

В своей предыдущей статье (Часть 24) мы усовершенствовали инструмент для просмотра трехмерного биномиального распределения, добавив сегментированную кривую, режим панорамирования и интерактивный куб обзора с анимированными переходами камеры. В Части 25 мы расширим инструмент построения графиков для поддержки нескольких статистических распределений, включая распределение Пуассона, нормальное, распределение Вейбулла, гамма-распределение и другие. Мы добавим циклический перебор распределений с помощью значка переключения в заголовке, загрузку данных и вычисление гистограмм для каждого типа распределения, а также динамические заголовки, метки осей и панели параметров, которые адаптируются к дискретным и непрерывным моделям. В статье рассмотрим следующие темы:

  1. Исследование структуры переключения между несколькими распределениями
  2. Реализация средствами MQL5
  3. Тестирование на истории
  4. Заключение

В итоге у вас будет универсальный инструмент в MQL5, поддерживающий несколько распределений с интерактивным переключением, готовый к настройке. Перейдём к реализации!


Исследование структуры переключения между несколькими распределениями

Каждое статистическое распределение описывает различную картину неопределенности: дискретные распределения, такие как распределение Пуассона и отрицательное биномиальное распределение, описывают исходы, основанные на подсчете, например, количество сделок, достигших целевого уровня за сессию, в то время как непрерывные распределения, такие как нормальное распределение, распределение Вейбулла и гамма-распределение, описывают явления с действительными значениями, такие как доходность, продолжительность просадок или уровни волатильности. Интерактивное переключение позволяет трейдеру накладывать теоретические функции распределения вероятностей/плотности на одни и те же данные выборки и быстро видеть, какая модель лучше всего соответствует наблюдаемой гистограмме, без перезагрузки инструмента или изменения жестко заданных параметров. Структура использует перечисление типов распределений и единую точку диспетчеризации для маршрутизации загрузки данных, вычисления гистограммы, расчета плотности и маркировки панели. Для добавления нового распределения требуется только новая функция загрузки и новая запись enum.

В реальной торговле используйте нормальное распределение для моделирования распределения ежедневной доходности и отмечайте бары, чья доходность выходит за пределы двух стандартных отклонений, как потенциальные точки входа для возврата к среднему значению. Переключитесь на экспоненциальное распределение, чтобы анализировать время между значительными движениями цены и калибровать расстояния стоп-лоссов. Используйте гамма-распределение или распределение Вейбулла для моделирования продолжительности просадки и оценки вероятности восстановления перед определением размера позиций.

Определим перечисление для всех поддерживаемых типов, добавим отдельные группы ввода для параметров каждого распределения, реализуем функции загрузки данных, специфичные для каждого типа, которые обрабатывают генерацию выборок, варианты вычисления гистограмм и расчеты плотности, а также интегрируем значок переключения в заголовке с обратной связью при наведении курсора и обработкой щелчков для переключения между типами. Вкратце, ниже представлена визуализация структуры, которую мы будем создавать.

MULTIPLE DISTRIBUTIONS FRAMEWORK


Реализация средствами MQL5

Реализация начинается с включения всех необходимых статистических библиотек, определения перечисления типов распределений, добавления отдельных групп входных данных для параметров каждого распределения и объявления глобальных переменных, управляющих состоянием переключения.

Включение библиотек, определение перечисления распределений и настройка входных параметров

Чтобы сделать инструмент независимым от типа распределения с самого начала, мы включим библиотеку для каждой поддерживаемой модели, определим единое перечисление, в котором перечислены все семнадцать типов распределений, добавим отдельную группу ввода для каждого распределения, чтобы параметры всегда были видны в диалоговом окне настроек, и объявим глобальные переменные состояния переключения и константы значков, на которые будет опираться остальная часть реализации.

//+------------------------------------------------------------------+
//|  Canvas Graphing PART 4 - Statistical Distributions (More).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

//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>              // Include canvas drawing library
#include <Math\Stat\Binomial.mqh>         // Include binomial distribution library
#include <Math\Stat\Hypergeometric.mqh>   // Include hypergeometric distribution library
#include <Math\Stat\Poisson.mqh>          // Include Poisson distribution library
#include <Math\Stat\Normal.mqh>           // Include normal distribution library
#include <Math\Stat\Weibull.mqh>          // Include Weibull distribution library
#include <Math\Stat\NoncentralChiSquare.mqh> // Include noncentral Chi-Square library
#include <Math\Stat\Gamma.mqh>            // Include gamma distribution library
#include <Math\Stat\ChiSquare.mqh>        // Include Chi-Square distribution library
#include <Math\Stat\Cauchy.mqh>           // Include Cauchy distribution library
#include <Math\Stat\Math.mqh>             // Include statistical math utilities
#include <Math\Stat\F.mqh>                // Include F distribution library
#include <Math\Stat\NegativeBinomial.mqh> // Include negative binomial library
#include <Math\Stat\Exponential.mqh>      // Include exponential distribution library
#include <Math\Stat\Beta.mqh>             // Include beta distribution library
#include <Math\Stat\NoncentralBeta.mqh>   // Include noncentral beta library
#include <Math\Stat\T.mqh>                // Include T distribution library
#include <Math\Stat\NoncentralT.mqh>      // Include noncentral T library
#include <Math\Stat\Uniform.mqh>          // Include uniform distribution library

enum DistributionType
  {
   BINOMIAL_DISTRIBUTION,             // Binomial distribution type
   HYPERGEOMETRIC_DISTRIBUTION,       // Hypergeometric distribution type
   POISSON_DISTRIBUTION,              // Poisson distribution type
   NORMAL_DISTRIBUTION,               // Normal distribution type
   WEIBULL_DISTRIBUTION,              // Weibull distribution type
   NONCENTRAL_CHISQUARE_DISTRIBUTION, // Noncentral Chi-Square distribution type
   GAMMA_DISTRIBUTION,                // Gamma distribution type
   CHISQUARE_DISTRIBUTION,            // Chi-Square distribution type
   CAUCHY_DISTRIBUTION,               // Cauchy distribution type
   F_DISTRIBUTION,                    // F distribution type
   NEGATIVEBINOMIAL_DISTRIBUTION,     // Negative Binomial distribution type
   EXPONENTIAL_DISTRIBUTION,          // Exponential distribution type
   BETA_DISTRIBUTION,                 // Beta distribution type
   NONCENTRALBETA_DISTRIBUTION,       // Noncentral Beta distribution type
   T_DISTRIBUTION,                    // T distribution type
   NONCENTRALT_DISTRIBUTION,          // Noncentral T distribution type
   UNIFORM_DISTRIBUTION               // Uniform distribution type
  };

input group "=== HYPERGEOMETRIC DISTRIBUTION SETTINGS ==="
input double           hypergeometricTotalObjects   = 60; // Total Objects (m)
input double           hypergeometricSuccessObjects = 30; // Objects with Characteristic (k)
input double           hypergeometricDraws          = 30; // Number of Draws (n)

input group "=== POISSON DISTRIBUTION SETTINGS ==="
input double           poissonLambda = 10; // Lambda (mean)

input group "=== NORMAL DISTRIBUTION SETTINGS ==="
input double           normalMean              = 0; // Mean (μ)
input double           normalStandardDeviation = 1; // Standard Deviation (σ)

input group "=== WEIBULL DISTRIBUTION SETTINGS ==="
input double           weibullShape = 1; // Shape Parameter (a)
input double           weibullScale = 5; // Scale Parameter (b)

input group "=== NONCENTRAL CHI-SQUARE SETTINGS ==="
input double           noncentralChiSquareDegreesOfFreedom = 8; // Degrees of Freedom (ν)
input double           noncentralChiSquareNoncentrality    = 1; // Noncentrality Parameter (σ)

input group "=== GAMMA DISTRIBUTION SETTINGS ==="
input double           gammaShape = 9;   // Shape Parameter (α)
input double           gammaRate  = 0.5; // Rate Parameter (β)

input group "=== CHI-SQUARE DISTRIBUTION SETTINGS ==="
input double           chiSquareDegreesOfFreedom = 5; // Degrees of Freedom (ν)

input group "=== CAUCHY DISTRIBUTION SETTINGS ==="
input double           cauchyLocation = -2; // Location Parameter (a)
input double           cauchyScale    =  1; // Scale Parameter (b)

input group "=== F DISTRIBUTION SETTINGS ==="
input double           fDegreesOfFreedom1 = 100; // First Degrees of Freedom (nu1)
input double           fDegreesOfFreedom2 = 100; // Second Degrees of Freedom (nu2)

input group "=== NEGATIVE BINOMIAL DISTRIBUTION SETTINGS ==="
input double           negativeBinomialSuccesses    = 40;   // Number of Successes (r)
input double           negativeBinomialProbability  = 0.75; // Success Probability (p)

input group "=== EXPONENTIAL DISTRIBUTION SETTINGS ==="
input double           exponentialMean = 1.5; // Mean (mu)

input group "=== BETA DISTRIBUTION SETTINGS ==="
input double           betaShape1 = 2; // Shape1 (alpha)
input double           betaShape2 = 5; // Shape2 (beta)

input group "=== NONCENTRAL BETA DISTRIBUTION SETTINGS ==="
input double           noncentralBetaShape1        = 2; // Shape1 (a)
input double           noncentralBetaShape2        = 5; // Shape2 (b)
input double           noncentralBetaNoncentrality = 1; // Noncentrality (lambda)

input group "=== T DISTRIBUTION SETTINGS ==="
input double           tDegreesOfFreedom = 10; // Degrees of Freedom (nu)

input group "=== NONCENTRAL T DISTRIBUTION SETTINGS ==="
input double           noncentralTDegreesOfFreedom = 30; // Degrees of Freedom (nu)
input double           noncentralTNoncentrality    =  5; // Noncentrality (delta)

input group "=== UNIFORM DISTRIBUTION SETTINGS ==="
input double           uniformLowerBound =  0; // Lower Bound (a)
input double           uniformUpperBound = 10; // Upper Bound (b)

input DistributionType initialDistributionType = BINOMIAL_DISTRIBUTION; // Initial Distribution

bool isHoveringSwitchIcon = false;                                      // Flag for mouse hovering over switch icon

const int SWITCH_ICON_SIZE  = 24;                                       // Pixel size of the switch icon
const int SWITCH_ICON_MARGIN = 6;                                       // Margin around switch icon in pixels
const int TOTAL_DISTRIBUTIONS = (int)UNIFORM_DISTRIBUTION + 1;          // Total number of supported distributions

DistributionType currentDistributionType = BINOMIAL_DISTRIBUTION;       // Active distribution type being displayed

Начнем реализацию с включения широкого спектра статистических библиотек с директивами, такими как "#include <Math\Stat\Binomial.mqh>" для биномиальных функций и до "#include <Math\Stat\Uniform.mqh>" для равномерных распределений, обеспечивая доступ к генерации и вычислениям плотности для различных распределений. Далее определим перечисление "DistributionType", перечисляющее типы от "BINOMIAL_DISTRIBUTION" до "UNIFORM_DISTRIBUTION", что позволяет безопасно переключаться между моделями. Затем добавим группы входных параметров, специфичных для каждого распределения, например, "hypergeometricTotalObjects" для гипергеометрического или "poissonLambda" для распределения Пуассона, что позволяет настраивать параметры через диалоговое окно настроек программы.

Входной параметр "initialDistributionType" задает начальный тип, по умолчанию равный "BINOMIAL_DISTRIBUTION". Для глобальных переменных объявим экземпляр CCanvas "mainCanvas" для рендеринга, строку "canvasObjectName" как "DistributionCanvas_Main", переменные положения и размеров инициализируются из входных параметров, флаги взаимодействия, такие как "isDraggingCanvas", равными false, трекеры мыши равны 0, константы для минимальных размеров и высоты заголовка, массивы данных, такие как "sampleData", минимальные/максимальные значения равными 0.0, флаг загрузки равен false, а статистические данные, такие как "sampleMean", равными 0.0. Мы также установим "isHoveringSwitchIcon" в значение false для наведения курсора на переключатель типа, определим константы значков, вычислим "TOTAL_DISTRIBUTIONS" как размер перечисления плюс один и инициализируем "currentDistributionType" значением "BINOMIAL_DISTRIBUTION" при запуске. После объявления входных признаков и глобальных переменных определим функции загрузки данных для каждого типа, начиная с дискретных распределений.

Загрузка данных для дискретных и непрерывных распределений

Каждая функция загрузки имеет одинаковую структуру: инициализация генератора случайных чисел, генерация выборок, вычисление гистограммы, построение теоретического массива плотности, масштабирование частот до теоретического пика, вычисление статистики и установка флага успешного выполнения. Реализация различается в зависимости от распределения: дискретные модели используют гистограммы, выровненные по целым числам, с принудительно заданными диапазонами, в то время как непрерывные модели используют теоретические массивы на основе шагов в диапазоне действительных значений.

//+------------------------------------------------------------------+
//| Load binomial distribution data                                  |
//+------------------------------------------------------------------+
bool loadBinomialData()
  {
   //--- Seed and generate binomial random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomBinomial(numTrials, successProbability, sampleSize, sampleData);

   //--- Build discrete histogram over the full trial range [0, n]
   if (!computeHistogramDiscrete(sampleData, histogramIntervals, histogramFrequencies,
                                 maxDataValue, minDataValue, histogramCells, 0, numTrials))
     {
      Print("ERROR: Failed to calculate binomial histogram");
      return false;
     }

   //--- Generate theoretical PMF values at each integer in [0, n]
   ArrayResize(theoreticalXValues, numTrials + 1);
   ArrayResize(theoreticalYValues, numTrials + 1);
   MathSequence(0, numTrials, 1, theoreticalXValues);
   MathProbabilityDensityBinomial(theoreticalXValues, numTrials, successProbability, false, theoreticalYValues);

   //--- Find peaks to compute the scale factor that aligns histogram and curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   //--- Rescale histogram frequencies so they match the theoretical curve's peak
   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded binomial distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load hypergeometric distribution data                            |
//+------------------------------------------------------------------+
bool loadHypergeometricData()
  {
   //--- Seed and generate hypergeometric random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomHypergeometric(hypergeometricTotalObjects, hypergeometricSuccessObjects,
                            hypergeometricDraws, sampleSize, sampleData);

   //--- Build discrete histogram over the range [0, n draws]
   if (!computeHistogramDiscrete(sampleData, histogramIntervals, histogramFrequencies,
                                 maxDataValue, minDataValue, histogramCells,
                                 0, (int)hypergeometricDraws))
     {
      Print("ERROR: Failed to calculate hypergeometric histogram");
      return false;
     }

   //--- Generate theoretical PMF values at each integer in [0, maxK]
   int maxK = (int)hypergeometricDraws;
   ArrayResize(theoreticalXValues, maxK + 1);
   ArrayResize(theoreticalYValues, maxK + 1);
   MathSequence(0, maxK, 1, theoreticalXValues);
   MathProbabilityDensityHypergeometric(theoreticalXValues, hypergeometricTotalObjects,
                                        hypergeometricSuccessObjects, hypergeometricDraws,
                                        false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded hypergeometric distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load Poisson distribution data                                   |
//+------------------------------------------------------------------+
bool loadPoissonData()
  {
   //--- Seed and generate Poisson random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomPoisson(poissonLambda, sampleSize, sampleData);

   //--- Determine the observed range before building the histogram
   double tempMin = sampleData[ArrayMinimum(sampleData)];
   double tempMax = sampleData[ArrayMaximum(sampleData)];

   //--- Build discrete histogram over [0, observed max]
   if (!computeHistogramDiscrete(sampleData, histogramIntervals, histogramFrequencies,
                                 maxDataValue, minDataValue, histogramCells,
                                 0, (int)MathCeil(tempMax)))
     {
      Print("ERROR: Failed to calculate Poisson histogram");
      return false;
     }

   //--- Generate theoretical PMF values over the observed integer range
   int maxRange = (int)MathCeil(tempMax);
   ArrayResize(theoreticalXValues, maxRange + 1);
   ArrayResize(theoreticalYValues, maxRange + 1);
   MathSequence(0, maxRange, 1, theoreticalXValues);
   MathProbabilityDensityPoisson(theoreticalXValues, poissonLambda, false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Poisson distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load normal distribution data                                    |
//+------------------------------------------------------------------+
bool loadNormalData()
  {
   //--- Seed and generate normal random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomNormal(normalMean, normalStandardDeviation, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Normal histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityNormal(theoreticalXValues, normalMean, normalStandardDeviation,
                                false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Normal distribution data");
   return true;
  }

Сначала определим функцию "loadBinomialData" для генерации и подготовки данных биномиального распределения. Инициализируем генератор случайных чисел с помощью MathSrand с помощью GetTickCount для обеспечения вариативности, изменим размер "sampleData" до "sampleSize" с помощью ArrayResize и сгенерируем выборки с помощью MathRandomBinomial на основе "numTrials" и "successProbability". Вычислим дискретную гистограмму с помощью функции "computeHistogramDiscrete" с заданным диапазоном значений от 0 до "numTrials", изменим размер и заполним теоретические массивы значениями плотности "MathSequence" для X и MathProbabilityDensityBinomial для Y, найдем максимальные значения с помощью ArrayMaximum, масштабируем частоты в соответствии с теоретическим максимумом, вызовем функцию "computeAdvancedStatistics" для получения метрик, установим значение "dataLoadedSuccessfully" в значение true, выведем сообщение об успехе и вернем значение true.

Аналогично, для гипергеометрических данных мы реализуем функцию "loadHypergeometricData": инициализируем генератор случайных чисел, изменим размер выборок, генерируем случайные числа с помощью MathRandomHypergeometric, используя "hypergeometricTotalObjects", "hypergeometricSuccessObjects" и "hypergeometricDraws", вычислим дискретную гистограмму, принудительно устанавливаем значение 0-(int)"hypergeometricDraws", установим теоретические значения X/Y с помощью "MathSequence" и MathProbabilityDensityHypergeometric, масштабируем, вычислим статистику, флагом отметим успех и выйдем из функции. Для распределения Пуассона в функции "loadPoissonData" мы сгенерируем выборки с помощью MathRandomPoisson на основе "poissonLambda", найдем минимальное/максимальное значение temp в данных с помощью "ArrayMinimum" и "ArrayMaximum", вычислим дискретную гистограмму, принудительно ограничивающую tempMax значением от 0-ceil, используя MathCeil, подготовим теоретические данные до этого диапазона с помощью MathProbabilityDensityPoisson и следуем шаблону масштабирования, статистики, флагов и вывода.

Далее функция "loadNormalData" обрабатывает нормальное распределение: генерируем данные с помощью MathRandomNormal, используя "normalMean" и "normalStandardDeviation", вычислим непрерывную гистограмму с помощью "computeHistogramContinuous" без принудительного диапазона, установим теоретические значения с более мелким шагом (диапазон гистограммы/(ячейки*5)) с помощью MathProbabilityDensityNormal и завершаем масштабированием, статистикой и подтверждением успешного выполнения. Эти функции следуют единому паттерну для каждого распределения, адаптируя тип гистограммы (дискретный для числовых, например, биномиальный, непрерывный для вещественнозначных, например, нормальный) и теоретические вычисления для модели. Обеспечим единообразную подготовку данных для рендеринга при обработке определенных входных параметров. Остальные непрерывные распределения следуют той же схеме, за исключением распределения Коши, которое требует принудительного диапазона из-за своих тяжелых "хвостов".

//+------------------------------------------------------------------+
//| Load Weibull distribution data                                   |
//+------------------------------------------------------------------+
bool loadWeibullData()
  {
   //--- Seed and generate Weibull random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomWeibull(weibullShape, weibullScale, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Weibull histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityWeibull(theoreticalXValues, weibullShape, weibullScale,
                                 false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Weibull distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load noncentral Chi-Square distribution data                     |
//+------------------------------------------------------------------+
bool loadNoncentralChiSquareData()
  {
   //--- Seed and generate noncentral Chi-Square random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomNoncentralChiSquare(noncentralChiSquareDegreesOfFreedom,
                                 noncentralChiSquareNoncentrality, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Noncentral Chi-Square histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityNoncentralChiSquare(theoreticalXValues,
                                             noncentralChiSquareDegreesOfFreedom,
                                             noncentralChiSquareNoncentrality,
                                             false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Noncentral Chi-Square distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load gamma distribution data                                     |
//+------------------------------------------------------------------+
bool loadGammaData()
  {
   //--- Seed and generate gamma random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomGamma(gammaShape, gammaRate, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Gamma histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityGamma(theoreticalXValues, gammaShape, gammaRate,
                               false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Gamma distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load Chi-Square distribution data                                |
//+------------------------------------------------------------------+
bool loadChiSquareData()
  {
   //--- Seed and generate Chi-Square random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomChiSquare(chiSquareDegreesOfFreedom, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Chi-Square histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityChiSquare(theoreticalXValues, chiSquareDegreesOfFreedom,
                                   false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Chi-Square distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load Cauchy distribution data                                    |
//+------------------------------------------------------------------+
bool loadCauchyData()
  {
   //--- Seed and generate Cauchy random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomCauchy(cauchyLocation, cauchyScale, sampleSize, sampleData);

   //--- Force a finite display range because Cauchy has extremely heavy tails
   minDataValue = -20;
   maxDataValue =  20;

   //--- Build continuous histogram using the forced range to exclude tail outliers
   if (!computeHistogramContinuousForced(sampleData, histogramIntervals, histogramFrequencies,
                                         maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Cauchy histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the forced range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityCauchy(theoreticalXValues, cauchyLocation, cauchyScale,
                                false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Cauchy distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load F distribution data                                         |
//+------------------------------------------------------------------+
bool loadFData()
  {
   //--- Seed and generate F random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomF(fDegreesOfFreedom1, fDegreesOfFreedom2, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate F histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityF(theoreticalXValues, fDegreesOfFreedom1, fDegreesOfFreedom2,
                           false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded F distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load negative binomial distribution data                         |
//+------------------------------------------------------------------+
bool loadNegativeBinomialData()
  {
   //--- Seed and generate negative binomial random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomNegativeBinomial(negativeBinomialSuccesses, negativeBinomialProbability,
                              sampleSize, sampleData);

   //--- Determine the observed range before building the histogram
   double tempMin = sampleData[ArrayMinimum(sampleData)];
   double tempMax = sampleData[ArrayMaximum(sampleData)];

   //--- Build discrete histogram over [0, observed max]
   if (!computeHistogramDiscrete(sampleData, histogramIntervals, histogramFrequencies,
                                 maxDataValue, minDataValue, histogramCells,
                                 0, (int)MathCeil(tempMax)))
     {
      Print("ERROR: Failed to calculate Negative Binomial histogram");
      return false;
     }

   //--- Generate theoretical PMF values over the observed integer range
   int maxRange = (int)MathCeil(tempMax);
   ArrayResize(theoreticalXValues, maxRange + 1);
   ArrayResize(theoreticalYValues, maxRange + 1);
   MathSequence(0, maxRange, 1, theoreticalXValues);
   MathProbabilityDensityNegativeBinomial(theoreticalXValues, negativeBinomialSuccesses,
                                          negativeBinomialProbability, false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Negative Binomial distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load exponential distribution data                               |
//+------------------------------------------------------------------+
bool loadExponentialData()
  {
   //--- Seed and generate exponential random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomExponential(exponentialMean, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Exponential histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityExponential(theoreticalXValues, exponentialMean,
                                     false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Exponential distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load beta distribution data                                      |
//+------------------------------------------------------------------+
bool loadBetaData()
  {
   //--- Seed and generate beta random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomBeta(betaShape1, betaShape2, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Beta histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityBeta(theoreticalXValues, betaShape1, betaShape2,
                              false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Beta distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load noncentral beta distribution data                           |
//+------------------------------------------------------------------+
bool loadNoncentralBetaData()
  {
   //--- Seed and generate noncentral beta random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomNoncentralBeta(noncentralBetaShape1, noncentralBetaShape2,
                            noncentralBetaNoncentrality, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Noncentral Beta histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityNoncentralBeta(theoreticalXValues, noncentralBetaShape1,
                                        noncentralBetaShape2, noncentralBetaNoncentrality,
                                        false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Noncentral Beta distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load T distribution data                                         |
//+------------------------------------------------------------------+
bool loadTData()
  {
   //--- Seed and generate Student's T random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomT(tDegreesOfFreedom, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate T histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityT(theoreticalXValues, tDegreesOfFreedom, false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded T distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load noncentral T distribution data                              |
//+------------------------------------------------------------------+
bool loadNoncentralTData()
  {
   //--- Seed and generate noncentral T random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomNoncentralT(noncentralTDegreesOfFreedom, noncentralTNoncentrality,
                         sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Noncentral T histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityNoncentralT(theoreticalXValues, noncentralTDegreesOfFreedom,
                                     noncentralTNoncentrality, false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Noncentral T distribution data");
   return true;
  }

//+------------------------------------------------------------------+
//| Load uniform distribution data                                   |
//+------------------------------------------------------------------+
bool loadUniformData()
  {
   //--- Seed and generate uniform random samples
   MathSrand(GetTickCount());
   ArrayResize(sampleData, sampleSize);
   MathRandomUniform(uniformLowerBound, uniformUpperBound, sampleSize, sampleData);

   //--- Build continuous histogram using the data's natural range
   if (!computeHistogramContinuous(sampleData, histogramIntervals, histogramFrequencies,
                                   maxDataValue, minDataValue, histogramCells))
     {
      Print("ERROR: Failed to calculate Uniform histogram");
      return false;
     }

   //--- Generate a dense set of theoretical PDF points across the data range
   double step      = (maxDataValue - minDataValue) / (histogramCells * 5);
   int    numPoints = (int)((maxDataValue - minDataValue) / step) + 1;
   ArrayResize(theoreticalXValues, numPoints);
   ArrayResize(theoreticalYValues, numPoints);
   MathSequence(minDataValue, maxDataValue, step, theoreticalXValues);
   MathProbabilityDensityUniform(theoreticalXValues, uniformLowerBound, uniformUpperBound,
                                 false, theoreticalYValues);

   //--- Find peaks and rescale histogram to align with theoretical curve
   maxFrequency        = histogramFrequencies[ArrayMaximum(histogramFrequencies)];
   maxTheoreticalValue = theoreticalYValues[ArrayMaximum(theoreticalYValues)];

   double scaleFactor = maxFrequency / maxTheoreticalValue;
   for (int i = 0; i < histogramCells; i++)
      histogramFrequencies[i] /= scaleFactor;

   computeAdvancedStatistics();
   dataLoadedSuccessfully = true;
   Print("SUCCESS: Loaded Uniform distribution data");
   return true;
  }

В этой части определим функцию "loadWeibullData" для распределения Вейбулла: зададим начальное значение случайной переменной, изменим размер выборок, сгенерируем с помощью MathRandomWeibull, используя "weibullShape" и "weibullScale". Вычислим непрерывную гистограмму с помощью "computeHistogramContinuous", подготовим теоретические данные с помощью пошаговых функций MathSequence и MathProbabilityDensityWeibull, масштабируем частоты, вычислим статистику, отметим успех, выведем сообщение о результате и вернем значение true.

Для нецентрального критерия хи-квадрат в функции "loadNoncentralChiSquareData" мы сгенерируем данные с помощью MathRandomNoncentralChiSquare на основе "noncentralChiSquareDegreesOfFreedom" и "noncentralChiSquareNoncentrality", используем непрерывную гистограмму, теоретические данные с помощью MathProbabilityDensityNoncentralChiSquare и следуем стандартному паттерну масштабирования, статистики и успеха. Функция "loadGammaData" обрабатывает гамма-распределение: сгенерируем с помощью MathRandomGamma с параметрами "gammaShape" и "gammaRate", непрерывную гистограмму, плотности из "MathProbabilityDensityGamma", масштабирование и т. д. Функция "loadChiSquareData" для критерия хи-квадрат: MathRandomChiSquare с "chiSquareDegreesOfFreedom", непрерывная гистограмма, "MathProbabilityDensityChiSquare" для теории.

Для распределения Коши в функции "loadCauchyData" сгенерируем с помощью MathRandomCauchy, используя "cauchyLocation" и "cauchyScale", принудительно задаём конечный диапазон отображения от -20 до 20 из-за "тяжелых хвостов". Используем "computeHistogramContinuousForced", теоретическую плотность вычисляем с помощью "MathProbabilityDensityCauchy". "loadFData" для F: "MathRandomF" с "fDegreesOfFreedom1" и "fDegreesOfFreedom2", непрерывная гистограмма, "MathProbabilityDensityF". "loadNegativeBinomialData" для отрицательного биномиального распределения: MathRandomNegativeBinomial с "negativeBinomialSuccesses" и "negativeBinomialProbability", дискретная гистограмма до ceil max, MathProbabilityDensityNegativeBinomial. "loadExponentialData" для экспоненциального распределения: MathRandomExponential с "exponentialMean", непрерывная гистограмма, "MathProbabilityDensityExponential". "loadBetaData" для бета-распределения: MathRandomBeta с "betaShape1" and "betaShape2", непрерывная, MathProbabilityDensityBeta.

"loadNoncentralBetaData" для нецентральной бета-версии: "MathRandomNoncentralBeta" с формами и нецентральностью, непрерывная, "MathProbabilityDensityNoncentralBeta". "loadTData" для T: "MathRandomT" с "tDegreesOfFreedom", непрерывная, "MathProbabilityDensityT". "loadNoncentralTData" для нецентральной T: "MathRandomNoncentralT" с числом степеней свободы (DF) и нецентральностью, непрерывная, "MathProbabilityDensityNoncentralT". "loadUniformData" для равномерного распределения: MathRandomUniform с "uniformLowerBound" и "uniformUpperBound", непрерывная, функция MathProbabilityDensityUniform

Каждая из них адаптируется к характеру распределения, используя непрерывные гистограммы для вещественных данных и дискретные гистограммы для подсчетов, с принудительными диапазонами там, где это необходимо, подобно распределению Коши, для обработки выбросов, обеспечивая согласованную подготовку для поддержки нескольких типов. После определения всех загрузчиков, специфичных для каждого типа, заменим единый вызов биномиальной загрузки центральной функцией диспетчеризации, которая направляет запрос к корректному загрузчику на основе активного типа.

Диспетчеризация загрузки данных по типу распределения

Вместо прямого вызова конкретной функции загрузки, эта единая точка входа использует оператор switch для параметра "currentDistributionType" для делегирования корректному загрузчику. При этом сохраняется остальная часть рендеринга и кода обработки событий независимой от того, какое распределение в данный момент активно.

//+------------------------------------------------------------------+
//| Load distribution data based on type                             |
//+------------------------------------------------------------------+
bool loadDistributionData()
  {
   //--- Dispatch to the correct loader based on the active distribution type
   switch(currentDistributionType)
     {
      case BINOMIAL_DISTRIBUTION:              return loadBinomialData();
      case HYPERGEOMETRIC_DISTRIBUTION:        return loadHypergeometricData();
      case POISSON_DISTRIBUTION:               return loadPoissonData();
      case NORMAL_DISTRIBUTION:                return loadNormalData();
      case WEIBULL_DISTRIBUTION:               return loadWeibullData();
      case NONCENTRAL_CHISQUARE_DISTRIBUTION:  return loadNoncentralChiSquareData();
      case GAMMA_DISTRIBUTION:                 return loadGammaData();
      case CHISQUARE_DISTRIBUTION:             return loadChiSquareData();
      case CAUCHY_DISTRIBUTION:                return loadCauchyData();
      case F_DISTRIBUTION:                     return loadFData();
      case NEGATIVEBINOMIAL_DISTRIBUTION:      return loadNegativeBinomialData();
      case EXPONENTIAL_DISTRIBUTION:           return loadExponentialData();
      case BETA_DISTRIBUTION:                  return loadBetaData();
      case NONCENTRALBETA_DISTRIBUTION:        return loadNoncentralBetaData();
      case T_DISTRIBUTION:                     return loadTData();
      case NONCENTRALT_DISTRIBUTION:           return loadNoncentralTData();
      case UNIFORM_DISTRIBUTION:               return loadUniformData();
      default:                                 return false;
     }
  }

Чтобы централизовать подготовку данных путем выбора соответствующего метода загрузки на основе текущего типа распределения, определим функцию "loadDistributionData". Мы используем оператор switch для функции "currentDistributionType" для вызова соответствующей функции, специфичной для данного типа, например, "loadBinomialData" для биномиального распределения или "loadUniformData" для равномерного распределения, что позволяет модульно обрабатывать различные модели. Если тип не распознан, мы вернем значение false, указывая на ошибку, обеспечим надежную обработку ошибок и плавное переключение без избыточного кода. После определения функции диспетчеризации определим две утилиты для построения непрерывных гистограмм, на которые опираются функции загрузки.

Вычисление непрерывных гистограмм с принудительным диапазоном и без него

Для вычисления непрерывных гистограмм предусмотрены два варианта: стандартный вариант вычисляет диапазон данных из крайних значений выборки и группирует все точки в интервалы. В то время как вариант с принудительным диапазоном принимает заданный вызывающей стороной диапазон и молча отбрасывает выборки, выходящие за пределы диапазона. Это необходимо для распределений с тяжелыми хвостами, таких как распределение Коши, где экстремальные выбросы в противном случае объединили бы все интервалы в один пик.

//+------------------------------------------------------------------+
//| Compute continuous histogram                                     |
//+------------------------------------------------------------------+
bool computeHistogramContinuous(const double &data[], double &intervals[], double &frequency[],
                                double &maxv, double &minv, const int cells)
  {
   //--- Validate cell count and data size before proceeding
   if (cells <= 1) return false;
   int size = ArraySize(data);
   if (size < 1) return false;

   //--- Derive range directly from the data extremes
   minv = data[ArrayMinimum(data)];
   maxv = data[ArrayMaximum(data)];
   double range = maxv - minv;
   double width = range / cells;
   //--- Abort if the bin width degenerates to zero
   if (width == 0) return false;

   //--- Initialize all bins to zero frequency
   ArrayResize(intervals, cells);
   ArrayResize(frequency, cells);
   for (int i = 0; i < cells; i++)
     {
      intervals[i] = minv + (i + 0.5) * width; // Place interval label at bin center
      frequency[i] = 0;
     }

   //--- Bin each data point, clamping to the valid index range
   for (int i = 0; i < size; i++)
     {
      int ind = (int)((data[i] - minv) / width);
      if (ind >= cells) ind = cells - 1;
      if (ind < 0)      ind = 0;
      frequency[ind]++;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| Compute continuous histogram with forced range                   |
//+------------------------------------------------------------------+
bool computeHistogramContinuousForced(const double &data[], double &intervals[], double &frequency[],
                                      double &maxv, double &minv, const int cells)
  {
   //--- Validate cell count and data size before proceeding
   if (cells <= 1) return false;
   int size = ArraySize(data);
   if (size < 1) return false;

   //--- Use the range already set by the caller (minv and maxv pre-assigned)
   double range = maxv - minv;
   double width = range / cells;
   //--- Abort if the bin width degenerates to zero
   if (width == 0) return false;

   //--- Initialize all bins to zero frequency
   ArrayResize(intervals, cells);
   ArrayResize(frequency, cells);
   for (int i = 0; i < cells; i++)
     {
      intervals[i] = minv + (i + 0.5) * width; // Place interval label at bin center
      frequency[i] = 0;
     }

   //--- Bin each data point, skipping values outside the forced range
   for (int i = 0; i < size; i++)
     {
      if (data[i] < minv || data[i] > maxv) continue;
      int ind = (int)((data[i] - minv) / width);
      if (ind >= cells) ind = cells - 1;
      if (ind < 0)      ind = 0;
      frequency[ind]++;
     }
   return true;
  }

Сначала определим функцию "computeHistogramContinuous" для построения гистограммы для распределений непрерывных данных. Мы проверяем наличие недопустимых значений в ячейках или пустых данных и в этом случае возвращаем false. Далее установим минимальное и максимальное значения из крайних точек массива с помощью ArrayMinimum и "ArrayMaximum", вычислим диапазон и ширину интервала, изменим размер интервалов и частот с помощью ArrayResize. Инициализируем центры как средние точки и пройдем циклом по данным до индексов интервалов, ограничим и увеличим частоты, вернем значение true в случае успеха. Аналогично, для случаев, требующих предопределенных диапазонов, таких как распределения с тяжелыми хвостами, реализуем "computeHistogramContinuousForced", пропустим минимальное/максимальное значение из данных и используем предоставленные значения, пропуская точки, выходящие за пределы диапазона, в цикле разбиения на интервалы. При этом сохраним ту же структуру для интервалов, частот и возвращаемых значений. После того, как утилиты для работы с гистограммами готовы, добавим в заголовок значок переключателя, по которому пользователь может переключаться в цикле между типами распределения.

Визуализация значка переключения распределений

Чтобы предоставить пользователю наглядный элемент управления циклическим распределением данных, в правой части заголовка отображается круглый значок со стрелками влево и вправо, соединенными горизонтальной линией. Значок темнеет при наведении курсора и светлеет в других состояниях. Так обеспечивается понятная обратная связь перед тем, как щелчок активирует переключение типа.

//+------------------------------------------------------------------+
//| Draw switch icon                                                 |
//+------------------------------------------------------------------+
void drawSwitchIcon()
  {
   //--- Compute the icon's top-left position in canvas-local coordinates
   int iconX = currentWidthPixels - SWITCH_ICON_SIZE - SWITCH_ICON_MARGIN;
   int iconY = (HEADER_BAR_HEIGHT - SWITCH_ICON_SIZE) / 2;

   //--- Choose a lighter background when hovered, darker when not
   color iconBgColor;
   if (isHoveringSwitchIcon)
      iconBgColor = DarkenColor(themeColor, 0.1);
   else
      iconBgColor = LightenColor(themeColor, 0.5);
   uint argbIconBg = ColorToARGB(iconBgColor, 255);

   //--- Fill the circular button background
   mainCanvas.FillCircle(iconX + SWITCH_ICON_SIZE / 2, iconY + SWITCH_ICON_SIZE / 2,
                         SWITCH_ICON_SIZE / 2, argbIconBg);

   //--- Draw the circular border in the theme color
   uint argbBorder = ColorToARGB(themeColor, 255);
   mainCanvas.Circle(iconX + SWITCH_ICON_SIZE / 2, iconY + SWITCH_ICON_SIZE / 2,
                     SWITCH_ICON_SIZE / 2, argbBorder);

   //--- Draw the double-arrow glyph in white
   uint argbArrow = ColorToARGB(clrWhite, 255);
   int  centerX   = iconX + SWITCH_ICON_SIZE / 2;
   int  centerY   = iconY + SWITCH_ICON_SIZE / 2;

   //--- Draw right-pointing arrow shaft and arrowhead pixels
   for (int i = -4; i <= 4; i++)
      mainCanvas.PixelSet(centerX + 6, centerY + i, argbArrow);
   mainCanvas.PixelSet(centerX + 5, centerY - 4, argbArrow);
   mainCanvas.PixelSet(centerX + 4, centerY - 5, argbArrow);
   mainCanvas.PixelSet(centerX + 5, centerY + 4, argbArrow);
   mainCanvas.PixelSet(centerX + 4, centerY + 5, argbArrow);

   //--- Draw left-pointing arrow shaft and arrowhead pixels
   for (int i = -4; i <= 4; i++)
      mainCanvas.PixelSet(centerX - 6, centerY + i, argbArrow);
   mainCanvas.PixelSet(centerX - 5, centerY - 4, argbArrow);
   mainCanvas.PixelSet(centerX - 4, centerY - 5, argbArrow);
   mainCanvas.PixelSet(centerX - 5, centerY + 4, argbArrow);
   mainCanvas.PixelSet(centerX - 4, centerY + 5, argbArrow);

   //--- Draw the horizontal connecting bar between both arrow shafts
   for (int i = -6; i <= 6; i++)
      mainCanvas.PixelSet(centerX + i, centerY, argbArrow);
  }

В этой части определим функцию "drawSwitchIcon" для создания интерактивной иконки-переключателя в панели заголовка для прохождения в цикле между распределениями. Значок располагается справа с использованием значений "currentWidthPixels", "SWITCH_ICON_SIZE" и "SWITCH_ICON_MARGIN". Цвет фона определяется условно: затемним "themeColor" с помощью "DarkenColor", если "isHoveringSwitchIcon" для обратной связи, или осветлим его в противном случае с помощью "LightenColor". Далее преобразуем в ARGB с помощью ColorToARGB. Для создания значка мы заполним круг в центре с помощью FillCircle, используя фоновое цветовое пространство ARGB, добавим рамку в виде круга с помощью Circle в теме ARGB и используем белые пиксели ARGB, заданные с помощью PixelSet, для формирования стрелок влево/вправо и средней линии, создавая символ двунаправленного переключателя. После того, как значок нарисован, обновим заголовок, чтобы отобразить динамический заголовок, отражающий активное распределение, и счетчик позиций, указывающий, где мы находимся в полном списке.

Генерируем динамический заголовок страницы, счетчик и обновляемый заголовок

Функция заголовка страницы возвращает отформатированную строку для каждого типа распределения, включая его ключевые параметры. Счетчик отображает текущий индекс из общего числа в заголовке справа от строки заголовка, при этом активное число выделяется красным; а функция отрисовки заголовка вызывает обе функции для сборки полной панели заголовка для того распределения, которое отображается в данный момент.

//+------------------------------------------------------------------+
//| Get distribution title                                           |
//+------------------------------------------------------------------+
string getDistributionTitle()
  {
   //--- Return a parameter-annotated title string for the active distribution
   switch(currentDistributionType)
     {
      case BINOMIAL_DISTRIBUTION:
         return StringFormat("Binomial Distribution (n=%d, p=%.2f)",
                             numTrials, successProbability);
      case HYPERGEOMETRIC_DISTRIBUTION:
         return StringFormat("Hypergeometric Distribution (m=%.0f, k=%.0f, n=%.0f)",
                             hypergeometricTotalObjects, hypergeometricSuccessObjects, hypergeometricDraws);
      case POISSON_DISTRIBUTION:
         return StringFormat("Poisson Distribution (λ=%.2f)", poissonLambda);
      case NORMAL_DISTRIBUTION:
         return StringFormat("Normal Distribution (μ=%.2f, σ=%.2f)",
                             normalMean, normalStandardDeviation);
      case WEIBULL_DISTRIBUTION:
         return StringFormat("Weibull Distribution (a=%.2f, b=%.2f)",
                             weibullShape, weibullScale);
      case NONCENTRAL_CHISQUARE_DISTRIBUTION:
         return StringFormat("Noncentral χ² (ν=%.1f, σ=%.1f)",
                             noncentralChiSquareDegreesOfFreedom, noncentralChiSquareNoncentrality);
      case GAMMA_DISTRIBUTION:
         return StringFormat("Gamma Distribution (α=%.2f, β=%.2f)",
                             gammaShape, gammaRate);
      case CHISQUARE_DISTRIBUTION:
         return StringFormat("Chi-Square Distribution (ν=%.1f)", chiSquareDegreesOfFreedom);
      case CAUCHY_DISTRIBUTION:
         return StringFormat("Cauchy Distribution (a=%.2f, b=%.2f)",
                             cauchyLocation, cauchyScale);
      case F_DISTRIBUTION:
         return StringFormat("F Distribution (nu1=%.1f, nu2=%.1f)",
                             fDegreesOfFreedom1, fDegreesOfFreedom2);
      case NEGATIVEBINOMIAL_DISTRIBUTION:
         return StringFormat("Negative Binomial Distribution (r=%.1f, p=%.2f)",
                             negativeBinomialSuccesses, negativeBinomialProbability);
      case EXPONENTIAL_DISTRIBUTION:
         return StringFormat("Exponential Distribution (mu=%.2f)", exponentialMean);
      case BETA_DISTRIBUTION:
         return StringFormat("Beta Distribution (alpha=%.2f, beta=%.2f)",
                             betaShape1, betaShape2);
      case NONCENTRALBETA_DISTRIBUTION:
         return StringFormat("Noncentral Beta Distribution (a=%.2f, b=%.2f, λ=%.2f)",
                             noncentralBetaShape1, noncentralBetaShape2, noncentralBetaNoncentrality);
      case T_DISTRIBUTION:
         return StringFormat("T Distribution (nu=%.1f)", tDegreesOfFreedom);
      case NONCENTRALT_DISTRIBUTION:
         return StringFormat("Noncentral T Distribution (nu=%.1f, δ=%.2f)",
                             noncentralTDegreesOfFreedom, noncentralTNoncentrality);
      case UNIFORM_DISTRIBUTION:
         return StringFormat("Uniform Distribution (a=%.2f, b=%.2f)",
                             uniformLowerBound, uniformUpperBound);
      default:
         return "Unknown Distribution";
     }
  }

//+------------------------------------------------------------------+
//| Draw header bar                                                  |
//+------------------------------------------------------------------+
void drawHeaderBar()
  {
   //--- Choose a color that reflects the current interaction state
   color headerColor;
   if (isDraggingCanvas)
      headerColor = DarkenColor(themeColor, 0.1);
   else if (isHoveringHeader)
      headerColor = LightenColor(themeColor, 0.4);
   else
      headerColor = LightenColor(themeColor, 0.7);
   uint argbHeader = ColorToARGB(headerColor, 255);

   //--- Fill the header rectangle with the state-dependent color
   mainCanvas.FillRectangle(0, 0, currentWidthPixels - 1, HEADER_BAR_HEIGHT, argbHeader);

   //--- Overlay the border lines on top of the header fill if enabled
   if (showBorderFrame)
     {
      uint argbBorder = ColorToARGB(themeColor, 255);
      mainCanvas.Rectangle(0, 0, currentWidthPixels - 1, HEADER_BAR_HEIGHT, argbBorder);
      mainCanvas.Rectangle(1, 1, currentWidthPixels - 2, HEADER_BAR_HEIGHT - 1, argbBorder);
     }

   //--- Draw the distribution title centered in the header bar
   mainCanvas.FontSet("Arial Bold", titleFontSize);
   uint   argbText   = ColorToARGB(titleTextColor, 255);
   string titleText  = getDistributionTitle();
   mainCanvas.TextOut(currentWidthPixels / 2, (HEADER_BAR_HEIGHT - titleFontSize) / 2,
                      titleText, argbText, TA_CENTER);

   //--- Draw the distribution counter (e.g. "3/17") to the left of the switch icon
   string countStr      = StringFormat("%d/%d", (int)currentDistributionType + 1, TOTAL_DISTRIBUTIONS);
   string currentNumStr = IntegerToString((int)currentDistributionType + 1); // Active index in red
   string remainingStr  = "/" + IntegerToString(TOTAL_DISTRIBUTIONS);        // Total count in normal color
   int    countX        = currentWidthPixels - SWITCH_ICON_SIZE - SWITCH_ICON_MARGIN - 15;
   int    countY        = (HEADER_BAR_HEIGHT - titleFontSize) / 2;
   int    fullWidth     = mainCanvas.TextWidth(countStr);
   int    leftPos       = countX - fullWidth;

   //--- Render the active index in red and the remainder in the default text color
   uint redArgb = ColorToARGB(clrRed, 255);
   mainCanvas.TextOut(leftPos, countY, currentNumStr, redArgb, TA_LEFT);
   int slashPos = leftPos + mainCanvas.TextWidth(currentNumStr);
   mainCanvas.TextOut(slashPos, countY, remainingStr, argbText, TA_LEFT);
  }

Определим функцию "getDistributionTitle" для создания настраиваемой строки заголовка страницы для заголовка на основе текущего типа распределения. Используем оператор switch для функции "currentDistributionType" для форматирования и возврата строки, включающей название распределения и его ключевые параметры, такие как "Binomial Distribution" для биномиального распределения или "Normal Distribution" для нормального распределения, обрабатывая все типы с соответствующим форматированием и значениями по умолчанию для неизвестного типа. Далее обновим функцию "drawHeaderBar" для отображения динамического заголовка страницы и форматирования количества. Визуализируем заголовок страницы из функции "getDistributionTitle" и центрируем его с помощью метода TextOut.  Для отображения прогресса отформатируем строку количества, например, "1/17", отобразим текущее число красным цветом ARGB, а остальное — стандартным цветом текста, располагая его слева от значка переключателя для визуальной обратной связи об активном распределении среди итоговых значений. После компиляции получаем следующий результат.

DYNAMIC TITLE, COUNTER AND SWITCH ICON

После обновления заголовка мы определим функции обнаружения наведения курсора, логики переключения, поиска имени и динамической установки метки осей, которые завершают систему циклического перебора распределений.

Обнаружение состояний наведения курсора, типов циклического отображения и адаптация меток осей

Эти четыре функции работают вместе: проверка наведения курсора определяет, находится ли курсор в пределах границ значка переключателя, функция цикла увеличивает индекс типа и вызывает соответствующий загрузчик при щелчке, поиск имени возвращает обычную строку для каждого типа, используемого в логировании, а функция меток оси возвращает соответствующую метку оси X, чтобы график точно показывал, представляет ли горизонтальная ось количество, действительные значения или неотрицательные величины.

//+------------------------------------------------------------------+
//| Check if mouse over switch icon                                  |
//+------------------------------------------------------------------+
bool isMouseOverSwitchIcon(int mouseX, int mouseY)
  {
   //--- Compute the icon's top-left corner in screen coordinates
   int iconX = currentPositionX + currentWidthPixels - SWITCH_ICON_SIZE - SWITCH_ICON_MARGIN;
   int iconY = currentPositionY + (HEADER_BAR_HEIGHT - SWITCH_ICON_SIZE) / 2;

   //--- Return true if the mouse falls inside the icon bounding box
   return (mouseX >= iconX && mouseX <= iconX + SWITCH_ICON_SIZE &&
           mouseY >= iconY && mouseY <= iconY + SWITCH_ICON_SIZE);
  }

//+------------------------------------------------------------------+
//| Switch to next distribution type                                 |
//+------------------------------------------------------------------+
void switchDistributionType()
  {
   //--- Advance to the next type, wrapping back to the first after the last
   int nextDist = (int)currentDistributionType + 1;
   if (nextDist > UNIFORM_DISTRIBUTION)
      nextDist = BINOMIAL_DISTRIBUTION;
   currentDistributionType = (DistributionType)nextDist;

   //--- Log the switch and reload data for the new distribution
   string distName = getDistributionName(currentDistributionType);
   Print("Switched to ", distName);

   if (loadDistributionData())
     {
      renderVisualization();
      ChartRedraw();
     }
  }

//+------------------------------------------------------------------+
//| Get name of distribution type                                    |
//+------------------------------------------------------------------+
string getDistributionName(DistributionType dist)
  {
   //--- Return the human-readable name for each supported distribution type
   switch(dist)
     {
      case BINOMIAL_DISTRIBUTION:             return "Binomial Distribution";
      case HYPERGEOMETRIC_DISTRIBUTION:       return "Hypergeometric Distribution";
      case POISSON_DISTRIBUTION:              return "Poisson Distribution";
      case NORMAL_DISTRIBUTION:               return "Normal Distribution";
      case WEIBULL_DISTRIBUTION:              return "Weibull Distribution";
      case NONCENTRAL_CHISQUARE_DISTRIBUTION: return "Noncentral Chi-Square Distribution";
      case GAMMA_DISTRIBUTION:                return "Gamma Distribution";
      case CHISQUARE_DISTRIBUTION:            return "Chi-Square Distribution";
      case CAUCHY_DISTRIBUTION:               return "Cauchy Distribution";
      case F_DISTRIBUTION:                    return "F Distribution";
      case NEGATIVEBINOMIAL_DISTRIBUTION:     return "Negative Binomial Distribution";
      case EXPONENTIAL_DISTRIBUTION:          return "Exponential Distribution";
      case BETA_DISTRIBUTION:                 return "Beta Distribution";
      case NONCENTRALBETA_DISTRIBUTION:       return "Noncentral Beta Distribution";
      case T_DISTRIBUTION:                    return "T Distribution";
      case NONCENTRALT_DISTRIBUTION:          return "Noncentral T Distribution";
      case UNIFORM_DISTRIBUTION:              return "Uniform Distribution";
      default:                                return "Unknown Distribution";
     }
  }

//+------------------------------------------------------------------+
//| Get X-axis label based on type                                   |
//+------------------------------------------------------------------+
string getXAxisLabel()
  {
   //--- Return an appropriate X-axis label describing the variable measured
   switch(currentDistributionType)
     {
      case BINOMIAL_DISTRIBUTION:
      case HYPERGEOMETRIC_DISTRIBUTION:
      case POISSON_DISTRIBUTION:
      case NEGATIVEBINOMIAL_DISTRIBUTION:
         return "Number of Successes (k)"; // Discrete count distributions
      case NORMAL_DISTRIBUTION:
      case CAUCHY_DISTRIBUTION:
      case T_DISTRIBUTION:
      case NONCENTRALT_DISTRIBUTION:
         return "Value (x)"; // Symmetric continuous distributions
      case WEIBULL_DISTRIBUTION:
      case GAMMA_DISTRIBUTION:
      case CHISQUARE_DISTRIBUTION:
      case NONCENTRAL_CHISQUARE_DISTRIBUTION:
      case F_DISTRIBUTION:
      case EXPONENTIAL_DISTRIBUTION:
      case BETA_DISTRIBUTION:
      case NONCENTRALBETA_DISTRIBUTION:
         return "Value (x ≥ 0)"; // Non-negative continuous distributions
      case UNIFORM_DISTRIBUTION:
         return "Value (x)";
      default:
         return "Value";
     }
  }

В этой части мы определим функцию "isMouseOverSwitchIcon" для определения того, находится ли курсор мыши над областью значка переключателя в заголовке. Вычислим ограничивающую рамку значка, используя такие глобальные переменные, как "currentPositionX", "currentWidthPixels", "SWITCH_ICON_SIZE" и "SWITCH_ICON_MARGIN". Далее вернем значение true, если координаты мыши попадают в нее, обеспечивая обратную связь при наведении курсора для переключателя распределения. Далее мы реализуем функцию "switchDistributionType" для циклического переключения типов распределения при щелчке по значку. Увеличим "currentDistributionType" на единицу, сбросим значение на "BINOMIAL_DISTRIBUTION", если оно превышает "UNIFORM_DISTRIBUTION", получим новое имя с помощью "getDistributionName", выводим переключатель, и если "loadDistributionData" выполняется успешно, повторно визуализируем данные с помощью "renderVisualization" и ChartRedraw для немедленного обновления.

Для обеспечения удобочитаемых имен создадим функцию "getDistributionName", которая использует перечисление типов, возвращая строки, такие как "Binomial Distribution" или "Uniform Distribution", используемые для логирования и, возможно, для пользовательского интерфейса. Для адаптивной разметки определим функцию "getXAxisLabel", которая возвращает метки оси X, специфичные для каждого типа данных, с помощью переключателя: "Number of Successes (k)" для распределений, основанных на подсчете, "Value (x)" для симметричных или общих распределений или "Value (x >= 0)" для неотрицательных значений, что обеспечивает точность построения графика без жесткого кодирования. Это поможет нам создать динамический график. В функции "drawDistributionPlot" мы изменим "yAxisLabel" на фиксированное значение "Probability / Scaled Frequency", а "xAxisLabel" — на использование функции "getXAxisLabel" следующим образом.

//--- the previous was: "Number of Successes (k)";

string xAxisLabel = getXAxisLabel();

Это изменение делает метку оси X динамической на основе активного распределения — например, "Value (x ≥ 0)" для неотрицательных непрерывных типов — заменяя предыдущую жестко закодированную метку. Далее обновим панели, которые не имеют статических значений. Мы будем динамически обновлять статистику, вызывая следующую функцию при создании панели.

Отображение параметров, специфичных для типа данных, на панели статистики

Вместо того чтобы постоянно отображать только параметры биномиального распределения, эта функция включает активный тип распределения и отображает только соответствующие строки параметров — например, только лямбда для распределения Пуассона или форму и масштаб для распределения Вейбулла — увеличивая вертикальный текстовый курсор после каждой строки, чтобы последующие общие статистические данные всегда корректно располагались под параметрами.

//+------------------------------------------------------------------+
//| Draw distribution parameters                                     |
//+------------------------------------------------------------------+
void drawDistributionParameters(int panelX, int &textY, int lineSpacing, uint argbText)
  {
   //--- Render the parameter rows specific to each distribution type
   switch(currentDistributionType)
     {
      case BINOMIAL_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Trials (n): %d", numTrials), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Prob (p): %.2f", successProbability), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case HYPERGEOMETRIC_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Total (m): %.0f", hypergeometricTotalObjects), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Success (k): %.0f", hypergeometricSuccessObjects), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Draws (n): %.0f", hypergeometricDraws), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case POISSON_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Lambda: %.2f", poissonLambda), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case NORMAL_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Mean (μ): %.2f", normalMean), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("StdDev (σ): %.2f", normalStandardDeviation), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case WEIBULL_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Shape (a): %.2f", weibullShape), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Scale (b): %.2f", weibullScale), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case NONCENTRAL_CHISQUARE_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("DF (ν): %.1f", noncentralChiSquareDegreesOfFreedom), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("NC (σ): %.1f", noncentralChiSquareNoncentrality), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case GAMMA_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Alpha (α): %.2f", gammaShape), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Beta (β): %.2f", gammaRate), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case CHISQUARE_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("DF (ν): %.1f", chiSquareDegreesOfFreedom), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case CAUCHY_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Location (a): %.2f", cauchyLocation), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Scale (b): %.2f", cauchyScale), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case F_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("DF1 (nu1): %.1f", fDegreesOfFreedom1), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("DF2 (nu2): %.1f", fDegreesOfFreedom2), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case NEGATIVEBINOMIAL_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Successes (r): %.1f", negativeBinomialSuccesses), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Prob (p): %.2f", negativeBinomialProbability), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case EXPONENTIAL_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Mean (mu): %.2f", exponentialMean), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case BETA_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Alpha: %.2f", betaShape1), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Beta: %.2f", betaShape2), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case NONCENTRALBETA_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Shape1 (a): %.2f", noncentralBetaShape1), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Shape2 (b): %.2f", noncentralBetaShape2), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Lambda: %.2f", noncentralBetaNoncentrality), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case T_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("DF (nu): %.1f", tDegreesOfFreedom), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case NONCENTRALT_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("DF (nu): %.1f", noncentralTDegreesOfFreedom), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Delta: %.2f", noncentralTNoncentrality), argbText, TA_LEFT);
         textY += lineSpacing;
         break;

      case UNIFORM_DISTRIBUTION:
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Lower (a): %.2f", uniformLowerBound), argbText, TA_LEFT);
         textY += lineSpacing;
         mainCanvas.TextOut(panelX + 8, textY,
                            StringFormat("Upper (b): %.2f", uniformUpperBound), argbText, TA_LEFT);
         textY += lineSpacing;
         break;
     }
  }

Определим функцию "drawDistributionParameters" для отображения параметров, специфичных для конкретного типа данных, на панели статистики, адаптируем отображение к текущему распределению и выведем соответствующую информацию вверху. Мы используем оператор switch для "currentDistributionType" для обработки каждого случая: для биномиального распределения рисуем "Trials (n)" и "Prob (p)" с помощью TextOut, используя StringFormat для значений, увеличим "textY" на "lineSpacing"; для гипергеометрического распределения добавим линии для общего числа объектов, числа объектов с признаком и числа извлечений; для распределения Пуассона просто используем лямбда-функцию. Аналогично и для других распределений, таких как нормальное распределение (среднее значение и стандартное отклонение), распределение Вейбулла (форма и масштаб), нецентральное распределение хи-квадрат (число степеней свободы (DF) и параметр нецентральности (NC)), гамма-распределение (альфа и бета), распределение хи-квадрат (DF), распределение Коши (расположение и масштаб), F-распределение (DF1 и DF2), отрицательное биномиальное распределение (успехи и вероятность), экспоненциальное распределение (среднее значение), бета-распределение (альфа и бета), нецентральное бета-распределение (форма1, форма2, лямбда), T-распределение (DF), нецентральное T-распределение (DF и дельта), равномерное распределение (нижняя и верхняя границы), с пропуском неизвестных типов.

Этот динамический подход обеспечит отображение на панели только соответствующих параметров, повышая ясность и избегая излишней загроможденности, а последующие общие статистические данные будут располагаться ниже путем обновления ссылки "textY". После завершения работы над панелью параметров обновим обработчик событий графика, чтобы отслеживать состояние при наведении курсора на значок переключателя и направлять щелчки по значку в функцию циклического переключения.

Обновление обработчика событий графика для наведения курсора и щелчка по значку

Два целевых дополнения расширяют существующую обработку событий мыши: предыдущее состояние при наведении курсора на значок сохраняется перед его обновлением с помощью функции "isMouseOverSwitchIcon". Результат включается в условие перерисовки, а проверка в верхней части блока обработки нажатия кнопки мыши вызывает функцию "switchDistributionType", если значок был нажат, прежде чем перейти к существующей логике перетаскивания и изменения размера.

//+------------------------------------------------------------------+
//| Handle chart events                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if (id == CHARTEVENT_MOUSE_MOVE)
     {
      //--- Store previous icon hover
      bool previousIconHoverState = isHoveringSwitchIcon;
      //--- Update icon hover
      isHoveringSwitchIcon = isMouseOverSwitchIcon(mouseX, mouseY);
      //--- Add to needRedraw check
      bool needRedraw = (... || previousIconHoverState != isHoveringSwitchIcon);

      //--- Handle mouse button press (transition from 0 to 1)
      if (mouseState == 1 && previousMouseButtonState == 0)
        {
         if (isHoveringSwitchIcon)
           {
            //--- Cycle to the next distribution on icon click
            switchDistributionType();
           }
         else if ... // Existing drag/resize logic
  }

В обработчике OnChartEvent добавим хранилище для предыдущего состояния при наведении курсора на значок переключателя с помощью параметра "previousIconHoverState", установленного в значение "isHoveringSwitchIcon". Обновим текущее состояние с помощью параметра "isMouseOverSwitchIcon" с координатами мыши и включаем это в проверку "needRedraw", чтобы обновлять данные при изменении состояния при наведении курсора. В блоке обработки события mouse-down (состояние 1 из 0) мы в первую очередь проверяем, является ли "isHoveringSwitchIcon", вызываем "switchDistributionType" для переключения типов в цикле, если значение равно true, прежде чем переходить к существующей логике перетаскивания или изменения размера, что позволяет переключаться между значками с перерисовкой. На этом реализация завершена. Осталось провести тестирование системы. Это рассматривается в следующем разделе.


Тестирование на истории

Мы провели тестирование, а ниже показан итоговый результат визуализации в формате Graphics Interchange Format (GIF).

BACKTEST GIF

В ходе тестирования все семнадцать типов распределения загружались и отображались корректно при переключении, дискретные распределения отображали гистограммы с целочисленным выравниванием, а непрерывные — плавные кривые, соответствующие плотности распределения, а принудительный диапазон Коши успешно подавлял искажения, вызванные выбросами, которые в противном случае привели бы к схлопыванию интервалов гистограммы.


Заключение

В заключении отметим, что мы расширили инструмент построения графиков на MQL5 для поддержки семнадцати статистических распределений с циклическим перебором распределений с помощью значка переключения в заголовке. Реализация включала в себя функции загрузки данных, специфичные для каждого типа, как для дискретных, так и для непрерывных моделей, два варианта вычисления гистограмм, включая версию с принудительным диапазоном для распределений с тяжелыми хвостами, центральную функцию диспетчеризации для маршрутизации, а также динамические заголовки, метки осей и параметры панели статистики, которые автоматически адаптируются к активному типу распределения. После прочтения статьи вы сможете:

  • Переключаться между семнадцатью типами распределения на одном графике и визуально сравнивать, насколько хорошо каждая теоретическая кривая плотности соответствует гистограмме выборки, мгновенно определяя наиболее подходящую модель для ваших данных
  • Использовать наложение нормального распределения, чтобы отметить бары с доходностью, превышающей два стандартных отклонения, как потенциальные кандидаты на возврат к среднему значению, а экспоненциальное распределение — для оценки времени между значительными движениями цены для калибровки стоп-лосса
  • Определять, когда необходим принудительный диапазон гистограммы для распределений с тяжелыми хвостами, таких как распределение Коши, и применять тот же принцип при расширении инструмента дополнительными типами распределения

В следующей части мы рассмотрим частотный анализ одной функции распределения для расширения возможностей статистического анализа. Следите за обновлениями!

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21337

Возможности Мастера MQL5, которые вам нужно знать (Часть 76): Использование паттернов Awesome Oscillator и каналов конвертов с обучением с учителем Возможности Мастера MQL5, которые вам нужно знать (Часть 76): Использование паттернов Awesome Oscillator и каналов конвертов с обучением с учителем
В продолжение нашей предыдущей статьи о паре индикаторов Awesome Oscillator и каналов конвертов (Envelope Channels), мы рассмотрим, как эту пару можно улучшить с помощью обучения с учителем. Awesome Oscillator и канал конвертов — это взаимодополняющее сочетание инструментов, позволяющих выявлять тренды и создавать уровни поддержки/сопротивления. Наш подход к обучению с учителем представляет собой сверточную нейронную сеть (CNN), которая использует ядро скалярного произведения (Dot Product Kernel) с механизмом внимания во времени (Cross-Time-Attention) для определения размеров своих ядер и каналов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для сборки советника.
Адаптивный индикатор Malaysian Engulfing (Часть 1): Обнаружение паттернов и валидация ретеста Адаптивный индикатор Malaysian Engulfing (Часть 1): Обнаружение паттернов и валидация ретеста
Реализуем концепцию Malaysian Engulfing в MQL5 с помощью двух согласованных индикаторов. Один применяет строгие правила поглощения по телам свечей для точного обнаружения паттерна; другой использует модель, управляемую состояниями, чтобы отслеживать дальнейшее развитие — откаты и ретесты в заданном временном окне — прямо на графике. В результате получается повторяемый рабочий процесс на основе правил, который заменяет визуальные догадки программируемой логикой.
Создаем объемные 3D бары на MQL5 Создаем объемные 3D бары на MQL5
Переносим 3D-бары из Python в нативный MQL5: вместо plotly и моста к терминалу — сцена на CCanvas3D и DirectX 11 прямо на графике. Цена, время и тиковый объём раскладываются по трём осям, геометрия собирается вручную из вершин и треугольников, а орбитальная камера на событиях мыши даёт интерактивный осмотр без внешних зависимостей.
Архитектура машинного обучения для MetaTrader 5 (Часть 15): Как калибровать уровни тейк-профита и стоп-лосса по синтетическим данным Архитектура машинного обучения для MetaTrader 5 (Часть 15): Как калибровать уровни тейк-профита и стоп-лосса по синтетическим данным
В статье применяется оптимальное торговое правило из главы 13 AFML для задания уровней тейк-профита и стоп-лосса без внутривыборочной калибровки. Мы моделируем P&L после входа дискретным процессом Орнштейна–Уленбека, выполняем поиск по 100 000 траекториям и используем Python, multiprocessing и параллельное ядро Numba с декоратором @njit (в 242 раза быстрее). Результат — оптимальная пара (PT, SL) для трех спецификаций прогноза с ограничением по дневному лимиту убытка проп-фирмы.