English Deutsch 日本語
preview
Советник для размещения сделок на основе риска с графическим интерфейсом на графике (Часть 1): Проектирование пользовательского интерфейса

Советник для размещения сделок на основе риска с графическим интерфейсом на графике (Часть 1): Проектирование пользовательского интерфейса

MetaTrader 5Трейдинг |
52 0
Chacha Ian Maroa
Chacha Ian Maroa

Введение

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

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

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

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


Концепция и дизайн

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

Дизайн графического интерфейса

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

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

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

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


Подготовка структуры советника

Начнем с создания рабочего файла. Откройте MetaEditor, перейдите в меню Файл --> Создать --> Советник (шаблон) и создайте пустой файл советника. Назовите его SmartRiskTrader. Мы выбираем это имя, потому что оно ясно отражает назначение нашего инструмента. Это интеллектуальный торговый помощник, который управляет размещением сделок на основе риска.

После создания файла удалите из него весь автоматически сгенерированный код, затем вставьте следующий исходный код:

//+------------------------------------------------------------------+
//|                                              SmartRiskTrader.mq5 |
//|          Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian |
//|                          https://www.mql5.com/en/users/chachaian |
//+------------------------------------------------------------------+

#property copyright "Copyright 2025, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/en/users/chachaian"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- create timer
   EventSetTimer(60);
      
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   //--- destroy timer
   EventKillTimer();
   
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
   
}

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest     &request,
                        const MqlTradeResult      &result)
{

}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int32_t id,
                  const long    &lparam,
                  const double  &dparam,
                  const string  &sparam)
{

}

//+------------------------------------------------------------------+

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

  • OnInit — Эта функция вызывается при первой загрузке советника на график. Внутри нее мы устанавливаем событие таймера, которое срабатывает через заданный интервал времени. В нашем случае — каждые 60 секунд. Обычно именно в этой функции выполняются операции инициализации советника.
  • OnDeinit — Эта функция выполняется, когда советник удаляется с графика или когда терминал завершает работу. Она очищает таймеры и ресурсы, созданные советником.
  • OnTick — Эта функция вызывается каждый раз, когда приходит новый ценовой тик. Позже мы будем использовать ее для обработки торговой логики.
  • OnTimer — Эта функция выполняется периодически на основе таймера, который мы установили в OnInit. Она отлично подходит для фоновых задач, таких как обновление панелей или проверка торговых условий.
  • OnTradeTransaction — Эта функция вызывается каждый раз, когда происходит торговое действие, например открытие, изменение или закрытие позиции. Мы можем использовать ее для мониторинга торговой активности и реакции на нее.
  • OnChartEvent — Эта функция обрабатывает взаимодействия пользователя с графиком, например нажатия кнопок. Она необходима для создания нашей панели управления на графике.

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

//--- UTILITY FUNCTIONS

//+------------------------------------------------------------------+
//| Function to generate a unique object name with a given prefix    |
//+------------------------------------------------------------------+
string GenerateUniqueName(string prefix){
   int attempt = 0;
   string uniqueName;
   while(true)
   {
      uniqueName = prefix + IntegerToString(MathRand() + attempt);
      if(ObjectFind(0, uniqueName) < 0){
         break;
      }
      attempt++;
   }
   return uniqueName;
}              

//--- Reusable GUI elements
//+------------------------------------------------------------------+
//| 1. To create a Rectangular panel                                 |
//+------------------------------------------------------------------+
bool CREATE_OBJ_RECTANGLE_LABEL(
   string objName,
   int xDistance,
   int yDistance,
   int width,
   int height,
   color clrBackground,
   int borderWidth,
   color borderColor            = clrNONE,
   ENUM_BORDER_TYPE borderType  = BORDER_FLAT,
   ENUM_LINE_STYLE  borderStyle = STYLE_SOLID
){

   ResetLastError();
   
   //--- Create a rectangular panel
   if(!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)){
      Print("Error while creating a rectangular panel: ", GetLastError());
      return false;
   }
   
   //--- Set values for corresponding object properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance);
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance);
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, width);
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, height);
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBackground);
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType);
   ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle);
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, borderWidth);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, borderColor);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
   
   ChartRedraw();
   return true;
}
 
//+------------------------------------------------------------------+
//| 2. To create a Button Object                                     |
//+------------------------------------------------------------------+
bool CREATE_OBJ_BUTTON(
   string objName,
   int xDistance,
   int yDistance,
   int width,
   int height,
   string text           = "Activate",
   color textColor       = clrDarkGray,
   int fontSize          = 12,
   int borderWidth       = 0,
   color backgroundColor = clrWhiteSmoke,
   color borderColor     = clrBlack,
   string font           = "Tahoma"
){
   
   ResetLastError();
   
   //--- Create a button object
   if(!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)){
      Print("Error while creating a button: ", GetLastError());
      return false;
   }
   
   //--- Set values for corresponding object properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance);
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance);
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, width);
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, height);
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString (0, objName, OBJPROP_TEXT, text);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);
   ObjectSetString (0, objName, OBJPROP_FONT, font);
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, backgroundColor);
   ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, borderWidth);
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
   
   ChartRedraw();   
   return true;
}

//+------------------------------------------------------------------+
//| 3. To create an Input field                                      |
//+------------------------------------------------------------------+
bool CREATE_OBJ_EDIT(
   string objName,
   int xDistance,
   int yDistance,
   int width,
   int height,
   string text           = "Say sth'...",
   color textColor       = clrGray,
   int fontSize          = 12,
   color backgroundColor = clrWhite,
   color borderColor     = clrBlack,
   string font           = "Tahoma"
){
   
   ResetLastError();
   
   //--- Create an input field
   if(!ObjectCreate(0, objName, OBJ_EDIT, 0, 0, 0)){
      Print("Error while creating a text input: ", GetLastError());
      return false;
   }
   
   //--- Set values for corresponding object properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance);
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance);
   ObjectSetInteger(0, objName, OBJPROP_XSIZE, width);
   ObjectSetInteger(0, objName, OBJPROP_YSIZE, height);
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString (0, objName, OBJPROP_TEXT, text);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);
   ObjectSetString (0, objName, OBJPROP_FONT, font);
   ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, backgroundColor);
   ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, borderColor);
   ObjectSetInteger(0, objName, OBJPROP_ALIGN, ALIGN_LEFT);
   ObjectSetInteger(0, objName, OBJPROP_READONLY, false);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
   
   ChartRedraw();   
   return true;
}

//+------------------------------------------------------------------+
//| 4. To create a Text label                                        |
//+------------------------------------------------------------------+
bool CREATE_OBJ_LABEL(
   string objName,
   int xDistance,
   int yDistance,
   string text           = "Name sth'...",
   color textColor       = clrDarkGray,
   int fontSize          = 12,
   string font           = "Tahoma"
){
   
   ResetLastError();
   
   //--- Create a text label
   if(!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)){
      Print("Error while creating a text label: ", GetLastError());
      return false;
   }
   
   //--- Set values for corresponding object properties
   ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xDistance);
   ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yDistance);
   ObjectSetInteger(0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString (0, objName, OBJPROP_TEXT, text);
   ObjectSetInteger(0, objName, OBJPROP_COLOR, textColor);
   ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize);
   ObjectSetString (0, objName, OBJPROP_FONT, font);
   ObjectSetInteger(0, objName, OBJPROP_BACK, false);
   ObjectSetInteger(0, objName, OBJPROP_STATE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, objName, OBJPROP_SELECTED, false);
   
   ChartRedraw();   
   return true;
}

Теперь разберем, что делает каждая из этих функций.

1. GenerateUniqueName

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

2. CREATE_OBJ_RECTANGLE_LABEL

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

3. CREATE_OBJ_BUTTON

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

4. CREATE_OBJ_EDIT

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

5. CREATE_OBJ_LABEL

Эта функция создает простую текстовую метку, которая отображает информацию или описания рядом с полями ввода и кнопками.

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


Пошаговая сборка пользовательского интерфейса

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

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

}

//+------------------------------------------------------------------+

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

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- create timer
   EventSetTimer(60);
   
   //--- render the graphical user interface
   CREATE_GUI();
      
   return(INIT_SUCCEEDED);
}

...

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

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

//+------------------------------------------------------------------+
//| 4. This function configures the chart's appearance.              |                          |
//+------------------------------------------------------------------+
bool ConfigureChartAppearance()
{
   if(!ChartSetInteger(0, CHART_SHOW_ONE_CLICK, false)){
      Print("Error while setting one click buttons, ", GetLastError());
      return false;
   }
   return true;
}

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   ...
   
   //--- Configure chart appearance
   if(!ConfigureChartAppearance()){
      Print("Error while customizing the chart's appearance");
      return(INIT_FAILED);
   }
      
   return(INIT_SUCCEEDED);
}

Хорошая практика — убедиться, что все графические объекты, созданные нашим советником, корректно удаляются при снятии программы с графика. Это предотвращает захламление и исключает появление оставшихся объектов от предыдущих запусков.

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ...
   
   //--- delete all graphical objects
   ObjectsDeleteAll(0);
      
}

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

...

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+
#define SmartRiskTrader   "SmartRiskTrader"

...

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

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   //--- Background panel
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 20, 20, 320, 380, clrWhiteSmoke, 1, clrDarkBlue, BORDER_FLAT, STYLE_DASHDOTDOT);

}

...

Мы используем GenerateUniqueName(SmartRiskTrader) чтобы гарантировать уникальность имени объекта, и задаем ему аккуратный фон WhiteSmoke с темно-синей рамкой.

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

основная прямоугольная панель

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

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

После добавления ваш код должен выглядеть так:

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   //--- Background panel
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 20, 20, 320, 380, clrWhiteSmoke, 1, clrDarkBlue, BORDER_FLAT, STYLE_DASHDOTDOT);
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 30, 30, 300, 360, clrWhite, 1, clrDarkBlue, BORDER_FLAT, STYLE_SOLID);

}

...

Это простое добавление придает интерфейсу более аккуратный и многослойный вид. Скомпилируйте и запустите код, чтобы оценить обновленный дизайн.

вложенная панель

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

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

Начнем с создания небольшого прямоугольника, в котором будет находиться кнопка закрытия:

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   //--- Background panel
   ...
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 30, 30, 300, 360, clrWhite, 1, clrDarkBlue, BORDER_FLAT, STYLE_SOLID);
   
   //--- Header Section Components
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 300, 40, 20, 20, clrWhiteSmoke, 1, clrDarkBlue, BORDER_FLAT, STYLE_SOLID);
   
}

...

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

кнопка закрытия интерфейса

Теперь поместим кнопку закрытия («X») прямо внутрь этого поля. В отличие от декоративных компонентов, которые мы создавали до сих пор, эта кнопка действительно будет реагировать на действия пользователя — при нажатии она будет закрывать наш интерфейс. Поэтому важно дать ей уникальное и легко узнаваемое имя, к которому мы позже сможем обращаться в коде.

...

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+
#define SmartRiskTrader   "SmartRiskTrader"
#define BTN_CLOSE_GUI     "BTN_CLOSE_GUI"

...

Теперь добавим код, который фактически отображает кнопку.

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
   
   //--- Header Section Components
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 300, 40, 20, 20, clrWhiteSmoke, 1, clrDarkBlue, BORDER_FLAT, STYLE_SOLID);
   CREATE_OBJ_LABEL(BTN_CLOSE_GUI, 305, 40, "X", clrDarkBlue, 12);
   
}

Скомпилируйте советник, и вы должны увидеть символ «X» в правой верхней части панели. 

кнопка закрытия интерфейса

Теперь добавим метку заголовка для интерфейса. Это сделает панель более профессиональной и информативной.

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
   
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 37, "Smart Risk Trader", clrDarkBlue, 14, "Comic Sans Ms");
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 37, "Smart Risk Trader", clrDarkBlue, 14, "Comic Sans Ms");
   
}

...

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

заголовок

Наконец, создадим горизонтальную линию, которая отделит заголовок от остальных компонентов ниже:

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
   
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 37, "Smart Risk Trader", clrDarkBlue, 14, "Comic Sans Ms");
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 30, 70, 300, 1, clrDarkBlue, 1, clrDarkBlue, BORDER_FLAT);
   
}

...

Скомпилируйте код еще раз, чтобы убедиться, что все на месте.

разделитель секции заголовка

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

Теперь добавим первое поле ввода и его метку. Это поле позволит пользователю выбрать тип ордера, например Buy, Sell, Buy Limit и другие. Как и раньше, начнем с отображения текстовой метки, описывающей назначение поля.

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

...

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+
...

#define BTN_ORDER_TYPES   "BTN_ORDER_TYPES"

...

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

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
   
   //--- Order Types
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 90, "Order Type: ", C'20, 20, 20'); 
   CREATE_OBJ_BUTTON(BTN_ORDER_TYPES, 140, 90, 140, 25, "Select Order Type", C'20, 20, 20', 12, 1, clrWhiteSmoke, clrDarkBlue);  
   
}

...

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

типы ордеров

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

#define ORDER_TYPE_GROUP  "ORDER_TYPE_GROUP"
#define MARKET_BUY        "ORDER_TYPE_GROUP_MARKET_BUY"
#define MARKET_SELL       "ORDER_TYPE_GROUP_MARKET_SELL"
#define BUY_LIMIT         "ORDER_TYPE_GROUP_BUY_LIMIT"
#define SELL_LIMIT        "ORDER_TYPE_GROUP_SELL_LIMIT"
#define BUY_STOP          "ORDER_TYPE_GROUP_BUY_STOP"
#define SELL_STOP         "ORDER_TYPE_GROUP_SELL_STOP"

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

...

//+------------------------------------------------------------------+
//| Function to create the order types dropdown                      |
//+------------------------------------------------------------------+
void CREATE_ORDER_TYPE_DROPDOWN(){
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(ORDER_TYPE_GROUP), 140, 116, 140, 151, clrWhiteSmoke, 1, clrDarkBlue, BORDER_FLAT);
   CREATE_OBJ_LABEL(MARKET_BUY , 150, 120, "Market Buy ", C'20, 20, 20');
   CREATE_OBJ_LABEL(MARKET_SELL, 150, 145, "Market Sell", C'20, 20, 20');
   CREATE_OBJ_LABEL(BUY_LIMIT  , 150, 170, "Buy Limit  ", C'20, 20, 20');
   CREATE_OBJ_LABEL(SELL_LIMIT , 150, 195, "Sell Limit ", C'20, 20, 20');
   CREATE_OBJ_LABEL(BUY_STOP   , 150, 220, "Buy Stop   ", C'20, 20, 20');
   CREATE_OBJ_LABEL(SELL_STOP  , 150, 245, "Sell Stop  ", C'20, 20, 20');
}

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

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- create timer
   EventSetTimer(60);
   
   //--- render the graphical user interface
   CREATE_GUI();
   CREATE_ORDER_TYPE_DROPDOWN();
      
   return(INIT_SUCCEEDED);
}

...

Теперь скомпилируйте исходный код и посмотрите на график: вы должны увидеть аккуратное выпадающее меню, красиво отображенное в вашем интерфейсе.

order types dropdown 

Убедившись, что оно работает правильно, важно закомментировать вызов функции, потому что позже мы будем показывать выпадающий список только после нажатия пользователем кнопки «Select Order Type». Это обеспечит динамическое поведение выпадающего списка, как и должно быть в профессиональном интерфейсе.

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- create timer
   EventSetTimer(60);
   
   //--- render the graphical user interface
   CREATE_GUI();
   //CREATE_ORDER_TYPE_DROPDOWN();
      
   return(INIT_SUCCEEDED);
}

...

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

...

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+
...

#define FIELD_ENTRY_PRICE "FIELD_ENTRY_PRICE"

...

После определения макроса можно добавить метку и поле ввода в функцию CREATE_GUI. Эти две строки кода отобразят подпись «Entry Price» и редактируемое поле, в которое пользователь сможет ввести значение.

...

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
  
   //--- Entry Price
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 125, "Entry Price: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_ENTRY_PRICE, 140, 125, 100, 25, "1.14030", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
}

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

entry price field

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

...

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+

...

#define FIELD_STOP_LOSS   "FIELD_STOP_LOSS"
#define FIELD_TAKE_PROFIT "FIELD_TAKE_PROFIT"
#define RISK              "RISK"

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


//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
     
   //--- Stop Loss
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 160, "Stop Loss: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_STOP_LOSS, 140, 160, 100, 25, "1.13302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   //--- Take Profit
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 195, "Take Profit: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(FIELD_TAKE_PROFIT, 140, 195, 100, 25, "1.16302", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);
   
   //--- Risk
   CREATE_OBJ_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 230, "Risk %: ", C'20, 20, 20');
   CREATE_OBJ_EDIT(RISK, 140, 230, 100, 25, "2.0", C'20, 20, 20', 12, clrWhiteSmoke, clrDarkBlue);

}

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

other inputs

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

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+

...

#define BTN_SEND_ORDER    "BTN_SEND_ORDER"
#define RESULTS_TEXT      "RESULTS_TEXT"
#define BTN_GUI_OPEN      "BTN_GUI_OPEN"

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

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
    
   //--- Execution Buttons
   CREATE_OBJ_BUTTON(BTN_CALC_LOT, 40, 270, 140, 40, "CALCULATE LOT", clrWhite, 12, 1, clrDarkBlue, clrBlack);
   CREATE_OBJ_BUTTON(BTN_SEND_ORDER, 190, 270, 120, 40, "SEND ORDER", clrWhite, 12, 1, clrDarkGreen, clrBlack);
  
}

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

execution buttons

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

//+------------------------------------------------------------------+
//| Macros                                                           |
//+------------------------------------------------------------------+

...

#define RESULTS_TEXT      "RESULTS_TEXT"

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

//+------------------------------------------------------------------+
//| Function to render the main GUI                                  |
//+------------------------------------------------------------------+
void CREATE_GUI(){

   ...
   
   //--- Execution Results
   CREATE_OBJ_RECTANGLE_LABEL(GenerateUniqueName(SmartRiskTrader), 40, 320, 270, 50, clrWhiteSmoke, 1, clrDarkBlue, BORDER_FLAT);
   CREATE_OBJ_LABEL(RESULTS_TEXT, 60, 333, "Result: Lot Size = 0.23", clrDarkGreen, 14);
   
}

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

final gui look

С этим добавлением мы завершили статический дизайн интерфейса «Smart Risk Trader». Чтобы вам было проще следовать материалу, мы приложили полный исходный код для текущего этапа проекта. Вы можете открыть его в MetaEditor, изучить рассмотренные функции и убедиться, что все корректно компилируется. В следующем разделе мы сосредоточимся на том, чтобы сделать GUI интерактивным и реагирующим на действия пользователя — фактически оживим его.


Заключение

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

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

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

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

Прикрепленные файлы |
SmartRiskTrader.mq5 (15.31 KB)
Разрабатываем пользовательский индикатор рыночных настроений Разрабатываем пользовательский индикатор рыночных настроений
В этой статье мы разрабатываем пользовательский индикатор рыночных настроений, который классифицирует рыночные условия как бычьи, медвежьи, в режиме risk-on, в режиме risk-off или нейтральные. Благодаря анализу нескольких таймфреймов индикатор дает трейдерам более ясное представление об общей направленности рынка и краткосрочных подтверждениях.
TradeMux как Quant Backbone: Подключение институциональных Python-пайплайнов к разным терминалам и брокерам TradeMux как Quant Backbone: Подключение институциональных Python-пайплайнов к разным терминалам и брокерам
Статья описывает TradeMux как мост между Python-пайплайном и терминалом MetaTrader 5 для чистой передачи торговых решений без дублирования логики. Разобрана production-архитектура из четырёх слоёв и полный Python execution service: подключение, чтение счёта и позиций, генерация сигналов (включая CatBoost), предторговый риск-контроль, kill_switch и supervisor. Практическая польза — кросс-брокерная нормализация (RoboForex, IC Markets, Alpari, OANDA) и масштабирование от одного счёта к мультисчётному broadcast без изменения торговой логики.
От начального до среднего уровня: События в объектах (I) От начального до среднего уровня: События в объектах (I)
В этой статье мы рассмотрим три из шести событий, которые MetaTrader 5 может генерировать при возникновении каких-либо изменений с объектом на графике. Данные события очень полезны с точки зрения взаимодействия с пользователями. Это происходит так, потому что без понимания этих событий нам придётся приложить гораздо больше усилий для поддержания определённой конфигурации в графике при попытке управлять объектами для конкретных целей.
Знакомство с языком MQL5 (Часть 42): Руководство для начинающих по работе с файлами в MQL5 (IV) Знакомство с языком MQL5 (Часть 42): Руководство для начинающих по работе с файлами в MQL5 (IV)
В этой статье показано, как создать индикатор на языке MQL5, который считывает торговую историю из CSV, извлекает значения из столбца Profit($) и общее число сделок, а затем рассчитывает накопительную кривую баланса. Мы строим кривую в отдельном окне индикатора, автоматически масштабируем ось Y и рисуем горизонтальную и вертикальную оси для выравнивания. Индикатор обновляется по таймеру и перерисовывается только при появлении новых сделок. Необязательные метки показывают прибыль или убыток по каждой сделке, помогая прямо на графике оценивать результаты торговли и просадки.