Торговые инструменты на MQL5 (Часть 11): Панель корреляционной матрицы (Пирсон, Спирман, Кенделл) с тепловой картой и стандартным режимом
Введение
В своей предыдущей статье (Часть 10) мы разработали систему отслеживания стратегий на MetaQuotes Language 5 (MQL5) с визуальными уровнями и показателями успеха, которая обнаруживала сигналы пересечения скользящих средних, отслеживала сделки с несколькими уровнями тейк-профита и стоп-лоссов, а также визуализировала результаты на графике. В части 11 мы разработаем панель мониторинга корреляционной матрицы с использованием методов Пирсона, Спирмена и Кенделла, включающую тепловую карту и стандартные режимы. Эта панель вычисляет взаимосвязи активов, используя выбранный метод, за настраиваемый таймфрейм и в заданных барах. Она поддерживает стандартный режим с цветовыми порогами и звездочками значимости p-значений, а также режим тепловой карты с градиентными визуальными элементами. Дополнительно она включает в себя интерактивный пользовательский интерфейс с селекторами таймфреймов, переключателями режимов и динамической легендой. В статье рассмотрим следующие темы:
- Понимание структуры панели корреляционной матрицы
- Реализация средствами MQL5
- Тестирование на истории
- Заключение
В итоге у вас будет функциональная панель мониторинга корреляционной матрицы в MQL5 для анализа взаимозависимостей активов, готовая к настройке. Перейдём к реализации!
Понимание структуры панели корреляционной матрицы
Структура панели корреляционной матрицы анализирует взаимосвязи между финансовыми активами путем вычисления коэффициентов корреляции, помогая нам выявлять взаимозависимости, влияющие на диверсификацию портфеля, хеджирование или стратегии с использованием нескольких активов. Она обрабатывает изменения цен по выбранным пользователем символам за определенный период и таймфрейм, применяя один из трех статистических методов — коэффициент Пирсона для линейных зависимостей, коэффициент Спирмена для основанных на рангах монотонных ассоциаций или коэффициент Кенделла для согласованности рангов — для количественной оценки того, как активы движутся вместе или в противоположных направлениях. Значимость оценивается с помощью p-значений, указывающих на надежность, а визуальные подсказки, такие как цветовые пороги или градиенты, выделяют сильные положительные, сильные отрицательные, слабые или нейтральные корреляции, что позволяет быстро распознавать паттерны без ручных вычислений.
В стандартном режиме панель использует предопределенные пороговые значения для классификации корреляций, применяя различимые цвета для сильных положительных или отрицательных результатов и добавляя звездочки для уровней значимости p-значения, чтобы обозначить статистическую достоверность. В режиме тепловой карты используется непрерывный цветовой градиент для более детальной визуализации интенсивности корреляции от отрицательной к положительной, что делает незначительные изменения более заметными. Интерфейс включает интерактивные элементы, такие как селекторы таймфреймов для переключения периодов анализа, переключатели режимов или тем, а также динамическую легенду для интерпретации цветов и значений. Всё это расположено в виде сетки с символами на осях и ячейками, отображающими попарные корреляции.
Наш план состоит в том, чтобы проанализировать список символов, вычислить корреляции и p-значения, используя выбранный метод для ценовых дельт, отобразить пользовательский интерфейс с панелями для заголовков, таймфреймов, символов, ячеек и легенд, а также динамически обновлять визуальные элементы на основе режимов и пороговых значений. Мы добавим обработку событий для таких взаимодействий, как переключение режимов или изменение таймфреймов, обеспечивая обновление панели при появлении новых данных для получения аналитической информации в режиме реального времени. Вкратце, вот наглядное представление наших целей.

Реализация средствами MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Советники» (Experts), щелкните кнопкой мыши на вкладке "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нужно будет объявить некоторые входные параметры и глобальные переменные, которые будем использовать во всей программе.
//+------------------------------------------------------------------+ //| Correlation Matrix Dashboard PART1.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" #include <Math\Stat\Math.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input string SymbolsList = "EURUSDm,GBPUSDm,USDJPYm,AUDUSDm,BTCUSDm,NZDUSDm,US500m,XAUUSDm"; // Comma-separated symbols (up to 30) input ENUM_TIMEFRAMES CorrelationTimeframe = PERIOD_CURRENT; // Timeframe for correlation calculation input int CorrelationBars = 100; // Number of bars for correlation calculation (min 20) input double StrongPositiveThresholdPct = 70.0; // Strong positive threshold in % (e.g., 70.0 for >=0.70) input double StrongNegativeThresholdPct = -70.0; // Strong negative threshold in % (e.g., -70.0 for <=-0.70) input double PValueThreshold1 = 0.01; // P-value for *** significance input double PValueThreshold2 = 0.05; // P-value for ** significance input double PValueThreshold3 = 0.10; // P-value for * significance input color ColorStrongPositiveBg = clrLimeGreen; // Background for strong positive input color ColorStrongNegativeBg = clrOrangeRed; // Background for strong negative input color ColorNeutralBg = C'70,70,70'; // Background for neutral/mild/zero/diagonal cells input color ColorDiagonalBg = C'40,40,40'; // Background for diagonal cells input color ColorTextStrong = clrWhite; // Text color for strong correlations input color ColorTextPositive = clrDeepSkyBlue; // Text color for mild positive input color ColorTextNegative = clrRed; // Text color for mild negative input color ColorTextZero = clrWhite; // Text color for zero or diagonal //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ enum DisplayMode { MODE_STANDARD, // Standard Thresholds MODE_HEATMAP // Heatmap Gradient }; input DisplayMode DashboardMode = MODE_STANDARD; // Dashboard Display Mode enum CorrelationMethod { PEARSON, // Pearson correlation SPEARMAN, // Spearman rank correlation KENDALL // Kendall tau correlation }; input CorrelationMethod CorrMethod = PEARSON; // Correlation calculation method //+------------------------------------------------------------------+ //| Defines | //+------------------------------------------------------------------+ #define MAIN_PANEL "PANEL_MAIN" // Define main panel rectangle identifier #define HEADER_PANEL "PANEL_HEADER" // Define header panel rectangle identifier #define LEGEND_PANEL "PANEL_LEGEND" // Define legend panel rectangle identifier #define HEADER_PANEL_ICON "PANEL_HEADER_ICON" // Define header icon label identifier #define HEADER_PANEL_TEXT "PANEL_HEADER_TEXT" // Define header title label identifier #define CLOSE_BUTTON "BUTTON_CLOSE" // Define close button identifier #define TOGGLE_BUTTON "BUTTON_TOGGLE" // Define toggle (minimize/maximize) button identifier #define HEATMAP_BUTTON "BUTTON_HEATMAP" // Define heatmap toggle button identifier #define PVAL_BUTTON "BUTTON_PVAL" // Define P-value toggle button identifier #define SORT_BUTTON "BUTTON_SORT" // Define sort button identifier #define THEME_BUTTON "BUTTON_THEME" // Define theme toggle button identifier #define TF_CELL_RECT "TF_CELL_RECT_" // Define timeframe cell rectangle prefix #define TF_CELL_TEXT "TF_CELL_TEXT_" // Define timeframe cell text prefix #define SYMBOL_ROW_RECTANGLE "SYMBOL_ROW_" // Define row symbol rectangle prefix #define SYMBOL_ROW_TEXT "SYMBOL_ROW_TEXT_" // Define row symbol text label prefix #define SYMBOL_COL_RECTANGLE "SYMBOL_COL_" // Define column symbol rectangle prefix #define SYMBOL_COL_TEXT "SYMBOL_COL_TEXT_" // Define column symbol text label prefix #define CELL_RECTANGLE "CELL_" // Define correlation cell rectangle prefix #define CELL_TEXT "CELL_TEXT_" // Define correlation cell text label prefix #define LEGEND_CELL_RECTANGLE "LEGEND_CELL_" // Define legend cell rectangle prefix #define LEGEND_CELL_TEXT "LEGEND_CELL_TEXT_" // Define legend cell text prefix #define WIDTH_SYMBOL 80 // Define width of symbol rectangles #define WIDTH_CELL 80 // Define width of correlation cells #define WIDTH_TF_CELL 45 // Define width of TF cells #define WIDTH_LEGEND_CELL 45 // Define width of legend cells #define HEIGHT_RECTANGLE 30 // Define height of all rectangles #define HEIGHT_HEADER 27 // Define height of header #define HEIGHT_TF_CELL 25 // Define height of TF cells #define HEIGHT_LEGEND 30 // Define height of legend cells #define HEIGHT_LEGEND_PANEL 34 // Define height of legend panel (with padding) #define LEGEND_SPACING 5 // Define spacing between legend cells #define NUM_LEGEND_ITEMS 15 // Define increased to cover max possible (e.g., 11 in heatmap) #define GAP_HEIGHT 8 // Define gap between sections #define GAP_MAIN_LEGEND 2 // Define vertical gap between main panel and legend #define COLOR_WHITE clrWhite // Define white color for text and backgrounds #define COLOR_BLACK clrBlack // Define black color for borders and text #define COLOR_LIGHT_GRAY C'230,230,230' // Define light gray for neutral cells #define COLOR_DARK_GRAY C'105,105,105' // Define dark gray for headers #define MAX_SYMBOLS 30 // Define maximum symbols supported #define NUM_TF 8 // Define number of timeframes //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ int panel_x = 20, panel_y = 40; //--- Initialize panel position coordinates string symbols_array[MAX_SYMBOLS]; //--- Declare array to store symbols int num_symbols = 0; //--- Initialize number of valid symbols double correlation_matrix[MAX_SYMBOLS][MAX_SYMBOLS]; //--- Declare matrix to store correlations double pvalue_matrix[MAX_SYMBOLS][MAX_SYMBOLS]; //--- Declare matrix to store p-values DisplayMode global_display_mode; //--- Declare runtime display mode ENUM_TIMEFRAMES global_correlation_tf; //--- Declare runtime timeframe int current_tf_index = -1; //--- Initialize index of current TF int num_tf_visible; //--- Declare dynamic number of visible TF cells int num_legend_visible; //--- Declare dynamic number of visible legend items double visible_corr_vals[]; //--- Declare array of visible correlation values for legend ENUM_TIMEFRAMES tf_list[NUM_TF] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1}; //--- Initialize timeframe list string tf_strings[NUM_TF] = {"M1", "M5", "M15", "M30", "H1", "H4", "D1", "W1"}; //--- Initialize timeframe strings // Enhanced gradient colors for heatmap color heatmap_colors[] = {clrRed, clrOrangeRed, clrOrange, clrYellow, clrLightGray, clrLime, clrLimeGreen, clrGreen}; //--- Initialize heatmap color gradient array
Мы начинаем реализацию с включения библиотеки Math с помощью команды "#include <Math\Stat\Math.mqh>", которая предоставляет статистические функции, необходимые для вычислений корреляции. Далее определяем входные параметры, позволяющие настраивать панель. Сюда входят: "SymbolsList" в виде строки, разделенной запятыми, содержащей до тридцати символов; "CorrelationTimeframe" с использованием перечисления ENUM_TIMEFRAMES для периода расчета; "CorrelationBars" в виде целого числа, указывающего количество баров (минимум двадцать); пороговые значения, такие как "StrongPositiveThresholdPct" и "StrongNegativeThresholdPct" в процентах для классификации корреляций; пороговые значения p-значения, такие как "PValueThreshold1" для уровней значимости; и цветовые входные параметры, такие как "ColorStrongPositiveBg", установленный на лаймовый цвет для ярко выраженного положительного фона, а также другие цвета для отрицательных, нейтральных, диагональных и текстовых значений.
Затем создаём перечисления для параметров конфигурации. Перечисление "DisplayMode" предлагает значения "MODE_STANDARD" для отображения на основе пороговых значений и "MODE_HEATMAP" для градиентной визуализации, при этом значение "DashboardMode" как вводный параметр по умолчанию равно "standard". Аналогично, перечисление "CorrelationMethod" предоставляет значения "PEARSON" для линейных корреляций, "SPEARMAN" для ранговых корреляций и "KENDALL" для тау-коэффициента, при этом в качестве вводного параметра для "CorrMethod" по умолчанию равно значение Pearson. После этого мы используем defines для установки констант для элементов пользовательского интерфейса и структуры, используя директиву #define. К ним относятся идентификаторы, такие как "MAIN_PANEL" для главного прямоугольника, "HEADER_PANEL" для заголовка и префиксы, такие как "TF_CELL_RECT" для ячеек таймфреймов, а также размеры, такие как "WIDTH_SYMBOL" значением восемьдесят пикселей, "HEIGHT_RECTANGLE" значением тридцать, промежутки, такие как "GAP_HEIGHT" значением восемь, а директивы цвета, такие как "COLOR_WHITE" установлены на белый.
Мы объявляем глобальные переменные для управления состоянием и данными. К ним относятся "panel_x" и "panel_y", инициализированные как двадцать и сорок для позиции, "symbols_array" как строковый массив с максимальным размером символов, "num_symbols", начинающийся с нуля, двумерные двойные массивы "correlation_matrix" и "pvalue_matrix" для хранения значений, "global_display_mode" из перечисления отображений, "global_correlation_tf" из таймфреймов, "current_tf_index" с отрицательным значением и целые числа, такие как "num_tf_visible" и "num_legend_visible", плюс двойной массив "visible_corr_vals" для значений условных обозначений. Наконец, мы инициализируем массивы для таймфреймов и цветов: "tf_list" как массив "ENUM_TIMEFRAMES" со значениями от одной минуты до недели, "tf_strings" как соответствующие строковые метки и "heatmap_colors" как массив цветов для градиентов от красного до зеленого. Следующее, что мы сделаем, - это определим некоторые вспомогательные функции для проведения статистического анализа.
//+------------------------------------------------------------------+ //| Approximate Normal CDF | //+------------------------------------------------------------------+ bool NormalCDF(double mean, double stddev, double x, double &cdf) { if (stddev <= 0.0) return false; //--- Check for invalid standard deviation double z = (x - mean) / stddev; //--- Compute z-score if (z < -10) { //--- Handle extreme low z-value cdf = 0.0; //--- Set CDF to 0 return true; //--- Return success } if (z > 10) { //--- Handle extreme high z-value cdf = 1.0; //--- Set CDF to 1 return true; //--- Return success } double t = 1 / (1 + 0.2316419 * MathAbs(z)); //--- Compute t for approximation double d = 0.3989423 * MathExp(-z * z / 2); //--- Compute density d cdf = d * t * (0.3193815 + t * (-0.3565638 + t * (1.7814779 + t * (-1.821256 + t * 1.3302744)))); //--- Calculate CDF approximation if (z > 0) cdf = 1 - cdf; //--- Adjust for positive z return true; //--- Return success } //+------------------------------------------------------------------+ //| Approximate Student t CDF | //+------------------------------------------------------------------+ bool StudentCDF(int df, double x, double &cdf) { if (df <= 0) return false; //--- Check for invalid degrees of freedom double a = df / 2.0; //--- Compute alpha parameter double b = 0.5; //--- Set beta parameter double xt = df / (df + x * x); //--- Compute xt for incomplete beta double ib = MathBetaIncomplete(xt, a, b); //--- Compute incomplete beta double beta = MathExp(MathGammaLog(a) + MathGammaLog(b) - MathGammaLog(a + b)); //--- Compute beta function double regularized = ib / beta; //--- Compute regularized incomplete beta if (x >= 0) { //--- Handle non-negative x cdf = 1 - 0.5 * regularized; //--- Set CDF for positive side } else { //--- Handle negative x cdf = 0.5 * regularized; //--- Set CDF for negative side } return true; //--- Return success } //+------------------------------------------------------------------+ //| Calculate p-value based on method and correlation | //+------------------------------------------------------------------+ double calculate_pvalue(double corr, CorrelationMethod method, int n) { if (n < 3) return 1.0; //--- Return invalid if insufficient samples double cdf = 0.0; //--- Initialize CDF variable if (method == KENDALL) { //--- Handle Kendall method double sigma = MathSqrt((4.0 * n + 10.0) / (9.0 * n * (n - 1.0))); //--- Compute sigma if (sigma == 0) return 1.0; //--- Return invalid if sigma zero double z = corr / sigma; //--- Compute z-score if (!NormalCDF(0, 1, MathAbs(z), cdf)) return 1.0; //--- Compute Normal CDF or return invalid return 2 * (1 - cdf); //--- Return two-tailed p-value } else { //--- Handle PEARSON or SPEARMAN double r2 = corr * corr; //--- Compute squared correlation if (r2 >= 1.0) return 0.0; //--- Return zero for perfect correlation double denom = 1.0 - r2; //--- Compute denominator if (denom <= 0.0) return 0.0; //--- Avoid division by zero double t = corr * MathSqrt((n - 2.0) / denom); //--- Compute t-statistic if (!StudentCDF(n - 2, MathAbs(t), cdf)) return 1.0; //--- Compute Student CDF or return invalid return 2 * (1 - cdf); //--- Return two-tailed p-value } }
Сначала мы определяем функцию "NormalCDF" для аппроксимации кумулятивной функции распределения (CDF) для обеспечения нормального распределения, которая используется при расчете p-значения. Она принимает параметры для среднего значения, стандартного отклонения, значения x и ссылки для хранения результата CDF. Сначала мы проверяем, является ли стандартное отклонение недопустимым, и возвращаем значение false, если это так. Затем мы вычисляем z-показатель, стандартизируя x. Для экстремальных значений z ниже отрицательных десяти или выше десяти мы устанавливаем значение CDF равным нулю или единице соответственно и возвращаем значение true. В противном случае мы вычисляем приближение, используя полиномиальное разложение, основанное на t и плотности d, корректируя CDF на положительное значение z перед возвратом значения true. Обычный график CDF выглядел бы следующим образом.

Далее мы реализуем функцию "StudentCDF" для аппроксимации кумулятивной функции распределения (CDF) для t-распределения Студента, необходимой для p-значений в методах Пирсона и Спирмена, которые мы определим позже. Она принимает степени свободы df, значение x и ссылку на CDF. Мы проверяем df и возвращаем значение false, если недействительно. Затем вычисляем параметры a и b, за которыми следует xt для неполной бета-функции. Используя "MathBetaIncomplete" для получения ib и вычисляя полную бета с помощью гамма-логарифмов, мы получаем упорядоченную неполную бета. В зависимости от того, является ли x неотрицательным или отрицательным, мы соответствующим образом корректируем CDF и возвращаем значение true. График CDF учащихся будет выглядеть следующим образом.

Затем мы создаем функцию "calculate_pvalue" для вычисления p-значения на основе коэффициента корреляции, метода и размера выборки n. Если n меньше трех, мы возвращаем единицу, чтобы указать на недействительность. Инициализируем переменную кумулятивной функции распределения и обрабатываем коэффициент Кенделла отдельно, вычисляя сигму, проверяя наличие нуля, выводя z и используя "NormalCDF" для получения двустороннего p-значения. В отношении коэффициентов Пирсона и Спирмана мы вычисляем квадрат корреляции r2, обрабатываем идеальные корреляции, возвращая ноль, вычисляем знаменатель и t-статистику, затем используем "StudentCDF" с скорректированным df для получения двустороннего p-значения. Следующим шагом нам нужно создать функцию для преобразования списка символов в массивы, которыми мы сможем управлять. Для достижения этого результата мы использовали следующую логику.
//+------------------------------------------------------------------+ //| Parse symbols list into array | //+------------------------------------------------------------------+ void parse_symbols() { string temp = SymbolsList; //--- Copy input symbols list num_symbols = 0; //--- Reset symbol count while (StringFind(temp, ",") >= 0 && num_symbols < MAX_SYMBOLS) { //--- Loop through comma-separated symbols int pos = StringFind(temp, ","); //--- Find comma position string sym = StringSubstr(temp, 0, pos); //--- Extract symbol if (SymbolSelect(sym, true)) { //--- Select symbol if available symbols_array[num_symbols] = sym; //--- Store valid symbol num_symbols++; //--- Increment count } else { //--- Handle unavailable symbol Print("Warning: Symbol ", sym, " not available."); //--- Print warning } temp = StringSubstr(temp, pos + 1); //--- Update remaining string } if (StringLen(temp) > 0 && num_symbols < MAX_SYMBOLS) { //--- Handle last symbol if (SymbolSelect(temp, true)) { //--- Select last symbol if available symbols_array[num_symbols] = temp; //--- Store last valid symbol num_symbols++; //--- Increment count } else { //--- Handle unavailable last symbol Print("Warning: Symbol ", temp, " not available."); //--- Print warning } } if (num_symbols < 2) { //--- Check minimum symbols Print("Error: At least 2 valid symbols required. Found: ", num_symbols); //--- Print error ExpertRemove(); //--- Remove expert } }
Здесь мы реализуем функцию "parse_symbols", чтобы преобразовать предоставленный пользователем список символов в полезный массив для панели. Она начинается с копирования входного параметра "SymbolsList" во временную строковую переменную и обнуления счетчика "num_symbols". Затем мы входим в цикл, который продолжается до тех пор, пока во временной строке обнаруживается запятая и количество символов не превышает предел "MAX_SYMBOLS". Внутри цикла мы определяем позицию следующей запятой с помощью StringFind, извлекаем подстроку символа от начала до этой позиции с помощью StringSubstr и пытаемся выбрать символ в обзоре рынка с помощью SymbolSelect с параметром true, чтобы добавить его при необходимости. В случае соблюдения условия мы сохраняем символ в массиве "symbols_array" по текущему индексу и увеличиваем "num_symbols"; в противном случае выводим предупреждение о недоступных символах. Мы обновляем временную строку до остатка после запятой, используя еще одну функцию «StringSubstr».
После завершения цикла мы проверяем, есть ли во временной строке какой—либо оставшийся текст, указывающий на последний символ, и не превышает ли его количество допустимых значений. Мы пытаемся выбрать и сохранить этот последний символ аналогичным образом, выводя предупреждение, если оно недоступно. Наконец, если было найдено менее двух допустимых символов, мы выводим сообщение об ошибке и удаляем программу с помощью ExpertRemove, чтобы убедиться, что матрице требуется как минимум два актива для корреляции. Вывод массива на экран дает следующий результат.

Теперь мы можем перейти к определению статистических функций для использования в расчетах.
//+------------------------------------------------------------------+ //| Rank data for Spearman correlation | //+------------------------------------------------------------------+ void rank_data(const double &data[], double &ranks[]) { int size = ArraySize(data); //--- Get data size int indices[]; //--- Declare indices array ArrayResize(indices, size); //--- Resize indices for (int i = 0; i < size; i++) indices[i] = i; //--- Initialize indices // Sort indices based on data values for (int i = 0; i < size - 1; i++) { //--- Loop outer for sorting for (int j = i + 1; j < size; j++) { //--- Loop inner for comparison if (data[indices[i]] > data[indices[j]]) { //--- Check if swap needed int temp = indices[i]; //--- Store temporary indices[i] = indices[j]; //--- Swap indices indices[j] = temp; //--- Complete swap } } } // Assign ranks, handling ties for (int i = 0; i < size; ) { //--- Loop through sorted indices int start = i; //--- Set start of tie group double value = data[indices[i]]; //--- Get current value while (i < size && data[indices[i]] == value) i++; //--- Skip ties double rank = (start + i - 1) / 2.0 + 1.0; //--- Compute average rank for (int k = start; k < i; k++) { //--- Assign rank to group ranks[indices[k]] = rank; //--- Set rank } } } //+------------------------------------------------------------------+ //| Calculate Pearson correlation | //+------------------------------------------------------------------+ double pearson_correlation(const double &deltas1[], const double &deltas2[], int size) { double mean1 = 0, mean2 = 0; //--- Initialize means for (int i = 0; i < size; i++) { //--- Loop to compute sums mean1 += deltas1[i]; //--- Accumulate first deltas mean2 += deltas2[i]; //--- Accumulate second deltas } mean1 /= size; //--- Compute first mean mean2 /= size; //--- Compute second mean double var1 = 0, var2 = 0, cov = 0; //--- Initialize variances and covariance for (int i = 0; i < size; i++) { //--- Loop to compute deviations double dev1 = deltas1[i] - mean1; //--- Compute first deviation double dev2 = deltas2[i] - mean2; //--- Compute second deviation var1 += dev1 * dev1; //--- Accumulate first variance var2 += dev2 * dev2; //--- Accumulate second variance cov += dev1 * dev2; //--- Accumulate covariance } if (var1 == 0 || var2 == 0) return 0.0; //--- Return zero if no variance return cov / MathSqrt(var1 * var2); //--- Return correlation } //+------------------------------------------------------------------+ //| Calculate Spearman correlation | //+------------------------------------------------------------------+ double spearman_correlation(const double &deltas1[], const double &deltas2[], int size) { double ranks1[], ranks2[]; //--- Declare rank arrays ArrayResize(ranks1, size); //--- Resize first ranks ArrayResize(ranks2, size); //--- Resize second ranks rank_data(deltas1, ranks1); //--- Rank first deltas rank_data(deltas2, ranks2); //--- Rank second deltas return pearson_correlation(ranks1, ranks2, size); //--- Return Pearson on ranks } //+------------------------------------------------------------------+ //| Calculate Kendall correlation | //+------------------------------------------------------------------+ double kendall_correlation(const double &deltas1[], const double &deltas2[], int size) { int concordant = 0, discordant = 0; //--- Initialize pair counts for (int i = 0; i < size - 1; i++) { //--- Loop outer pairs for (int j = i + 1; j < size; j++) { //--- Loop inner pairs double sign1 = deltas1[i] - deltas1[j]; //--- Compute first sign double sign2 = deltas2[i] - deltas2[j]; //--- Compute second sign if (sign1 * sign2 > 0) concordant++; //--- Increment concordant else if (sign1 * sign2 < 0) discordant++; //--- Increment discordant // Ties are ignored in basic Kendall tau } } int total_pairs = size * (size - 1) / 2; //--- Compute total pairs if (total_pairs == 0) return 0.0; //--- Return zero if no pairs return (concordant - discordant) / (double)total_pairs; //--- Return tau }
Во-первых, мы реализуем функцию "rank_data" для присвоения рангов массиву значений данных, что крайне важно для метода корреляции Спирмена для обработки непараметрического ранга. Он принимает постоянную ссылку на массив данных и ссылку на массив рангов. Сначала мы определяем размер с помощью функции ArraySize и объявляем массив индексов, изменяя его размер до соответствия и инициализируя его последовательными значениями от нуля до размера минус один. Затем мы выполняем пузырьковую сортировку индексов на основе соответствующих значений данных, меняя индексы местами, если значение данных в текущем индексе больше, чем в следующем. После сортировки мы проходим циклом по отсортированным индексам, чтобы присвоить ранги, обрабатывая совпадения путем определения групп с одинаковым значением, вычисления среднего ранга для группы по формуле (начало + конец - 1) / 2,0 + 1,0 и применения этого ранга к исходным позициям посредством индексов.
Далее мы определяем функцию "pearson_correlation" для вычисления коэффициента корреляции Пирсона между двумя массивами ценовых дельт заданного размера. Она инициализирует среднее значение для обоих массивов равным нулю, затем в цикле суммирует значения дельт и вычисляет среднее значение путем деления на размер. Затем обнуляем дисперсии и ковариацию и снова выполняем цикл для вычисления отклонений от средних значений, накапливая квадраты отклонений для дисперсий и произведение для ковариации. Если хотя бы одна из дисперсий равна нулю, мы возвращаем 0,0, чтобы избежать деления на ноль; в противном случае мы возвращаем ковариацию, деленную на квадратный корень из произведения дисперсий, используя функцию MathSqrt. Затем мы создаём функцию "spearman_correlation" для вычисления корреляции рангов Спирмена. Она объявляет и изменяет размер двух массивов рангов до размера входных данных, вызывает функцию "rank_data" для каждого массива дельт, чтобы заполнить ранги, и возвращает результат функции "pearson_correlation", примененной к этим массивам рангов, вместо исходных дельт.
Наконец, мы реализуем функцию "kendall_correlation" для коэффициента тау Кенделла. Счетчики для согласованных и несогласованных пар инициализируются нулем. Мы используем вложенные циклы по всему объему данных для сравнения каждой пары элементов и вычисления знаков как разностей в каждом массиве. Если произведение знаков положительное, то увеличивается количество согласованных пар; если отрицательное, то несогласованных — связи игнорируются. Общее количество пар рассчитывается как размер, умноженный на (размер - 1) / 2. Если пар нет, мы возвращаем 0,0, или разницу между согласованными и несогласованными значениями, деленную на общее количество пар. При обычном бивариантном сравнении наполнения они выглядят следующим образом.

Используя функции анализа, мы можем создать вспомогательные функции для обработки стандартизированных вычислений корреляции следующим образом.
//+------------------------------------------------------------------+ //| Calculate correlation based on method | //+------------------------------------------------------------------+ double calculate_correlation(string sym1, string sym2) { if (sym1 == sym2) return 1.0; //--- Return self-correlation double prices1[], prices2[]; //--- Declare price arrays ArrayResize(prices1, CorrelationBars); //--- Resize first prices ArrayResize(prices2, CorrelationBars); //--- Resize second prices if (CopyClose(sym1, global_correlation_tf, 0, CorrelationBars, prices1) < CorrelationBars || //--- Copy first closes or check failure CopyClose(sym2, global_correlation_tf, 0, CorrelationBars, prices2) < CorrelationBars) { return 0.0; //--- Return insufficient data } // Compute price changes (deltas) double deltas1[], deltas2[]; //--- Declare delta arrays ArrayResize(deltas1, CorrelationBars - 1); //--- Resize first deltas ArrayResize(deltas2, CorrelationBars - 1); //--- Resize second deltas for (int i = 0; i < CorrelationBars - 1; i++) { //--- Loop to compute deltas deltas1[i] = prices1[i + 1] - prices1[i]; //--- Set first delta deltas2[i] = prices2[i + 1] - prices2[i]; //--- Set second delta } int size = CorrelationBars - 1; //--- Set effective size switch (CorrMethod) { //--- Switch on method case PEARSON: //--- Handle Pearson return pearson_correlation(deltas1, deltas2, size); //--- Return Pearson result case SPEARMAN: //--- Handle Spearman return spearman_correlation(deltas1, deltas2, size); //--- Return Spearman result case KENDALL: //--- Handle Kendall return kendall_correlation(deltas1, deltas2, size); //--- Return Kendall result default: //--- Handle default return 0.0; //--- Return zero } } //+------------------------------------------------------------------+ //| Update correlation matrix values | //+------------------------------------------------------------------+ void update_correlations() { int n = CorrelationBars - 1; //--- Set sample size if (n < 2) { //--- Check minimum bars Print("Error: Insufficient bars for correlation (need at least 3)."); //--- Print error return; //--- Exit function } for (int i = 0; i < num_symbols; i++) { //--- Loop rows for (int j = 0; j < num_symbols; j++) { //--- Loop columns double corr = calculate_correlation(symbols_array[i], symbols_array[j]); //--- Compute correlation correlation_matrix[i][j] = corr; //--- Store correlation if (i == j) { //--- Handle diagonal pvalue_matrix[i][j] = 0.0; //--- Set p-value to zero } else if (corr == 0.0 && n < 3) { //--- Handle insufficient data pvalue_matrix[i][j] = 1.0; //--- Set p-value to one } else { //--- Handle normal case pvalue_matrix[i][j] = calculate_pvalue(corr, CorrMethod, n); //--- Compute and store p-value } } } }
Мы создаём функцию "calculate_correlation" для вычисления коэффициента корреляции между двумя символами, возвращающую значение типа double. Она принимает строковые параметры для sym1 и sym2. Если они идентичны, функция немедленно возвращает 1,0, что означает идеальную самокорреляцию. Мы объявляем и изменяем размер двух массивов типа double "prices1" и "prices2" до размера "CorrelationBars". Мы используем CopyClose для получения цен закрытия на каждый символ из таймфрейма "global_correlation_tf", начиная с нуля текущего бара. Если ни одна из копий не может получить полное количество, возвращаем 0,0, указывая на недостаточность данных.
Чтобы сосредоточиться на изменениях цен, мы объявляем и изменяем размер дельта-массивов "deltas1" и "deltas2" на единицу меньше, чем "CorrelationBars". Мы используем цикл для вычисления каждой дельты как разницы между последовательными ценами. Действующий размер устанавливается равным "CorrelationBars" минус один. Оператор switch на перечислении "CorrMethod" вызывает соответствующую функцию корреляции. Для "PEARSON" мы вызываем "pearson_correlation" с помощью delta и size. Для "SPEARMAN" вызываем "spearman_correlation". Для "KENDALL" вызываем "kendall_correlation". Значение по умолчанию возвращает 0.0.
Далее мы реализуем функцию "update_correlations", чтобы заполнить корреляционные матрицы и матрицы p-значений новыми вычислениями. Она вычисляет размер выборки n как "CorrelationBars" минус единица и проверяет, меньше ли она двух, выводя ошибку, если это так, поскольку для значимых корреляций требуется как минимум три бара, а затем завершает работу досрочно. Мы используем вложенные циклы по "num_symbols" для рядов i и столбцов j, вызывая "calculate_correlation" с соответствующими символами из "symbols_array", чтобы получить corr и сохранить его в "correlation_matrix" в [i][j]. Для p-значений, если i равно j для диагонали, мы устанавливаем значение "pvalue_matrix" в [i][j] равным 0.0; если значение corr равно 0.0, а n меньше трех, устанавливаем значение 1.0; в противном случае вычисляем его, используя "calculate_pvalue" с помощью corr, "CorrMethod" и n, сохраняя результат. Далее нам понадобятся вспомогательные программы для создания тепловой карты.
//+------------------------------------------------------------------+ //| Get significance stars based on p-value | //+------------------------------------------------------------------+ string get_significance_stars(double pval) { if (pval < PValueThreshold1) return "***"; //--- Return three stars if (pval < PValueThreshold2) return "**"; //--- Return two stars if (pval < PValueThreshold3) return "*"; //--- Return one star return ""; //--- Return empty } //+------------------------------------------------------------------+ //| Interpolate between multiple colors based on value (-1 to 1) | //+------------------------------------------------------------------+ color interpolate_heatmap_color(double value) { if (value == 0.0) return ColorNeutralBg; //--- Return neutral for zero double abs_val = MathAbs(value); //--- Compute absolute value int num_stops = ArraySize(heatmap_colors) / 2; //--- Compute stops per side double step = 1.0 / (num_stops - 1); //--- Compute step size if (value > 0.0) { //--- Handle positive int idx = (int)MathFloor(abs_val / step); //--- Compute index if (idx >= num_stops - 1) idx = num_stops - 2; //--- Clamp index double factor = (abs_val - idx * step) / step; //--- Compute factor return interpolate_color(heatmap_colors[idx + num_stops], heatmap_colors[idx + num_stops + 1], factor); //--- Interpolate positive } else { //--- Handle negative int idx = (int)MathFloor(abs_val / step); //--- Compute index if (idx >= num_stops - 1) idx = num_stops - 2; //--- Clamp index double factor = (abs_val - idx * step) / step; //--- Compute factor return interpolate_color(heatmap_colors[idx], heatmap_colors[idx + 1], factor); //--- Interpolate negative } } //+------------------------------------------------------------------+ //| Interpolate between two colors based on factor (0 to 1) | //+------------------------------------------------------------------+ color interpolate_color(color c1, color c2, double factor) { uchar r1 = (uchar)(c1 & 0xFF), g1 = (uchar)((c1 >> 8) & 0xFF), b1 = (uchar)((c1 >> 16) & 0xFF); //--- Extract RGB from first color uchar r2 = (uchar)(c2 & 0xFF), g2 = (uchar)((c2 >> 8) & 0xFF), b2 = (uchar)((c2 >> 16) & 0xFF); //--- Extract RGB from second color uchar r = (uchar)MathMax(0, MathMin(255, r1 + factor * (r2 - r1) + 0.5)); //--- Interpolate red uchar g = (uchar)MathMax(0, MathMin(255, g1 + factor * (g2 - g1) + 0.5)); //--- Interpolate green uchar b = (uchar)MathMax(0, MathMin(255, b1 + factor * (b2 - b1) + 0.5)); //--- Interpolate blue return (color)((b << 16) | (g << 8) | r); //--- Return interpolated color }
Определяем функцию "get_significance_stars" для определения количества символов звездочки, представляющих статистическую значимость, на основе заданного p-значения, возвращая строку. Она принимает параметр типа double "pval" и использует условные проверки: если "pval" меньше, чем "PValueThreshold1", мы возвращаем "***" для наивысшего значения; если меньше, чем "PValueThreshold2", возвращаем "**"; если меньше, чем "PValueThreshold3", возвращаем "*". В противном случае вернем пустую строку, не имеющую значения.
Далее мы реализуем функцию "interpolate_heatmap_color", чтобы сгенерировать цвет для режима тепловой карты путем интерполяции в массиве градиентов на основе значения корреляции между отрицательным значением и единицей, возвращая тип цвета. Она обрабатывает ноль, возвращая непосредственно "ColorNeutralBg". Мы вычисляем абсолютное значение "abs_val", определяем количество стопов для сторон, деля размер массива "heatmap_colors" на два, и вычисляем размер шага как единицу, деленную на стопы минус единицу. Для положительных значений мы находим индекс с помощью MathFloor типа "abs_val" с шагом, фиксируем его, если он находится на последнем интервале или за его пределами, вычисляем коэффициент как остаток с шагом и вызываем "interpolate_color" с цветами из положительной половины массива плюс коэффициент. Для отрицательных значений поступаем аналогично, но используем отрицательную половину массива.
Затем создаем функцию "interpolate_color" для линейного смешивания двух цветов на основе коэффициента от нуля до единицы, возвращая результирующий цвет. Она извлекает красный, зеленый и синий компоненты в виде символов без знака из "c1" и "c2", используя побитовые операции: маскируя с помощью 0xFF для красного, сдвигая вправо на восемь и маскируя для зеленого, и сдвигая на шестнадцать для синего. Мы интерполируем каждый канал, начиная со значения первого цвета, добавляя коэффициент, умноженный на разницу, ко второму, добавляя 0,5 для округления и фиксируя значение от нуля до 255 с помощью функций MathMax и MathMin. Наконец, объединяем интерполированные компоненты в единое цветовое значение, используя битовые сдвиги: синий смещается влево на шестнадцать, зеленый - на восемь, а также красный, затем побитово ИЛИ вместе. Теперь мы можем приступить к созданию панели, поскольку у нас есть большинство вспомогательных функций, необходимых для выполнения вычислений.
//+------------------------------------------------------------------+ //| Create rectangle for UI | //+------------------------------------------------------------------+ bool create_rectangle(string object_name, int x_distance, int y_distance, int x_size, int y_size, color background_color, color border_color = clrNONE) { if (!ObjectCreate(0, object_name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle or check failure Print(__FUNCTION__, ": failed to create Rectangle: ", GetLastError()); //--- Print error return false; //--- Return failure } ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance); //--- Set x distance ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance); //--- Set y distance ObjectSetInteger(0, object_name, OBJPROP_XSIZE, x_size); //--- Set x size ObjectSetInteger(0, object_name, OBJPROP_YSIZE, y_size); //--- Set y size ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetInteger(0, object_name, OBJPROP_BGCOLOR, background_color); //--- Set background ObjectSetInteger(0, object_name, OBJPROP_COLOR, border_color); //--- Set border color ObjectSetInteger(0, object_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); //--- Set border type ObjectSetInteger(0, object_name, OBJPROP_BACK, false); //--- Set back property return true; //--- Return success } //+------------------------------------------------------------------+ //| Create text label for UI | //+------------------------------------------------------------------+ bool create_label(string object_name, string text, int x_distance, int y_distance, int font_size = 10, color text_color = COLOR_WHITE, string font = "Arial Rounded MT Bold") { if (!ObjectCreate(0, object_name, OBJ_LABEL, 0, 0, 0)) { //--- Create label or check failure Print(__FUNCTION__, ": failed to create Label: ", GetLastError()); //--- Print error return false; //--- Return failure } ObjectSetInteger(0, object_name, OBJPROP_XDISTANCE, x_distance); //--- Set x distance ObjectSetInteger(0, object_name, OBJPROP_YDISTANCE, y_distance); //--- Set y distance ObjectSetInteger(0, object_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner ObjectSetString(0, object_name, OBJPROP_TEXT, text); //--- Set text ObjectSetString(0, object_name, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, object_name, OBJPROP_FONTSIZE, font_size); //--- Set font size ObjectSetInteger(0, object_name, OBJPROP_COLOR, text_color); //--- Set text color ObjectSetInteger(0, object_name, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set anchor return true; //--- Return success } //+------------------------------------------------------------------+ //| Create full dashboard UI | //+------------------------------------------------------------------+ void create_full_dashboard() { color main_bg = C'30,30,30'; //--- Set main background color header_bg = C'60,60,60'; //--- Set header background color text_color = COLOR_WHITE; //--- Set text color color neutral_bg = ColorNeutralBg; //--- Set neutral background color button_text = clrGold; //--- Set button text color color theme_icon_color = clrWhite; //--- Set theme icon color color close_text = clrWhite; //--- Set close text color color header_icon_color = clrAqua; //--- Set header icon color int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute panel width int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute panel height create_rectangle(MAIN_PANEL, panel_x, panel_y, panel_width, panel_height, main_bg); //--- Create main panel create_rectangle(HEADER_PANEL, panel_x, panel_y, panel_width, HEIGHT_HEADER, header_bg); //--- Create header panel create_label(HEADER_PANEL_ICON, CharToString(181), panel_x + 12, panel_y + 14, 18, header_icon_color, "Wingdings"); //--- Create header icon create_label(HEADER_PANEL_TEXT, "Correlation Matrix", panel_x + 90, panel_y + 12, 13, text_color); //--- Create header text create_label(CLOSE_BUTTON, CharToString('r'), panel_x + (panel_width - 17), panel_y + 14, 18, close_text, "Webdings"); //--- Create close button create_label(TOGGLE_BUTTON, CharToString('r'), panel_x + (panel_width - 47), panel_y + 14, 18, button_text, "Wingdings"); //--- Create toggle button string heatmap_icon = CharToString(global_display_mode == MODE_STANDARD ? (uchar)82 : (uchar)110); //--- Set heatmap icon create_label(HEATMAP_BUTTON, heatmap_icon, panel_x + (panel_width - 77), panel_y + 14, 18, button_text, "Wingdings"); //--- Create heatmap button create_label(PVAL_BUTTON, CharToString('X'), panel_x + (panel_width - 107), panel_y + 14, 18, button_text, "Wingdings"); //--- Create PVAL button string sort_icon = CharToString('N'); //--- Set sort icon create_label(SORT_BUTTON, sort_icon, panel_x + (panel_width - 137), panel_y + 14, 18, button_text, "Wingdings 3"); //--- Create sort button create_label(THEME_BUTTON, CharToString('['), panel_x + (panel_width - 167), panel_y + 14, 18, theme_icon_color, "Wingdings"); //--- Create theme button // Timeframe cells row int tf_y = panel_y + HEIGHT_HEADER; //--- Compute TF y position int tf_x_start = panel_x + 2; //--- Set TF start x for (int i = 0; i < num_tf_visible; i++) { //--- Loop visible TFs int x_offset = tf_x_start + i * WIDTH_TF_CELL; //--- Compute offset string rect_name = TF_CELL_RECT + IntegerToString(i); //--- Get rectangle name string text_name = TF_CELL_TEXT + IntegerToString(i); //--- Get text name color bg = (i == current_tf_index) ? ColorStrongPositiveBg : header_bg; //--- Set background create_rectangle(rect_name, x_offset, tf_y, WIDTH_TF_CELL, HEIGHT_TF_CELL, bg); //--- Create TF rectangle create_label(text_name, tf_strings[i], x_offset + (WIDTH_TF_CELL / 2), tf_y + (HEIGHT_TF_CELL / 2), 10, text_color, "Arial Bold"); //--- Create TF text } // Create row symbols (left column), pushed down int matrix_y = tf_y + HEIGHT_TF_CELL + GAP_HEIGHT; //--- Compute matrix y create_rectangle("SYMBOL_ROW_HEADER", panel_x + 2, matrix_y, WIDTH_SYMBOL, HEIGHT_RECTANGLE, header_bg); //--- Create row header rectangle create_label("SYMBOL_ROW_HEADER_TEXT", "Symbols", panel_x + (WIDTH_SYMBOL / 2 + 2), matrix_y + (HEIGHT_RECTANGLE / 2), 10, text_color, "Arial Bold"); //--- Create row header text for (int i = 0; i < num_symbols; i++) { //--- Loop row symbols int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i); //--- Compute y offset create_rectangle(SYMBOL_ROW_RECTANGLE + IntegerToString(i), panel_x + 2, y_offset, WIDTH_SYMBOL, HEIGHT_RECTANGLE, header_bg); //--- Create row rectangle create_label(SYMBOL_ROW_TEXT + IntegerToString(i), symbols_array[i], panel_x + (WIDTH_SYMBOL / 2 + 2), y_offset + (HEIGHT_RECTANGLE / 2 - 1), 10, text_color, "Arial Bold"); //--- Create row text } // Create column symbols (top row), pushed down for (int j = 0; j < num_symbols; j++) { //--- Loop column symbols int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1; //--- Compute x offset create_rectangle(SYMBOL_COL_RECTANGLE + IntegerToString(j), x_offset, matrix_y, WIDTH_CELL, HEIGHT_RECTANGLE, header_bg); //--- Create column rectangle create_label(SYMBOL_COL_TEXT + IntegerToString(j), symbols_array[j], x_offset + (WIDTH_CELL / 2), matrix_y + (HEIGHT_RECTANGLE / 2), 10, text_color, "Arial Bold"); //--- Create column text } // Create correlation cells, pushed down for (int i = 0; i < num_symbols; i++) { //--- Loop rows for cells int y_offset = matrix_y + HEIGHT_RECTANGLE * (i + 1) - (1 + i); //--- Compute y offset for (int j = 0; j < num_symbols; j++) { //--- Loop columns for cells string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j); //--- Get text name int x_offset = panel_x + WIDTH_SYMBOL + j * WIDTH_CELL - j + 1; //--- Compute x offset create_rectangle(cell_name, x_offset, y_offset, WIDTH_CELL, HEIGHT_RECTANGLE, neutral_bg); //--- Create cell rectangle create_label(text_name, "0.00", x_offset + (WIDTH_CELL / 2), y_offset + (HEIGHT_RECTANGLE / 2 - 1), 10, text_color, "Arial"); //--- Create cell text } } ChartRedraw(0); //--- Redraw chart }
Здесь мы определяем функцию "create_rectangle" для генерации прямоугольного графического объекта для пользовательского интерфейса, возвращающую логическое значение в случае успеха. Функция принимает в качестве параметров имя объекта в виде строки, целочисленные значения расстояний по осям x и y, размеры, цвет фона, а также опционный цвет рамки, который по умолчанию отсутствует. Мы пытаемся создать объект с помощью функции ObjectCreate с нулевым подокном, именем типа OBJ_RECTANGLE_LABEL и координатами по умолчанию, выводя сообщение об ошибке с именем функции и последним кодом ошибки в случае неудачи и возвращая значение false. В случае успеха мы настраиваем свойства с помощью ObjectSetInteger: расстояния по осям x и y, размеры, угол до левого верхнего угла, цвет фона, цвет рамки, тип границы (плоский) и обратно на false, затем возвращаем значение true.
Далее мы реализуем функцию "create_label", которая создает объект текстовой метки для пользовательского интерфейса и возвращает логическое значение "success". Она принимает имя объекта и текст в виде строк, расстояния по осям x и y, опциональный размер шрифта (по умолчанию — десять), цвет текста (по умолчанию — белый) и шрифт (по умолчанию — Arial Rounded MT Bold). Мы используем "ObjectCreate" с типом OBJ_LABEL и выводим сообщение об ошибке в случае сбоя. В случае успеха мы устанавливаем расстояния по осям x и y, угол в левом верхнем углу, текст, шрифт, размер шрифта, цвет текста и привязку к центру, используя соответствующие вызовы функций "ObjectSetInteger" и ObjectSetString, возвращая значение true.
Затем создаём функцию "create_full_dashboard", которая, используя указанные выше вспомогательные функции, формирует всю структуру пользовательского интерфейса. Она задает локальные цвета, такие как основной фон — темно-серый, фон заголовка — средне-серый, цвет текста — белый, нейтральный фон поля ввода, текст кнопок — золотой, тема и значки закрытия — белый, а значок заголовка — аква (aqua). Рассчитываем ширину панели на основе ширины символа плюс ячеек, скорректированную с учетом количества символов, и высоту, включающую заголовок, строку таймфрейма, пробел и строки матрицы.
Вызываем "create_rectangle" для главной панели и панели заголовков, затем "create_label" для таких элементов, как значок заголовка с символом 181 из Wingdings, заголовок в качестве корреляционной матрицы, кнопка закрытия с буквой "r" из Webdings, кнопка переключения аналогично, кнопка тепловой карты с динамическим значком на основе режима отображения с использованием приведений CharToString и uchar, кнопка p-значений с 'X' из Wingdings, кнопка сортировки с 'N' из Wingdings 3 и кнопка темы с '[' из Wingdings. Вы можете использовать любой из приведенных ниже символов шрифта по вашему выбору.

Для строки таймфрейма рассчитываем позицию y под заголовком и начало оси x, затем перебираем видимые таймфреймы, чтобы создать прямоугольники и метки: рассчитываем смещения, формируем названия с префиксом ячейки таймфрейма и строкой индекса, устанавливаем фон в зависимости от текущего индекса на ярко-положительный цвет или цвет заголовка, а также добавляем метки со строками таймфреймов, набранными шрифтом Arial Bold. Устанавливаем положение матрицы по оси Y ниже строки таймфрейма плюс промежуток, затем создаем прямоугольник заголовка строки и обозначаем его как Symbols («Символы»). Для символов строк слева мы используем цикл для вычисления смещений по оси Y, создаем прямоугольники с префиксом и индексом строки, а также метки с именами символов из массива, выделенные шрифтом Arial Bold. Аналогично, для символов столбцов вверху мы используем цикл для вычисления смещений по оси x, создания прямоугольников с префиксами столбцов и меток с названиями символов.
Для ячеек корреляции мы вкладываем циклы по строкам и столбцам, вычисляя смещения, формируя имена ячеек с префиксом в виде прямоугольника и индексами, разделенными подчеркиванием, и аналогично текстовые имена, затем вызываем функцию "create_rectangle" с нейтральным фоном и функцию "create_label", инициализированную значением 0.00 шрифтом Arial. Наконец, перерисовываем график с помощью функции ChartRedraw в нулевом подокне. Теперь мы можем вызвать эту функцию в обработчике "OnInit", чтобы она выполнила основную работу.
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { global_display_mode = DashboardMode; //--- Set display mode global_correlation_tf = (CorrelationTimeframe == PERIOD_CURRENT ? (ENUM_TIMEFRAMES)_Period : CorrelationTimeframe); //--- Set timeframe for (int i = 0; i < NUM_TF; i++) { //--- Loop to find index if (tf_list[i] == global_correlation_tf) { //--- Check match current_tf_index = i; //--- Set index break; //--- Exit loop } } if (current_tf_index == -1) current_tf_index = 3; //--- Default to H1 global_correlation_tf = tf_list[current_tf_index]; //--- Update timeframe parse_symbols(); //--- Parse symbols int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute width num_tf_visible = MathMin(NUM_TF, (panel_width - 2) / WIDTH_TF_CELL); //--- Set visible TFs if (current_tf_index >= num_tf_visible) current_tf_index = num_tf_visible - 1; //--- Clamp index global_correlation_tf = tf_list[current_tf_index]; //--- Update timeframe ArrayInitialize(pvalue_matrix, 1.0); //--- Initialize p-values create_full_dashboard(); //--- Create dashboard return(INIT_SUCCEEDED); //--- Return success }
В обработчике OnInit мы инициализируем параметры выполнения программы и пользовательский интерфейс. Устанавливаем параметр "global_display_mode" равным вводному значению "DashboardMode" и определяем "global_correlation_tf", проверяя, является ли "CorrelationTimeframe" значением PERIOD_CURRENT. В этом случае мы преобразуем текущий период графика _Period в ENUM_TIMEFRAMES; в противном случае мы используем входные параметры напрямую. Затем мы проходим циклом по массиву "tf_list", чтобы найти индекс, соответствующий "global_correlation_tf", и присваиваем его значению "current_tf_index", завершая работу досрочно при совпадении. Если совпадение не найдено — в этом случае значение параметра "current_tf_index" будет равно минус единице — мы по умолчанию устанавливаем его равным трем, что соответствует часовому таймфрейму. Для обеспечения согласованности мы обновляем значение параметра "global_correlation_tf" до значения, соответствующего этому индексу в "tf_list".
Далее мы вызываем функцию "parse_symbols" для обработки списка символов. Вычисляем "panel_width" на основе "WIDTH_SYMBOL" плюс поправка на количество символов, умноженная на "WIDTH_CELL". Значение "num_tf_visible" устанавливается равным минимальному значению "NUM_TF" и доступного пространства в панели, деленного на "WIDTH_TF_CELL". Если значение "current_tf_index" превышает это видимое значение, мы ограничиваем его последним видимым индексом минус один и соответствующим образом вновь обновляем значение "global_correlation_tf". Перед расчетами мы инициализируем массив "pvalue_matrix" значением 1.0, используя параметр ArrayInitialize в качестве состояния по умолчанию. Наконец, мы вызываем функцию "create_full_dashboard" для построения пользовательского интерфейса и возвращаем INIT_SUCCEEDED, что указывает на успешную инициализацию. После компиляции получаем следующий результат.

На изображении видно, что панель успешно создана. Нам нужно расширить её функциональность, добавив панель легенды, чтобы она была создана совместно с панелью.
//+------------------------------------------------------------------+ //| Recreate legend objects based on current mode | //+------------------------------------------------------------------+ void recreate_legend() { // Delete existing legend objects for (int i = 0; i < NUM_LEGEND_ITEMS; i++) { //--- Loop legend items ObjectDelete(0, LEGEND_CELL_RECTANGLE + IntegerToString(i)); //--- Delete rectangle ObjectDelete(0, LEGEND_CELL_TEXT + IntegerToString(i)); //--- Delete text } // Define full correlation values based on mode (more points for finer legend) double full_corr_vals[]; //--- Declare full values array if (global_display_mode == MODE_HEATMAP) { //--- Handle heatmap mode double heatmap_vals[] = {-1.0, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0}; //--- Set heatmap values ArrayCopy(full_corr_vals, heatmap_vals); //--- Copy to full } else { //--- Handle standard mode double standard_vals[] = {StrongNegativeThresholdPct / 100.0, -0.75, -0.5, -0.25, 0.0, 0.25, 0.5, 0.75, StrongPositiveThresholdPct / 100.0, 1.0}; //--- Set standard values ArrayCopy(full_corr_vals, standard_vals); //--- Copy to full } // Copy to visible and reduce dynamically if needed ArrayCopy(visible_corr_vals, full_corr_vals); //--- Copy to visible int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute panel width int available_width = panel_width - 4; //--- Compute available width double item_cost = WIDTH_LEGEND_CELL + LEGEND_SPACING; //--- Compute item cost int max_fit = (int)MathFloor((available_width + LEGEND_SPACING) / item_cost); //--- Compute max fit if (max_fit < 1) max_fit = 1; //--- Ensure minimum fit while (ArraySize(visible_corr_vals) > max_fit) { //--- Loop to reduce int sz = ArraySize(visible_corr_vals); //--- Get current size int zero_pos = -1; //--- Initialize zero position for (int p = 0; p < sz; p++) { //--- Loop to find zero if (visible_corr_vals[p] == 0.0) { //--- Check for zero zero_pos = p; //--- Set position break; //--- Exit loop } } if (zero_pos >= 0) { //--- Handle found zero ArrayRemove(visible_corr_vals, zero_pos, 1); //--- Remove zero continue; //--- Continue reduction } // Find min abs > 0 double min_abs = DBL_MAX; //--- Initialize min abs for (int p = 0; p < sz; p++) { //--- Loop to find min double av = MathAbs(visible_corr_vals[p]); //--- Get absolute if (av > 0 && av < min_abs) min_abs = av; //--- Update min } if (min_abs == DBL_MAX) break; //--- Exit if no more // Remove all == ±min_abs for (int p = sz - 1; p >= 0; p--) { //--- Loop backward to remove if (MathAbs(visible_corr_vals[p]) == min_abs) ArrayRemove(visible_corr_vals, p, 1); //--- Remove match } } num_legend_visible = ArraySize(visible_corr_vals); //--- Set visible count // Calculate positions int panel_height = HEIGHT_HEADER + HEIGHT_TF_CELL + GAP_HEIGHT + HEIGHT_RECTANGLE * (num_symbols + 1) - num_symbols + 2; //--- Compute panel height int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND; //--- Compute legend y int total_legend_width = num_legend_visible * WIDTH_LEGEND_CELL + (num_legend_visible - 1) * LEGEND_SPACING; //--- Compute total width int x_start = panel_x + (panel_width - total_legend_width) / 2; //--- Compute start x color neutral_bg = ColorNeutralBg; //--- Set neutral background color text_color = COLOR_WHITE; //--- Set text color // Create new legend objects for (int i = 0; i < num_legend_visible; i++) { //--- Loop to create int x_offset = x_start + i * (WIDTH_LEGEND_CELL + LEGEND_SPACING); //--- Compute offset string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i); //--- Get rectangle name string text_name = LEGEND_CELL_TEXT + IntegerToString(i); //--- Get text name create_rectangle(rect_name, x_offset, legend_y + 2, WIDTH_LEGEND_CELL, HEIGHT_LEGEND, neutral_bg); //--- Create rectangle create_label(text_name, "0%", x_offset + WIDTH_LEGEND_CELL / 2, legend_y + 2 + HEIGHT_LEGEND / 2 - 1, 10, text_color, "Arial"); //--- Create label } } //+------------------------------------------------------------------+ //| Create full dashboard UI | //+------------------------------------------------------------------+ void create_full_dashboard() { //--- Other dashboard creation logic // Create separate legend panel int legend_y = panel_y + panel_height + GAP_MAIN_LEGEND; //--- Compute legend y create_rectangle(LEGEND_PANEL, panel_x, legend_y, panel_width, HEIGHT_LEGEND_PANEL, main_bg); //--- Create legend panel recreate_legend(); //--- Recreate legend ChartRedraw(0); //--- Redraw chart }
Мы реализуем функцию "recreate_legend" для динамического перестраивания объектов легенды на основе текущего режима отображения, гарантируя, что она соответствуют панели и отражает соответствующие значения корреляции. Мы циклически удаляем существующие прямоугольники условных обозначений и текстовые метки, используя ObjectDelete с именами, образованными из префиксов и индексных строк.
Мы объявляем полный массив значений корреляции и заполняем его условно: для режима тепловой карты, используя локальный массив со значениями от минус 1,0 до 1,0, с шагом 0,2, скопированными в него; для стандартного режима, используя пороговые значения, преобразованные в десятичные дроби и фиксированные точки, такие как минус 0,75. Чтобы подогнать под размер пространства, мы копируем данные в видимый массив, вычисляем ширину панели, доступную ширину, стоимость элемента с учетом пробелов и максимальное размещение с разделением пола, гарантируя наличие как минимум одного элемента. В цикле, если массив превышает заданный размер, мы удаляем ноль, если он присутствует, или находим и удаляем все экземпляры наименьшего ненулевого абсолютного значения, сканируя массив и используя функцию ArrayRemove в обратном порядке.
Мы установили видимое количество равным обновленному размеру массива. Для позиционирования мы рассчитываем высоту панели на основе констант и строк, легенду по оси y под панелью с отступом, общую ширину легенды с учетом ячеек и интервалов, а также начальную координату x для центрирования. Мы задаем нейтральный цвет фона и текста, затем в цикле создаем прямоугольники и метки: вычисляем смещения, формируем имена, используем функцию "create_rectangle" с нейтральным фоном и функцию "create_label", инициализированную значением 0% шрифтом Arial. В функции "create_full_dashboard", после выполнения остальной логики интерфейса, мы вычисляем значение легенды по оси Y и создаем прямоугольник панели легенды с основным фоном, вызываем функцию "recreate_legend" для его заполнения и перерисовываем график. После компиляции получаем следующий результат.

После добавления легенды нам осталось только обновить панель и легенду, чтобы вычисления вступили в силу.
//+------------------------------------------------------------------+ //| Update TF highlights | //+------------------------------------------------------------------+ void update_tf_highlights() { color inactive_bg = C'60,60,60'; //--- Set inactive background for (int i = 0; i < num_tf_visible; i++) { //--- Loop visible TFs string rect_name = TF_CELL_RECT + IntegerToString(i); //--- Get rectangle name color bg = (i == current_tf_index) ? ColorStrongPositiveBg : inactive_bg; //--- Set background ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg); //--- Update background } ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Update legend colors and texts based on mode | //+------------------------------------------------------------------+ void update_legend() { color default_txt = ColorTextStrong; //--- Set default text color for (int i = 0; i < num_legend_visible; i++) { //--- Loop visible legends string rect_name = LEGEND_CELL_RECTANGLE + IntegerToString(i); //--- Get rectangle name string text_name = LEGEND_CELL_TEXT + IntegerToString(i); //--- Get text name double corr = visible_corr_vals[i]; //--- Get correlation value int decimals = (MathAbs(corr) == 0.5 || corr == 0.0 || MathAbs(corr) == 1.0) ? 0 : 1; //--- Set decimals string txt_str = DoubleToString(corr * 100, decimals) + "%"; //--- Format text color bg_color = ColorNeutralBg; //--- Initialize background color txt_color = default_txt; //--- Initialize text color if (corr == 1.0) { //--- Handle perfect positive bg_color = ColorDiagonalBg; //--- Set diagonal background txt_color = default_txt; //--- Set text color } else if (corr == 0.0) { //--- Handle zero bg_color = ColorNeutralBg; //--- Set neutral background txt_color = default_txt; //--- Set text color } else { //--- Handle other values if (global_display_mode == MODE_STANDARD) { //--- Handle standard mode double strong_pos = StrongPositiveThresholdPct / 100.0; //--- Set positive threshold double strong_neg = StrongNegativeThresholdPct / 100.0; //--- Set negative threshold if (corr >= strong_pos) { //--- Check strong positive bg_color = ColorStrongPositiveBg; //--- Set positive background txt_color = default_txt; //--- Set text color } else if (corr <= strong_neg) { //--- Check strong negative bg_color = ColorStrongNegativeBg; //--- Set negative background txt_color = default_txt; //--- Set text color } else { //--- Handle mild bg_color = ColorNeutralBg; //--- Set neutral background txt_color = (corr > 0.0) ? ColorTextPositive : ColorTextNegative; //--- Set mild text color } } else { //--- Handle heatmap mode txt_color = default_txt; //--- Set text color bg_color = interpolate_heatmap_color(corr); //--- Interpolate background } } ObjectSetInteger(0, rect_name, OBJPROP_BGCOLOR, bg_color); //--- Update background ObjectSetString(0, text_name, OBJPROP_TEXT, txt_str); //--- Update text ObjectSetInteger(0, text_name, OBJPROP_COLOR, txt_color); //--- Update text color } ChartRedraw(0); //--- Redraw chart } //+------------------------------------------------------------------+ //| Update dashboard cells with correlation values and colors | //+------------------------------------------------------------------+ void update_dashboard() { update_correlations(); //--- Update correlations double strong_pos = StrongPositiveThresholdPct / 100.0; //--- Set positive threshold double strong_neg = StrongNegativeThresholdPct / 100.0; //--- Set negative threshold color text_base = ColorTextStrong; //--- Set base text color for (int i = 0; i < num_symbols; i++) { //--- Loop rows for (int j = 0; j < num_symbols; j++) { //--- Loop columns double corr = correlation_matrix[i][j]; //--- Get correlation double pval = pvalue_matrix[i][j]; //--- Get p-value string text = DoubleToString(corr * 100, 1) + "%" + get_significance_stars(pval); //--- Format text color bg_color = ColorNeutralBg; //--- Initialize background color txt_color = ColorTextZero; //--- Initialize text color if (i == j) { //--- Handle diagonal bg_color = ColorDiagonalBg; //--- Set diagonal background txt_color = text_base; //--- Set text color } else { //--- Handle off-diagonal if (global_display_mode == MODE_STANDARD) { //--- Handle standard mode if (corr >= strong_pos) { //--- Check strong positive bg_color = ColorStrongPositiveBg; //--- Set positive background txt_color = text_base; //--- Set text color } else if (corr <= strong_neg) { //--- Check strong negative bg_color = ColorStrongNegativeBg; //--- Set negative background txt_color = text_base; //--- Set text color } else { //--- Handle mild bg_color = ColorNeutralBg; //--- Set neutral background if (corr > 0.0) { //--- Check positive mild txt_color = ColorTextPositive; //--- Set positive text } else if (corr < 0.0) { //--- Check negative mild txt_color = ColorTextNegative; //--- Set negative text } else { //--- Handle zero txt_color = text_base; //--- Set base text } } } else { //--- Handle heatmap mode txt_color = text_base; //--- Set text color bg_color = interpolate_heatmap_color(corr); //--- Set interpolated background } } string cell_name = CELL_RECTANGLE + IntegerToString(i) + "_" + IntegerToString(j); //--- Get cell name string text_name = CELL_TEXT + IntegerToString(i) + "_" + IntegerToString(j); //--- Get text name ObjectSetInteger(0, cell_name, OBJPROP_BGCOLOR, bg_color); //--- Update background ObjectSetString(0, text_name, OBJPROP_TEXT, text); //--- Update text ObjectSetInteger(0, text_name, OBJPROP_COLOR, txt_color); //--- Update text color } } update_legend(); //--- Update legend ChartRedraw(0); //--- Redraw chart }
Здесь мы реализуем функцию "update_tf_highlights", чтобы визуально выделить выбранную ячейку таймфрейма на панели. Мы устанавливаем неактивный цвет фона на средне-серый (medium gray) и в цикле перебираем количество видимых таймфреймов. Для каждого из них мы формируем имя прямоугольника, используя префикс "TF_CELL_RECT" и строку индекса, затем условно устанавливаем цвет фона на "ColorStrongPositiveBg", если он соответствует "current_tf_index", или на неактивный цвет в противном случае. Мы обновляем свойство фона объекта с помощью ObjectSetInteger, используя "OBJPROP_BGCOLOR", и перерисовываем график.
Далее определяем функцию "update_legend", чтобы обновить цвета и тексты легенды в соответствии с режимом. Мы устанавливаем цвет текста по умолчанию с помощью "ColorTextStrong" и в цикле перебираем видимые элементы условных обозначений. Для каждого из них мы извлекаем названия для rectangle и text, используя префиксы и индекс, получаем значение корреляции из "visible_corr_vals", определяем десятичные дроби на основе проверки абсолютных значений для четкого форматирования и форматируем текстовую строку в виде процентов с помощью функции DoubleToString.
Мы инициализируем цвета фона и текста, затем обрабатываем особые случаи: для 1.0 используем диагональный фон; для 0.0 — нейтральный фон - оба с текстом по умолчанию. Для других, в стандартном режиме, вычисляем сильные пороговые значения в процентах, устанавливая фон и цвета текста для сильных положительных, сильных отрицательных или слабых значений на основе сравнений, используя положительный или отрицательный слабый текст для ненулевых значений. В режиме тепловой карты устанавливаем текст по умолчанию и интерполируем фон с помощью "interpolate_heatmap_color". Мы обновляем "OBJPROP_BGCOLOR" прямоугольника, "OBJPROP_TEXT" текста и "OBJPROP_COLOR" текста, используя методы-установщики объектов, затем перерисовываем график.
Затем создаем функцию "update_dashboard", чтобы обновить все ячейки текущими корреляциями и визуальными элементами. Сначала вызываем "update_correlations", вычисляем сильные пороговые значения из процентных значений и устанавливаем базовый цвет текста с помощью "ColorTextStrong".
Во вложенных циклах над символами для строк и столбцов мы извлекаем корреляцию и p-значение из матриц, форматируем текст ячейки в процентах плюс звездочки значимости из "get_significance_stars" и инициализируем цвета. Для диагональных ячеек, где индексы совпадают, мы используем диагональный фон и базовый текст. Для недиагонального изображения в стандартном режиме устанавливаем фон и текст для сильно положительного, сильно отрицательного или умеренного значения с условным положительным / отрицательным / нулевым текстом; в режиме тепловой карты - базовый текст и интерполированный фон из "interpolate_heatmap_color". Мы формируем имена ячеек и текста с помощью префиксов и индексов, обновляем фон с помощью OBJPROP_BGCOLOR, содержимое текста с помощью "OBJPROP_TEXT", а цвет текста с помощью "OBJPROP_COLOR" через методы-установщики.
Наконец, вызываем "update_legend" и перерисовываем график. При инициализации мы вызываем эти функции как события тиков, чтобы применить все эффекты, как показано ниже.
//+------------------------------------------------------------------+ //| Initialize expert | //+------------------------------------------------------------------+ int OnInit() { global_display_mode = DashboardMode; //--- Set display mode global_correlation_tf = (CorrelationTimeframe == PERIOD_CURRENT ? (ENUM_TIMEFRAMES)_Period : CorrelationTimeframe); //--- Set timeframe for (int i = 0; i < NUM_TF; i++) { //--- Loop to find index if (tf_list[i] == global_correlation_tf) { //--- Check match current_tf_index = i; //--- Set index break; //--- Exit loop } } if (current_tf_index == -1) current_tf_index = 3; //--- Default to H1 global_correlation_tf = tf_list[current_tf_index]; //--- Update timeframe parse_symbols(); //--- Parse symbols int panel_width = WIDTH_SYMBOL + num_symbols * (WIDTH_CELL - 1) + 4; //--- Compute width num_tf_visible = MathMin(NUM_TF, (panel_width - 2) / WIDTH_TF_CELL); //--- Set visible TFs if (current_tf_index >= num_tf_visible) current_tf_index = num_tf_visible - 1; //--- Clamp index global_correlation_tf = tf_list[current_tf_index]; //--- Update timeframe ArrayInitialize(pvalue_matrix, 1.0); //--- Initialize p-values create_full_dashboard(); //--- Create dashboard update_tf_highlights(); //--- Update highlights update_dashboard(); //--- Update initial return(INIT_SUCCEEDED); //--- Return success } //+------------------------------------------------------------------+ //| Handle tick event | //+------------------------------------------------------------------+ void OnTick() { update_dashboard(); //--- Update on tick }
В обработчике OnInit вызываем "update_tf_highlights" для визуальных элементов таймфрейма и "update_dashboard" для первоначального заполнения данных, возвращая INIT_SUCCEEDED. В обработчике OnTick мы просто вызываем "update_dashboard", чтобы обновить корреляции и визуализацию при каждом новом тике. Если хотите обрабатывать вычисления по каждому бару, можно добавить управляющую логику, но пока мы оставляем все как есть, поскольку у нас всего несколько наборов символов. После компиляции получаем следующий результат.

На изображениях видно, что мы корректно настроили панель корреляционной матрицы со всеми выполненными наполнениями, тем самым достигнув поставленных целей. Теперь осталось протестировать работоспособность системы, и это рассматривается в предыдущем разделе.
Тестирование на истории
Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).

Заключение
В заключение, мы разработали панель корреляционной матрицы на MQL5, которая вычисляет взаимосвязи между заданными пользователем символами с использованием методов Пирсона, Спирмена или Кенделла в течение настраиваемого таймфрейма и баров, включая проверку значимости p-значения для оценки надежности. Система поддерживает стандартный режим с цветовой кодировкой на основе пороговых значений и звездочками для обозначения уровней значимости, а также режим тепловой карты с градиентной интерполяцией для визуального представления сильных сторон корреляции, включающий интерактивный пользовательский интерфейс с селекторами таймфреймов, переключателями режимов и динамической легендой для анализа взаимозависимостей активов. В следующих частях мы оживим панель, чтобы сделать ее доступной для наведения курсора мыши и перетаскивания, а также сделаем интерактивными значки, на которые вы нажимаете, и они будут выполнять соответствующие функции. Следите за обновлениями!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/20945
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Переосмысливаем классические стратегии (Часть 14): Анализ нескольких стратегий
Реализация частичного закрытия позиций в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте, Аллан Мунене Мутирия,
Спасибо, что поделились блестящей идеей в своей статье. В свою очередь, я подготовил ваш исходный код для использования в производстве
Файл .ex5 удален модератором - на форуме разрешен только исходный код