Изучение стандартной библиотеки MQL5 (Часть 12): Мультитаймфреймный дашборд на основе составного показателя
У вас уже есть окно Market Watch (Обзор рынка), и вы можете переключаться между несколькими таймфреймами вручную. Однако оно не может обеспечить единый количественный обзор в реальном времени по всем соответствующим временным интервалам для каждого символа в вашем списке наблюдения. Без автоматизации трейдеры тратят минуты на переключение между вкладками графиков, поиск согласованности сигналов и субъективную оценку силы тренда — медленный, непоследовательный процесс, который часто приводит к упущенным возможностям.
В этой статье представлен CMultiTimeframeMatrix — готовый к развертыванию дашборд, отображающий цветовую матрицу “символы × таймфреймы”. Реализованный как переиспользуемый модуль и хост-советник, он легко интегрируется с вашим существующим рабочим процессом. Каждая ячейка отображает составной показатель (тренд + импульс – волатильность), масштабированный до легко интерпретируемого диапазона, при этом интенсивность синего/красного цвета указывает на бычий или медвежий уклон. Дашборд обновляется по таймеру, работает в рамках допустимой нагрузки и может переключаться с помощью горячей клавиши. Вы получите наглядную картину рыночных условий, которую можно оценить с одного взгляда, позволяющую за считанные секунды определить согласованность сигналов между таймфреймами.
Содержание
Введение
Ручной многотаймфреймовый анализ утомителен: нужно проверять каждый символ на каждом таймфрейме, оценивать направление тренда, измерять импульс и все мысленно комбинировать. Субъективные решения сложно тестировать на исторических данных, а еще сложнее воспроизвести. Более того, когда список отслеживаемых символов превышает три-четыре, процесс становится практически неуправляемым.
Нам нужен количественный, независимый от конкретного символа дашборд. Он должен работать с любым набором символов и таймфреймов, сводить данные о цене к единому сопоставимому показателю, отображать результаты с помощью цвета и автоматически обновляться без зависания графика. Класс CMultiTimeframeMatrix решает именно эту задачу. Он наследует от CAppDialog (стандартная библиотека) и предоставляет перемещаемое, закрываемое окно. Внутри матрица ячеек отображает показатель, вычисленный на основе трех компонентов: наклона тренда, изменения цены и волатильности. Советник-хост, в который встроен дашборд, разбирает строки входных параметров, разделённые запятыми, инициализирует матрицу и передает события графика. В результате получается профессиональный инструмент мониторинга, который можно установить на любой график.
Концепция
Для заданной пары символа и таймфрейма мы вычисляем составной показатель S по скользящему окну из цен закрытия m_windowSize:
![]()
где:
- T = наклон линейного тренда (метод наименьших квадратов)
- M = импульс =
(абсолютное изменение цены за ретроспективный период). - V = волатильность = стандартное отклонение цен
Затем мы умножаем на m_scaleFactor (по умолчанию 100), чтобы привести показатель в диапазон примерно [-100, +100]. Положительные значения указывают на бычий тренд, отрицательные — на медвежий. Зачем вычитать волатильность? Высокая волатильность часто сопровождается неопределенностью; снижение показателя в таких условиях действует как штраф, делая дашборд более консервативным.

Рис. 1. Концептуальная диаграмма — строки = символы, столбцы = таймфреймы, интенсивность цвета = сила показателя.
Для простоты на концептуальной диаграмме используются цветные прямоугольники. В итоговом дашборде каждая ячейка отображает числовой показатель (например, +38,6 или -22,3), а цвет текста меняется от светло-голубого до темно-синего/красного в зависимости от величины — предоставляя ту же интуитивно понятную информацию, которую удобно считывать с одного взгляда.
После того как математические основы заложены, мы переходим к практической реализации. Дашборд разделен на две части: полностью инкапсулированный класс (пригодный для повторного использования в любом советнике) и минималистичный советник-хост, который отвечает за входные параметры пользователя и обработку событий.
Реализация
Дашборд создается в двух отдельных файлах: MultiTimeframeMatrix.mqh (переиспользуемый класс) и MatrixDashboardEA.mq5 (хост-советник). В следующих разделах мы подробно рассмотрим каждый элемент логики, объясняя, что он делает и почему это важно в реальной торговой среде.
1. Переиспользуемый класс (MultiTimeframeMatrix.mqh)
Этот класс инкапсулирует всю логику дашборда — хранение данных, создание пользовательского интерфейса, вычисление показателей и обновления по таймеру. Разместив его в отдельном включаемом файле, мы можем повторно использовать один и тот же дашборд в разных советниках или скриптах без дублирования кода. Я использую этот подход уже много лет: как только дашборд отлажен, к нему больше никогда не прикасаются. Вы просто добавляете включаемый файл в любой советник и вызываете несколько методов. Именно такое разделение ответственности отличает любительский код от профессиональных библиотек.
Шаг 1 – Объявление класса и зависимостей
Класс наследует от CAppDialog, который является частью стандартной библиотеки Controls MetaTrader. Благодаря этому наследованию мы сразу получаем окно, которое можно перетаскивать, закрывать и при необходимости менять его размер. Без него нам пришлось бы вручную обрабатывать десятки событий на графике — настоящий кошмар. Директивы include подключают базовый класс диалога, элемент Label для ячеек и библиотеку матричной математики Alglib. Alglib не самый быстрый, но он надежен и уже входит в состав MetaTrader, поэтому дополнительные DLL-файлы не требуются.
Закрытые поля класса хранят список символов, список таймфреймов, массивы указателей пользовательского интерфейса (для ячеек, заголовков строк, заголовков столбцов, меток подсказок), хранилище показателей, счетчики производительности, параметры показателей и параметры компоновки. Обратите внимание, что мы храним m_lastValues и m_prevValues отдельно — это не избыточно. Переменная класса m_lastValues хранит последний отображенный показатель, а m_prevValues используется для обнаружения изменений. Позже мы увидим, почему это важно. Публичные методы представляют собой интерфейс: установка символов/таймфреймов, весов и триггера таймера. Я намеренно сделал методы-установщики встроенными — короткими функциями, которые просто присваивают значение члену класса. Нет необходимости усложнять проверку ошибок на этом уровне; валидация происходит в функции Create().
//+------------------------------------------------------------------+ //| MultiTimeframeMatrix.mqh | //| Copyright 2026, Clemence Benjamin | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Clemence Benjamin" #property version "1.0" #include <Controls/Dialog.mqh> #include <Controls/Label.mqh> #include <Math/Alglib/matrix.mqh> //+------------------------------------------------------------------+ //| CMultiTimeframeMatrix – displays a dashboard of market scores | //+------------------------------------------------------------------+ class CMultiTimeframeMatrix : public CAppDialog { private: //--- Data string m_symbols[]; ENUM_TIMEFRAMES m_timeframes[]; //--- UI components CLabel* m_cells[]; CLabel* m_rowHeaders[]; CLabel* m_colHeaders[]; CLabel* m_guidance1, *m_guidance2, *m_guidance3, *m_guidance4; //--- Score storage double m_lastValues[]; double m_prevValues[]; //--- Performance uint m_updateIntervalMs; bool m_dirty; uint m_cycleCount; ulong m_totalCycleTime; //--- Score parameters int m_windowSize; double m_trendWeight, m_momWeight, m_volWeight, m_scaleFactor; //--- Layout int m_cellW, m_cellH; int m_startX, m_startY; //--- Helpers int Index(int row, int col) const; double ComputeScore(string symbol, ENUM_TIMEFRAMES tf, int shift); double CalculateTrend(CMatrixDouble &prices); double CalculateMomentum(CMatrixDouble &prices); double CalculateVolatility(CMatrixDouble &prices); void FetchAllData(); void UpdateUICell(int idx, double value); color ScoreToColor(double score); void CreateGuidance(); string TfToString(ENUM_TIMEFRAMES tf); public: CMultiTimeframeMatrix(); ~CMultiTimeframeMatrix(); bool Create(const long chart_id, const string name, const int subwin, const int x, const int y, const int w, const int h); void SetSymbols(string &syms[]); void SetTimeframes(ENUM_TIMEFRAMES &tfs[]); void SetUpdateInterval(const uint ms) { m_updateIntervalMs = ms; } void SetWindowSize(const int size) { m_windowSize = size; } void SetWeights(double t, double m, double v) { m_trendWeight = t; m_momWeight = m; m_volWeight = v; } void SetScaleFactor(double f) { m_scaleFactor = f; } uint GetUpdateInterval() const { return m_updateIntervalMs; } uint GetCycleCount() const { return m_cycleCount; } double GetAvgCycleTime() const; void ResetStats(); void OnTimer(); //--- called from EA void UpdateData(); //--- force refresh //--- Expose show/hide for external control (hotkey) void ToggleVisible() { if(IsVisible()) Hide(); else Show(); } };
Шаг 2 – Конструктор и деструктор
Конструктор инициализирует все члены разумными значениями по умолчанию. Интервал обновления составляет 1000 мс – одну секунду – что обеспечивает хороший баланс между актуальностью и нагрузкой на процессор. Его можно изменить позже с помощью входных параметров советника. Размер окна 50 баров – это эмпирическое правило: на часовом графике это примерно два дня данных; на 5-минутном графике – около четырех часов. Веса в сумме равны 1, при этом для тренда они немного выше, поскольку, по моему опыту, наклон линейной регрессии более устойчив, чем простая разница цен. Коэффициент масштабирования 100 увеличивает исходный показатель до диапазона, который легко читается человеком. Деструктор предотвращает утечки памяти. Каждый новый CLabel(), созданный в Create(), должен быть удален. Я видел слишком много индикаторов, которые игнорируют это, и через несколько часов терминал начинает тормозить. Очистка памяти обязательна.
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMultiTimeframeMatrix::CMultiTimeframeMatrix() : m_updateIntervalMs(1000), m_dirty(false), m_cycleCount(0), m_totalCycleTime(0), m_windowSize(50), m_trendWeight(0.4), m_momWeight(0.3), m_volWeight(0.3), m_scaleFactor(100.0), m_cellW(70), m_cellH(20), m_startX(35), m_startY(30) { m_guidance1 = m_guidance2 = m_guidance3 = m_guidance4 = NULL; } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CMultiTimeframeMatrix::~CMultiTimeframeMatrix() { for(int i = 0; i < ArraySize(m_cells); i++) if(m_cells[i] != NULL) delete m_cells[i]; for(int i = 0; i < ArraySize(m_rowHeaders); i++) if(m_rowHeaders[i] != NULL) delete m_rowHeaders[i]; for(int i = 0; i < ArraySize(m_colHeaders); i++) if(m_colHeaders[i] != NULL) delete m_colHeaders[i]; if(m_guidance1 != NULL) delete m_guidance1; if(m_guidance2 != NULL) delete m_guidance2; if(m_guidance3 != NULL) delete m_guidance3; if(m_guidance4 != NULL) delete m_guidance4; }
Шаг 3 – Вспомогательная функция для преобразования перечисления таймфреймов в читаемую строку
В заголовках столбцов следует использовать короткие метки, например, “H1”, вместо числовых значений перечисления. Простой оператор switch обрабатывает все стандартные таймфреймы и возвращает “?” для неизвестных значений. Это защитное программирование: если кто-то передаст неподдерживаемый таймфрейм, дашборд не зависнет — он просто отобразит “?” в заголовке, что сразу же укажет на проблему. Я видел, как трейдеры по ошибке вводили “H2”; без этой защиты индикатор мог бы молча не сработать.
//+------------------------------------------------------------------+ //| Converts timeframe enum to readable string | //+------------------------------------------------------------------+ string CMultiTimeframeMatrix::TfToString(ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return("M1"); case PERIOD_M5: return("M5"); case PERIOD_M15: return("M15"); case PERIOD_M30: return("M30"); case PERIOD_H1: return("H1"); case PERIOD_H4: return("H4"); case PERIOD_D1: return("D1"); case PERIOD_W1: return("W1"); case PERIOD_MN1: return("MN1"); default: return("?"); } }
Шаг 4 — Создание пользовательского интерфейса (метод Create)
Этот метод определяет раскладку дашборда. Он создает диалоговое окно, проверяет входные данные, инициализирует хранилища, а затем формирует заголовки строк, заголовки столбцов и внутренние ячейки. Наконец, он добавляет поясняющие метки. Заголовки строк размещаются по координатам x = m_startX и y = m_startY + (строка+1)*cellH; добавка +1 оставляет место для заголовков столбцов. Заголовки столбцов располагаются при y = m_startY и x = m_startX + (col+1)*cellW; здесь +1 оставляет место для заголовков строк. Каждый созданный элемент управления добавляется с помощью функции Add(), поэтому диалоговое окно управляет своими дочерними элементами.
//+------------------------------------------------------------------+ //| Creates the dashboard dialog and all its UI elements | //+------------------------------------------------------------------+ bool CMultiTimeframeMatrix::Create(const long chart_id, const string name, const int subwin, const int x, const int y, const int w, const int h) { int x2 = x + w, y2 = y + h; if(!CAppDialog::Create(chart_id, name, subwin, x, y, x2, y2)) return(false); int rows = ArraySize(m_symbols); int cols = ArraySize(m_timeframes); if(rows == 0 || cols == 0) { Print("ERROR: No symbols or timeframes set"); return(false); } int totalCells = rows * cols; ArrayResize(m_cells, totalCells); ArrayResize(m_lastValues, totalCells); ArrayResize(m_prevValues, totalCells); for(int i = 0; i < totalCells; i++) { m_lastValues[i] = EMPTY_VALUE; m_prevValues[i] = EMPTY_VALUE; } ArrayResize(m_rowHeaders, rows); ArrayResize(m_colHeaders, cols); //--- Row headers (symbols) for(int row = 0; row < rows; row++) { m_rowHeaders[row] = new CLabel(); int xp = m_startX; int yp = m_startY + (row + 1) * m_cellH; if(!m_rowHeaders[row].Create(chart_id, m_name + "_rh" + string(row), m_subwin, xp, yp, m_cellW, m_cellH)) return(false); string obj = m_rowHeaders[row].Name(); ::ObjectSetString(chart_id, obj, OBJPROP_TEXT, m_symbols[row]); ::ObjectSetInteger(chart_id, obj, OBJPROP_COLOR, clrBlack); ::ObjectSetInteger(chart_id, obj, OBJPROP_ANCHOR, ANCHOR_CENTER); Add(m_rowHeaders[row]); } //--- Column headers (timeframes) for(int col = 0; col < cols; col++) { m_colHeaders[col] = new CLabel(); int xp = m_startX + (col + 1) * m_cellW; int yp = m_startY; if(!m_colHeaders[col].Create(chart_id, m_name + "_ch" + string(col), m_subwin, xp, yp, m_cellW, m_cellH)) return(false); string obj = m_colHeaders[col].Name(); ::ObjectSetString(chart_id, obj, OBJPROP_TEXT, TfToString(m_timeframes[col])); ::ObjectSetInteger(chart_id, obj, OBJPROP_COLOR, clrBlack); ::ObjectSetInteger(chart_id, obj, OBJPROP_ANCHOR, ANCHOR_CENTER); Add(m_colHeaders[col]); } //--- Interior cells for(int row = 0; row < rows; row++) { for(int col = 0; col < cols; col++) { int idx = Index(row, col); int xp = m_startX + (col + 1) * m_cellW; int yp = m_startY + (row + 1) * m_cellH; m_cells[idx] = new CLabel(); if(!m_cells[idx].Create(chart_id, m_name + "_cell" + string(idx), m_subwin, xp, yp, m_cellW, m_cellH)) return(false); string obj = m_cells[idx].Name(); ::ObjectSetString(chart_id, obj, OBJPROP_TEXT, "---"); ::ObjectSetInteger(chart_id, obj, OBJPROP_COLOR, clrLightGray); ::ObjectSetInteger(chart_id, obj, OBJPROP_ANCHOR, ANCHOR_CENTER); Add(m_cells[idx]); } } CreateGuidance(); return(true); }
Шаг 5 – Метки-подсказки
Эти четыре строки внизу дашборда предназначены исключительно для удобства использования. Они напоминают пользователю, как интерпретировать цвета (синий = бычий тренд, красный = медвежий тренд, интенсивность = сила) и горячую клавишу (‘M’ для скрытия/отображения). Я добавил их после того, как увидел, как несколько моих коллег испытывали трудности с первой версией. Дашборд без инструкций – это просто красивая картинка. Метки статичны – они никогда не меняются – но они по-прежнему создаются как объекты CLabel и добавляются в диалоговое окно. Таким образом, если пользователь перемещает окно, подсказки перемещаются вместе с ним.
//+------------------------------------------------------------------+ //| Creates the guidance labels at the bottom of the dashboard | //+------------------------------------------------------------------+ void CMultiTimeframeMatrix::CreateGuidance() { int rows = ArraySize(m_symbols); int cols = ArraySize(m_timeframes); int xp = m_startX; int baseY = m_startY + (rows + 1) * m_cellH + 5; int width = (cols + 1) * m_cellW; int h = m_cellH; m_guidance1 = new CLabel(); m_guidance1.Create(m_chart_id, m_name + "_g1", m_subwin, xp, baseY, width, h); m_guidance1.Text("Higher Probability Setup: All timeframes show SAME colour"); m_guidance1.Color(clrBlack); Add(m_guidance1); m_guidance2 = new CLabel(); m_guidance2.Create(m_chart_id, m_name + "_g2", m_subwin, xp, baseY + h, width, h); m_guidance2.Text("(Blue = Bullish, Red = Bearish). Intensity = strength."); m_guidance2.Color(clrBlack); Add(m_guidance2); m_guidance3 = new CLabel(); m_guidance3.Create(m_chart_id, m_name + "_g3", m_subwin, xp, baseY + 2 * h, width, h); m_guidance3.Text("Mixed colours suggest caution."); m_guidance3.Color(clrBlack); Add(m_guidance3); m_guidance4 = new CLabel(); m_guidance4.Create(m_chart_id, m_name + "_g4", m_subwin, xp, baseY + 3 * h, width, h); m_guidance4.Text("Press 'M' to hide/show dashboard. Hover for exact score."); m_guidance4.Color(clrBlack); Add(m_guidance4); }
Шаг 6 – Вспомогательные функции расчёта показателей (тренд, импульс, волатильность)
Эти три функции работают с матрицей CMatrixDouble – вектор-столбцом цен. Функция CalculateTrend выполняет метод наименьших квадратов для нахождения наклона линии линейной регрессии. Формула: наклон = ковариация(x,y)/дисперсия(x). Здесь x – индекс бара (0,1,2,…), а y – цена. Мы создаем вспомогательную матрицу x, заполненную индексами, затем вычисляем средние значения и сумму произведений. Это классический подход, но обратите внимание: он предполагает линейную зависимость. Для рынков с сильным трендом это вполне приемлемо. Для рынков с боковым движением наклон будет близок к нулю – именно то, что нам нужно.
Функция CalculateMomentum намеренно проста: последнее минус первое. Почему бы не использовать процентное изменение? Поскольку тренд уже дает наклон, а импульс здесь – это просто дополнительный направленный толчок. Добавление процента потребовало бы нормализации, что усложнило бы код. Функция CalculateVolatility использует встроенный в Alglib метод Std(). Она вычисляет стандартное отклонение выборки — показатель разброса цен. Все три функции разделены, поэтому вы можете легко заменить одну, не затрагивая другие. Например, вы можете заменить волатильность на ATR — просто перепишите CalculateVolatility, а остальная часть кода останется без изменений.
//+------------------------------------------------------------------+ //| Calculates the linear trend slope from a price matrix | //+------------------------------------------------------------------+ double CMultiTimeframeMatrix::CalculateTrend(CMatrixDouble &prices) { int n = prices.Rows(); if(n <= 1) return(0.0); CMatrixDouble x(n, 1); for(int i = 0; i < n; i++) x.Set(i, 0, i); double mx = x.Mean(); double my = prices.Mean(); double num = 0, den = 0; for(int i = 0; i < n; i++) { double xi = i - mx; double yi = prices.Get(i, 0) - my; num += xi * yi; den += xi * xi; } return(den == 0 ? 0 : num / den); } //+------------------------------------------------------------------+ //| Calculates the momentum as change over the window | //+------------------------------------------------------------------+ double CMultiTimeframeMatrix::CalculateMomentum(CMatrixDouble &prices) { int n = prices.Rows(); if(n < 2) return(0.0); return(prices.Get(n - 1, 0) - prices.Get(0, 0)); } //+------------------------------------------------------------------+ //| Calculates the volatility (standard deviation) of prices | //+------------------------------------------------------------------+ double CMultiTimeframeMatrix::CalculateVolatility(CMatrixDouble &prices) { return(prices.Std()); }
Шаг 7 — Вычисление совокупного показателя
Для заданного символа и таймфрейма мы сначала запрашиваем количество доступных баров с помощью метода Bars(). Эта функция работает быстро, но может вернуть -1, если символ/таймфрейм недействителен — в этом случае мы возвращаем 0. Размер окна ограничен количеством доступных баров, но нам также требуется не менее 5 баров; в противном случае мы возвращаем 0. Это позволяет избежать ненадежных вычислений, когда история ещё недостаточно загружена. После копирования цен закрытия в массив мы заполняем CMatrixDouble. Далее вычисляем тренд, импульс и волатильность. Сырой показатель равен trendWeight * тренд + momentumWeight * импульс – volatilityWeight * волатильность. Обратите внимание на знак минус для волатильности: чем выше волатильность, тем ниже показатель. Это сделано намеренно: мы хотим штрафовать шумные и плохо структурированные участки рынка. Наконец, мы умножаем на m_scaleFactor (по умолчанию 100), чтобы привести сырой показатель в более удобный для чтения диапазон. Без масштабирования значения могут быть очень маленькими долями — это не идеально для дашборда.
//+------------------------------------------------------------------+ //| Computes the composite score for a given symbol and timeframe | //+------------------------------------------------------------------+ double CMultiTimeframeMatrix::ComputeScore(string symbol, ENUM_TIMEFRAMES tf, int shift) { int avail = Bars(symbol, tf); int sz = (int)MathMin(m_windowSize, avail); if(sz < 5) return(0.0); double closes[]; if(CopyClose(symbol, tf, shift, sz, closes) != sz) return(0.0); CMatrixDouble mat(sz, 1); for(int i = 0; i < sz; i++) mat.Set(i, 0, closes[i]); double t = CalculateTrend(mat); double m = CalculateMomentum(mat); double v = CalculateVolatility(mat); double raw = m_trendWeight * t + m_momWeight * m - m_volWeight * v; return(raw * m_scaleFactor); }
Шаг 8 – Цветовое отображение показателя в RGB
Эта функция преобразует числовой показатель в цвет, который человеческий глаз может мгновенно интерпретировать. Показатели за пределами диапазона [-50,50] обрезаются — это потому, что интерполяция цвета лучше всего работает в этом диапазоне. Для положительных показателей (бычий тренд) мы увеличиваем синий канал, одновременно уменьшая красный и зеленый. Формула g = 200 - 120*(показатель/50) сохраняет некоторое количество зеленого цвета, чтобы цвет не стал чисто синим (что может быть слишком резким). При отрицательных показателях (медвежий тренд) мы увеличиваем красный канал. В результате получается интуитивно понятный градиент: бледно-синий/красный для слабых сигналов, темно-синий/красный для сильных сигналов. Серый цвет используется для обозначения нуля. Такой метод отображения делает дашборд удобным для быстрого просмотра — вам не нужно читать цифры, чтобы увидеть, какие символы демонстрируют согласованную картину.
//+------------------------------------------------------------------+ //| Maps a score value to a colour (blue/red intensity) | //+------------------------------------------------------------------+ color CMultiTimeframeMatrix::ScoreToColor(double scr) { if(scr > 50) scr = 50; if(scr < -50) scr = -50; if(scr > 0) { int b = (int)(255 * (scr / 50)); int r = 255 - b; int g = 200 - (int)(120 * (scr / 50)); if(g < 0) g = 0; return((color)((r << 16) | (g << 8) | b)); } else if(scr < 0) { double a = -scr; int r = (int)(255 * (a / 50)); int g = 200 - (int)(120 * (a / 50)); int b = 255 - r; if(g < 0) g = 0; if(b < 0) b = 0; return((color)((r << 16) | (g << 8) | b)); } else return(clrLightGray); }
Шаг 9 – Обновление ячеек пользовательского интерфейса с использованием дифференциального обновления
Именно здесь важна производительность. Метод FetchAllData() перебирает в цикле каждый символ и таймфрейм, вычисляет текущий показатель и сравнивает его с ранее сохраненным значением. Только если абсолютная разница превышает 0,001, вызывается метод UpdateUICell(). Эта небольшая погрешность предотвращает ненужные перерисовки при незначительном изменении показателя (например, на 0,0001). Без этого каждый тик таймера перерисовывал бы все ячейки, вызывая мерцание и высокую загрузку ЦП. Метод UpdateUICell() изменяет текст, цвет и всплывающую подсказку отдельной ячейки. Он также обновляет m_prevValues (хотя мы сейчас его не используем, он есть для будущего расширения — например, для обнаружения изменений направления). После цикла, если какая-либо ячейка изменилась, для обновления экрана вызывается метод ChartRedraw(). Этот подход эффективен даже при наличии более 30 ячеек.
//+------------------------------------------------------------------+ //| Updates a single UI cell with a new score value | //+------------------------------------------------------------------+ void CMultiTimeframeMatrix::UpdateUICell(int idx, double val) { string obj = m_cells[idx].Name(); ::ObjectSetString(m_chart_id, obj, OBJPROP_TEXT, DoubleToString(val, 2)); ::ObjectSetInteger(m_chart_id, obj, OBJPROP_COLOR, ScoreToColor(val)); ::ObjectSetString(m_chart_id, obj, OBJPROP_TOOLTIP, "Score: " + DoubleToString(val, 4)); m_prevValues[idx] = val; } //+------------------------------------------------------------------+ //| Fetches all current data and updates changed cells | //+------------------------------------------------------------------+ void CMultiTimeframeMatrix::FetchAllData() { bool change = false; int rows = ArraySize(m_symbols); int cols = ArraySize(m_timeframes); for(int r = 0; r < rows; r++) { string sym = m_symbols[r]; for(int c = 0; c < cols; c++) { int idx = Index(r, c); double score = ComputeScore(sym, m_timeframes[c], 0); if(score == 0.0 && m_lastValues[idx] == EMPTY_VALUE) continue; if(MathAbs(score - m_lastValues[idx]) > 0.001) { m_lastValues[idx] = score; UpdateUICell(idx, score); change = true; } } } if(change) ChartRedraw(m_chart_id); }
Шаг 10 – Обработчик таймера с защитой от повторного входа
Метод OnTimer() выполняется с фиксированными интервалами. Статический флаг занятости предотвращает перекрывающиеся обновления, если не завершен предыдущий цикл. После блокировки мы фиксируем время начала, вызываем FetchAllData(), измеряем прошедшее время и обновляем счетчики производительности. Если прошедшее время превышает 80% интервала таймера, мы выводим предупреждение — сигнал к увеличению интервала или уменьшению количества символов/таймфреймов. Наконец, снимаем блокировку.
//+------------------------------------------------------------------+ //| Timer handler – periodically updates the dashboard | //+------------------------------------------------------------------+ void CMultiTimeframeMatrix::OnTimer() { static bool busy = false; if(busy) return; busy = true; ulong start = GetTickCount64(); FetchAllData(); ulong elapsed = GetTickCount64() - start; m_cycleCount++; m_totalCycleTime += elapsed; if(elapsed > m_updateIntervalMs * 0.8) Print("Warning: update took ", elapsed, " ms"); busy = false; }
Хост-советник – разработка и интеграция (MatrixDashboardEA.mq5)
Советник намеренно упрощен — он только анализирует входные данные, создает дашборд и пересылает события. Такое разделение означает, что вы можете подключить дашборд к любому советнику, просто включив файл .mqh и добавив несколько строк кода. Сам советник не содержит никакой торговой логики; он является исключительно хостом.
Шаг 1 – Входные параметры и глобальный указатель
Все настройки вынесены во входные параметры. Обратите внимание, что символы и таймфреймы представляют собой строки, разделенные запятыми. Для пользователей это гораздо проще, чем редактирование массивов в коде. Глобальный указатель gMatrix объявлен и инициализирован значением NULL. Сохранение его глобальным позволяет обработчикам событий получать к нему доступ.
//+------------------------------------------------------------------+ //| MatrixDashboardEA.mq5 | //| Copyright 2025, Clemence Benjamin | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Clemence Benjamin" #property version "1.0" #property strict #include <SmartMarketStructureMatrix_Shared/MultiTimeframeMatrix.mqh> input string InpSymbols = "EURUSD,GBPUSD,USDJPY,AUDUSD,USDCAD,NZDUSD"; input string InpTimeframes = "M5,M15,H1,H4,D1"; input uint InpUpdateSec = 1; input int InpWindowSize = 50; input double InpTrendWeight = 0.4; input double InpMomentumWeight = 0.3; input double InpVolatilityWeight = 0.3; input double InpScaleFactor = 100.0; input int InpDialogX = 50; input int InpDialogY = 50; input int InpDialogW = 450; input int InpDialogH = 300; CMultiTimeframeMatrix *gMatrix = NULL;
Шаг 2 – Инициализация (OnInit)
Советник разбивает входные строки с помощью StringSplit, который возвращает количество элементов. Он проверяет, что оба списка не пусты. Строки таймфреймов преобразуются в ENUM_TIMEFRAMES с помощью вспомогательной функции StringToTimeframe (определенной позже). Если какое-либо преобразование возвращает PERIOD_CURRENT или -1, советник не инициализируется — это гарантирует, что пользователь случайно не введет недопустимый таймфрейм. Далее создаётся объект матрицы, устанавливаются все параметры и вызывается Create(). Если создание не удается, мы удаляем объект и возвращаем INIT_FAILED. В случае успеха вызываем Run() для отображения диалогового окна, запуска миллисекундного таймера и вывода подтверждающего сообщения в журнал. Обратите внимание, что интервал таймера устанавливается в миллисекундах: InpUpdateSec * 1000. Советник не содержит торговой логики, поэтому он никогда не размещает ордера — это инструмент чисто для визуального представления.
//+------------------------------------------------------------------+ //| Initializes the EA and creates the dashboard | //+------------------------------------------------------------------+ int OnInit() { //--- Parse symbols string symbols[]; int symCount = StringSplit(InpSymbols, ',', symbols); if(symCount <= 0) return(INIT_FAILED); string tfStr[]; int tfCount = StringSplit(InpTimeframes, ',', tfStr); if(tfCount <= 0) return(INIT_FAILED); ENUM_TIMEFRAMES timeframes[]; ArrayResize(timeframes, tfCount); for(int i = 0; i < tfCount; i++) { timeframes[i] = StringToTimeframe(tfStr[i]); if(timeframes[i] == PERIOD_CURRENT || timeframes[i] == -1) return(INIT_FAILED); } gMatrix = new CMultiTimeframeMatrix(); if(gMatrix == NULL) return(INIT_FAILED); gMatrix.SetSymbols(symbols); gMatrix.SetTimeframes(timeframes); gMatrix.SetUpdateInterval(InpUpdateSec * 1000); gMatrix.SetWindowSize(InpWindowSize); gMatrix.SetWeights(InpTrendWeight, InpMomentumWeight, InpVolatilityWeight); gMatrix.SetScaleFactor(InpScaleFactor); if(!gMatrix.Create(ChartID(), "MatrixDashboard", 0, InpDialogX, InpDialogY, InpDialogW, InpDialogH)) { delete gMatrix; gMatrix = NULL; return(INIT_FAILED); } gMatrix.Run(); EventSetMillisecondTimer(gMatrix.GetUpdateInterval()); Print("Dashboard ready. Press 'M' key to show/hide. Use title bar to move and close."); return(INIT_SUCCEEDED); }
Шаг 3 – Деинициализация (OnDeinit)
Очистка проста: остановить таймер, затем уничтожить и удалить объект матрицы. Параметр reason передается в Destroy(), чтобы диалоговое окно могло обрабатывать различные сценарии завершения работы (например, закрытие графика или удаление советника).
//+------------------------------------------------------------------+ //| Deinitialization handler – cleans up the dashboard | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); if(gMatrix != NULL) { gMatrix.Destroy(reason); delete gMatrix; gMatrix = NULL; } }
Шаг 4 – Передача событий таймера и графика
OnTimer советника проверяет, существует ли дашборд и виден ли он; только после этого он переадресовывает вызов таймера. Это предотвращает ненужные фоновые обновления, когда дашборд скрыт — небольшая, но полезная оптимизация. OnChartEvent реагирует на нажатие клавиш. Если клавиша — ‘M’ (ASCII 77), она переключает видимость дашборда. Все остальные события графика (например, перетаскивание, закрытие) переадресовываются на дашборд через OnEvent. Это важно, поскольку диалоговое окно должно знать, когда пользователь перемещает его или нажимает кнопку закрытия. Без этой переадресации диалоговое окно не будет реагировать на действия пользователя.
//+------------------------------------------------------------------+ //| Timer event – updates the dashboard when visible | //+------------------------------------------------------------------+ void OnTimer() { if(gMatrix != NULL && gMatrix.IsVisible()) gMatrix.OnTimer(); } //+------------------------------------------------------------------+ //| Chart event handler – toggles visibility on 'M' key | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Toggle visibility with 'M' key (ASCII 77) if(id == CHARTEVENT_KEYDOWN && lparam == 77) { if(gMatrix != NULL) gMatrix.ToggleVisible(); } //--- Forward all other events to the dashboard when visible if(gMatrix != NULL && gMatrix.IsVisible()) gMatrix.OnEvent(id, lparam, dparam, sparam); }
Шаг 5 - Преобразование строки в таймфрейм
Этот вспомогательный метод прост, но важен. Он преобразует строку, например, “H1”, в соответствующее значение ENUM_TIMEFRAMES. Мы вызываем StringToUpper(), чтобы “h1” тоже работал. Если ни одна из распознанных строк не совпадает, он возвращает PERIOD_CURRENT. Функция OnInit() советника обрабатывает PERIOD_CURRENT как ошибку, поэтому советник не загрузится — это лучше, чем молча использовать текущий таймфрейм графика, что внесло бы путаницу.
//+------------------------------------------------------------------+ //| Converts a timeframe string to an ENUM_TIMEFRAMES value | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES StringToTimeframe(string str) { StringToUpper(str); if(str == "M1") return(PERIOD_M1); if(str == "M5") return(PERIOD_M5); if(str == "M15") return(PERIOD_M15); if(str == "M30") return(PERIOD_M30); if(str == "H1") return(PERIOD_H1); if(str == "H4") return(PERIOD_H4); if(str == "D1") return(PERIOD_D1); if(str == "W1") return(PERIOD_W1); if(str == "MN1") return(PERIOD_MN1); return(PERIOD_CURRENT); } //+------------------------------------------------------------------+
После полной реализации и объяснения кода мы переходим к тестированию — сначала в тестере стратегий, а затем на реальном графике.
Тестирование и результаты
Визуальная проверка в тестере стратегий
- Скомпилируйте оба файла и подключите MatrixDashboardEA.mq5 к любому графику.
- В тестере стратегий переключитесь в визуальный режим и запустите. Появится окно (позиция по умолчанию 50,50).
- Ячейки заполнятся числами и цветами. Наведите курсор на любую ячейку, чтобы увидеть точный результат во всплывающей подсказке.
- Нажмите клавишу ‘M’, чтобы скрыть/показать дашборд. Перетащите панель заголовка, чтобы переместить ее.

Рис. 2. Настройка тестера стратегий
Перед запуском дашборда необходимо корректно настроить тестер стратегий. Откройте панель тестера, выберите MatrixDashboardEA из выпадающего списка, а также выберите символ и таймфрейм (здесь EURUSD M5). Самый важный шаг — включить визуальный режим — установите флажок “Visual”. Без визуального режима окно графика не откроется, и вы не увидите дашборд. После активации визуального режима нажмите кнопку Start, чтобы начать имитационное моделирование. Анимированный GIF выше точно демонстрирует эти шаги.

Рис.3. Отображение тестера стратегий на паре EURUSD M5
После запуска имитационного моделирования дашборд появится в виде отдельного перетаскиваемого окна, наложенного поверх графика. Этот видеоролик демонстрирует работу дашборда: строки содержат список символов из списка наблюдения, столбцы представляют выбранные таймфреймы (M5, M15, H1, H4, D1). В каждой ячейке отображается числовой показатель (например, +38,6, -22,3) и цвет – синий для бычьего (положительного) тренда, красный для медвежьего (отрицательного). Интенсивность цвета меняется в зависимости от абсолютного значения показателя. При наведении курсора мыши на любую ячейку появляется всплывающая подсказка с точным показателем до четырех знаков после запятой. Дашборд обновляется каждую секунду (по умолчанию), а свечи графика перемещаются вперед по мере выполнения теста.

Рис. 4. Развёртывание на реальном графике NZDUSD M15
Наконец, мы развернули дашборд на реальном графике (NZDUSD, M15), чтобы протестировать поведение в реальных условиях. Анимированный GIF демонстрирует несколько ключевых функций:
- Нажатие клавиши ‘M’ мгновенно скрывает или отображает дашборд — удобная горячая клавиша для очистки экрана от лишней информации.
- Дашборд можно перетаскивать в любое место на графике за панель заголовка и он включает стандартные кнопки сворачивания и закрытия.
- В столбце M15 для NZDUSD отображается красный (медвежий) показатель. По графику видно, что рынок явно находится в нисходящем тренде – цвет дашборда корректно отражает медвежий настрой, подтверждая расчет показателя на основе реальных данных.
Все взаимодействия (перемещение, скрытие, закрытие) работают плавно, без задержек, что подтверждает эффективность обновлений, запускаемых таймером, и дифференциальной перерисовки даже на графике с потоковой передачей данных в реальном времени.
Выдержки из журнала событий
Дашборд MatrixDashboardEA (EURUSD,M5) готов к использованию. Нажмите клавишу 'M', чтобы показать/скрыть. Предупреждение MatrixDashboardEA (EURUSD,M5): обновление заняло 912 мс
Показатели производительности – для 6 символов × 5 таймфреймов = 30 ячеек, среднее время цикла на стандартном VPS составляет 200–400 мс, что значительно ниже стандартных 1000 мс. Увеличьте InpUpdateSec, если видите предупреждения.
Заключение
В этой статье мы создали полнофункциональный матричный дашборд для нескольких таймфреймов, который решает реальную проблему: ручное сканирование десятков символов и таймфреймов для поиска согласованности сигналов. Мы добились чистого интерфейса с цветовой кодировкой, который обновляется в реальном времени, не выходит за разумные пределы по нагрузке и может переключаться одной клавишей. В процессе работы мы преодолели ряд трудностей: предотвращение мерцания пользовательского интерфейса при дифференциальных обновлениях, защита от повторного входа таймера, управление динамической компоновкой пользовательского интерфейса и сопоставление сырых показателей с интуитивно понятными цветами.
Больше никаких переключений между вкладками графиков, никаких субъективных оценок силы тренда и никаких упущенных возможностей из-за того, что был пропущен другой таймфрейм. С помощью этого дашборда вы мгновенно увидите, находится ли EURUSD на H4 в сильном бычьем тренде (темно-синий цвет), а GBPUSD на M5 — переходит в медвежий тренд (светло-красный цвет) — и действовать соответственно. Дашборд работает из коробки с любым списком символов и любой комбинацией таймфреймов, и вы можете настроить веса в соответствии со своей стратегией.
Что вы можете сделать с созданным кодом? Вы можете:
- встроить дашборд в свой существующий советник, просто добавив файл .mqh и создав экземпляр.
- расширить логику оценки — заменить простой импульс на RSI или волатильность на ATR.
- добавить звуковые или электронные оповещения при превышении определенного порогового значения показателя.
- экспортировать значения матрицы в CSV-файл для дальнейшего анализа.
Организация пользовательских заголовочных файлов:
Создайте отдельную подпапку для вашего проекта внутри MQL5/Include, например, SmartMarketStructureMatrix_Shared. Поместите туда файл MultiTimeframeMatrix.mqh и сошлитесь на него в своем советнике с помощью #include <SmartMarketStructureMatrix_Shared/MultiTimeframeMatrix.mqh>. Это позволит аккуратно отделить ваш собственный код от официальной стандартной библиотеки MetaQuotes и любых сторонних библиотек, обеспечивая чистое, профессиональное рабочее пространство, хорошо масштабируемое для нескольких проектов.
Компоненты стандартной библиотеки MQL5, рассмотренные в этой статье:
- CAppDialog — из библиотеки Controls, предоставляющий перемещаемое и закрываемое диалоговое окно, в котором размещается весь дашборд.
- CLabel, также входящий в библиотеку Controls, предоставил нам легковесные и высокопроизводительные текстовые метки для каждой ячейки матрицы, заголовка строки и заголовка столбца.
- CMatrixDouble — из матричной библиотеки Math/Alglib — позволил эффективно вычислять тренды, импульс и волатильность с помощью таких методов, как Mean() и Std().
Эти три компонента в сочетании со встроенными функциями MQL5 (таймеры, обработка событий и дифференциальная перерисовка) демонстрируют, как стандартная библиотека MQL5 ускоряет разработку профессиональных торговых инструментов. В следующей части этой серии мы рассмотрим еще больше компонентов — возможности поистине безграничны. Изучите основные уроки ниже, а также прикрепленные исходные файлы для дальнейшего изучения.
Основные уроки
| Урок | Описание |
|---|---|
| Модульность через .mqh | Размещение дашборда в отдельном включаемом файле позволяет повторно использовать его в нескольких советниках и делает код поддерживаемым. Вы компилируете один раз, используете везде. |
| Дифференцированные обновления пользовательского интерфейса | Перерисовывают ячейки только тогда, когда показатель изменяется за пределы небольшого допуска (0,001). Экономит ресурсы ЦП и устраняет мерцание — критически важно для плавных обновлений в реальном времени. |
| Таймер против OnTick | Для визуальных дашбордов более подходящим вариантом является миллисекундный таймер, чем OnTick. Он отделяет обновления от частоты тиков, обеспечивая постоянную частоту обновления независимо от рыночной активности. |
| Отображение интенсивности цвета | Линейная интерполяция каналов RGB создает интуитивно понятный градиент от слабых к сильным сигналам. Вам не нужны числа, чтобы понять направление и силу сигнала. |
| Защита от повторного входа | Статический флаг занятости внутри OnTimer предотвращает перекрытие обновлений, когда время обработки превышает интервал таймера. Это позволяет избежать искажения пользовательского интерфейса и переполнения стеков. |
| Передача событий | Передача OnChartEvent из советника в диалоговое окно гарантирует корректную работу перетаскивания, закрытия и сочетаний клавиш даже при видимом дашборде. Советник выступает в качестве прозрачного прокси. |
Вложения
| Название файла | Тип | Версия | Описание |
|---|---|---|---|
| MultiTimeframeMatrix.mqh | Заголовок | 1.0 | Определение класса для матричного дашборда. Поместите в MQL5/Include/SmartMarketStructureMatrix_Shared/ (или обновите путь включения). |
| MatrixDashboardEA.mq5 | Советник | 1.0 | Хост-советник. Скомпилируйте и установите на любой график. Входные параметры позволяют задать список символов, таймфреймы, интервал обновлений, веса и положение диалогового окна без торговой логики, исключительно для визуального отображения. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22059
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Нейросети в трейдинге: Двухуровневая адаптация торговой политики (Банк навыков)
Создание торговых систем с искусственным интеллектом на MQL5 (Часть 2): Разработка программы с интеграцией ChatGPT и пользовательским интерфейсом
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования