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

Anatoli Kazharski | 10 мая, 2018


Содержание

Введение

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

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

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

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

  • Создание графического интерфейса.
  • Получение списка символов с заданными свойствами. 
  • Элементы управления торговыми операциями. 
  • Быстрое переключение символов и таймфреймов на графиках без переинициализации эксперта.
  • Управление свойствами графиков через пользовательский интерфейс.
  • Получение сигналов индикатора от множества символов с цветовой индикацией.
  • Работа с открытыми позициями. 
  • Обновления библиотеки EasyAndFast.

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

Элементы графического интерфейса

Разработку эксперта начнем с построения графического интерфейса, через который будет реализовано взаимодействие с пользователем и визуализация данных. Можно создать графический интерфейс, воспользовавшись возможностями стандартной библиотеки, но в моем примере он будет реализован на основе библиотеки EasyAndFast. Ее богатые возможности позволяют сосредоточиться на функционале самой программы, не отвлекаясь на доработку её графической части.

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

Рис. 1 - Общая схема графического интерфейса с пояснениями.

Рис. 1. Общая схема графического интерфейса с пояснениями.

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

  • Форма для элементов управления
    • Строка состояния для показа дополнительной итоговой информации
    • Группа вкладок:
      • Trade:
        • Поле ввода с чек-боксом для фильтрации списка символов
        • Кнопка запроса для начала сбора списка символов
        • Кнопка для торговой операции SELL
        • Кнопка для торговой операции BUY
        • Поле ввода для установки объёма сделки
        • Кнопка для закрытия всех открытых позиций
        • Поле ввода установки уровня для сигнала на продажу
        • Поле ввода установки уровня для сигнала на покупку
        • Таблица символов для торговли
        • График для визуализации данных символов. Для удобства сделаем так, чтобы некоторыми свойствами графика можно было управлять с помощью следующей группы элементов:
          • Комбо-бокс с выпадающим списком для выбора переключения таймфрейма 
          • Чек-бокс для включения/выключения временной шкалы 
          • Чек-бокс для включения/выключения ценовой шкалы 
          • Поле ввода для управления масштабом 
          • Кнопка для включения отступа 
          • Чек-бокс для показа индикатора 
      • Positions:
        • Таблица позиций
    • Индикатор выполнения процесса повторного проигрывания фреймов

В главном классе программы (CProgram) объявляем методы и экземпляры классов вышеперечисленных элементов. Код методов для создания элементов вынесен в отдельный файл и подключается к файлу с классом MQL-программы:

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Окно
   CWindow           m_window1;
   //--- Статусная строка
   CStatusBar        m_status_bar;
   //--- Вкладки
   CTabs             m_tabs1;
   //--- Поля ввода
   CTextEdit         m_symb_filter;
   CTextEdit         m_lot;
   CTextEdit         m_up_level;
   CTextEdit         m_down_level;
   CTextEdit         m_chart_scale;
   //--- Кнопки
   CButton           m_request;
   CButton           m_chart_shift;
   CButton           m_buy;
   CButton           m_sell;
   CButton           m_close_all;
   //--- Комбо-боксы
   CComboBox         m_timeframes;
   //--- Чек-боксы
   CCheckBox         m_date_scale;
   CCheckBox         m_price_scale;
   CCheckBox         m_show_indicator;
   //--- Таблицы
   CTable            m_table_positions;
   CTable            m_table_symb;
   //--- Стандартный график
   CStandardChart    m_sub_chart1;
   //--- Индикатор выполнения
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Создаёт графический интерфейс
   bool              CreateGUI(void);
   //---
private:
   //--- Форма
   bool              CreateWindow(const string text);
   //--- Статусная строка
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- Вкладки
   bool              CreateTabs1(const int x_gap,const int y_gap);
   //--- Поля ввода
   bool              CreateSymbFilter(const int x_gap,const int y_gap,const string text);
   bool              CreateLot(const int x_gap,const int y_gap,const string text);
   bool              CreateUpLevel(const int x_gap,const int y_gap,const string text);
   bool              CreateDownLevel(const int x_gap,const int y_gap,const string text);
   bool              CreateChartScale(const int x_gap,const int y_gap,const string text);
   //--- Кнопки
   bool              CreateRequest(const int x_gap,const int y_gap,const string text);
   bool              CreateChartShift(const int x_gap,const int y_gap,const string text);
   bool              CreateBuy(const int x_gap,const int y_gap,const string text);
   bool              CreateSell(const int x_gap,const int y_gap,const string text);
   bool              CreateCloseAll(const int x_gap,const int y_gap,const string text);
   //--- Комбо-бокс
   bool              CreateComboBoxTF(const int x_gap,const int y_gap,const string text);
   //--- Чек-боксы
   bool              CreateDateScale(const int x_gap,const int y_gap,const string text);
   bool              CreatePriceScale(const int x_gap,const int y_gap,const string text);
   bool              CreateShowIndicator(const int x_gap,const int y_gap,const string text);
   //--- Таблицы
   bool              CreatePositionsTable(const int x_gap,const int y_gap);
   bool              CreateSymbolsTable(const int x_gap,const int y_gap);
   //--- Стандартный график
   bool              CreateSubChart1(const int x_gap,const int y_gap);
   //--- Индикатор выполнения
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| Методы для создания элементов управления                         |
//+------------------------------------------------------------------+
#include "CreateGUI.mqh"
//+------------------------------------------------------------------+

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

Сборка графического интерфейса

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

  • Форма для элементов управления (CWindow)
  • Статусная строка (CStatusBar)
  • Группа вкладок (CTabs)
  • Поле ввода (CTextEdit)
  • Кнопка (CButton)
  • Комбо-бокс с выпадающим списком (CComboBox)
  • Чек-бокс (CCheckBox)
  • Таблица (CTable)
  • Стандартный график (CStandardChart)
  • Индикатор выполнения (CProgressBar)

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

Форма для элементов управления

Ниже представлен код метода для создания формы, на которой будут располагаться все остальные элементы. В начале нужно добавить форму в список элементов графического интерфейса программы. Для этого нужно вызвать метод CWndContainer::AddWindow(), передав в него объект элемента типа CWindow. Затем, перед созданием формы, устанавливаем её свойства. Мы устанавливаем следующие свойства (в той же последовательности, что и в листинге ниже):

  • Размеры формы (ширина и высота)
  • Размер шрифта в заголовке
  • Режим перемещения формы (в пределах графика)
  • Режим изменения размеров формы вручную (перетаскиванием границ)
  • Кнопки формы. Видимость каждой кнопки включается отдельно. В данном случае задействованы следующие из них:
    • Закрытие формы. В главной форме программы при нажатии на эту кнопку, выйдет диалоговое окно с вопросом о закрытии программы.
    • Сворачивание и развёртывание формы. 
    • Всплывающие подсказки элементов. Эта кнопка тоже имеет два состояния. Если она нажата, то в элементах управления будут показываться всплывающие подсказки (при условии, что они были заданы).
    • Разворачивание формы на всю область графика терминала. Развернув форму, её затем можно вернуть в предыдущие размеры, нажав эту кнопку повторно.
  • Для каждой кнопки формы можно установить всплывающую подсказку.

После того, как свойства заданы, нужно вызвать метод создания формы — CWindow::CreateWindow() и передать в него:

  • идентификатор графика,
  • номер подокна графика,
  • текст заголовка,
  • начальные координаты расположения для формы.
//+------------------------------------------------------------------+
//| Создаёт форму для элементов управления                           |
//+------------------------------------------------------------------+
bool CProgram::CreateWindow(const string caption_text)
  {
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_window1);
//--- Свойства
   m_window1.XSize(750);
   m_window1.YSize(450);
   m_window1.FontSize(9);
   m_window1.IsMovable(true);
   m_window1.ResizeMode(true);
   m_window1.CloseButtonIsUsed(true);
   m_window1.CollapseButtonIsUsed(true);
   m_window1.TooltipsButtonIsUsed(true);
   m_window1.FullscreenButtonIsUsed(true);
//--- Установим всплывающие подсказки
   m_window1.GetCloseButtonPointer().Tooltip("Close");
   m_window1.GetTooltipButtonPointer().Tooltip("Tooltips");
   m_window1.GetFullscreenButtonPointer().Tooltip("Fullscreen");
   m_window1.GetCollapseButtonPointer().Tooltip("Collapse/Expand");
//--- Создание формы
   if(!m_window1.CreateWindow(m_chart_id,m_subwin,caption_text,1,1))
      return(false);
//---
   return(true);
  }

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

Рис. 2 - Форма для элементов управления.

Рис. 2. Форма для элементов управления.

Ниже будут показаны скриншоты всех промежуточных результатов.

Статусная строка

Код метода для создания статусной строки начинается с указания главного элемента. От него будет рассчитываться позиционирование и выравнивание по размеру привязанных к нему элементов. Это экономит  время при разработке приложения: целую группу связанных элементов можно будет переместить, изменив координаты только у главного из них. Для привязки элемента его указатель передается в метод CElement::MainPointer(). В данном примере мы привязываем статусную строку к форме, поэтому в метод передаём объект формы.

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

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

После того, как свойства заданы, создаём элемент. Теперь он готов для работы, и мы можем изменять текст в его пунктах во время выполнения программы. В нашем примере установим в первом пункте текст «For Help, press F1»

В конце метода нужно обязательно сохранить указатель созданного элемента в общем списке элементов графического интерфейса. Для этого вызовем метод CWndContainer::AddToElementsArray(), передадим в него индекс формы и объект элемента. Поскольку форма у нас только одна, то ее индекс будет 0.

//+------------------------------------------------------------------+
//| Создаёт статусную строку                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateStatusBar(const int x_gap,const int y_gap)
  {
#define STATUS_LABELS_TOTAL 3
//--- Сохраним указатель на окно
   m_status_bar.MainPointer(m_window1);
//--- Свойства
   m_status_bar.AutoXResizeMode(true);
   m_status_bar.AutoXResizeRightOffset(1);
   m_status_bar.AnchorBottomWindowSide(true);
//--- Укажем, сколько должно быть частей и установим им свойства
   int width[STATUS_LABELS_TOTAL]={0,200,110};
   for(int i=0; i<STATUS_LABELS_TOTAL; i++)
      m_status_bar.AddItem(width[i]);
//--- Создадим элемент управления
   if(!m_status_bar.CreateStatusBar(x_gap,y_gap))
      return(false);
//--- Установка текста в пункты статусной строки
   m_status_bar.SetValue(0,"For Help, press F1");
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_status_bar);
   return(true);
  }

Остальные элементы библиотеки EasyAndFast создаются по тому же принципу. Поэтому дальше будем рассматривать только задаваемые свойства, которые будут использоваться в нашем эксперте.

Рис. 3 - Добавления статусной строки.

Рис. 3.  Добавление статусной строки.

Группа вкладок

В методе для создания группы вкладок зададим следующие свойства элементу:

  • Текст во вкладках сделаем по центру.
  • Вкладки расположим в верхней части рабочей области.
  • Размеры будут автоматически подстраиваться под область главного элемента (форма). В таком случае необязательно указывать размеры области группы вкладок.
  • Укажем отступы от правого и нижнего края главного элемента. При изменении размеров формы эти отступы будут сохраняться.
  • При добавлении следующих вкладок в метод передаются также название вкладки и её ширина.

Вот код метода:

//+------------------------------------------------------------------+
//| Создаёт группу с вкладками 1                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateTabs1(const int x_gap,const int y_gap)
  {
#define TABS1_TOTAL 2
//--- Сохраним указатель на главный элемент
   m_tabs1.MainPointer(m_window1);
//--- Свойства
   m_tabs1.IsCenterText(true);
   m_tabs1.PositionMode(TABS_TOP);
   m_tabs1.AutoXResizeMode(true);
   m_tabs1.AutoYResizeMode(true);
   m_tabs1.AutoXResizeRightOffset(3);
   m_tabs1.AutoYResizeBottomOffset(25);
//--- Добавим вкладки с указанными свойствами
   string tabs_names[TABS1_TOTAL]={"Trade","Positions"};
   for(int i=0; i<TABS1_TOTAL; i++)
      m_tabs1.AddTab(tabs_names[i],100);
//--- Создадим элемент управления
   if(!m_tabs1.CreateTabs(x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_tabs1);
   return(true);
  }

Рис. 4 - Добавление группы вкладок.

Рис. 4. Добавление группы вкладок.

Поле ввода

Для примера рассмотрим поле ввода, в котором пользователь может указать валюты и/или валютные пары для формирования списка символов в таблице. Главным элементом для него будет группа вкладок. Здесь же нужно указать, на какой вкладке нужно отображать это поле ввода. Для этого вызовем метод CTabs::AddToElementsArray(), передадим в него индекс вкладки и объект присоединяемого элемента.

Далее рассмотрим свойства, устанавливаемые для этого поля ввода.

  • По умолчанию в поле ввода будет введён текст «USD»: программа будет собирать в таблицу символы, в которых есть USD. Указывать валюты и/или символы в этом поле ввода нужно через запятую. Ниже я покажу метод, с помощью которого формируется список символов по перечисленным через запятую строкам в этом поле ввода.
  • Поле ввода будет с чек-боксом. При отключении чек-бокса текст в поле ввода игнорируется, и в список символов попадут все найденные валютные пары.
  • Ширина поля ввода будет на всю ширину главного элемента, и при изменении ширины области вкладок будет тоже корректироваться.
  • Справа от поля ввода будет кнопка Request. Во время работы программы в поле ввода можно указать другие валюты и/или символы, а для того, чтобы список сформировался, нужно будет нажать на эту кнопку. Так как подразумевается автоматическое изменение ширины поля ввода, а кнопка Request должна быть всегда справа от него, то нужно, чтобы у правой стороны поля ввода всегда был отступ от правого края главного элемента. 

Элемент типа CTextEdit состоит из нескольких других элементов. Поэтому, если понадобится изменить их свойства, есть возможность получать указатели на них. Нам потребовалось изменить некоторые свойства текстового поля ввода (CTextBox). Рассмотрим их в той же последовательности, как это реализовано в листинге кода ниже.

  • Отступ поля ввода от левого края главного элемента (CTextEdit). 
  • Автоматическое изменение ширины относительно главного элемента.
  • При активации поля ввода (клик левой кнопки мыши на поле ввода) текст будет выделяться полностью автоматически для возможности быстрой замены.
  • Если в поле ввода вообще не будет текста, то будет показываться такая подсказка: «Example: EURUSD, GBP, NOK».  

По умолчанию чек-бокс поля ввода включен. Для этого нужно его активировать сразу после создания элемента.

//+------------------------------------------------------------------+
//| Создаёт чекбокс с полем ввода "Symbols filter"                   |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsFilter(const int x_gap,const int y_gap,const string text)
  {
//--- Сохраним указатель на главный элемент
   m_symb_filter.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_symb_filter);
//--- Свойства
   m_symb_filter.SetValue("USD"); // "EUR,USD" "EURUSD,GBPUSD" "EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCHF"
   m_symb_filter.CheckBoxMode(true);
   m_symb_filter.AutoXResizeMode(true);
   m_symb_filter.AutoXResizeRightOffset(90);
   m_symb_filter.GetTextBoxPointer().XGap(100);
   m_symb_filter.GetTextBoxPointer().AutoXResizeMode(true);
   m_symb_filter.GetTextBoxPointer().AutoSelectionMode(true);
   m_symb_filter.GetTextBoxPointer().DefaultText("Example: EURUSD,GBP,NOK");
//--- Создадим элемент управления
   if(!m_symb_filter.CreateTextEdit(text,x_gap,y_gap))
      return(false);
//--- Включим чек-бокс
   m_symb_filter.IsPressed(true);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_symb_filter);
   return(true);
  }

Кроме текстового поля ввода, в графическом интерфейсе будут и числовые. Например, поле ввода Lot (объём для открытия позиций). Для полей ввода такого типа нужно указывать другие свойства.

  • Общая ширина элемента.
  • Максимальное значение для ввода.
  • Минимальное значение для ввода.
  • Шаг при переключении кнопками инкремента и декремента.
  • Количество знаков после запятой.
  • Для того, чтобы поле ввода стало числовым, нужно это указать с помощью метода CTextEdit::SpinEditMode().
  • Значение по умолчанию после загрузки программы на график в терминале.
  • Ширина поля ввода.
  • Автоматическое выделение текста в поле ввода при клике на нём.
  • Примагничивание поля ввода к правому краю элемента.

Вот код этого метода:

//+------------------------------------------------------------------+
//| Создаёт поле ввода "Lot"                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateLot(const int x_gap,const int y_gap,const string text)
  {
//--- Сохраним указатель на главный элемент
   m_lot.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_lot);
//--- Свойства
   m_lot.XSize(80);
   m_lot.MaxValue(1000);
   m_lot.MinValue(0.01);
   m_lot.StepValue(0.01);
   m_lot.SetDigits(2);
   m_lot.SpinEditMode(true);
   m_lot.SetValue((string)0.1);
   m_lot.GetTextBoxPointer().XSize(50);
   m_lot.GetTextBoxPointer().AutoSelectionMode(true);
   m_lot.GetTextBoxPointer().AnchorRightWindowSide(true);
//--- Создадим элемент управления
   if(!m_lot.CreateTextEdit(text,x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_lot);
   return(true);
  }

Рис. 5 - Добавление полей ввода.

Рис. 5.  Добавление полей ввода.

Картинка выглядит не очень логичной, но при добавлении остальных элементов всё окажется на своих местах.

Кнопка

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

  • Ширина кнопки.
  • Текст кнопки будет точно по центру, как по вертикали, так и по горизонтали.
  • Цвет фона кнопки.
  • Цвет фона при наведении курсора.
  • Цвет фона при клике левой кнопкой.
  • Цвет текста кнопки.
  • Цвет текста при наведении курсора.
  • Цвет текста при клике левой кнопкой.
  • Цвет рамки кнопки.
  • Цвет рамки при наведении курсора.
  • Цвет рамки при клике левой кнопкой.

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

//+------------------------------------------------------------------+
//| Создаёт кнопку 'Sell'                                            |
//+------------------------------------------------------------------+
bool CProgram::CreateSell(const int x_gap,const int y_gap,const string text)
  {
//--- Сохранить указатель на главный элемент
   m_sell.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_sell);
//--- Свойства
   m_sell.XSize(80);
   m_sell.IsCenterText(true);
   m_sell.BackColor(C'255,51,51');
   m_sell.BackColorHover(C'255,100,100');
   m_sell.BackColorPressed(C'195,0,0');
   m_sell.LabelColor(clrWhite);
   m_sell.LabelColorHover(clrWhite);
   m_sell.LabelColorPressed(clrWhite);
   m_sell.BorderColor(clrBlack);
   m_sell.BorderColorHover(clrBlack);
   m_sell.BorderColorPressed(clrBlack);
//--- Создадим элемент управления
   if(!m_sell.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_sell);
   return(true);
  }

Рис. 6 - Добавление кнопок.

Рис. 6. Добавление кнопок.

Комбо-бокс с выпадающим списком

Для изменения таймфрейма сделаем комбо-бокс с выпадающим списком. Перечислим свойства для его настройки.

  • Общая ширина элемента. 
  • Количество пунктов в списке (в нашем случае 21, по количеству таймфреймов в терминале).
  • Элемент будет привязан к правой части области вкладок.
  • Ширина кнопки комбо-бокса.
  • Кнопка привязана к правой части элемента.

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

  • Подсветка пунктов по наведению курсора мыши.
  • Выделенный пункт. В данном случае это будет пункт по индексу 18 (таймфрейм D1).

Вот код метода для создания этого комбо-бокса:

//+------------------------------------------------------------------+
//| Создаёт комбо-бокс для выбора таймфреймов                        |
//+------------------------------------------------------------------+
bool CProgram::CreateComboBoxTF(const int x_gap,const int y_gap,const string text)
  {
//--- Общее количество пунктов в списке
#define ITEMS_TOTAL2 21
//--- Передать объект панели
   m_timeframes.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_timeframes);
//--- Свойства
   m_timeframes.XSize(115);
   m_timeframes.ItemsTotal(ITEMS_TOTAL2);
   m_timeframes.AnchorRightWindowSide(true);
   m_timeframes.GetButtonPointer().XSize(50);
   m_timeframes.GetButtonPointer().AnchorRightWindowSide(true);
//--- Сохраним значения пунктов в список комбо-бокса
   string items_text[ITEMS_TOTAL2]={"M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30","H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"};
   for(int i=0; i<ITEMS_TOTAL2; i++)
      m_timeframes.SetValue(i,items_text[i]);
//--- Получим указатель списка
   CListView *lv=m_timeframes.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   lv.SelectItem(18);
//--- Создадим элемент управления
   if(!m_timeframes.CreateComboBox(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_timeframes);
   return(true);
  }

Рис. 7 - Добавление комбо-бокса.

Рис. 7. Добавление комбо-бокса.

Чек-бокс

Чек-бокс — самый простой элемент. Для него достаточно указать два свойства.

  • Ширина.
  • Расположение в правой части главного элемента.

После создания элемента можно программно включить чек-бокс.

//+------------------------------------------------------------------+
//| Создаёт чекбокс "Date scale"                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateDateScale(const int x_gap,const int y_gap,const string text)
  {
//--- Сохраним указатель на окно
   m_date_scale.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_date_scale);
//--- Свойства
   m_date_scale.XSize(70);
   m_date_scale.AnchorRightWindowSide(true);
//--- Создадим элемент управления
   if(!m_date_scale.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Включить чек-бокс
   m_date_scale.IsPressed(true);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_date_scale);
   return(true);
  }

Рис. 8 - Добавление чек-боксов.

Рис. 8. Добавление чек-боксов.

Таблица

В графическом интерфейсе будут две таблицы. Рассмотрим ту, которая визуализирует сформированный список символов и сигналов для открытия позиций. Она размещается на первой вкладке. Сначала объявляем и инициализируем массивы для установки свойств таблицы. Настроим следующие свойства.

  • Ширина элемента.
  • Размерность таблицы (количество столбцов и строк).
  • Ширина столбцов (значения передаются в массиве).
  • Выравнивание текста (значения передаются в массиве).
  • Отступ для текста от краёв ячеек.
  • Показ заголовков.
  • Возможность выделять строки.
  • Возможность изменять размеры столбцов вручную, захватывая границу заголовка.
  • Отображение с форматированием в стиле «Зебра».
  • Автоматическое изменение размера по вертикали относительно главного элемента.
  • Отступ от нижнего края главного элемента.

Текст для заголовков можно указать после создания таблицы:

//+------------------------------------------------------------------+
//| Создаёт таблицу символов                                         |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolsTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 2
#define ROWS1_TOTAL    1
//--- Сохраним указатель на главный элемент
   m_table_symb.MainPointer(m_tabs1);
//--- Закрепить за вкладкой
   m_tabs1.AddToElementsArray(0,m_table_symb);
//--- Массив ширины столбцов
   int width[COLUMNS1_TOTAL]={95,58};
//--- Массив выравнивания текста в столбцах
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]={ALIGN_LEFT,ALIGN_RIGHT};
//--- Массив отступа текста в столбцах по оси X
   int text_x_offset[COLUMNS1_TOTAL]={5,5};
//--- Свойства
   m_table_symb.XSize(168);
   m_table_symb.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table_symb.ColumnsWidth(width);
   m_table_symb.TextAlign(align);
   m_table_symb.TextXOffset(text_x_offset);
   m_table_symb.ShowHeaders(true);
   m_table_symb.SelectableRow(true);
   m_table_symb.ColumnResizeMode(true);
   m_table_symb.IsZebraFormatRows(clrWhiteSmoke);
   m_table_symb.AutoYResizeMode(true);
   m_table_symb.AutoYResizeBottomOffset(2);
//--- Создадим элемент управления
   if(!m_table_symb.CreateTable(x_gap,y_gap))
      return(false);
//--- Установим названия заголовков
   m_table_symb.SetHeaderText(0,"Symbol");
   m_table_symb.SetHeaderText(1,"Values");
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_table_symb);
   return(true);
  }

Вторая таблица отображает некоторые свойства открытых позиций. Десять ее столбцов будут отображать следующие данные.

  • Символ позиции.
  • Количество позиций.
  • Общий объём всех открытых позиций.
  • Объём позиций BUY.
  • Объём позиций SELL.
  • Текущий общий результат по всем открытым позициям.
  • Текущий результат по всем открытым BUY-позициям.
  • Текущий результат по всем открытым SELL-позициям.
  • Загрузка депозита по каждому символу отдельно.
  • Средняя цена.

Во второй таблице дополнительно нужно настроить следующие свойства.

  • Отступы для картинок от правого и верхнего краёв ячеек.
  • Возможность сортировки значений.
  • Автоматическое изменение размера по горизонтали относительно главного элемента.
  • Отступ от правого края главного элемента.

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

//+------------------------------------------------------------------+
//| Создаёт таблицу позиций                                          |
//+------------------------------------------------------------------+
bool CProgram::CreatePositionsTable(const int x_gap,const int y_gap)
  {
...
//--- Свойства
   m_table_positions.TableSize(COLUMNS2_TOTAL,ROWS2_TOTAL);
   m_table_positions.ColumnsWidth(width);
   m_table_positions.TextAlign(align);
   m_table_positions.TextXOffset(text_x_offset);
   m_table_positions.ImageXOffset(image_x_offset);
   m_table_positions.ImageYOffset(image_y_offset);
   m_table_positions.ShowHeaders(true);
   m_table_positions.IsSortMode(true);
   m_table_positions.SelectableRow(true);
   m_table_positions.ColumnResizeMode(true);
   m_table_positions.IsZebraFormatRows(clrWhiteSmoke);
   m_table_positions.AutoXResizeMode(true);
   m_table_positions.AutoYResizeMode(true);
   m_table_positions.AutoXResizeRightOffset(2);
   m_table_positions.AutoYResizeBottomOffset(2);
...
   return(true);
  }

Рис. 9 - Добавление таблицы на первой вкладке.

Рис. 9. Добавление таблицы на первой вкладке.

Рис. 10 - Добавление таблицы на второй вкладке.

Рис. 10.  Добавление таблицы на второй вкладке.

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

Стандартный график

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

  • Горизонтальный скроллинг.
  • Автоматическая подстройка ширины.
  • Автоматическая подстройка высоты.
  • Отступ от правого края главного элемента.
  • Отступ от нижнего края главного элемента.

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

//+------------------------------------------------------------------+
//| Создаёт стандартный график 1                                     |
//+------------------------------------------------------------------+
bool CProgram::CreateSubChart1(const int x_gap,const int y_gap)
  {
//--- Сохраним указатель на окно
   m_sub_chart1.MainPointer(m_tabs1);
//--- Закрепить за 1-ой вкладкой
   m_tabs1.AddToElementsArray(0,m_sub_chart1);
//--- Свойства
   m_sub_chart1.XScrollMode(true);
   m_sub_chart1.AutoXResizeMode(true);
   m_sub_chart1.AutoYResizeMode(true);
   m_sub_chart1.AutoXResizeRightOffset(125);
   m_sub_chart1.AutoYResizeBottomOffset(2);
//--- Добавим графики
   m_sub_chart1.AddSubChart("EURUSD",PERIOD_D1);
//--- Создадим элемент управления
   if(!m_sub_chart1.CreateStandardChart(x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_sub_chart1);
   return(true);
  }

Рис. 11 - Добавление графика.

Рис. 11.  Добавление графика.

Индикатор выполнения

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

  • Общая высота элемента.
  • Высота индикатора (полоса прогресса выполнения).
  • Отступ индикатора по оси X.
  • Отступ индикатора по оси Y.
  • Отступ главной текстовой метки по оси X.
  • Отступ главной текстовой метки по оси Y.
  • Отступ процентной текстовой метки по оси X.
  • Отступ процентной текстовой метки по оси Y.
  • Признак выпадающего элемента (для автоматического скрытия). 
  • Шрифт. 
  • Цвет  рамки индикатора.
  • Цвет фона индикатора.
  • Цвет полосы прогресса индикатора.
  • Автоматическая подстройка ширины.
  • Отступ от правого края главного элемента.

Ниже я покажу примеры использования индикатора выполнения.

//+------------------------------------------------------------------+
//| Создаёт прогресс-бар                                             |
//+------------------------------------------------------------------+
bool CProgram::CreateProgressBar(const int x_gap,const int y_gap,const string text)
  {
//--- Сохраним указатель на главный элемент
   m_progress_bar.MainPointer(m_status_bar);
//--- Свойства
   m_progress_bar.YSize(17);
   m_progress_bar.BarYSize(14);
   m_progress_bar.BarXGap(0);
   m_progress_bar.BarYGap(1);
   m_progress_bar.LabelXGap(5);
   m_progress_bar.LabelYGap(2);
   m_progress_bar.PercentXGap(5);
   m_progress_bar.PercentYGap(2);
   m_progress_bar.IsDropdown(true);
   m_progress_bar.Font("Consolas");
   m_progress_bar.BorderColor(clrSilver);
   m_progress_bar.IndicatorBackColor(clrWhiteSmoke);
   m_progress_bar.IndicatorColor(clrLightGreen);
   m_progress_bar.AutoXResizeMode(true);
   m_progress_bar.AutoXResizeRightOffset(2);
//--- Создание элемента
   if(!m_progress_bar.CreateProgressBar(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,m_progress_bar);
   return(true);
  }

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

Обновления библиотеки EasyAndFast

В библиотеке EasyAndFast в классе CTable доработан публичный метод CTable::SortData(). Теперь в качестве второго аргумента можно указать направление сортировки таблицы (необязательный параметр). Раньше повторный вызов метода CTable::SortData() начинал сортировку в противоположном направлении от текущего. Также добавлены методы для получения текущего направления сортировки и индекса отсортированного столбца. Теперь, если таблица была отсортирована пользователем вручную (кликом по заголовку), а затем данные в таблице были обновлены не в той же последовательности, то, выяснив текущее направление сортировки, можно её восстановить. 

//+------------------------------------------------------------------+
//| Класс для создания нарисованной таблицы                          |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
...
   //--- Сортировать данные по указанному столбцу
   void              SortData(const uint column_index=0,const int direction=WRONG_VALUE);
   //--- (1) Текущее направление сортировки, (2) индекс отсортированного массива
   int               IsSortDirection(void)             const { return(m_last_sort_direction);    }
   int               IsSortedColumnIndex(void)         const { return(m_is_sorted_column_index); }
...
  };
//+------------------------------------------------------------------+
//| Сортировать данные по указанному столбцу                         |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0,const int direction=WRONG_VALUE)
  {
//--- Выйти, если выходим за пределы таблицы
   if(column_index>=m_columns_total)
      return;
//--- Индекс, с которого нужно начать сортировку
   uint first_index=0;
//--- Последний индекс
   uint last_index=m_rows_total-1;
//--- Без контроля направления пользователем
   if(direction==WRONG_VALUE)
     {
      //--- В первый раз будет отсортировано по возрастанию, а затем каждый раз в противоположном направлении
      if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
         m_last_sort_direction=SORT_ASCEND;
      else
         m_last_sort_direction=SORT_DESCEND;
     }
   else
     {
      m_last_sort_direction=(ENUM_SORT_MODE)direction;
     }
//--- Запомним индекс последнего отсортированного столбца данных
   m_is_sorted_column_index=(int)column_index;
//--- Сортировка
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
  }

Ещё одно маленькое дополнение добавлено в класс CKeys в метод CKeys::KeySymbol(). Раньше цифровая клавиатура (отдельный блок клавиш в правой части клавиатуры) не обрабатывалась. Теперь можно вводить цифры, а также специальные символы и с этого раздела клавиатуры.

//+------------------------------------------------------------------+
//| Возвращает символ нажатой клавиши                                |
//+------------------------------------------------------------------+
string CKeys::KeySymbol(const long key_code)
  {
   string key_symbol="";
//--- Если нужно ввести пробел (клавиша "Space")
   if(key_code==KEY_SPACE)
     {
      key_symbol=" ";
     }
//--- Если нужно ввести (1) алфавитный символ или (2) символ цифровой клавиши или (3) специальный символ
   else if((key_code>=KEY_A && key_code<=KEY_Z) ||
           (key_code>=KEY_0 && key_code<=KEY_9) ||
           (key_code>=KEY_NUMLOCK_0 && key_code<=KEY_NUMLOCK_SLASH) ||
           (key_code>=KEY_SEMICOLON && key_code<=KEY_SINGLE_QUOTE))
     {
      key_symbol=::ShortToString(::TranslateKey((int)key_code));
     }
//--- Вернуть символ
   return(key_symbol);
  }

Новые версии классов CTable и CKeys можно скачать в конце статьи.

Заключение

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

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

Наименование файла Комментарий
MQL5\Experts\TradePanel\TradePanel.mq5 Торговый эксперт для ручной торговли с графическим интерфейсом
MQL5\Experts\TradePanel\Program.mqh Файл с классом программы
MQL5\Experts\TradePanel\CreateGUI.mqh Файл с реализацией методов для создания графического интерфейса из класса программы в файле Program.mqh
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh Обновлённый класс CTable
MQL5\Include\EasyAndFastGUI\Keys.mqh Обновлённый класс CKeys