English
preview
Торговые инструменты на MQL5 (Часть 14): Прокручиваемый текстовый холст с пиксельной точностью, сглаживанием и закругленной полосой прокрутки

Торговые инструменты на MQL5 (Часть 14): Прокручиваемый текстовый холст с пиксельной точностью, сглаживанием и закругленной полосой прокрутки

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

Введение

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

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

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


Изучение структуры прокручиваемого текстового холста с пиксельной точностью

Эта идеально точная прокручиваемая текстовая структура решает проблемы, связанные с ограничениями MQL5 встроенной функции прокрутки текста, за счет использования пользовательского рендеринга на уровне пикселей со сглаживанием для получения плавных краев, закругленной полосы прокрутки, которая расширяется при наведении курсора для повышения удобства использования, а также интерактивных элементов, таких как кнопки вверх/вниз и перетаскиваемый ползунок для навигации по длинному контенту, включая руководства по использованию. Она поддерживает фоны тем оформления с регулируемой непрозрачностью, динамический перенос линий в соответствии с шириной панели с сохранением цветов заголовков / ссылок и прокрутку колесика мыши в теле текста, чтобы избежать помех при масштабировании графика, обеспечивая точное управление без использования встроенных объектов. Интеграция с дашбордом позволяет оперативно обновлять информацию о событиях, поддерживая согласованность графиков/статистики/ и текстовых панелей для создания целостного инструмента мониторинга.

Мы решили не использовать статические объекты в MQL5 для линий и хотим полностью изучить возможности холста. Хорошо то, что с холстом нам на самом деле не нужно беспокоиться о перетекании текста через рамки, как это было в предыдущих статьях, в которых мы использовали встроенные объекты; холст автоматически обрезает тексты, помогая нам добиться эффекта прокрутки веб-сайта. Кроме того, закругленная динамическая полоса прокрутки была подсказана​привлекательным оформлением терминалов MetaQuotes, которое появилось в последних обновлениях. Взгляните на то, чего мы в итоге хотим достичь в результате такого влияния.

METAQUOTES SCROLLBAR INSPIRATION

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

TEXT CANVAS OBJECTIVES FRAMEWORK


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

Для улучшения программы в MQL5 нам потребуется объявить новую текстовую панель canvas (холст) и добавить некоторые дополнительные входные параметры и глобальные переменные для динамического управления ее отображением.

//+------------------------------------------------------------------+
//|                                       Canvas Dashboard PART2.mq5 |
//|                           Copyright 2026, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
#property version "1.00"
#property strict

//+------------------------------------------------------------------+
//| Canvas objects                                                   |
//+------------------------------------------------------------------+

//---

CCanvas canvasText;                  // Declare text canvas

//+------------------------------------------------------------------+
//| Canvas names                                                     |
//+------------------------------------------------------------------+

//---

string canvasTextName = "TextCanvas";   // Set text canvas name

//--- Added Extra inputs

input bool EnableTextPanel            = true; // Enable Text Panel
input int TextPanelHeight             = 200; // Text Panel Height
input double TextBackgroundOpacityPercent = 85.0; // Text Background Opacity Percent

input double StatsHeaderBgOpacityPercent = 20.0; // Stats Header Bg Opacity Percent
input int StatsHeaderBgRadius         = 8; // Stats Header Bg Radius

//--- Extended the globals

uint bg_pixels_text[];               //--- Store text background pixels

int prev_mouse_state = 0;            //--- Initialize previous mouse state
int last_mouse_x = 0, last_mouse_y = 0; //--- Initialize last mouse positions
int header_height = 27;              //--- Set header height
int gap_y = 7;                       //--- Set Y gap
int button_size = 25;                //--- Set button size
int theme_x_offset = -75;            //--- Set theme X offset
int minimize_x_offset = -50;         //--- Set minimize X offset
int close_x_offset = -25;            //--- Set close X offset
bool panels_minimized = false;       //--- Initialize panels minimized flag
color HeaderColor = C'60,60,60';     //--- Set header color
color HeaderHoverColor = clrRed;     //--- Set header hover color
color HeaderDragColor = clrMediumBlue; //--- Set header drag color

Первое, что мы делаем для реализации улучшений, - это объявляем дополнительный объект CCanvas с именем "canvasText" для новой текстовой панели, которая будет обрабатывать прокручиваемое содержимое руководства по использованию. Мы добавляем строковую константу "canvasTextName" со значением "TextCanvas", чтобы однозначно идентифицировать этот холст, как и другие. Далее мы расширяем входные параметры для поддержки текстовой панели: Включим "EnableTextPanel" в качестве логического значения по умолчанию, равного true, для её включения / выключения, "TextPanelHeight" - двести пикселей, чтобы задать её высоту, "TextBackgroundOpacityPercent" - 85,0 для контроля прозрачности фона, "StatsHeaderBgOpacityPercent" - 20,0 для непрозрачности фона заголовка статистики и "StatsHeaderBgRadius" - в значении восемь для закругления углов заголовков статистики. Добавляем новый массив uint "bg_pixels_text" для хранения масштабированных пикселей фона для текстовой панели, обеспечивая согласованность с графиком и статистикой, если используются фоновые изображения.

Далее мы сохраняем и инициализируем глобальные параметры для взаимодействия с мышью. Сюда относится значение "prev_mouse_state" равное нулю, а также значения "last_mouse_x" и "last_mouse_y" равные нулю для отслеживания позиций. Устанавливаем "header_height" в значение двадцать семь, а "gap_y" - в значение семь для показателя интервала по вертикали. Значение параметра "button_size" равно двадцати пяти. Для размещения кнопок относительно правого края заголовка мы используем смещения: "theme_x_offset" равное минус семьдесят пять, "minimize_x_offset" равное минус пятьдесят и "close_x_offset" равное минус двадцать пять. Мы также сохраняем значение "panels_minimized" как false, чтобы начать расширение. Цвета определены: Для состояний заголовка используются следующие значения: "HeaderColor" — средне-серый (medium gray), "HeaderHoverColor" — красный и "HeaderDragColor" — сине-голубой (medium blue). Следующий шаг — добавить текст для отображения на текстовую панель и определить его глобальные переменные управления следующим образом.

string text_usage_text = 
"\nCanvas Dashboard Usage Guide\n\n"
"Welcome to the Canvas Dashboard – Your Interactive Tool for Real-Time Market Monitoring in MetaTrader 5!\n\n"
"Enhance your trading experience with this dynamic dashboard that visualizes price data, account stats, and interactive controls. Designed for ease of use, it allows customization through dragging, resizing, theme switching, and more, while providing essential market insights at a glance.\n\n"
"Key Features:\n"
"- Price Graph Panel: Displays recent bar closes with a fog gradient fill, background image support, and resize indicators.\n"
"- Stats Panel: Shows account balance, equity, and current bar OHLC values with customizable backgrounds (single color or gradient).\n"
"- Header Controls: Drag to move the dashboard; buttons for theme toggle (dark/light), minimize/maximize panels, and close.\n"
"- Text Panel: Scrollable guide (this panel) with hover-expand scrollbar, up/down buttons, and slider for navigation.\n"
"- Interactivity: Hover for highlights/tooltips; resize via borders (bottom, right, corner); wheel scroll in text area.\n"
"- Theme Support: Switch between dark and light modes for better visibility on different chart backgrounds.\n"
"- Background Options: Enable images with fog overlay; blend modes for transparency.\n\n"
"Usage Instructions:\n"
"1. Move the Dashboard: Click and drag the header (excluding buttons) to reposition on the chart.\n"
"2. Resize Panels: Hover near the graph's bottom/right edges; click and drag when the icon appears (arrows for direction).\n"
"3. Toggle Theme: Click the '[' icon in the header to switch between dark and light modes.\n"
"4. Minimize/Maximize: Click the 'r' or 'o' icon to hide/show panels (header remains visible).\n"
"5. Navigate Text: Use the scrollbar (hovers to expand with buttons/slider); click up/down or drag slider; wheel scroll in body.\n"
"6. Close Dashboard: Click the 'X' icon in the header to remove it from the chart.\n\n"
"Important Notes:\n"
"- Risk Disclaimer: This dashboard is for informational purposes only. Always verify data and trade responsibly.\n"
"- Compatibility Check: Ensure chart settings allow mouse events; test on demo for interactions.\n"
"- Optimization Tips: Adjust input parameters like graph bars, fonts, colors, and opacity for your setup.\n"
"- Security Measures: No account modifications; use on trusted platforms.\n"
"- Legal Notice: No guarantees of accuracy. Consult professionals as needed.\n\n"
"Contact Methods:\n"
"NB:\n"
"********************************************\n"
" >*** FOR SUPPORT, QUERIES, OR CUSTOMIZATIONS, REACH OUT IMMEDIATELY: ***<\n"
" __________________________________________\n\n"
"1. Email: mutiiriallan.forex@gmail.com (Primary Support Channel)\n"
"2. Telegram Channel: @ForexAlgo-Trader (Updates & Community)\n"
"3. Telegram Group: https://t.me/Forex_Algo_Trader (Direct Assistance & Discussions)\n\n"
"********************************************\n\n"
"Thank you for choosing our Canvas Dashboard solutions. Use wisely, monitor confidently, and elevate your trading journey! 🚀\n"; //--- Set text usage content

int text_scroll_pos = 0;                   //--- Initialize text scroll position
int text_max_scroll = 0;                   //--- Initialize text max scroll
int text_slider_height = 20;               //--- Set text slider height
bool text_movingStateSlider = false;       //--- Initialize text slider moving flag
int text_mlbDownY_Slider = 0;              //--- Initialize text slider down Y
int text_mlbDown_YD_Slider = 0;            //--- Initialize text slider down YD
int text_total_height = 0;                 //--- Initialize text total height
int text_visible_height = 0;               //--- Initialize text visible height
bool text_scroll_visible = false;          //--- Initialize text scroll visible flag
bool text_mouse_in_body = false;           //--- Initialize text mouse in body flag
bool prev_text_mouse_in_body = false;      //--- Initialize previous text mouse in body flag
bool text_scroll_up_hovered = false;       //--- Initialize text scroll up hovered flag
bool text_scroll_down_hovered = false;     //--- Initialize text scroll down hovered flag
bool text_scroll_slider_hovered = false;   //--- Initialize text scroll slider hovered flag
bool text_scroll_area_hovered = false;     //--- Initialize text scroll area hovered flag
const int TEXT_MAX_LINES = 100;            //--- Set text max lines
int text_adjustedLineHeight = 0;           //--- Initialize text adjusted line height

int text_scrollbar_full_width = 16;        //--- Set text scrollbar full width
int text_scrollbar_thin_width = 2;         //--- Set text scrollbar thin width
int text_track_width = 16;                 //--- Set text track width
int text_scrollbar_margin = 5;             //--- Set text scrollbar margin
int text_button_size = 16;                 //--- Set text button size
color text_leader_color_dark = C'45,45,45'; //--- Set dark text leader color
color text_leader_color_light = C'200,200,200'; //--- Set light text leader color
color text_button_bg_dark = C'60,60,60';        //--- Set dark text button bg
color text_button_bg_light = C'220,220,220';    //--- Set light text button bg
color text_button_bg_hover_dark = C'70,70,70';  //--- Set dark text button bg hover
color text_button_bg_hover_light = C'180,180,180'; //--- Set light text button bg hover
color text_arrow_color_dark = C'150,150,150';   //--- Set dark text arrow color
color text_arrow_color_light = C'50,50,50';     //--- Set light text arrow color
color text_arrow_color_disabled_dark = C'80,80,80'; //--- Set dark disabled arrow color
color text_arrow_color_disabled_light = C'150,150,150'; //--- Set light disabled arrow color
color text_arrow_color_hover_dark = C'100,100,100'; //--- Set dark hover arrow color
color text_arrow_color_hover_light = C'0,0,0';  //--- Set light hover arrow color
color text_slider_bg_dark = C'80,80,80';        //--- Set dark slider bg
color text_slider_bg_light = C'150,150,150';    //--- Set light slider bg
color text_slider_bg_hover_dark = C'100,100,100';  //--- Set dark slider bg hover
color text_slider_bg_hover_light = C'100,100,100'; //--- Set light slider bg hover

color text_bg_light = clrWhite;                 //--- Set light text bg
color text_bg_dark = clrBlack;                  //--- Set dark text bg
color text_base_light = clrBlack;               //--- Set light text base
color text_base_dark = clrWhite;                //--- Set dark text base

Здесь мы определяем строку "text_usage_text", содержащую полное содержимое руководства по использованию, которое будет отображаться на текстовой панели, структурированное новыми строками для абзацев, разделами, такими как приветственное сообщение, ключевыми функциями с пунктами маркированного списка для панелей / элементов управления / интерактивности/тем/фона, инструкциями по использованию в виде пронумерованных шагов для перемещения/изменения размера / переключения / навигации / закрытия, важные замечания о рисках / совместимости / оптимизации / безопасности / юридических аспектах, способы связи с помощью электронной почты / Telegram и благодарственное письмо - все это отформатировано таким образом, чтобы обеспечить полное руководство пользователя. Это всего лишь произвольное форматирование текста, которое мы решили использовать в качестве демонстрации. Не стесняйтесь изменять его в соответствии с вашими конкретными потребностями.

Затем мы инициализируем переменные для управления прокруткой текста: "text_scroll_pos" - в нулевое значение для текущего смещения, "text_max_scroll" - в нулевое значение для максимального размера прокрутки, "text_slider_height" - в значение двадцать для размера ползунка, "text_movingStateSlider" - в значение false для отслеживания при перетаскивании ползунка, "text_mlbDownY_Slider" и "text_mlbDown_YD_Slider" - в нулевое значение для перемещения мыши в позиции на оси y во время взаимодействия с ползунком, значения "text_total_height" и "text_visible_height" в нулевое значение для полного содержимого и доступной для просмотра высоты, значение "text_scroll_visible" в значение false для управления отображением полосы прокрутки, "text_mouse_in_body" и "prev_text_mouse_in_body" в значение false для текущего/предыдущего состояния мыши в текстовом поле, флаги наведения курсора, такие как "text_scroll_up_hovered", в значение false для направления вверх / вниз / ползунка /области, значение константы "TEXT_MAX_LINES" равным сто в качестве ограничения буфера, а "text_adjustedLineHeight" в нулевое значение для межстрочного интервала. Устанавливаем размеры для полосы прокрутки: "text_scrollbar_full_width" - в зачение шестнадцать для развернутого состояния, "text_scrollbar_thin_width" - в зачение два для свернутого состояния, "text_track_width" - в зачение шестнадцать для установки ширины текста, "text_scrollbar_margin" - в зачение пять для отступов, "text_button_size" - в зачение шестнадцать для кнопок вверх/вниз. Определяем цвета тем оформления: выноска / кнопка bg / нормальная / при наведении курсора, стрелка нормальная / отключена / при наведении курсора для темного / светлого режимов, используя обозначения C'...', а базовый bg / текст - как белый / черный для светлой темы или черный / белый для темной, чтобы обеспечить видимость в разных темах оформления.

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

//+------------------------------------------------------------------+
//| Fill rounded rectangle                                           |
//+------------------------------------------------------------------+
void FillRoundedRectangle(CCanvas &cvs, int x, int y, int w, int h, int radius, uint argb_color)
  {
   if (radius <= 0) {
      cvs.FillRectangle(x, y, x + w - 1, y + h - 1, argb_color);                //--- Fill rectangle if no radius
      return;                          //--- Exit
     }
   radius = MathMin(radius, MathMin(w / 2, h / 2));                             //--- Adjust radius

   cvs.Arc(x + radius, y + radius, radius, radius, DegreesToRadians(180), DegreesToRadians(90), argb_color); //--- Draw top-left arc
   cvs.Arc(x + w - radius - 1, y + radius, radius, radius, DegreesToRadians(270), DegreesToRadians(90), argb_color); //--- Draw top-right arc
   cvs.Arc(x + w - radius - 1, y + h - radius - 1, radius, radius, DegreesToRadians(0), DegreesToRadians(90), argb_color); //--- Draw bottom-right arc
   cvs.Arc(x + radius, y + h - radius - 1, radius, radius, DegreesToRadians(90), DegreesToRadians(90), argb_color); //--- Draw bottom-left arc

   cvs.FillCircle(x + radius, y + radius, radius, argb_color);                  //--- Fill top-left circle
   cvs.FillCircle(x + w - radius - 1, y + radius, radius, argb_color);          //--- Fill top-right circle
   cvs.FillCircle(x + w - radius - 1, y + h - radius - 1, radius, argb_color);  //--- Fill bottom-right circle
   cvs.FillCircle(x + radius, y + h - radius - 1, radius, argb_color);          //--- Fill bottom-left circle

   cvs.FillRectangle(x + radius, y, x + w - radius - 1, y + h - 1, argb_color); //--- Fill horizontal areas

   cvs.FillRectangle(x, y + radius, x + w - 1, y + h - radius - 1, argb_color); //--- Fill vertical areas
  }

Мы реализуем функцию "FillRoundedRectangle", чтобы нарисовать прямоугольник со скругленными углами на заданном контрольном объекте CCanvas, принимая параметры для положения x/y, ширины w/высоты h, радиуса угла и цвета ARGB. Если радиус равен нулю или меньше, мы просто заполняем стандартный прямоугольник от (x,y) до (x+w-1, y+h-1) методом FillRectangle и досрочно завершаем работу. В противном случае, чтобы избежать переполнения поля, мы ограничиваем радиус минимумом, равным половине ширины или высоты, используя MathMin функцию. Мы рисуем четверти дуги с помощью Arc для каждого угла: верхний левый угол от 180 до 270 градусов, верхний правый угол от 270 до 360, нижний правый угол от 0 до 90, нижний левый угол от 90 до 180. Все значения преобразуются в радианы с помощью вспомогательной функции, такой как "DegreesToRadians". Мы определим эту функцию позднее.

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

//+------------------------------------------------------------------+
//| Convert degrees to radians                                       |
//+------------------------------------------------------------------+
double DegreesToRadians(double degrees) {
   return((M_PI * degrees) / 180.0); //--- Perform conversion
}

Мы просто делим умножение 'pi' или M_PI на целевые градусы на 180. Затем мы можем использовать функцию скругления прямоугольников (rounded rectangle) для отображения наших заголовков на панели статистики, обновив эту функцию.

//+------------------------------------------------------------------+
//| Update stats on canvas                                           |
//+------------------------------------------------------------------+
void UpdateStatsOnCanvas()
  {

//---

string headerText = "Account Stats";                               //--- Set header text
canvasStats.FontSet("Arial Bold", StatsHeaderFontSize);            //--- Set font
int textW = canvasStats.TextWidth(headerText);                     //--- Get text width
int textH = canvasStats.TextHeight(headerText);                    //--- Get text height
int pad = 5;                                                       //--- Set padding
int rectX = (statsWidth - textW) / 2 - pad;                        //--- Calculate rect X
int rectY = yPos - pad / 2;                                        //--- Calculate rect Y
int rectW = textW + 2 * pad;                                       //--- Calculate rect width
int rectH = textH + pad;                                           //--- Calculate rect height
uchar alpha = (uchar)(255 * (StatsHeaderBgOpacityPercent / 100.0)); //--- Calculate alpha
uint argbHeaderBg = ColorToARGB(GetStatsHeaderBgColor(), alpha);   //--- Convert bg to ARGB

color header_border = is_dark_theme ? clrWhite : clrBlack;         //--- Get border color
uint argbHeaderBorder = ColorToARGB(header_border, 255);           //--- Convert to ARGB
FillRoundedRectangle(canvasStats, rectX - 1, rectY - 1, rectW + 2, rectH + 2, StatsHeaderBgRadius + 1, argbHeaderBorder); //--- Fill outer rectangle

FillRoundedRectangle(canvasStats, rectX, rectY, rectW, rectH, StatsHeaderBgRadius, argbHeaderBg); //--- Fill inner rectangle

uint argbHeader = ColorToARGB(headerColor, 255);                   //--- Convert header to ARGB
canvasStats.TextOut(statsWidth / 2, yPos, headerText, argbHeader, TA_CENTER); //--- Draw header

//---

   }

Для реализации изменений модифицируем функцию "UpdateStatsOnCanvas", чтобы улучшить визуализацию заголовка с закругленным фоном для более «причесанного» вида. Для каждого заголовка, такого как 'Account Stats' или 'Current Bar Stats', мы устанавливаем шрифт Arial Bold в "StatsHeaderFontSize" с помощью FontSet, измеряем ширину и высоту текста, используя TextWidth и TextHeight, определяем отступы в пять пикселей и вычисляем положение / размеры прямоугольника, отцентрованного с помощью отступов: x как (ширина минус ширина текста)/2 минус отступ, y как текущее значение "yPos" минус половина отступа, ширина как текст плюс двойной отступ, высота как текст плюс отступ.

Мы вычисляем альфа-канал из "StatsHeaderBgOpacityPercent" более чем 100,0 раз по 255, преобразуем его в uchar, а цвет фона преобразуем из "GetStatsHeaderBgColor" в ARGB с этим альфа-каналом. Для рамок мы выбираем белый цвет в темной теме или черный в светлой, преобразуем в полную непрозрачность ARGB и вызываем функцию "FillRoundedRectangle" дважды: один раз для внешней рамки с позицией минус один, размером плюс два, радиусом плюс один, используя ARGB для рамки; затем для внутренней рамки с базовой позицией/размером/радиусом и фоном ARGB. Мы преобразуем цвет заголовка в ARGB с параметром 255, рисуем текст по центру с шириной статистики/2 и координатами "yPos" с помощью TextOut и выравнивания по центру, затем увеличиваем "yPos" на тридцать для создания интервала перед следующим разделом. Остальная часть функции остаётся такой, какой мы её определили ранее. После компиляции получаем следующий результат.

UPDATED STATISTICS HEADINGS WITH ROUNDED RECTANGLES

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

//+------------------------------------------------------------------+
//| Lighten color                                                    |
//+------------------------------------------------------------------+
color LightenColor(color colorValue, double factor)
  {
   int blue = (int)MathMin(255, (colorValue & 0xFF) * factor);         //--- Lighten blue
   int green = (int)MathMin(255, ((colorValue >> 8) & 0xFF) * factor); //--- Lighten green
   int red = (int)MathMin(255, ((colorValue >> 16) & 0xFF) * factor);  //--- Lighten red
   return (color)(blue | (green << 8) | (red << 16));                  //--- Return lightened color
  }

//+------------------------------------------------------------------+
//| Calculate slider height                                          |
//+------------------------------------------------------------------+
int TextCalculateSliderHeight()
  {
   int scroll_area_height = TextPanelHeight - 2 - 2 * text_button_size; //--- Calculate area height
   int slider_min_height = 20;         //--- Set min height
   if (text_total_height <= text_visible_height) return scroll_area_height; //--- Return full if no scroll
   double visible_ratio = (double)text_visible_height / text_total_height;  //--- Calculate ratio
   int height = (int)MathFloor(scroll_area_height * visible_ratio);         //--- Calculate height
   return MathMax(slider_min_height, height);                           //--- Return max of min and calculated
  }

//+------------------------------------------------------------------+
//| Scroll text up                                                   |
//+------------------------------------------------------------------+
void TextScrollUp()
  {
   if (text_adjustedLineHeight > 0 && text_scroll_pos > 0)
     {
      text_scroll_pos = MathMax(0, text_scroll_pos - text_adjustedLineHeight); //--- Scroll up
      UpdateTextOnCanvas();                                            //--- Update canvas
     }
  }

//+------------------------------------------------------------------+
//| Scroll text down                                                 |
//+------------------------------------------------------------------+
void TextScrollDown()
  {
   if (text_adjustedLineHeight > 0 && text_scroll_pos < text_max_scroll)
     {
      text_scroll_pos = MathMin(text_max_scroll, text_scroll_pos + text_adjustedLineHeight); //--- Scroll down
      UpdateTextOnCanvas();                                            //--- Update canvas
     }
  }

//+------------------------------------------------------------------+
//| Update text hover effects                                        |
//+------------------------------------------------------------------+
void TextUpdateHoverEffects(int local_x, int local_y)
  {
   if (!text_scroll_visible) return;                                    //--- Exit if no scroll
   int scrollbar_x = canvasText.Width() - text_track_width - 1;         //--- Get scrollbar X
   int scrollbar_y = 1;                                                 //--- Set scrollbar Y
   int scrollbar_height = TextPanelHeight - 2;                          //--- Get height
   text_scroll_up_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                             local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1); //--- Check up hover
   int down_y = scrollbar_y + scrollbar_height - text_button_size;      //--- Calculate down Y
   text_scroll_down_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                               local_y >= down_y && local_y <= down_y + text_button_size - 1); //--- Check down hover
   int scroll_area_height = scrollbar_height - 2 * text_button_size;    //--- Calculate area height
   int slider_y = scrollbar_y + text_button_size + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
   text_scroll_slider_hovered = text_scroll_area_hovered && (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1 &&
                                 local_y >= slider_y && local_y <= slider_y + text_slider_height - 1); //--- Check slider hover
  }

//+------------------------------------------------------------------+
//| Get line color                                                   |
//+------------------------------------------------------------------+
color GetLineColor(string lineText)
  {
   if (StringLen(lineText) == 0 || lineText == " ") return C'25,25,25'; //--- Return gray for empty
   if (StringFind(lineText, "mutiiriallan.forex@gmail.com") >= 0) return C'255,100,100';   //--- Return red for email
   if (StringFind(lineText, "https://t.me/Forex_Algo_Trader") >= 0) return C'150,100,200'; //--- Return purple for link
   if (StringFind(lineText, "@ForexAlgo-Trader") >= 0) return C'100,150,255';              //--- Return blue for channel
   if (StringFind(lineText, "http") >= 0 || StringFind(lineText, "t.me") >= 0) return C'100,150,255'; //--- Return blue for links
   string start3 = StringSubstr(lineText, 0, 3);                        //--- Get start substring
   if ((start3 == "1. " || start3 == "2. " || start3 == "3. " || start3 == "4. " || start3 == "5. ") &&
       StringFind(lineText, "Initial Setup Instructions") < 0) return C'255,200,100';      //--- Return orange for numbered
   return clrWhite;                                                     //--- Return white default
  }

//+------------------------------------------------------------------+
//| Check if heading                                                 |
//+------------------------------------------------------------------+
bool IsHeading(string lineText)
  {
   if (StringLen(lineText) == 0) return false;                          //--- False for empty
   if (StringGetCharacter(lineText, StringLen(lineText) - 1) == ':') return true; //--- True for colon end
   if (StringFind(lineText, "Canvas Dashboard Usage Guide") >= 0) return true;    //--- True for guide
   if (StringFind(lineText, "Key Features") >= 0) return true;          //--- True for features
   if (StringFind(lineText, "Usage Instructions") >= 0) return true;    //--- True for instructions
   if (StringFind(lineText, "Important Notes") >= 0) return true;       //--- True for notes
   if (StringFind(lineText, "Contact Methods") >= 0) return true;       //--- True for contacts
   if (StringFind(lineText, "NB:") >= 0) return true;                   //--- True for NB
   return false;                                                        //--- Return false
  }

//+------------------------------------------------------------------+
//| Wrap text                                                        |
//+------------------------------------------------------------------+
void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], color &wrappedColors[])
  {
   ArrayResize(wrappedLines, 0);                                        //--- Reset lines
   ArrayResize(wrappedColors, 0);                                       //--- Reset colors
   string paragraphs[];                                                 //--- Declare paragraphs
   int numParagraphs = StringSplit(inputText, '\n', paragraphs);        //--- Split by newline
   for (int p = 0; p < numParagraphs; p++)
     {
      string para = paragraphs[p];                                      //--- Get paragraph
      color paraColor = GetLineColor(para);                             //--- Get color
      if (StringLen(para) == 0)
        {
         int size = ArraySize(wrappedLines);                            //--- Get size
         ArrayResize(wrappedLines, size + 1);                           //--- Resize
         wrappedLines[size] = " ";                                      //--- Add space
         ArrayResize(wrappedColors, size + 1);                          //--- Resize colors
         wrappedColors[size] = C'25,25,25';                             //--- Set gray
         continue;                                                      //--- Continue
        }
      string words[];                                                   //--- Declare words
      int numWords = StringSplit(para, ' ', words);                     //--- Split by space
      string currentLine = "";                                          //--- Initialize line
      for (int w = 0; w < numWords; w++)
        {
         string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line
         canvasText.FontSet(font, fontSize);                            //--- Set font
         int textW = canvasText.TextWidth(testLine);                    //--- Get width
         if (textW <= maxWidth)
           {
            currentLine = testLine;                                     //--- Update line
           }
         else
           {
            if (StringLen(currentLine) > 0)
              {
               int size = ArraySize(wrappedLines);                     //--- Get size
               ArrayResize(wrappedLines, size + 1);                    //--- Resize
               wrappedLines[size] = currentLine;                       //--- Add line
               ArrayResize(wrappedColors, size + 1);                   //--- Resize colors
               wrappedColors[size] = paraColor;                        //--- Set color
              }
            currentLine = words[w];                                    //--- Start new line
           }
        }
      if (StringLen(currentLine) > 0)
        {
         int size = ArraySize(wrappedLines);                           //--- Get size
         ArrayResize(wrappedLines, size + 1);                          //--- Resize
         wrappedLines[size] = currentLine;                             //--- Add last line
         ArrayResize(wrappedColors, size + 1);                         //--- Resize colors
         wrappedColors[size] = paraColor;                              //--- Set color
        }
     }
  }

Мы определяем пару вспомогательных функций. Сначала реализуем функцию "LightenColor", которая осветляет цвет, умножая компоненты RGB на коэффициент больше единицы, ограничивая значение до 255 во избежание перетекания, для использования в настройках темы оформления, например, для изменения цвета текста в темном режиме. Функция принимает значение цвета "colorValue" и множитель типа double, извлекает синий/зеленый/красный цвета с помощью маски/сдвигов, умножает каждый на множитель, преобразованный в целое число с помощью MathMin 255, и объединяет с помощью битовых сдвигов/ИЛИ, преобразует в цвет.

Далее мы создаём функцию "TextCalculateSliderHeight" для вычисления размера ползунка полосы прокрутки пропорционально соотношению видимого контента к общему объёму, обеспечивая минимальный размер для удобства использования. Она рассчитывает высоту области как "TextPanelHeight" минус рамки минус двойной размер "text_button_size", устанавливает минимальное значение равным двадцати, возвращает полную область, если нет прокрутки, в противном случае общую площадь области умноженную на видимый коэффициент, возвращает максимальное значение из минимального и значение. Затем определяем функцию "TextScrollUp" для прокрутки текста вверх, уменьшая значение "text_scroll_pos" на единицу "text_adjustedLineHeight", если оно положительно и больше нуля, фиксируем его равным нулю, вызываем функцию "UpdateTextOnCanvas" для перерисовки. Аналогично, "TextScrollDown" прокручивает текст вниз, увеличивая "text_scroll_pos" на высоту одной строки, если она меньше, чем "text_max_scroll", значение которой ограничено значением max, обновляем холст.

Функция "TextUpdateHoverEffects" определяет наведение курсора мыши на части полосы прокрутки на основе локальных координат для обеспечения визуальной обратной связи. Если невидима, завершает работу. Вычисляет положение/размеры полосы прокрутки, проверяет "text_scroll_up_hovered", если на область наведен курсор и находится в пределах верхней части кнопки, "text_scroll_down_hovered", если область находится в нижней части, "text_scroll_slider_hovered" для текущего диапазона положения ползунка, все зависит от "text_scroll_area_hovered". Мы реализуем "GetLineColor", чтобы классифицировать цвета строк по содержанию для стилизации: серый - для пустого пространства/пробела, красный - для электронной почты, фиолетовый / синий - для определенных ссылок / каналов /http/t.me, оранжевый - для нумерованных списков, не определенных фраз, белый - по умолчанию. "IsHeading" определяет заголовки: false - если они пустые, true - если заканчиваются двоеточием или соответствуют таким фразам, как руководство/функциональные возможности/инструкции/примечания/контакты/NB. На самом деле, для идентификации здесь мы могли бы использовать символы HTML, такие как format, но для простоты мы добавили те, которые хотим распознать в функции. Есть много идей, так что выбирайте ту, которая вам подходит.

Наконец, "WrapText" разбивает входной параметр на строки с ограниченной шириной и соответствующими цветами. Сбрасывает выходные массивы, разбивает их новой строкой на абзацы, для каждого получает цвет, добавляет пробел/серый, если он пуст. Разбивает абзацы на слова, создает /тестирует строки: если тест соответствует измеренной ширине (установлен шрифт temp), добавляем; в противном случае дополняем текущую строку цветом, начинаем новую. Добавляет последнюю строку, если она есть. С помощью этих вспомогательных функций мы теперь можем отрисовать главный холст, используя следующую логику.

//+------------------------------------------------------------------+
//| Update text on canvas                                            |
//+------------------------------------------------------------------+
void UpdateTextOnCanvas()
  {
   canvasText.Erase(0);                //--- Clear text canvas
   
   int textWidth = canvasText.Width(); //--- Get text width
   int textHeight = TextPanelHeight;   //--- Get text height
   
   color text_bg = is_dark_theme ? text_bg_dark : text_bg_light;                  //--- Get bg color
   uint argb_bg = ColorToARGB(text_bg, (uchar)(255 * (TextBackgroundOpacityPercent / 100.0))); //--- Convert to ARGB
   canvasText.FillRectangle(0, 0, textWidth - 1, textHeight - 1, argb_bg);        //--- Fill background
   
   uint argbBorder = ColorToARGB(GetBorderColor(), 255);                          //--- Convert border
   canvasText.Line(0, 0, textWidth - 1, 0, argbBorder);                           //--- Draw top
   canvasText.Line(textWidth - 1, 0, textWidth - 1, textHeight - 1, argbBorder);  //--- Draw right
   canvasText.Line(textWidth - 1, textHeight - 1, 0, textHeight - 1, argbBorder); //--- Draw bottom
   canvasText.Line(0, textHeight - 1, 0, 0, argbBorder); //--- Draw left
   
   int padding = 10;                   //--- Set padding
   int textAreaX = 1 + padding;        //--- Calculate area X
   int textAreaY = 1;                  //--- Set area Y
   int textAreaWidth = textWidth - 2 - padding * 2; //--- Calculate area width
   int textAreaHeight = textHeight - 2; //--- Calculate area height
   string font = "Arial";              //--- Set font
   int fontSize = 16;                  //--- Set font size
   canvasText.FontSet(font, fontSize); //--- Set font
   int lineHeight = canvasText.TextHeight("A"); //--- Get line height
   text_adjustedLineHeight = lineHeight + 3; //--- Adjust line height
   
   text_visible_height = textAreaHeight; //--- Set visible height
   static string wrappedLines[];       //--- Declare wrapped lines
   static color wrappedColors[];       //--- Declare wrapped colors
   static bool wrapped = false;        //--- Initialize wrapped flag
   bool need_scroll = false;           //--- Initialize scroll need
   int reserved_width = 0;             //--- Initialize reserved width
   if (!wrapped)
     {
      WrapText(text_usage_text, font, fontSize, textAreaWidth, wrappedLines, wrappedColors); //--- Wrap text
      wrapped = true;                  //--- Set wrapped flag
     }
   int numLines = ArraySize(wrappedLines);                 //--- Get number of lines
   text_total_height = numLines * text_adjustedLineHeight; //--- Calculate total height
   need_scroll = text_total_height > text_visible_height;  //--- Check if scroll needed
   if (need_scroll)
     {
      reserved_width = text_track_width; //--- Set reserved width
      textAreaWidth -= reserved_width;   //--- Adjust area width
      WrapText(text_usage_text, font, fontSize, textAreaWidth, wrappedLines, wrappedColors); //--- Rewrap text
      numLines = ArraySize(wrappedLines); //--- Update num lines
      text_total_height = numLines * text_adjustedLineHeight;               //--- Update total height
     }
   text_max_scroll = MathMax(0, text_total_height - text_visible_height);   //--- Calculate max scroll
   text_scroll_visible = need_scroll;                                       //--- Set scroll visible
   text_scroll_pos = MathMax(0, MathMin(text_scroll_pos, text_max_scroll)); //--- Adjust scroll pos
   if (text_scroll_visible)
     {
      int scrollbar_y = 1;                //--- Set scrollbar Y
      int scrollbar_height = textHeight - 2; //--- Calculate scrollbar height
      int scroll_area_height = scrollbar_height - 2 * text_button_size;      //--- Calculate area height
      text_slider_height = TextCalculateSliderHeight();                      //--- Calculate slider height
      int scrollbar_x = textWidth - text_track_width - 1;                    //--- Calculate scrollbar X
      color leader_color = is_dark_theme ? text_leader_color_dark : text_leader_color_light; //--- Get leader color
      uint argb_leader = ColorToARGB(leader_color, 255);                     //--- Convert to ARGB
      canvasText.FillRectangle(scrollbar_x, scrollbar_y, scrollbar_x + text_track_width - 1, scrollbar_y + scrollbar_height - 1, argb_leader); //--- Fill leader
      
      int slider_y = scrollbar_y + text_button_size + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
      
      if (text_scroll_area_hovered)
        {
         color button_bg = is_dark_theme ? text_button_bg_dark : text_button_bg_light;                   //--- Get button bg
         color button_bg_hover = is_dark_theme ? text_button_bg_hover_dark : text_button_bg_hover_light; //--- Get hover bg
         color up_bg = text_scroll_up_hovered ? button_bg_hover : button_bg;                             //--- Determine up bg
         uint argb_up_bg = ColorToARGB(up_bg, 255);                                                      //--- Convert to ARGB
         canvasText.FillRectangle(scrollbar_x, scrollbar_y, scrollbar_x + text_track_width - 1, scrollbar_y + text_button_size - 1, argb_up_bg); //--- Fill up button
         color arrow_color = is_dark_theme ? text_arrow_color_dark : text_arrow_color_light;             //--- Get arrow color
         color arrow_color_disabled = is_dark_theme ? text_arrow_color_disabled_dark : text_arrow_color_disabled_light; //--- Get disabled arrow
         color arrow_color_hover = is_dark_theme ? text_arrow_color_hover_dark : text_arrow_color_hover_light; //--- Get hover arrow
         color up_arrow = (text_scroll_pos == 0) ? arrow_color_disabled : (text_scroll_up_hovered ? arrow_color_hover : arrow_color); //--- Determine up arrow color
         uint argb_up_arrow = ColorToARGB(up_arrow, 255);                                                //--- Convert to ARGB
         canvasText.FontSet("Webdings", 22);                                                             //--- Set font
         int arrow_x = scrollbar_x + text_track_width / 2;                                               //--- Calculate arrow X
         int arrow_y = scrollbar_y + (text_button_size / 2) - (canvasText.TextHeight(CharToString(0x35)) / 2); //--- Calculate arrow Y
         canvasText.TextOut(arrow_x, arrow_y, CharToString(0x35), argb_up_arrow, TA_CENTER);             //--- Draw up arrow
         
         int down_y = scrollbar_y + scrollbar_height - text_button_size;                                 //--- Calculate down Y
         color down_bg = text_scroll_down_hovered ? button_bg_hover : button_bg;                         //--- Determine down bg
         uint argb_down_bg = ColorToARGB(down_bg, 255);                                                  //--- Convert to ARGB
         canvasText.FillRectangle(scrollbar_x, down_y, scrollbar_x + text_track_width - 1, down_y + text_button_size - 1, argb_down_bg); //--- Fill down button
         color down_arrow = (text_scroll_pos >= text_max_scroll) ? arrow_color_disabled : (text_scroll_down_hovered ? arrow_color_hover : arrow_color); //--- Determine down arrow
         uint argb_down_arrow = ColorToARGB(down_arrow, 255);                                            //--- Convert to ARGB
         int down_arrow_x = scrollbar_x + text_track_width / 2;                                          //--- Calculate down arrow X
         int down_arrow_y = down_y + (text_button_size / 2) - (canvasText.TextHeight(CharToString(0x36)) / 2); //--- Calculate down arrow Y
         canvasText.TextOut(down_arrow_x, down_arrow_y, CharToString(0x36), argb_down_arrow, TA_CENTER); //--- Draw down arrow
         
         int slider_x = scrollbar_x + text_scrollbar_margin;                                             //--- Calculate slider X
         int slider_w = text_track_width - 2 * text_scrollbar_margin;                                    //--- Calculate slider width
         int cap_radius = slider_w / 2;                                                                  //--- Calculate cap radius
         color slider_bg_color = is_dark_theme ? text_slider_bg_dark : text_slider_bg_light;             //--- Get slider bg
         color slider_bg_hover_color = is_dark_theme ? text_slider_bg_hover_dark : text_slider_bg_hover_light; //--- Get hover bg
         color slider_bg = text_scroll_slider_hovered || text_movingStateSlider ? slider_bg_hover_color : slider_bg_color; //--- Determine bg
         uint argb_slider = ColorToARGB(slider_bg, 255);                                                 //--- Convert to ARGB

         canvasText.Arc(slider_x + cap_radius, slider_y + cap_radius, cap_radius, cap_radius, DegreesToRadians(180), DegreesToRadians(360), argb_slider); //--- Draw top arc
         canvasText.FillCircle(slider_x + cap_radius, slider_y + cap_radius, cap_radius, argb_slider);   //--- Fill top circle

         canvasText.Arc(slider_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, cap_radius, DegreesToRadians(0), DegreesToRadians(180), argb_slider); //--- Draw bottom arc
         canvasText.FillCircle(slider_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, argb_slider); //--- Fill bottom circle

         canvasText.FillRectangle(slider_x, slider_y + cap_radius, slider_x + slider_w, slider_y + text_slider_height - cap_radius, argb_slider); //--- Fill middle
        }
      else
        {
         int thin_w = text_scrollbar_thin_width;                                                         //--- Set thin width
         int thin_x = scrollbar_x + (text_track_width - thin_w) / 2;                                     //--- Calculate thin X
         int cap_radius = thin_w / 2;                                                                    //--- Calculate cap radius
         color slider_bg_color = is_dark_theme ? text_slider_bg_dark : text_slider_bg_light;             //--- Get bg
         uint argb_slider = ColorToARGB(slider_bg_color, 255);                                           //--- Convert to ARGB

         canvasText.Arc(thin_x + cap_radius, slider_y + cap_radius, cap_radius, cap_radius, DegreesToRadians(180), DegreesToRadians(360), argb_slider); //--- Draw top
         canvasText.FillCircle(thin_x + cap_radius, slider_y + cap_radius, cap_radius, argb_slider);     //--- Fill top

         canvasText.Arc(thin_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, cap_radius, DegreesToRadians(0), DegreesToRadians(180), argb_slider); //--- Draw bottom
         canvasText.FillCircle(thin_x + cap_radius, slider_y + text_slider_height - cap_radius, cap_radius, argb_slider); //--- Fill bottom

         canvasText.FillRectangle(thin_x, slider_y + cap_radius, thin_x + thin_w, slider_y + text_slider_height - cap_radius, argb_slider); //--- Fill middle
        }
     }
   
   color text_base = is_dark_theme ? text_base_dark : text_base_light;                                   //--- Get base color
   for (int line = 0; line < numLines; line++)
     {
      string lineText = wrappedLines[line];   //--- Get line text
      if (StringLen(lineText) == 0) continue; //--- Skip empty
      color lineColor = wrappedColors[line];  //--- Get line color
      if (is_dark_theme) lineColor = (lineColor == clrWhite) ? clrWhite : LightenColor(lineColor, 1.5);  //--- Adjust for dark
      else lineColor = (lineColor == clrWhite) ? clrBlack : DarkenColor(lineColor, 0.7);                 //--- Adjust for light
      int line_y = textAreaY + line * text_adjustedLineHeight - text_scroll_pos;                         //--- Calculate line Y
      if (line_y + text_adjustedLineHeight < 0 || line_y > textAreaHeight) continue;                     //--- Skip out of view
      if (IsHeading(lineText))
        {
         canvasText.FontSet("Arial Bold", fontSize); //--- Set bold font
         lineColor = clrDodgerBlue;                  //--- Set heading color
        }
      else canvasText.FontSet(font, fontSize);       //--- Set regular font
      uint argbText = ColorToARGB(lineColor, 255);   //--- Convert to ARGB
      canvasText.TextOut(textAreaX, line_y, lineText, argbText, TA_LEFT); //--- Draw line
     }
   
   canvasText.Update();                       //--- Update text canvas
  }

Здесь мы реализуем функцию "UpdateTextOnCanvas" для отображения прокручиваемого текстового содержимого в объекте "canvasText", обрабатывая фон, рамки, динамическое обтекание, тематический текст с настройками цвета и пользовательскую полосу прокрутки с эффектами при наведении курсора для навигации. Выполняем очистку холста методом Erase, установленным на значение «ноль», получаем текущую ширину/высоту, определяем тематический цвет фона (темный или светлый), преобразуем в ARGB с прозрачностью, равной "TextBackgroundOpacityPercent" (100,0 × 255, преобразованной в uchar), и заполняем прямоугольник от (0,0) до ширина/высота минус один, используя метод FillRectangle.  Преобразуем цвет рамки из функции "GetBorderColor" в ARGB с значением 255 и рисуем верхнюю/правую/нижнюю/левую линии с помощью функции Line.

Устанавливаем отступ в десять, вычисляем площадь текста как x один плюс отступ, y один, ширину как общую минус рамки минус двойное отступление, высоту минус рамки. Установим шрифт Arial на шестнадцать с помощью FontSet, получаем высоту строки от символа A с помощью TextHeight, отрегулируем "text_adjustedLineHeight", добавив три для интервала. Устанавливаем "text_visible_height" для высоты области. Если текст не переносится (статическое значение "wrapped" равно false), вызываем функцию "WrapText" с текстом, шрифтом/размером/шириной области для заполнения статических значений "wrappedLines" и "wrappedColors", а затем устанавливаем значение "wrapped" равным true. Получаем количество строк из размера массива, вычисляем "text_total_height" как количество строк, умноженное на скорректированную высоту. Проверяем, требуется ли прокрутка, если общее пространство превышает видимое; если да, установите значение параметра "reserved_width" равным "text_track_width", уменьшаем ширину области, переносим текст, обновляем количество строк/общую высоту.

Рассчитываем "text_max_scroll" как максимальное значение, равное нулю, или общее значение минус видимая область, устанавливаем "text_scroll_visible" равным need_scroll, и ограничиваем значение "text_scroll_pos" в диапазоне от нуля до максимума. Если видимая, вычисляем положение: y — единица, высота — текст минус рамки, высота области минус двойное значение "text_button_size", "text_slider_height" из "TextCalculateSliderHeight", x — ширина минус дорожка минус единица. Получаем главный цвет темы, преобразуем его в ARGB и заполняем прямоугольник главного цвета темы. Вычислите значение ползунка по оси y на основе отношения положения к значению (площадь минус ползунок). Если "text_scroll_area_hovered", получаем тематический фон кнопки/при наведении курсора, определяем верхний фон; если "text_scroll_up_hovered", преобразовываем в ARGB, заполняем прямоугольник кнопки. Получаем цвета стрелок: обычные/отключенные/при наведении курсора, определяем цвет стрелки вверх (отключенная, если позиция равна нулю, иначе — при наведении курсора/обычный режим), преобразовываем, устанавливаем шрифт Webdings в значение двадцать два, вычисляем координаты стрелки по осям x/y с выравниванием по центру, рисуем стрелку вверх '0x35' с помощью TextOut с выравниванием по центру. Аналогично, для кнопки "вниз" в нижней части оси y, фон/ наведение курсора, заливка, цвет стрелки вниз отключен, если положение находится в максимальном значении, в противном случае при наведении курсора / обычный режим, нарисуем '0x36' по центру.

Для ползунка вычисляем x как x плюс margin, w как track минус double margin, ограничиваем радиус половиной w, получаем в теме фона/эффект наведения слайдера, определяем фон при наведении курсора или движении, преобразуем в ARGB. Нарисуем/зальем верхнюю/нижнюю дуги/окружности с помощью функции "Arc"/FillCircle, используя радианы из "DegreesToRadians", зальем средний прямоугольник. В противном случае, если курсор не наведен, нарисуем тонкий ползунок в центре x с небольшой шириной, радиусом закругления в половину, тематическим фоном ARGB, нарисуем /заполним верхнюю/нижнюю дуги / круги, заполним тонкую среднюю часть.

Получаем базовый цвет темы, проходим циклом по "wrappedLines": пропускаем пустые ячейки, получаем/корректируем цвет (если белый/черный, оставляем, иначе осветляем на 1,5 в темном цвете или затемняем на 0,7 в светлом), вычисляем значение y линии как площадь y плюс длина линии, умноженная на скорректированное значение, минус "text_scroll_pos", пропускаем, если линия выходит за пределы видимости (отрицательное значение или выходит за пределы области). Если задан "IsHeading", установим полужирный шрифт и яркий оттенок лазурного цвета (dodger blue); в противном случае — обычный шрифт. Преобразуем в ARGB, нарисуем с выравниванием по левому краю в области x и линии y. Наконец, для отображения текста обновляем холст с помощью Update. Вызываем функцию при инициализации после определения области холста следующим образом.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Other panels logic
   
   if (EnableTextPanel)
     {
      int textY = currentCanvasY + header_height + gap_y + currentHeight + PanelGap; //--- Calculate text Y
      if (!canvasText.CreateBitmapLabel(0, 0, canvasTextName, currentCanvasX, textY, header_width, TextPanelHeight, COLOR_FORMAT_ARGB_NORMALIZE))
        {
         Print("Failed to create Text Canvas");       //--- Log text creation failure
        }
      textCreated = true;                             //--- Set text created flag
     }
   
   if (EnableTextPanel) UpdateTextOnCanvas();         //--- Update text if enabled
   
   ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); //--- Enable mouse wheel events
   ChartRedraw();                                     //--- Redraw chart
   return(INIT_SUCCEEDED);                            //--- Return success
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if (textCreated) canvasText.Destroy();             //--- Destroy text if created
   ChartRedraw();                                     //--- Redraw chart
  }


//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBarTime = 0;                   //--- Initialize last time
   datetime currentBarTime = iTime(_Symbol, _Period, 0); //--- Get current time
   if (currentBarTime > lastBarTime)
     {
      UpdateGraphOnCanvas();                         //--- Update graph
      if (EnableStatsPanel) UpdateStatsOnCanvas();   //--- Update stats
      if (EnableTextPanel) UpdateTextOnCanvas();     //--- Update text
      ChartRedraw();                                 //--- Redraw chart
      lastBarTime = currentBarTime;                  //--- Update last time
     }
  }

Сначала изменяем обработчик OnInit, чтобы включить создание текстовой панели, если значение параметра "EnableTextPanel" равно true. Следуя логике других панелей, мы вычисляем ее положение по y как "currentCanvasY" плюс "header_height", "gap_y", "currentHeight" и "PanelGap", затем создаем графическую метку с CreateBitmapLabel в нулевом подокне, имя "canvasTextName", положение, ширину заголовка, "TextPanelHeight" и COLOR_FORMAT_ARGB_NORMALIZE, выводим сообщение об ошибке в случае сбоя, устанавливаем для "textCreated" значение true в случае успеха. Вызываем "UpdateTextOnCanvas", если включена, для первоначального отображения текста. Включаем события прокрутки колесика мыши с помощью ChartSetInteger, используя CHART_EVENT_MOUSE_WHEEL в значении true, которая понадобится для идентификации перемещения графика при динамической прокрутке, перерисовки с помощью ChartRedraw и возврата INIT_SUCCEEDED.

Затем обновляем обработчик "OnDeinit", чтобы условно уничтожить текстовый холст, если значение "textCreated" равно true, используя "canvasText.Destroy", а затем перерисовываем. Настраиваем обработчик OnTick таким образом, чтобы он включал обновления текстовой панели на новых барах. Используя статическое значение "lastBarTime", равное нулю, получаем время текущего бара с помощью iTime, если оно больше, вызываем "UpdateGraphOnCanvas", опциональную "UpdateStatsOnCanvas" если "EnableStatsPanel", "UpdateTextOnCanvas" если "EnableTextPanel", перерисовываем, обновляем время последнего бара. После компиляции получаем следующий результат.

RENDERED TEXT PANEL

На изображении видно, что текстовая панель теперь отрисована. Сейчас нам нужно обновить обработчик событий графика, чтобы он обрабатывал события графика для прокрутки текста, который мы отрисовали. Для достижения этого результата мы использовали следующую логику. Мы добавили только те изменения, которые взяты для достижения конечного результата, и исключили другую логику, реализованную нами в предыдущих версиях.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if (id == CHARTEVENT_CHART_CHANGE)
     {
      //--- Existing logic
      
      if (EnableTextPanel) UpdateTextOnCanvas(); //--- Update text
      ChartRedraw();                             //--- Redraw chart
     }
   else if (id == CHARTEVENT_MOUSE_MOVE)
     {
      //--- Existing logic
      
      if (EnableTextPanel && !panels_minimized)
        {
         int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE); //--- Get text X
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y
         int text_canvas_w = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XSIZE);     //--- Get width
         int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE);     //--- Get height
         bool is_over_text = (mouse_x >= text_canvas_x && mouse_x <= text_canvas_x + text_canvas_w &&
                              mouse_y >= text_canvas_y && mouse_y <= text_canvas_y + text_canvas_h); //--- Check over text
         bool prev_scroll_hovered = text_scroll_area_hovered;                             //--- Store prev scroll hover
         text_scroll_area_hovered = false;         //--- Reset area hover
         if (is_over_text)
           {
            int local_x = mouse_x - text_canvas_x; //--- Get local X
            int local_y = mouse_y - text_canvas_y; //--- Get local Y
            if (local_x >= text_canvas_w - text_track_width - 1)
              {
               text_scroll_area_hovered = true;    //--- Set area hover
              }
            bool prev_up = text_scroll_up_hovered; //--- Store prev up
            bool prev_down = text_scroll_down_hovered;     //--- Store prev down
            bool prev_slider = text_scroll_slider_hovered; //--- Store prev slider
            TextUpdateHoverEffects(local_x, local_y);      //--- Update hovers
            if (prev_scroll_hovered != text_scroll_area_hovered || prev_up != text_scroll_up_hovered || prev_down != text_scroll_down_hovered || prev_slider != text_scroll_slider_hovered)
              {
               UpdateTextOnCanvas();               //--- Update text
               ChartRedraw();                      //--- Redraw chart
              }
            text_mouse_in_body = (local_x < text_canvas_w - text_track_width - 1); //--- Set body flag
           }
         else
           {
            bool need_redraw = prev_scroll_hovered || text_scroll_up_hovered || text_scroll_down_hovered || text_scroll_slider_hovered; //--- Check redraw need
            if (need_redraw)
              {
               text_scroll_area_hovered = false;   //--- Reset area
               text_scroll_up_hovered = false;     //--- Reset up
               text_scroll_down_hovered = false;   //--- Reset down
               text_scroll_slider_hovered = false; //--- Reset slider
               UpdateTextOnCanvas();               //--- Update text
               ChartRedraw();                      //--- Redraw chart
              }
            text_mouse_in_body = false;            //--- Reset body flag
           }
         // New: Toggle CHART_MOUSE_SCROLL on enter/leave text body
         if (text_mouse_in_body != prev_text_mouse_in_body)
           {
            ChartSetInteger(0, CHART_MOUSE_SCROLL, !text_mouse_in_body); //--- Toggle scroll
            prev_text_mouse_in_body = text_mouse_in_body;                //--- Update prev
           }
        }
      
      if (mouse_state == 1 && prev_mouse_state == 0)
        {
         
         //--- Existing logic
         
         if (EnableTextPanel && !panels_minimized && text_scroll_visible && text_scroll_area_hovered)
           {
            int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE); //--- Get text X
            int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE); //--- Get text Y
            int local_x = mouse_x - text_canvas_x;  //--- Get local X
            int local_y = mouse_y - text_canvas_y;  //--- Get local Y
            int scrollbar_x = canvasText.Width() - text_track_width - 1;                     //--- Get scrollbar X
            int scrollbar_y = 1;                    //--- Set scrollbar Y
            int scrollbar_height = TextPanelHeight - 2;                                      //--- Get height
            int scroll_area_y = scrollbar_y + text_button_size;                              //--- Calculate area Y
            int scroll_area_height = scrollbar_height - 2 * text_button_size;                //--- Calculate area height
            int slider_y = scroll_area_y + (int)(((double)text_scroll_pos / text_max_scroll) * (scroll_area_height - text_slider_height)); //--- Calculate slider Y
            if (local_x >= scrollbar_x && local_x <= scrollbar_x + text_track_width - 1)
              {
               if (local_y >= scrollbar_y && local_y <= scrollbar_y + text_button_size - 1)
                 {
                  TextScrollUp();                   //--- Scroll up
                 }
               else if (local_y >= scrollbar_y + scrollbar_height - text_button_size && local_y <= scrollbar_y + scrollbar_height - 1)
                 {
                  TextScrollDown();                 //--- Scroll down
                 }
               else if (local_y >= scroll_area_y && local_y <= scroll_area_y + scroll_area_height - 1)
                 {
                  if (local_y >= slider_y && local_y <= slider_y + text_slider_height - 1)
                    {
                     text_movingStateSlider = true;  //--- Set moving slider
                     text_mlbDownY_Slider = local_y; //--- Set down Y
                     text_mlbDown_YD_Slider = slider_y;                                      //--- Set down YD
                     ChartSetInteger(0, CHART_MOUSE_SCROLL, false);                          //--- Disable scroll
                    }
                  else
                    {
                     int new_slider_y = local_y - text_slider_height / 2;                    //--- Calculate new Y
                     new_slider_y = MathMax(scroll_area_y, MathMin(new_slider_y, scroll_area_y + scroll_area_height - text_slider_height)); //--- Clamp Y
                     double ratio = (double)(new_slider_y - scroll_area_y) / (scroll_area_height - text_slider_height); //--- Calculate ratio
                     text_scroll_pos = (int)MathRound(ratio * text_max_scroll);              //--- Update pos
                    }
                  UpdateTextOnCanvas();             //--- Update text
                  ChartRedraw();                    //--- Redraw chart
                 }
              }
           }
        }

      else if (text_movingStateSlider && mouse_state == 1)
        {
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE);    //--- Get text Y
         int local_y = mouse_y - text_canvas_y;                                              //--- Get local Y
         int delta_y = local_y - text_mlbDownY_Slider;                                       //--- Calculate delta Y
         int new_slider_y = text_mlbDown_YD_Slider + delta_y;                                //--- Calculate new slider Y
         int scrollbar_y = 1;                                                                //--- Set scrollbar Y
         int scrollbar_height = TextPanelHeight - 2;                                         //--- Get height
         int slider_min_y = scrollbar_y + text_button_size;                                  //--- Calculate min Y
         int slider_max_y = scrollbar_y + scrollbar_height - text_button_size - text_slider_height;  //--- Calculate max Y
         new_slider_y = MathMax(slider_min_y, MathMin(new_slider_y, slider_max_y));                   //--- Clamp Y
         double scroll_ratio = (double)(new_slider_y - slider_min_y) / (slider_max_y - slider_min_y); //--- Calculate ratio
         int new_scroll_pos = (int)MathRound(scroll_ratio * text_max_scroll);                //--- Calculate new pos
         if (new_scroll_pos != text_scroll_pos)
           {
            text_scroll_pos = new_scroll_pos; //--- Update pos
            UpdateTextOnCanvas();             //--- Update text
            ChartRedraw();                    //--- Redraw chart
           }
        }
      else if (mouse_state == 0 && prev_mouse_state == 1)
        {
         //--- Existing logic
         
         if (text_movingStateSlider)
           {
            text_movingStateSlider = false;  //--- Reset slider moving
            ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable scroll
            UpdateTextOnCanvas();            //--- Update text
            ChartRedraw();                   //--- Redraw chart
           }
        }
      
      last_mouse_x = mouse_x;               //--- Update last X
      last_mouse_y = mouse_y;               //--- Update last Y
      prev_mouse_state = mouse_state;       //--- Update prev state
     }
   else if (id == CHARTEVENT_MOUSE_WHEEL)
     {
      int flg_keys = (int)(lparam >> 32);   //--- Get keys
      int mx = (int)(short)lparam;          //--- Get X
      int my = (int)(short)(lparam >> 16);  //--- Get Y
      int delta = (int)dparam;              //--- Get delta
      
      if (EnableTextPanel && !panels_minimized && text_scroll_visible)
        {
         int text_canvas_x = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XDISTANCE);       //--- Get text X
         int text_canvas_y = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YDISTANCE);       //--- Get text Y
         int text_canvas_w = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_XSIZE);           //--- Get width
         int text_canvas_h = (int)ObjectGetInteger(0, canvasTextName, OBJPROP_YSIZE);           //--- Get height
         bool is_over_text_body = (mx >= text_canvas_x && mx <= text_canvas_x + text_canvas_w - text_track_width &&
                                   my >= text_canvas_y && my <= text_canvas_y + text_canvas_h); //--- Check over body
         if (is_over_text_body)
           {
            int scroll_step = 20;            //--- Set step
            text_scroll_pos += (delta > 0 ? -scroll_step : scroll_step);                        //--- Adjust pos
            text_scroll_pos = MathMax(0, MathMin(text_scroll_pos, text_max_scroll));            //--- Clamp pos
            UpdateTextOnCanvas();            //--- Update text
            
            int current_scale = (int)ChartGetInteger(0, CHART_SCALE);                           //--- Get scale
            int adjust = (delta > 0 ? 1 : -1); // Swap to (delta > 0 ? -1 : 1) if wheel direction is opposite
            int revert_scale = current_scale + adjust;                                          //--- Calculate revert
            revert_scale = MathMax(0, MathMin(5, revert_scale));                                //--- Clamp scale
            ChartSetInteger(0, CHART_SCALE, revert_scale);                                      //--- Set scale
            
            ChartRedraw();                    //--- Redraw chart
           }
        }
     }
  }

Здесь мы модифицируем обработчик OnChartEvent, чтобы включить интерактивность текстовой панели, расширяя обработку перемещений мыши, если значение "EnableTextPanel" равно true, а не "panels_minimized". Мы извлекаем положение/ширину/высоту текстового холста с помощью ObjectGetInteger для OBJPROP_XDISTANCE/"OBJPROP_YDISTANCE"/"OBJPROP_XSIZE"/"OBJPROP_YSIZE", проверяем, находится ли мышь над всей текстовой областью, сохраняем предыдущее значение "text_scroll_area_hovered" и сбрасываем его в значение false. Если над ней, вычисляем локальные координаты x/ y от положения мыши минус положение холста, если локальная координата x в диапазоне полосы прокрутки (ширина минус "text_track_width" минус единица до конца), установим значение области наведения курсора как true.

Сохраняем предыдущие перемещения вверх / вниз / наведения ползунка, вызываем "TextUpdateHoverEffects" с локальными координатами, чтобы обновить их, если какое-либо наведение или область изменились по сравнению с предыдущими, вызываем "UpdateTextOnCanvas" и перерисовываем. Устанавливаем параметр "text_mouse_in_body" в значение true, если локальная точка x находится слева от полосы прокрутки. В противном случае, если курсор не находится над текстом, но предыдущие наведения указывают на необходимость перерисовки, сбрасываем значения параметров область/верх/низ/наведения ползунка на false, обновляем текст/выполняем перерисовку. Если значение параметра "text_mouse_in_body" изменилось по сравнению с "prev_text_mouse_in_body", переключаем прокрутку графика мышью с помощью ChartSetInteger "CHART_MOUSE_SCROLL" в значение false в теле окна (выход true), чтобы разрешить прокрутку колесиком мыши без масштабирования графика, обновляем предыдущий результат.

При нажатии кнопки мыши (состояние один, предыдущее — ноль), если наведен курсор на текст/прокрутку/область, получаем локальные координаты x/y/полосы прокрутки x/y/высоты/области y/высоты/ползунка y, как и раньше; если локальный x находится в полосе прокрутки: если локальный y при отжатой кнопке, вызываем "TextScrollUp", при нажатой кнопке — "TextScrollDown", иначе, если в области: если у ползунка "text_movingStateSlider" установлена в значение true, сохраняем "text_mlbDownY_Slider" как локальную координату y, "text_mlbDown_YD_Slider" как ползунок y, отключаем прокрутку. В противном случае вычисляем новый ползунок y как локальную координату y минус половина "text_slider_height" ограниченную областью y до области y плюс высота минус "text_slider_height"; соотношение как (новая минус область y) деленное на (высота минус "text_slider_height"), устанавливаем "text_scroll_pos" равной округленному соотношению, умноженному на "text_max_scroll". Затем обновляем текст/перерисовываем.

Если "text_movingStateSlider" имеет значение true и состояние равно единице (удерживается), получаем локальную координату по оси y от мыши минус текст по y, дельта как локальная координата минус "text_mlbDownY_Slider", новая координата ползунка по оси y, как "text_mlbDown_YD_Slider" плюс дельта, ограничиваем значение ползунка минимальным значением по оси y (y плюс "text_button_size") до максимального значения по оси y (y плюс высота минус "text_button_size" минус "text_slider_height"), соотношение как (новое значение минус минимальное) деленное на (максимальное значение минус минимальное), новое положение как округленное соотношение, умноженное на "text_max_scroll", если отличается от текущего значения, обновляем "text_scroll_pos", текст, выполняем перерисовку.

При не нажатой кнопке мыши (состояние ноль, предыдущее), если для параметра "text_movingStateSlider" установлено значение false, включаем прокрутку на значение true, обновляем текст и выполняем перерисовку. В событие изменения графика добавляем необязательный вызов функции "UpdateTextOnCanvas", если "EnableTextPanel" имеет значение true, перед перерисовкой. Для события прокрутки колесика мыши преобразуем "flg_keys" из "lparam" со сдвигом 32, mx из short "lparam", my из short "lparam" со сдвигом 16, delta из "dparam" как целые числа. Если текст/прокрутка видимы, получаем свойства текста, проверяем, не выходит ли он за пределы тела (mx/my по оси x до x плюс ширина минус "text_track_width", по оси y до y плюс высота), если да, то шаг "text_scroll_pos" в двадцать отрицательных/положительных значений при delta >0/<0, ограничиваем ноль значением "text_max_scroll", обновляем текст, получаем текущий масштаб графика с помощью ChartGetInteger "CHART_SCALE", отрегулируем +1/-1 при delta >0/<0 с ограничением 0-5, установим обратно с помощью "ChartSetInteger", чтобы отменить масштабирование, перерисовываем. После компиляции получаем следующий результат.

TEXT CANVAS TEST

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


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

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

COMPLETE CANVAS DASHBOARD BACKTEST


Заключение

В заключение, мы улучшили ценовую панель на основе холста в MQL5, реализовав прокручиваемый текстовый холст с пиксельной точностью для руководств по использованию, обойдя ограничения нативного интерфейса с помощью сглаживания для плавной отрисовки. Также, мы реализовали закругленную пользовательскую полосу прокрутки, которая расширяется при наведении курсора, с кнопками и ползунком для навигации, элементами, отражающими тему оформления, динамическим переносом текста с настройкой цвета и поддержкой функций колесика/кликов/перетаскивания мыши для бесшовного взаимодействия. Система интегрирует текстовую панель с существующими графиками / статистикой, поддерживая функции перетаскивания / изменения размера / оформления / сворачивания и обеспечивает эффективное обновление на основе событий для получения комплексного инструмента мониторинга. Благодаря этому усовершенствованию прокручиваемого текстового холста с пиксельной точностью вы можете предоставлять подробные инструкции для пользователей в пределах дашборда, что позволит далее оптимизировать вашу торговую деятельность. Удачной торговли!

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

Прикрепленные файлы |
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Нейросети в трейдинге: Многодоменная архитектура анализа финансовых данных (Основные компоненты) Нейросети в трейдинге: Многодоменная архитектура анализа финансовых данных (Основные компоненты)
В статье продолжается перенос подходов фреймворка MDL в область решения задач финансовых рынков. Рассмотрены модули унифицированной токенизации разнородных данных, доменно-ориентированного внимание и Feature Self-Iteration, позволяющий эффективнее работать с историей признаков. Особое внимание уделено архитектурным решениям, снижающим вычислительную нагрузку и сохраняющим рыночный контекст в процессе анализа.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Оптимизатор ястребов Харриса — Harris Hawks Optimization (HHO) Оптимизатор ястребов Харриса — Harris Hawks Optimization (HHO)
Мы реализуем в MQL5 алгоритм Harris Hawks Optimization и разбираем пять режимов движения агентов, управляемых единственным параметром — убывающей энергией побега E. Представлен класс C_AO_HHO, совместимый с унифицированным тестовым стендом, с воспроизводимой реализацией полёта Леви. Алгоритм протестирован на функциях Hilly, Forest и Megacity при 5, 25 и 500 координатах — результаты указывают на аномальное поведение.