English
preview
Торговые инструменты на MQL5 (Часть 19): Создание интерактивной палитры инструментов графической разметки

Торговые инструменты на MQL5 (Часть 19): Создание интерактивной палитры инструментов графической разметки

MetaTrader 5Трейдинг |
293 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

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

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

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


Разработка интерактивных палитр инструментов для графиков

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

TOOL PALETTE OBJECTIVES


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

Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Советники» (Experts), щелкните кнопкой мыши на вкладке "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нужно будет объявить некоторые входные параметры и глобальные переменные, которые будем использовать во всей программе.

//+------------------------------------------------------------------+
//|                                                Tools Palette.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

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CCanvas canvasHeader;                                   //--- Declare header canvas
CCanvas canvasTools;                                    //--- Declare tools canvas

string canvasHeaderName = "HeaderCanvas";               //--- Set header canvas name
string canvasToolsName = "ToolsCanvas";                 //--- Set tools canvas name

input int CanvasX = 20;                                 // Panel X position
input int CanvasY = 30;                                 // Panel Y position
input int CanvasWidth = 350;                            // Panel width
input int CanvasHeight = 200;                           // Panel height
input double BackgroundOpacity = 0.8;                   // Background opacity

bool is_dark_theme = true;                              //--- Set initial dark theme flag
color DarkHeaderColor = C'60,60,60';                    //--- Set dark header color
color DarkHeaderHoverColor = clrRed;                    //--- Set dark header hover color
color DarkHeaderDragColor = clrMediumBlue;              //--- Set dark header drag color
color DarkHeaderTextColor = clrWhite;                   //--- Set dark header text color
color DarkBorderColor = clrBlack;                       //--- Set dark border color
color DarkTopColor = clrBlack;                          //--- Set dark top color
color DarkIconColor = clrWhite;                         //--- Set dark icon color
color LightHeaderColor = clrSilver;                     //--- Set light header color
color LightHeaderHoverColor = clrRed;                   //--- Set light header hover color
color LightHeaderDragColor = clrMediumBlue;             //--- Set light header drag color
color LightHeaderTextColor = clrBlack;                  //--- Set light header text color
color LightBorderColor = clrBlack;                      //--- Set light border color
color LightTopColor = clrWhite;                         //--- Set light top color
color LightIconColor = clrBlack;                        //--- Set light icon color
color DarkInstructionColor = clrLime;                   //--- Set dark instruction color
color LightInstructionColor = clrGreen;                 //--- Set light instruction color
color DarkStatusColor = clrMagenta;                     //--- Set dark status color
color LightStatusColor = clrPurple;                     //--- Set light status color
color DarkActiveBtnColor = clrGreen;                    //--- Set dark active button color
color LightActiveBtnColor = clrDarkGreen;               //--- Set light active button color
color DarkHoverBtnColor = clrDarkGray;                  //--- Set dark hover button color
color LightHoverBtnColor = clrLightGray;                //--- Set light hover button color

int currentCanvasX = CanvasX;                           //--- Initialize current X position
int currentCanvasY = CanvasY;                           //--- Initialize current Y position
int currentWidth = CanvasWidth;                         //--- Initialize current width
int currentHeight = CanvasHeight;                       //--- Initialize current height
bool panel_dragging = false;                            //--- Initialize panel dragging flag
int panel_drag_x = 0, panel_drag_y = 0;                 //--- Initialize drag coordinates
int panel_start_x = 0, panel_start_y = 0;               //--- Initialize start coordinates
bool resizing = false;                                  //--- Initialize resizing flag

enum ENUM_RESIZE_MODE {
   NONE,                                                // No resize
   BOTTOM,                                              // Bottom resize
   RIGHT,                                               // Right resize
   BOTTOM_RIGHT                                         // Bottom-right resize
};

ENUM_RESIZE_MODE resize_mode = NONE;                    //--- Initialize resize mode
ENUM_RESIZE_MODE hover_mode = NONE;                     //--- Initialize hover mode
int resize_start_x = 0, resize_start_y = 0;             //--- Initialize resize start coordinates
int start_width = 0, start_height = 0;                  //--- Initialize start dimensions
const int resize_thickness = 5;                         //--- Set resize thickness
const int min_width = 150;                              //--- Set minimum width
const int min_height = 200;                             //--- Set minimum height
int hover_mouse_local_x = 0;                            //--- Initialize local hover X
int hover_mouse_local_y = 0;                            //--- Initialize local hover Y
bool header_hovered = false;                            //--- Initialize header hover flag
bool minimize_hovered = false;                          //--- Initialize minimize hover flag
bool close_hovered = false;                             //--- Initialize close hover flag
bool theme_hovered = false;                             //--- Initialize theme hover flag
bool resize_hovered = false;                            //--- Initialize resize hover flag
int prev_mouse_state = 0;                               //--- Initialize previous mouse state
int header_height = 27;                                 //--- Set header height
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 panel_minimized = false;                           //--- Initialize minimized flag

enum TOOL_TYPE {
   TOOL_NONE,                                           // No tool
   TOOL_CROSSHAIR,                                      // Crosshair tool
   TOOL_TRENDLINE,                                      // Trendline tool
   TOOL_HLINE,                                          // Horizontal line tool
   TOOL_VLINE,                                          // Vertical line tool
   TOOL_RECTANGLE,                                      // Rectangle tool
   TOOL_FIBO,                                           // Fibonacci tool
   TOOL_TEXT,                                           // Text tool
   TOOL_ARROW                                           // Arrow tool
};

TOOL_TYPE active_tool = TOOL_NONE;                      //--- Initialize active tool
bool drawing_first_click = false;                       //--- Initialize first click flag
datetime draw_time1 = 0;                                //--- Initialize draw time 1
double draw_price1 = 0.0;                               //--- Initialize draw price 1
long local_chart_id = 0;                                //--- Initialize local chart ID
string current_instruction = "";                        //--- Initialize current instruction

struct ToolButton {                                     // Define tool button structure
   string symbol;                                       // Store symbol
   string font;                                         // Store font
   TOOL_TYPE type;                                      // Store type
   string tooltip;                                      // Store tooltip
};

ToolButton buttons[8];                                  //--- Declare buttons array
int hovered_tool_index = -1;                            //--- Initialize hovered tool index

bool measuring = false;                                 //--- Initialize measuring flag
datetime fixed_time = 0;                                //--- Initialize fixed time
double fixed_price = 0.0;                               //--- Initialize fixed price
ulong last_click_time = 0;                              //--- Initialize last click time

const int tools_y_offset = -1;                          //--- Set tools Y offset

Начинаем реализацию с включения библиотеки Canvas с помощью команды "#include <Canvas/Canvas.mqh>", чтобы получить доступ к основным графическим функциям для создания палитры. Далее мы объявляем глобальные объекты Canvas "canvasHeader" и "canvasTools" для заголовка и основной области панели, присваивая им имена вроде "HeaderCanvas" и "ToolsCanvas" для дальнейшего обращения к ним в коде, а также указываем пользовательские входные параметры положения, размера и прозрачности фона для возможности настройки. Для поддержки тем оформления мы инициализируем параметр "is_dark_theme" значением true и определяем цветовые константы для темного и светлого режимов, охватывающие заголовки, границы, значки, указания, состояние и кнопки, обеспечивая визуальную согласованность между переключателями.

Мы отслеживаем состояние панели с помощью переменных, определяющих текущее положение и размеры, флажки перетаскивания, режимы изменения размера с помощью перечисления "ENUM_RESIZE_MODE" (NONE, BOTTOM, RIGHT, BOTTOM_RIGHT), состояния при наведении курсора, а также константы, такие как минимальные размеры и толщина для ограничения удобства использования. Для инструментов мы определяем перечисление "TOOL_TYPE", перечисляющее параметры от NONE до ARROW, инициализируем "active_tool" значением NONE и управляем флагами рисования, такими как "drawing_first_click", а также структурой "ToolButton", содержащей символ, шрифт, тип и всплывающую подсказку, создавая массив "buttons[8]" для элементов палитры. Наконец, мы настроили переменные для режима измерения, фиксированных точек, времени клика, индексов при наведении курсора и смещения по оси Y для выравнивания инструментов, подготавливая таким образом интерактивные функции. Затем нам понадобятся некоторые вспомогательные функции, чтобы сделать код модульным.

//+------------------------------------------------------------------+
//| Get theme-aware colors                                           |
//+------------------------------------------------------------------+
color GetHeaderColor() { return is_dark_theme ? DarkHeaderColor : LightHeaderColor; }                //--- Return header color based on theme
color GetHeaderHoverColor() { return is_dark_theme ? DarkHeaderHoverColor : LightHeaderHoverColor; } //--- Return header hover color based on theme
color GetHeaderDragColor() { return is_dark_theme ? DarkHeaderDragColor : LightHeaderDragColor; }    //--- Return header drag color based on theme
color GetHeaderTextColor() { return is_dark_theme ? DarkHeaderTextColor : LightHeaderTextColor; }    //--- Return header text color based on theme
color GetBorderColor() { return is_dark_theme ? DarkBorderColor : LightBorderColor; }                //--- Return border color based on theme
color GetTopColor() { return is_dark_theme ? DarkTopColor : LightTopColor; }                         //--- Return top color based on theme
color GetIconColor() { return is_dark_theme ? DarkIconColor : LightIconColor; }                      //--- Return icon color based on theme
color GetInstructionColor() { return is_dark_theme ? DarkInstructionColor : LightInstructionColor; } //--- Return instruction color based on theme
color GetStatusColor() { return is_dark_theme ? DarkStatusColor : LightStatusColor; }                //--- Return status color based on theme
color GetActiveBtnColor() { return is_dark_theme ? DarkActiveBtnColor : LightActiveBtnColor; }       //--- Return active button color based on theme
color GetHoverBtnColor() { return is_dark_theme ? DarkHoverBtnColor : LightHoverBtnColor; }          //--- Return hover button color based on theme

//+------------------------------------------------------------------+
//| Get tool name from type                                          |
//+------------------------------------------------------------------+
string GetToolName(TOOL_TYPE t) {
   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      if (buttons[i].type == t) {                               //--- Check if type matches
         return StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Return tool name
      }
   }
   return "None";                                               //--- Return default if no match
}

//+------------------------------------------------------------------+
//| Check if tool is single-click                                    |
//+------------------------------------------------------------------+
bool IsSingleClickTool(TOOL_TYPE t) {
   switch(t) {                                                  //--- Switch on tool type
      case TOOL_HLINE:                                          //--- Handle horizontal line
      case TOOL_VLINE:                                          //--- Handle vertical line
      case TOOL_TEXT:                                           //--- Handle text
      case TOOL_ARROW:                                          //--- Handle arrow
         return true;                                           //--- Return true for single-click tools
      default:                                                  //--- Handle default
         return false;                                          //--- Return false otherwise
   }
}

Далее мы определяем ряд функций-геттеров, таких как "GetHeaderColor", для получения цветов, зависящих от темы оформления, каждая из которых возвращает темный или светлый вариант на основе флага "is_dark_theme", обеспечивая согласованное оформление заголовков, наведений курсора, перетаскиваемых элементов, текста, границ, значков, указаний, статуса, активных кнопок и наведений курсора без избыточных проверок. Далее функция "GetToolName" извлекает имя инструмента из его типа, проходя циклом по массиву "buttons", сопоставляя тип и выделяя подстроку всплывающей подсказки до "Tool" для получения корректного результата; по умолчанию, если совпадения нет, значение устанавливается на "None". Для классификации инструментов мы реализуем функцию "IsSingleClickTool", используя оператор switch для типа инструмента, возвращая true для горизонтальных/вертикальных линий, текста и стрелок, требующих только одного щелчка, и false в противном случае для точной обработки событий. После этого мы можем инициализировать создание заголовка и основной части палитры.

//+------------------------------------------------------------------+
//| Draw header                                                      |
//+------------------------------------------------------------------+
void DrawHeader() {
   canvasHeader.Erase(0);                                       //--- Erase header canvas

   color header_bg = panel_dragging ? GetHeaderDragColor() : (header_hovered ? GetHeaderHoverColor() : GetHeaderColor()); //--- Get header background color
   canvasHeader.FillRectangle(0, 0, currentWidth - 1, header_height - 1, ColorToARGB(header_bg, 255)); //--- Fill header rectangle

   uint argbBorder = ColorToARGB(GetBorderColor(), 255);        //--- Get border ARGB
   canvasHeader.Rectangle(0, 0, currentWidth - 1, header_height - 1, argbBorder); //--- Draw border rectangle

   canvasHeader.FontSet("Arial Bold", 13);                      //--- Set font for title
   canvasHeader.TextOut(10, (header_height - 13)/2, "Tools Palette", ColorToARGB(GetHeaderTextColor(), 255)); //--- Draw title text

   int button_y = (header_height - 20)/2;                       //--- Compute button Y position

   int theme_button_x = currentWidth + theme_x_offset;          //--- Compute theme button X
   canvasHeader.FontSet("Wingdings", 20);                       //--- Set font for theme symbol
   string theme_symbol = CharToString(91);                      //--- Set theme symbol
   int theme_w = canvasHeader.TextWidth(theme_symbol);          //--- Get theme symbol width
   int theme_x = theme_button_x + (button_size - theme_w) / 2;  //--- Compute theme X position
   color theme_color = theme_hovered ? clrYellow : GetHeaderTextColor(); //--- Get theme color
   canvasHeader.TextOut(theme_x, button_y, theme_symbol, ColorToARGB(theme_color, 255)); //--- Draw theme symbol

   int min_button_x = currentWidth + minimize_x_offset;         //--- Compute minimize button X
   string min_symbol = panel_minimized ? CharToString(111) : CharToString(114); //--- Set minimize symbol
   int min_w = canvasHeader.TextWidth(min_symbol);              //--- Get minimize symbol width
   int min_x = min_button_x + (button_size - min_w) / 2;        //--- Compute minimize X position
   color min_color = minimize_hovered ? clrYellow : GetHeaderTextColor(); //--- Get minimize color
   canvasHeader.TextOut(min_x, button_y, min_symbol, ColorToARGB(min_color, 255)); //--- Draw minimize symbol

   int close_button_x = currentWidth + close_x_offset;          //--- Compute close button X
   canvasHeader.FontSet("Webdings", 20);                        //--- Set font for close symbol
   string close_symbol = CharToString(114);                     //--- Set close symbol
   int close_w = canvasHeader.TextWidth(close_symbol);          //--- Get close symbol width
   int close_x = close_button_x + (button_size - close_w) / 2;  //--- Compute close X position
   color close_color = close_hovered ? clrRed : GetHeaderTextColor(); //--- Get close color
   canvasHeader.TextOut(close_x, button_y, close_symbol, ColorToARGB(close_color, 255)); //--- Draw close symbol

   canvasHeader.Update();                                       //--- Update header canvas
}

//+------------------------------------------------------------------+
//| Draw tools panel                                                 |
//+------------------------------------------------------------------+
void DrawToolsPanel() {
   canvasTools.Erase(0);                                        //--- Erase tools canvas

   uint argb_fill = ColorToARGB(GetTopColor(), (uchar)(255 * BackgroundOpacity)); //--- Get fill ARGB
   canvasTools.FillRectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_fill); //--- Fill background rectangle

   uint argb_border = ColorToARGB(GetBorderColor(), 255);       //--- Get border ARGB
   canvasTools.Rectangle(0, 0, currentWidth - 1, currentHeight - 1, argb_border); //--- Draw border rectangle

   int columns = 4;                                             //--- Set number of columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height
   int start_x = padding;                                       //--- Set start X
   int start_y = padding;                                       //--- Set start Y

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = start_x + col * (button_width + padding);         //--- Compute X position
      int y = start_y + row * (button_height + padding);        //--- Compute Y position

      color btn_bg = (active_tool == buttons[i].type) ? GetActiveBtnColor() : ((i == hovered_tool_index) ? GetHoverBtnColor() : clrNONE); //--- Get button background
      if (btn_bg != clrNONE) canvasTools.FillRectangle(x, y, x + button_width - 1, y + button_height - 1, ColorToARGB(btn_bg, 255)); //--- Fill button rectangle if needed

      canvasTools.FontSet(buttons[i].font, 35);                 //--- Set font for icon
      int text_w = canvasTools.TextWidth(buttons[i].symbol);    //--- Get icon width
      int text_h = canvasTools.TextHeight(buttons[i].symbol);   //--- Get icon height
      int icon_x = x + (button_width - text_w) / 2;             //--- Compute icon X
      int icon_y = y + 5;                                       //--- Set icon Y
      canvasTools.TextOut(icon_x, icon_y, buttons[i].symbol, ColorToARGB(GetIconColor(), 255)); //--- Draw icon

      string tool_name = StringSubstr(buttons[i].tooltip, 0, StringFind(buttons[i].tooltip, " Tool")); //--- Get tool name
      canvasTools.FontSet("Arial Bold", 12);                    //--- Set font for name
      int name_w = canvasTools.TextWidth(tool_name);            //--- Get name width
      canvasTools.TextOut(x + (button_width - name_w) / 2, y + button_height - 20, tool_name, ColorToARGB(GetIconColor(), 255)); //--- Draw tool name
   }

   if (StringLen(current_instruction) > 0) {                    //--- Check if instruction exists
      canvasTools.FontSet("Arial", 14);                         //--- Set font for instruction
      int instr_w = canvasTools.TextWidth(current_instruction); //--- Get instruction width
      int instr_x = (currentWidth - instr_w) / 2;               //--- Compute instruction X
      canvasTools.TextOut(instr_x, currentHeight - 30, current_instruction, ColorToARGB(GetInstructionColor(), 255)); //--- Draw instruction
   }

   string status_text = "Active: " + GetToolName(active_tool);  //--- Set status text
   canvasTools.FontSet("Arial Bold", 14);                       //--- Set font for status
   int status_w = canvasTools.TextWidth(status_text);           //--- Get status width
   int status_x = (currentWidth - status_w) / 2;                //--- Compute status X
   canvasTools.TextOut(status_x, currentHeight - 15, status_text, ColorToARGB(GetStatusColor(), 255)); //--- Draw status

   if (resize_hovered || resizing) {                            //--- Check if resize hovered or resizing
      string icon_font = "Wingdings 3";                         //--- Set icon font
      int icon_size = 25;                                       //--- Set icon size
      uchar icon_code = 0;                                      //--- Initialize icon code
      int angle = 0;                                            //--- Initialize angle
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: icon_code = (uchar)'2'; angle = 0; break; //--- Set for bottom
         case RIGHT: icon_code = (uchar)'1'; angle = 0; break;  //--- Set for right
         case BOTTOM_RIGHT: icon_code = (uchar)'2'; angle = 450; break; //--- Set for bottom-right
      }
      if (icon_code != 0) {                                     //--- Check if icon code set
         canvasTools.FontSet(icon_font, icon_size);             //--- Set font for icon
         canvasTools.FontAngleSet(angle);                       //--- Set font angle
         color icon_color = (resize_hovered || resizing) ? clrRed : GetIconColor(); //--- Get icon color
         int icon_x = (hover_mode == BOTTOM) ? MathMin(MathMax(hover_mouse_local_x - icon_size/2, 0), currentWidth - icon_size) : currentWidth - icon_size - 2; //--- Compute icon X
         int icon_y = (hover_mode == RIGHT) ? MathMin(MathMax(hover_mouse_local_y - icon_size/2, 0), currentHeight - icon_size) : currentHeight - icon_size - 2; //--- Compute icon Y
         if (hover_mode == BOTTOM_RIGHT) { icon_x = currentWidth - icon_size - 10; icon_y = currentHeight - icon_size; } //--- Adjust for bottom-right
         canvasTools.TextOut(icon_x, icon_y, CharToString(icon_code), ColorToARGB(icon_color, 255)); //--- Draw icon
         canvasTools.FontAngleSet(0);                           //--- Reset font angle
      }
   }

   canvasTools.Update();                                        //--- Update tools canvas
}

Для создания начального отображения мы реализуем функцию "DrawHeader", которая отображает заголовок палитры на объекте Canvas, начиная с его стирания с помощью Erase, установленного на 0, затем заполняя прямоугольник динамическим цветом фона, полученным из методов-получателей, таких как "GetHeaderDragColor" при перетаскивании, "GetHeaderHoverColor" при наведении курсора или стандартным цветом в противном случае, преобразованным в ARGB. Далее, рисуем контур, используя Rectangle с цветом ARGB из функции "GetBorderColor", устанавливаем жирный шрифт "Arial" для заголовка "Tools Palette" с помощью FontSet и TextOut в цвете текста темы. Для добавления элементов управления мы вычисляем центрирование кнопки по оси Y, затем для темы устанавливаем шрифт Wingdings, позиционируем и окрашиваем символ '[' в зависимости от состояния при наведения курсора и рисуем с помощью "TextOut". Аналогично для сворачивания (переключение символа 'o' или 'r') и закрытия ('X' в Webdings) настраиваем цвета для состояний при наведении курсора, например, желтый или красный. Наконец, для отображения изменений обновляем объект Canvas с помощью Update. При необходимости можно настроить значки, используя собственные символы. Смотрите набор символов шрифта, которые можно использовать.

SYMBOL FONTS

В функции "DrawToolsPanel" стираем объект Canvas инструментов, заполняем его верхним цветом ARGB, скорректированным для параметра "BackgroundOpacity", и рисуем прямоугольник границы. Мы размещаем кнопки в 4 столбцах с отступами, проходим циклом для заполнения фона при активации или наведении курсора с помощью функций "GetActiveBtnColor" или "GetHoverBtnColor", устанавливаем пользовательские шрифты для значков, например, 'v' в формате "Wingdings", центрируем их и рисуем с помощью TextOut цветом значка, а затем добавляем жирным шрифтом названия инструментов ниже. Если в переменной "current_instruction" есть указание, мы отображаем его по центру внизу с помощью функции "GetInstructionColor"; аналогично добавляем статус активного инструмента. Для обозначения изменения размера, если установлено значение "resize_hovered" или "resizing", мы устанавливаем шрифт "Wingdings 3", определяем код стрелки и угол для каждого "hover_mode" (например, '2' на 450 для нижнего правого угла), динамически позиционируем, рисуем красным или цветом значка и сбрасываем угол. В завершение вызываем метод "Update" для обновления объекта Canvas. Теперь мы можем инициализировать объект, отрисовав его, используя следующую логику.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   currentCanvasX = CanvasX;                                    //--- Set current X from input
   currentCanvasY = CanvasY;                                    //--- Set current Y from input
   currentWidth = CanvasWidth;                                  //--- Set current width from input
   currentHeight = CanvasHeight;                                //--- Set current height from input

   if (!canvasHeader.CreateBitmapLabel(0, 0, canvasHeaderName, currentCanvasX, currentCanvasY, currentWidth, header_height, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create header canvas
      Print("Failed to create Header Canvas");                  //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   if (!canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create tools canvas
      Print("Failed to create Tools Canvas");                   //--- Print failure message
      return(INIT_FAILED);                                      //--- Return initialization failure
   }

   buttons[0].symbol = CharToString('v'); buttons[0].font = "Wingdings"; buttons[0].type = TOOL_CROSSHAIR; buttons[0].tooltip = "Crosshair Tool"; //--- Initialize button 0
   buttons[1].symbol = CharToString('.'); buttons[1].font = "Wingdings 3"; buttons[1].type = TOOL_TRENDLINE; buttons[1].tooltip = "Trendline Tool"; //--- Initialize button 1
   buttons[2].symbol = CharToString('*'); buttons[2].font = "Wingdings 3"; buttons[2].type = TOOL_HLINE; buttons[2].tooltip = "Horizontal Line Tool"; //--- Initialize button 2
   buttons[3].symbol = CharToString('+'); buttons[3].font = "Wingdings 3"; buttons[3].type = TOOL_VLINE; buttons[3].tooltip = "Vertical Line Tool"; //--- Initialize button 3
   buttons[4].symbol = CharToString('c'); buttons[4].font = "Webdings"; buttons[4].type = TOOL_RECTANGLE; buttons[4].tooltip = "Rectangle Tool"; //--- Initialize button 4
   buttons[5].symbol = CharToString('"'); buttons[5].font = "Webdings"; buttons[5].type = TOOL_FIBO; buttons[5].tooltip = "Fibonacci Tool"; //--- Initialize button 5
   buttons[6].symbol = CharToString('>'); buttons[6].font = "Webdings"; buttons[6].type = TOOL_TEXT; buttons[6].tooltip = "Text Tool"; //--- Initialize button 6
   buttons[7].symbol = CharToString(225); buttons[7].font = "Wingdings"; buttons[7].type = TOOL_ARROW; buttons[7].tooltip = "Arrow Tool"; //--- Initialize button 7

   DrawHeader();                                                //--- Draw header
   DrawToolsPanel();                                            //--- Draw tools panel

   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);            //--- Enable mouse move events
   ChartRedraw();                                               //--- Redraw chart
   local_chart_id = ChartID();                                  //--- Set local chart ID
   return(INIT_SUCCEEDED);                                      //--- Return initialization success
}

В обработчике OnInit мы инициализируем текущие положения и размеры панелей на основе пользовательских входных данных, обеспечивая соответствие заданным значениям. Далее мы создаём объект Canvas заголовка с помощью CreateBitmapLabel с параметром ARGB normalize для обеспечения прозрачности, выводя сообщения об ошибках и возвращая INIT_FAILED в случае неудачи. Аналогично для объекта Canvas инструментов, расположенного под заголовком, со смещением по оси Y. Для заполнения палитры мы инициализируем элементы массива "buttons" определенными символами (например, 'v' для перекрестия), шрифтами, такими как Wingdings, типами инструментов из перечисления и всплывающими подсказками для информации при наведении курсора. Затем мы отрисовываем заголовок и панель инструментов с помощью соответствующих функций, включаем события перемещения мыши с помощью ChartSetInteger для захвата взаимодействий, перерисовываем график, сохраняем локальный идентификатор графика для управления объектами и возвращаем INIT_SUCCEEDED. После компиляции получаем следующий результат.

PANEL INITIALIZATION

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

//+------------------------------------------------------------------+
//| Toggle theme                                                     |
//+------------------------------------------------------------------+
void ToggleTheme() {
   is_dark_theme = !is_dark_theme;                              //--- Toggle theme flag
   DrawHeader();                                                //--- Redraw header
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Toggle minimize                                                  |
//+------------------------------------------------------------------+
void ToggleMinimize() {
   panel_minimized = !panel_minimized;                          //--- Toggle minimized flag
   if (panel_minimized) {                                       //--- Check if minimized
      canvasTools.Destroy();                                    //--- Destroy tools canvas
   } else {                                                     //--- Handle restored
      canvasTools.CreateBitmapLabel(0, 0, canvasToolsName, currentCanvasX, currentCanvasY + header_height + tools_y_offset, currentWidth, currentHeight, COLOR_FORMAT_ARGB_NORMALIZE); //--- Recreate tools canvas
      DrawToolsPanel();                                         //--- Redraw tools panel
   }
   DrawHeader();                                                //--- Redraw header
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Is mouse over header excluding buttons                           |
//+------------------------------------------------------------------+
bool IsMouseOverHeader(int mx, int my) {
   if (mx < currentCanvasX || mx > currentCanvasX + currentWidth || my < currentCanvasY || my > currentCanvasY + header_height) return false; //--- Check outside header
   int btn_left = currentCanvasX + currentWidth + theme_x_offset; //--- Compute buttons left
   if (mx >= btn_left && mx <= btn_left + button_size * 3) return false; //--- Check over buttons
   return true;                                                 //--- Return true if over header
}

//+------------------------------------------------------------------+
//| Is mouse over specific button                                    |
//+------------------------------------------------------------------+
bool IsMouseOverButton(int mx, int my, int offset) {
   int btn_left = currentCanvasX + currentWidth + offset;       //--- Compute button left
   return (mx >= btn_left && mx <= btn_left + button_size && my >= currentCanvasY && my <= currentCanvasY + header_height); //--- Return if over button
}

//+------------------------------------------------------------------+
//| Is mouse over resize area                                        |
//+------------------------------------------------------------------+
bool IsMouseOverResize(int mx, int my, ENUM_RESIZE_MODE &rm) {
   if (panel_minimized) return false;                           //--- Return false if minimized
   int tx = currentCanvasX;                                     //--- Set tools X
   int ty = currentCanvasY + header_height + tools_y_offset;    //--- Set tools Y
   int tr = tx + currentWidth;                                  //--- Set right
   int tb = ty + currentHeight;                                 //--- Set bottom
   bool over_right = mx >= tr - resize_thickness && mx <= tr && my >= ty && my <= tb; //--- Check over right
   bool over_bottom = my >= tb - resize_thickness && my <= tb && mx >= tx && mx <= tr; //--- Check over bottom
   if (over_bottom && over_right) { rm = BOTTOM_RIGHT; return true; } //--- Set and return bottom-right
   if (over_bottom) { rm = BOTTOM; return true; }               //--- Set and return bottom
   if (over_right) { rm = RIGHT; return true; }                 //--- Set and return right
   return false;                                                //--- Return false otherwise
}

//+------------------------------------------------------------------+
//| Get hovered tool                                                 |
//+------------------------------------------------------------------+
int GetHoveredTool(int mx, int my) {
   int lx = mx - currentCanvasX;                                //--- Compute local X
   int ly = my - (currentCanvasY + header_height + tools_y_offset); //--- Compute local Y
   if (lx < 0 || lx > currentWidth || ly < 0 || ly > currentHeight) return -1; //--- Return -1 if outside

   int columns = 4;                                             //--- Set columns
   int padding = 5;                                             //--- Set padding
   int button_width = (currentWidth - (columns + 1) * padding) / columns; //--- Compute button width
   int button_height = 70;                                      //--- Set button height

   for (int i = 0; i < 8; i++) {                                //--- Loop over buttons
      int col = i % columns;                                    //--- Compute column
      int row = i / columns;                                    //--- Compute row
      int x = padding + col * (button_width + padding);         //--- Compute X
      int y = padding + row * (button_height + padding);        //--- Compute Y
      if (lx >= x && lx < x + button_width && ly >= y && ly < y + button_height) return i; //--- Return index if over
   }
   return -1;                                                   //--- Return -1 if none
}

//+------------------------------------------------------------------+
//| Toggle tool activation                                           |
//+------------------------------------------------------------------+
void ToggleTool(TOOL_TYPE type) {
   if (active_tool == type) {                                   //--- Check if already active
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair deactivation
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = TOOL_NONE;                                  //--- Deactivate tool
      drawing_first_click = false;                              //--- Reset first click
      current_instruction = "";                                 //--- Clear instruction
   } else {                                                     //--- Handle activation
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Deactivate previous crosshair
         DeleteCrosshair();                                     //--- Delete crosshair
         measuring = false;                                     //--- Reset measuring
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
      }
      active_tool = type;                                       //--- Set active tool
      drawing_first_click = false;                              //--- Reset first click
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Handle crosshair instruction
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      } else {                                                  //--- Handle other tools
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Deactivate current tool on Escape                                |
//+------------------------------------------------------------------+
void DeactivateTool() {
   if (active_tool == TOOL_CROSSHAIR) {                         //--- Check if crosshair active
      DeleteCrosshair();                                        //--- Delete crosshair
      measuring = false;                                        //--- Reset measuring
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
   }
   active_tool = TOOL_NONE;                                     //--- Deactivate tool
   drawing_first_click = false;                                 //--- Reset first click
   current_instruction = "";                                    //--- Clear instruction
   DrawToolsPanel();                                            //--- Redraw tools panel
   ChartRedraw();                                               //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Delete measure objects                                           |
//+------------------------------------------------------------------+
void DeleteMeasureObjects() {
   ObjectDelete(local_chart_id, "Cross_HF");                    //--- Delete fixed horizontal
   ObjectDelete(local_chart_id, "Cross_VF");                    //--- Delete fixed vertical
   ObjectDelete(local_chart_id, "Measure_Line");                //--- Delete measure line
   ObjectDelete(local_chart_id, "Measure_Label");               //--- Delete measure label
}

//+------------------------------------------------------------------+
//| Delete crosshair                                                 |
//+------------------------------------------------------------------+
void DeleteCrosshair() {
   ObjectDelete(local_chart_id, "Cross_H");                     //--- Delete moving horizontal
   ObjectDelete(local_chart_id, "Cross_V");                     //--- Delete moving vertical
   DeleteMeasureObjects();                                      //--- Delete measure objects
   ChartRedraw();                                               //--- Redraw chart
}

Здесь мы реализуем функцию "ToggleTheme" для переключения между темным и светлым режимами, инвертируя флаг "is_dark_theme", затем перерисовывая заголовок и панель инструментов для применения новых цветов и обновляя график для немедленной видимости. Далее мы создаём функцию "ToggleMinimize", которая будет чередовать состояние панели с "panel_minimized", уничтожая объект Canvas инструментов при сворачивании для экономии места или создавая его заново при восстановлении, после чего происходит перерисовка заголовка и обновление графика. Для обнаружения взаимодействий мы определяем параметр "IsMouseOverHeader", который проверяет, находится ли курсор мыши в пределах заголовка, но исключает область кнопки, начиная со смещения темы, и возвращает значение true только для областей, доступных для перетаскивания.

Аналогично, функция "IsMouseOverButton" проверяет положение курсора мыши над определенной кнопкой, вычисляя ее левый край на основе смещения и сравнивая его с размером кнопки и высотой заголовка. Для изменения размера функция "IsMouseOverResize" проверяет, находится ли курсор мыши вблизи нижнего, правого или углового края панели инструментов (игнорируя, если панель свернута), обновляя режим привязки, как в функции "BOTTOM_RIGHT", и возвращая значение true, если это обнаружено. Мы добавляем функцию "GetHoveredTool", которая идентифицирует кнопку под курсором мыши, преобразуя ее в локальные координаты, перебирая 4-колоночную сетку с отступами и возвращая индекс, если находится в пределах кнопки, либо -1 в противном случае.

Для управления инструментами функция "ToggleTool" активирует или деактивирует их на основе типа, обрабатывает очистку перекрестия при переключении, сбрасывает флаги рисования и устанавливает соответствующие указания, например, "Для рисования щелкните по графику", перед перерисовкой панели. Функция "DeactivateTool" полностью сбрасывает активный инструмент, в частности, очищает элементы перекрестия и включает прокрутку, затем обновляет указания и перерисовывает изображение. Наконец, мы выполняем очистку с помощью функции "DeleteMeasureObjects", удаляющей фиксированное перекрестие и линию/метку измерения, и функции "DeleteCrosshair", удаляющей движущееся перекрестие и вызывающей функцию удаления измерения, после чего происходит перерисовка графика. Теперь мы рассмотрим функции объектов и их создание.

//+------------------------------------------------------------------+
//| Handle chart drawing for tools                                   |
//+------------------------------------------------------------------+
void HandleDrawing(int mx, int my) {
   datetime time;                                               //--- Declare time
   double price;                                                //--- Declare price
   int sw;                                                      //--- Declare subwindow
   if (!ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) return; //--- Convert XY to time/price or return

   string name = "Tool_" + TimeToString(TimeCurrent(), TIME_SECONDS); //--- Set object name
   color col = clrBlue;                                         //--- Set color
   ENUM_OBJECT obj_type = OBJ_VLINE;                            //--- Initialize object type
   datetime time1 = time;                                       //--- Set time1
   double price1 = price;                                       //--- Set price1
   datetime time2 = 0;                                          //--- Set time2
   double price2 = 0.0;                                         //--- Set price2
   bool create = true;                                          //--- Set create flag
   bool valid_obj = false;                                      //--- Set valid object flag

   string tool_name = GetToolName(active_tool);                 //--- Get tool name

   if (IsSingleClickTool(active_tool)) {                        //--- Check if single-click tool
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_HLINE:                                       //--- Handle horizontal line
            obj_type = OBJ_HLINE;                               //--- Set type
            time1 = 0;                                          //--- Set time1
            price1 = price;                                     //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_VLINE:                                       //--- Handle vertical line
            obj_type = OBJ_VLINE;                               //--- Set type
            time1 = time;                                       //--- Set time1
            price1 = 0;                                         //--- Set price1
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_TEXT:                                        //--- Handle text
            obj_type = OBJ_TEXT;                                //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
         case TOOL_ARROW:                                       //--- Handle arrow
            obj_type = OBJ_ARROW;                               //--- Set type
            time2 = 0;                                          //--- Set time2
            price2 = 0;                                         //--- Set price2
            valid_obj = true;                                   //--- Set valid
            break;                                              //--- Break
      }
   } else {                                                     //--- Handle two-click tools
      if (!drawing_first_click) {                               //--- Check if first click
         draw_time1 = time;                                     //--- Set draw time1
         draw_price1 = price;                                   //--- Set draw price1
         drawing_first_click = true;                            //--- Set first click flag
         create = false;                                        //--- Set no create
         current_instruction = "Click second point for " + tool_name + "."; //--- Set instruction
      } else {                                                  //--- Handle second click
         ChartXYToTimePrice(local_chart_id, mx, my, sw, time2, price2); //--- Get time2/price2
         drawing_first_click = false;                           //--- Reset first click
         time1 = draw_time1;                                    //--- Set time1
         price1 = draw_price1;                                  //--- Set price1
         current_instruction = "Click on chart to draw.";       //--- Set instruction
      }
      switch (active_tool) {                                    //--- Switch on tool
         case TOOL_TRENDLINE: obj_type = OBJ_TREND; valid_obj = true; break; //--- Set trendline
         case TOOL_RECTANGLE: obj_type = OBJ_RECTANGLE; valid_obj = true; break; //--- Set rectangle
         case TOOL_FIBO: obj_type = OBJ_FIBO; valid_obj = true; break; //--- Set fibo
      }
   }

   if (create && valid_obj) {                                   //--- Check if create and valid
      if (ObjectCreate(local_chart_id, name, obj_type, sw, time1, price1, time2, price2)) { //--- Create object
         ObjectSetInteger(local_chart_id, name, OBJPROP_COLOR, col); //--- Set color
         ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
         ObjectSetInteger(local_chart_id, name, OBJPROP_WIDTH, 1); //--- Set width
         if (obj_type == OBJ_TEXT) ObjectSetString(local_chart_id, name, OBJPROP_TEXT, "Text"); //--- Set text
         if (obj_type == OBJ_ARROW) ObjectSetInteger(local_chart_id, name, OBJPROP_ARROWCODE, 233); //--- Set arrow code
         if (obj_type == OBJ_HLINE || obj_type == OBJ_VLINE) ObjectSetInteger(local_chart_id, name, OBJPROP_STYLE, STYLE_DASH); //--- Set dash for lines
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTABLE, true); //--- Set selectable
         ObjectSetInteger(local_chart_id, name, OBJPROP_SELECTED, true); //--- Set selected
         ChartRedraw(local_chart_id);                           //--- Redraw chart
         active_tool = TOOL_NONE;                               //--- Deactivate tool
         current_instruction = "";                              //--- Clear instruction
      }
   }
   DrawToolsPanel();                                            //--- Redraw tools panel
}

Для обработки построения графиков мы реализуем функцию "HandleDrawing", которая управляет созданием объектов графика на основе активного инструмента и положения мыши, начиная с преобразования координат во время и цену с помощью функции ChartXYToTimePrice и возвращаясь к исходному состоянию в случае неудачи. Далее мы генерируем уникальное имя объекта, используя текущее время, устанавливаем синий цвет по умолчанию и тип вертикальной линии, затем выбираем вариант для инструментов с одним щелчком мыши с помощью параметра "IsSingleClickTool", переключаясь на настройку типов, таких как OBJ_HLINE для горизонтальной линии (сброс времени), OBJ_VLINE для вертикальной линии (сброс цен), OBJ_TEXT или OBJ_ARROW, помечаем их как действительные и устанавливаем флаг создания.

Для инструментов с двумя щелчками мыши мы захватываем первую точку, если "drawing_first_click" равно false, обновляя глобальные переменные и указания без создания; при втором щелчке получаем конечную точку, сбрасываем флаг и настраиваем типы, такие как OBJ_TREND для линии тренда, OBJ_RECTANGLE или OBJ_FIBO, проверяя объект. Если создание отмечено флагом и действительно, мы используем ObjectCreate с параметрами, устанавливаем такие свойства, как цвет, стиль, ширина, текст или код стрелки (если применимо), пунктир для линий, выбираемое и выбранное состояния, затем перерисовываем график с помощью ChartRedraw и деактивируем инструмент, удаляя указания. Наконец для отражения изменений мы обновляем панель инструментов с помощью "DrawToolsPanel". Теперь перейдём к перекрестию. С остальными всё просто.

//+------------------------------------------------------------------+
//| Update moving crosshair                                          |
//+------------------------------------------------------------------+
void UpdateMovingCrosshair(datetime time, double price) {
   string h_name = "Cross_H";                                   //--- Set horizontal name
   string v_name = "Cross_V";                                   //--- Set vertical name

   if (ObjectFind(local_chart_id, h_name) < 0) {                //--- Check if horizontal exists
      ObjectCreate(local_chart_id, h_name, OBJ_HLINE, 0, 0, price); //--- Create horizontal line
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, h_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, h_name, 0, 0, price);          //--- Move horizontal
   }

   if (ObjectFind(local_chart_id, v_name) < 0) {                //--- Check if vertical exists
      ObjectCreate(local_chart_id, v_name, OBJ_VLINE, 0, time, 0); //--- Create vertical line
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_COLOR, clrRed); //--- Set color
      ObjectSetInteger(local_chart_id, v_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, v_name, 0, time, 0);           //--- Move vertical
   }
}

//+------------------------------------------------------------------+
//| Update fixed crosshair                                           |
//+------------------------------------------------------------------+
void UpdateFixedCrosshair() {
   string hf_name = "Cross_HF";                                 //--- Set fixed horizontal name
   string vf_name = "Cross_VF";                                 //--- Set fixed vertical name

   if (ObjectFind(local_chart_id, hf_name) < 0) {               //--- Check if fixed horizontal exists
      ObjectCreate(local_chart_id, hf_name, OBJ_HLINE, 0, 0, fixed_price); //--- Create fixed horizontal
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, hf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, hf_name, 0, 0, fixed_price);   //--- Move fixed horizontal
   }

   if (ObjectFind(local_chart_id, vf_name) < 0) {               //--- Check if fixed vertical exists
      ObjectCreate(local_chart_id, vf_name, OBJ_VLINE, 0, fixed_time, 0); //--- Create fixed vertical
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_COLOR, clrGreen); //--- Set color
      ObjectSetInteger(local_chart_id, vf_name, OBJPROP_STYLE, STYLE_SOLID); //--- Set style
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, vf_name, 0, fixed_time, 0);    //--- Move fixed vertical
   }
}

Мы реализовали функцию "UpdateMovingCrosshair" для управления динамическим перекрестием на графике, задав имена для горизонтальных линий "Cross_H" и вертикальных линий "Cross_V", проверив их наличие с помощью ObjectFind (в противном случае возвращается отрицательное значение), создав их с помощью "ObjectCreate" как OBJ_HLINE или "OBJ_VLINE" красным цветом, сплошным стилем, если они отсутствуют, или переместив их с помощью ObjectMove к текущему времени и цене, если они присутствуют, обеспечивая отслеживание курсора в реальном времени. Далее мы создаём функцию "UpdateFixedCrosshair" для статического референсного перекрестия, используя имена "Cross_HF" и "Cross_VF", аналогично проверяя его существование, создавая его в сплошном стиле, зелёного цвета, в точке с параметрами fixed_time и fixed_price, или обновляя позиции, включая постоянные маркеры в режиме измерения для сравнений. Теперь обработаем её нажатие.

//+------------------------------------------------------------------+
//| Handle crosshair click                                           |
//+------------------------------------------------------------------+
void HandleCrosshairClick(datetime time, double price) {
   ulong now = GetMicrosecondCount();                           //--- Get current microseconds
   if (now - last_click_time < 500000) {                        //--- Check for double-click
      if (!measuring) {                                         //--- Check not measuring
         fixed_time = time;                                     //--- Set fixed time
         fixed_price = price;                                   //--- Set fixed price
         measuring = true;                                      //--- Set measuring flag
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         current_instruction = "Measuring. Double-click to exit."; //--- Set instruction
      } else {                                                  //--- Handle exit measuring
         measuring = false;                                     //--- Reset measuring
         DeleteMeasureObjects();                                //--- Delete objects
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);          //--- Enable scroll
         current_instruction = "Move mouse for crosshair. Double-click to start measuring."; //--- Set instruction
      }
      DrawToolsPanel();                                         //--- Redraw tools panel
      last_click_time = 0;                                      //--- Reset click time
   } else {                                                     //--- Handle single click
      last_click_time = now;                                    //--- Update click time
   }
}

Мы реализуем функцию "HandleCrosshairClick" для обработки кликов в режиме перекрестия, получая текущее значение в микросекундах с помощью функции GetMicrosecondCount для обнаружения двойных кликов в течение 500 мс после "last_click_time". При двойном щелчке переключается измерение: если режим не активный, устанавливается фиксированная точка на текущее время и цену, включается измерение, отключается прокрутка графика с помощью ChartSetInteger, обновляется указание; если режим активный, сбрасывается измерение, удаляются объекты с помощью "DeleteMeasureObjects", снова включается прокрутка и восстанавливается указание по умолчанию, затем перерисовывается панель инструментов и сбрасывается время клика. Для одиночных кликов мы обновляем значение параметра "last_click_time" до текущего момента, что позволяет определять последующие клики как двойные. Теперь давайте определим логику обновления измерительной линии между перекрестиями.

//+------------------------------------------------------------------+
//| Update measure line                                              |
//+------------------------------------------------------------------+
void UpdateMeasureLine(datetime time, double price) {
   string line_name = "Measure_Line";                           //--- Set measure line name
   if (ObjectFind(local_chart_id, line_name) < 0) {             //--- Check if line exists
      ObjectCreate(local_chart_id, line_name, OBJ_TREND, 0, fixed_time, fixed_price, time, price); //--- Create trend line
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_COLOR, clrBlue); //--- Set color
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_STYLE, STYLE_DASHDOT); //--- Set style
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_WIDTH, 1); //--- Set width
      ObjectSetInteger(local_chart_id, line_name, OBJPROP_RAY, false); //--- Disable ray
   } else {                                                     //--- Handle existing
      ObjectMove(local_chart_id, line_name, 0, fixed_time, fixed_price); //--- Move point 0
      ObjectMove(local_chart_id, line_name, 1, time, price);    //--- Move point 1
   }
}

//+------------------------------------------------------------------+
//| Update measure label                                             |
//+------------------------------------------------------------------+
void UpdateMeasureLabel(int mx, int my, datetime time, double price) {
   string label_name = "Measure_Label";                         //--- Set measure label name
   long period_sec = PeriodSeconds(_Period);                    //--- Get period seconds
   long virtual_fixed = fixed_time / period_sec;                //--- Compute virtual fixed
   long virtual_time = time / period_sec;                       //--- Compute virtual time
   int bars_diff = (int)MathAbs(virtual_fixed - virtual_time);  //--- Compute bars difference
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);      //--- Get point value
   long digits = SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);     //--- Get digits
   double pip_size = (digits == 3 || digits == 5) ? point * 10 : point; //--- Compute pip size
   double pips = MathAbs(price - fixed_price) / pip_size;       //--- Compute pips
   string price_diff = DoubleToString(MathAbs(price - fixed_price), (int)digits); //--- Format price diff
   string text = StringFormat("%d bars, %.1f pips, Price Diff: %s", bars_diff, pips, price_diff); //--- Format text

   if (ObjectFind(local_chart_id, label_name) < 0) {            //--- Check if label exists
      ObjectCreate(local_chart_id, label_name, OBJ_LABEL, 0, 0, 0); //--- Create label
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Set corner
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Set X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Set Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Set text
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_FONTSIZE, 9); //--- Set font size
      ObjectSetString(local_chart_id, label_name, OBJPROP_FONT, "Arial"); //--- Set font
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_COLOR, clrBlue); //--- Set color
   } else {                                                     //--- Handle existing
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_XDISTANCE, mx + 20); //--- Update X distance
      ObjectSetInteger(local_chart_id, label_name, OBJPROP_YDISTANCE, my); //--- Update Y distance
      ObjectSetString(local_chart_id, label_name, OBJPROP_TEXT, text); //--- Update text
   }
}

Для измерительной линии мы реализуем функцию "UpdateMeasureLine", которая отображает соединительную линию в режиме измерения, задавая имя "Measure_Line", проверяя ее существование с помощью ObjectFind, создавая ее как OBJ_TREND от фиксированного значения к текущему значению времени/цены, в виде синей пунктирной линии, шириной 1 и без луча, если она отсутствует, или перемещая ее точки с помощью ObjectMove, если она присутствует, обеспечивая визуальную связь между точками. Мы создаём функцию "UpdateMeasureLabel" для отображения динамических метрик рядом с курсором мыши, вычисляя разницу баров путём деления времени на период в секундах из PeriodSeconds для получения виртуальных баров и вычисления абсолютного значения с помощью MathAbs, затем пипсов, используя значение символа из SymbolInfoDouble, скорректированное на 3 или 5 знаков (умноженное на 10), при этом абсолютная разница цен форматируется в цифры из функции SymbolInfoInteger

Для форматирования текста с помощью StringFormat, включая бары, пипсы, округленные до одного десятичного знака и разницу цен, мы проверяем наличие метки, создаем ее как OBJ_LABEL в верхнем левом углу с учетом смещения курсора мыши, шрифтом "Arial" размером 9, синим цветом (если метка новая) или обновляем положение и текст (если метка уже существует), улучшая обратную связь с пользователем по результатам измерений. Вычисление этих меток имеет решающее значение, поскольку точно определяет временные и ценовые диапазоны для различных значений точности символов и периодов, что позволяет проводить точный анализ без ручных вычислений. Чтобы оживить палитру, мы обработаем события графика в обработчике событий для обеспечения интуитивно понятного взаимодействия.

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_KEYDOWN) {                              //--- Check key down event
      if (lparam == 27) {                                       //--- Check escape key
         DeactivateTool();                                      //--- Deactivate tool
      }
      return;                                                   //--- Return
   }
   if (id != CHARTEVENT_MOUSE_MOVE) return;                     //--- Return if not mouse move
   int mx = (int)lparam;                                        //--- Set mouse X
   int my = (int)dparam;                                        //--- Set mouse Y
   int ms = (int)sparam;                                        //--- Set mouse state

   bool prev_hh = header_hovered;                               //--- Store previous header hover
   bool prev_th = theme_hovered;                                //--- Store previous theme hover
   bool prev_mh = minimize_hovered;                             //--- Store previous minimize hover
   bool prev_ch = close_hovered;                                //--- Store previous close hover
   bool prev_rh = resize_hovered;                               //--- Store previous resize hover

   header_hovered = IsMouseOverHeader(mx, my);                  //--- Update header hover
   theme_hovered = IsMouseOverButton(mx, my, theme_x_offset);   //--- Update theme hover
   minimize_hovered = IsMouseOverButton(mx, my, minimize_x_offset); //--- Update minimize hover
   close_hovered = IsMouseOverButton(mx, my, close_x_offset);   //--- Update close hover
   resize_hovered = IsMouseOverResize(mx, my, hover_mode);      //--- Update resize hover

   string resize_tooltip = "";                                  //--- Initialize resize tooltip
   if (resize_hovered) {                                        //--- Check resize hovered
      switch (hover_mode) {                                     //--- Switch on hover mode
         case BOTTOM: resize_tooltip = "Resize Bottom"; break;  //--- Set bottom tooltip
         case RIGHT: resize_tooltip = "Resize Right"; break;    //--- Set right tooltip
         case BOTTOM_RIGHT: resize_tooltip = "Resize Bottom-Right"; break; //--- Set bottom-right tooltip
      }
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, resize_tooltip); //--- Set tooltip
   } else if (!panel_minimized && hovered_tool_index >= 0) {    //--- Check tool hovered
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, buttons[hovered_tool_index].tooltip); //--- Set tool tooltip
   } else {                                                     //--- Handle no hover
      ObjectSetString(0, canvasToolsName, OBJPROP_TOOLTIP, ""); //--- Clear tooltip
   }

   if (prev_hh != header_hovered || prev_th != theme_hovered || prev_mh != minimize_hovered || prev_ch != close_hovered || prev_rh != resize_hovered) { //--- Check if hovers changed
      DrawHeader();                                             //--- Redraw header
      if (!panel_minimized) DrawToolsPanel();                   //--- Redraw tools if not minimized
      ChartRedraw();                                            //--- Redraw chart
   }

   if (!panel_minimized) {                                      //--- Check not minimized
      int ht = GetHoveredTool(mx, my);                          //--- Get hovered tool
      if (ht != hovered_tool_index) {                           //--- Check if changed
         hovered_tool_index = ht;                               //--- Update index
         DrawToolsPanel();                                      //--- Redraw tools
         ChartRedraw();                                         //--- Redraw chart
      }
   }

   if (resize_hovered || resizing) {                            //--- Check resize hovered or resizing
      hover_mouse_local_x = mx - currentCanvasX;                //--- Update local X
      hover_mouse_local_y = my - (currentCanvasY + header_height + tools_y_offset); //--- Update local Y
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 1 && prev_mouse_state == 0) {                      //--- Check mouse down
      if (header_hovered) {                                     //--- Check header hovered
         panel_dragging = true;                                 //--- Set dragging flag
         panel_drag_x = mx;                                     //--- Set drag X
         panel_drag_y = my;                                     //--- Set drag Y
         panel_start_x = currentCanvasX;                        //--- Set start X
         panel_start_y = currentCanvasY;                        //--- Set start Y
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);         //--- Disable scroll
         DrawHeader();                                          //--- Redraw header
         ChartRedraw();                                         //--- Redraw chart
      } else if (theme_hovered) {                               //--- Check theme hovered
         ToggleTheme();                                         //--- Toggle theme
      } else if (minimize_hovered) {                            //--- Check minimize hovered
         ToggleMinimize();                                      //--- Toggle minimize
      } else if (close_hovered) {                               //--- Check close hovered
         OnDeinit(0);                                           //--- Call deinit
      } else if (!panel_minimized) {                            //--- Check not minimized
         int ht = GetHoveredTool(mx, my);                       //--- Get hovered tool
         if (ht >= 0) {                                         //--- Check valid tool
            ToggleTool(buttons[ht].type);                       //--- Toggle tool
         } else if (active_tool != TOOL_NONE && active_tool != TOOL_CROSSHAIR) { //--- Check active tool
            HandleDrawing(mx, my);                              //--- Handle drawing
         } else if (IsMouseOverResize(mx, my, resize_mode)) {   //--- Check resize area
            resizing = true;                                    //--- Set resizing flag
            resize_start_x = mx;                                //--- Set start X
            resize_start_y = my;                                //--- Set start Y
            start_width = currentWidth;                         //--- Set start width
            start_height = currentHeight;                       //--- Set start height
            ChartSetInteger(0, CHART_MOUSE_SCROLL, false);      //--- Disable scroll
            DrawToolsPanel();                                   //--- Redraw tools
            ChartRedraw();                                      //--- Redraw chart
         }
      }
   }

   if (panel_dragging && ms == 1) {                             //--- Check dragging
      int dx = mx - panel_drag_x;                               //--- Compute delta X
      int dy = my - panel_drag_y;                               //--- Compute delta Y
      currentCanvasX = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentWidth, panel_start_x + dx)); //--- Update X
      currentCanvasY = MathMax(0, MathMin((int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - header_height - (panel_minimized ? 0 : currentHeight) - tools_y_offset, panel_start_y + dy)); //--- Update Y
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set header X
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YDISTANCE, currentCanvasY); //--- Set header Y
      if (!panel_minimized) {                                   //--- Check not minimized
         ObjectSetInteger(0, canvasToolsName, OBJPROP_XDISTANCE, currentCanvasX); //--- Set tools X
         ObjectSetInteger(0, canvasToolsName, OBJPROP_YDISTANCE, currentCanvasY + header_height + tools_y_offset); //--- Set tools Y
      }
      ChartRedraw();                                            //--- Redraw chart
   }

   if (resizing && ms == 1) {                                   //--- Check resizing
      int dx = mx - resize_start_x;                             //--- Compute delta X
      int dy = my - resize_start_y;                             //--- Compute delta Y
      int nw = MathMax(min_width, start_width + (resize_mode == RIGHT || resize_mode == BOTTOM_RIGHT ? dx : 0)); //--- Compute new width
      int nh = MathMax(min_height, start_height + (resize_mode == BOTTOM || resize_mode == BOTTOM_RIGHT ? dy : 0)); //--- Compute new height
      nw = MathMin(nw, (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - currentCanvasX); //--- Clamp width
      nh = MathMin(nh, (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS) - currentCanvasY - header_height - tools_y_offset); //--- Clamp height
      currentWidth = nw;                                        //--- Update width
      currentHeight = nh;                                       //--- Update height
      canvasTools.Resize(currentWidth, currentHeight);          //--- Resize tools canvas
      ObjectSetInteger(0, canvasToolsName, OBJPROP_XSIZE, currentWidth); //--- Set tools X size
      ObjectSetInteger(0, canvasToolsName, OBJPROP_YSIZE, currentHeight); //--- Set tools Y size
      canvasHeader.Resize(currentWidth, header_height);         //--- Resize header canvas
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_XSIZE, currentWidth); //--- Set header X size
      ObjectSetInteger(0, canvasHeaderName, OBJPROP_YSIZE, header_height); //--- Set header Y size
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   if (ms == 0 && prev_mouse_state == 1) {                      //--- Check mouse up
      panel_dragging = false;                                   //--- Reset dragging
      resizing = false;                                         //--- Reset resizing
      ChartSetInteger(0, CHART_MOUSE_SCROLL, true);             //--- Enable scroll
      DrawHeader();                                             //--- Redraw header
      DrawToolsPanel();                                         //--- Redraw tools
      ChartRedraw();                                            //--- Redraw chart
   }

   datetime time = 0;                                           //--- Initialize time
   double price = 0.0;                                          //--- Initialize price
   int sw = 0;                                                  //--- Initialize subwindow
   if (ChartXYToTimePrice(local_chart_id, mx, my, sw, time, price)) { //--- Convert XY to time/price
      if (active_tool == TOOL_CROSSHAIR) {                      //--- Check crosshair active
         UpdateMovingCrosshair(time, price);                    //--- Update moving crosshair
         if (measuring) {                                       //--- Check measuring
            UpdateFixedCrosshair();                             //--- Update fixed crosshair
            UpdateMeasureLine(time, price);                     //--- Update measure line
            UpdateMeasureLabel(mx, my, time, price);            //--- Update measure label
         }
         ChartRedraw();                                         //--- Redraw chart
      }
      if (ms == 1 && prev_mouse_state == 0 && active_tool == TOOL_CROSSHAIR && 
          !header_hovered && !theme_hovered && !minimize_hovered && !close_hovered && !resize_hovered && 
          (panel_minimized || GetHoveredTool(mx, my) == -1)) {   //--- Check crosshair click conditions
         HandleCrosshairClick(time, price);                      //--- Handle click
         ChartRedraw();                                          //--- Redraw chart
      }
   }

   prev_mouse_state = ms;                                       //--- Update previous state
}

Мы используем обработчик OnChartEvent для управления всеми взаимодействиями пользователя с палитрой и инструментами, начиная с проверки CHARTEVENT_KEYDOWN для обнаружения клавиши Escape (lparam 27) и деактивации текущего инструмента с помощью функции "DeactivateTool", после чего возвращаемся к исходному состоянию. Если вам интересно, то escape-символ в таблице ASCII имеет код 27. Смотрите ниже.

ASCII CHARACTER 27 - ESCAPE

Далее, если событие CHARTEVENT_MOUSE_MOVE не выполняется, мы выходим; в противном случае, извлекаем координаты мыши по осям X, Y и состояние из параметров, сохраняем предыдущие флаги наведения курсора и обновляем их с помощью таких функций, как "IsMouseOverHeader", "IsMouseOverButton" для темы/сворачивания/закрытия и "IsMouseOverResize", которая устанавливает "hover_mode". Далее, с помощью ObjectSetString устанавливаем всплывающие подсказки на объекте Canvas инструментов для режимов изменения размера или при наведении курсора на кнопки инструментов, очищая их, если ни один из элементов не находится под курсором; если при наведении курсора что-либо изменилось, перерисовываем заголовок и инструменты (если не свернуто), после чего выполняется ChartRedraw функция. Если окно не свернуто, мы определяем индекс инструмента, на который наведен курсор с помощью функции "GetHoveredTool" и перерисовываем его при изменении; для состояний изменения размера обновляем локальные координаты при наведении курсора и перерисовываем инструменты.

При нажатии кнопки мыши (1 мс, prev 0) мы инициируем перетаскивание, если на заголовок наведен курсор, устанавливая флаги, отключая прокрутку и перерисовывая заголовок; переключаем тему или сворачиваем, если наведен курсор на соответствующие кнопки; вызываем deinit при закрытии; или, если наведен курсор на кнопку инструмента, переключаем ее тип; обрабатываем рисование, не являющееся перекрестием, с помощью "HandleDrawing"; начинаем изменение размера, если область изменения размера выходит за пределы, устанавливая флаги, запуская и отключая прокрутку перед перерисовкой инструментов. Во время перетаскивания (1 мс) мы вычисляем дельты, ограничиваем новые позиции границами графика с помощью MathMax и MathMin функций, обновляем глобальные переменные и устанавливаем расстояния между объектами для заголовка и инструментов (если они видны). Затем перерисовываем график. Для изменения размера (1 мс) вычисляем и ограничиваем новые размеры на основе режима, обновляем глобальные переменные, изменяем размер объектов Canvas с помощью Resize и устанавливаем размеры с помощью ObjectSetInteger, затем перерисовываем заголовок, инструменты и график.

При отпускании кнопки мыши (мс 0, prev 1) сбрасываем флаги перетаскивания и изменения размера, снова включаем прокрутку, перерисовываем заголовок и инструменты, а также обновляем график. Мы преобразуем данные мыши во время/цену с помощью ChartXYToTimePrice, и если перекрестие активно, обновляем движущееся перекрестие; если производится измерение, также обновляем фиксированную линию, линию измерения и метку; при щелчке мышью за пределами палитры обрабатываем событие с помощью "HandleCrosshairClick" и перерисовываем изображение. Наконец, обновим значение параметра "prev_mouse_state" до текущего значения для последующих событий, обеспечивая отзывчивое и сохраняющее состояние взаимодействие. Наконец, необходимо удалить объекты, когда они не нужны.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   canvasHeader.Destroy();                                      //--- Destroy header canvas
   canvasTools.Destroy();                                       //--- Destroy tools canvas
   DeleteCrosshair();                                           //--- Delete crosshair
   measuring = false;                                           //--- Reset measuring flag
   ChartSetInteger(0, CHART_MOUSE_SCROLL, true);                //--- Enable mouse scroll

   int total = ObjectsTotal(local_chart_id);                    //--- Get total objects
   for (int i = total - 1; i >= 0; i--) {                       //--- Loop over objects backward
      string obj_name = ObjectName(local_chart_id, i);          //--- Get object name
      if (StringFind(obj_name, "Tool_") == 0) {                 //--- Check if our tool
         ObjectDelete(local_chart_id, obj_name);                //--- Delete object
      }
   }

   ChartRedraw();                                               //--- Redraw chart
}

В обработчике OnDeinit мы выполняем очистку, уничтожая объекты Canvas заголовка и инструментов с помощью метода Destroy, удаляя любое активное перекрестие с помощью метода "DeleteCrosshair", сбрасывая флаг измерения в значение false и повторно включая прокрутку мышью с помощью ChartSetInteger функции. Чтобы исключить остаточные объекты, мы получаем общее количество объектов с помощью ObjectsTotal на локальном графике, выполняем обратный цикл для безопасного удаления, получаем имена с помощью ObjectName и удаляем объекты с префиксом "Tool_" с помощью ObjectDelete функции. Наконец, мы вызываем ChartRedraw для обновления графика, завершая освобождение ресурсов при выходе из программы. После компиляции получаем следующий результат.

CROSSHAIR IN ACTION

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


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

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

TOOLS PALETTE GIF


Заключение

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

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

Прикрепленные файлы |
Tools_Palette.mq5 (62.96 KB)
Нейросети в трейдинге: Унифицированное смешивание признаков для торговых решений (Окончание) Нейросети в трейдинге: Унифицированное смешивание признаков для торговых решений (Окончание)
В статье представлена завершающая часть адаптации фреймворка UniMixer средствами MQL5, включая построение SiameseNorm и объекта верхнего уровня CNeuronUniMixerBlock. Описана полная цепочка обработки рыночных данных от токенизации и контекстного выделения до сценарного моделирования и смешивания признаков. Приведены результаты тестирования на исторических данных EURUSD, демонстрирующие умеренную прибыль.
Разработка инструментария для анализа Price Action (Часть 27): Инструмент выявления снятия ликвидности с MA-фильтром Разработка инструментария для анализа Price Action (Часть 27): Инструмент выявления снятия ликвидности с MA-фильтром
Понимание тонких механизмов, стоящих за движением цены, может дать вам серьезное преимущество. Одно из таких явлений – снятие ликвидности, то есть целенаправленный прием, который крупные трейдеры, особенно институциональные участники, используют, чтобы провести цену через ключевые уровни поддержки или сопротивления. Эти уровни часто совпадают со скоплениями стоп-лоссов розничных трейдеров, создавая зоны ликвидности, которые крупные игроки могут использовать для входа в крупные позиции или выхода из них с минимальным проскальзыванием.
Торговые инструменты на MQL5 (Часть 20): Построение графиков на Canvas с использованием статистической корреляции и регрессионного анализа Торговые инструменты на MQL5 (Часть 20): Построение графиков на Canvas с использованием статистической корреляции и регрессионного анализа
В этой статье мы создаем графический инструмент на основе Canvas в MQL5 для статистического корреляционного и линейного регрессионного анализа между двумя символами с возможностью перетаскивания и изменения размера. Мы включили ALGLIB для регрессионных расчетов, динамические метки тиков, точки данных и панель статистики, отображающую наклон, пересечение, корреляцию и R-квадрат. Эта интерактивная визуализация помогает лучше понять суть парной торговли, поддерживая настраиваемые темы, границы и обновление новых баров в режиме реального времени
Внедрение в MQL5 практических модулей из других языков (Часть 06): Операции файлового ввода-вывода в MQL5, как в Python Внедрение в MQL5 практических модулей из других языков (Часть 06): Операции файлового ввода-вывода в MQL5, как в Python
В этой статье показано, как упростить сложные операции MQL5 с файлами, создав интерфейс в стиле Python для удобного чтения и записи. В ней объясняется, как воссоздать интуитивно понятные шаблоны работы с файлами в Python с помощью пользовательских функций и классов. В результате получился более ясный и надежный подход к файловому вводу-выводу в языке MQL5.