English 中文 Español Deutsch 日本語 Português
Мультивалютный мониторинг торговых сигналов (Часть 2): Реализация визуальной части приложения

Мультивалютный мониторинг торговых сигналов (Часть 2): Реализация визуальной части приложения

MetaTrader 5Трейдинг | 30 января 2020, 12:23
3 304 0
Alexander Fedosov
Alexander Fedosov

Содержание

Введение

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


Первый шаг настройки: Символы

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

  • Окно приложения.
  • Быстрый выбор групп символов.
  • Поле ввода своей группы.
  • Кнопки Сохранить и Загрузить свою группу символов.
  • Полный список всех доступных символов в виде чекбоксов с текстовой меткой названия символа.
  • Кнопка Next(Далее) для перехода ко второму шагу настроек: Выбор таймфреймов.

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

Рис.1 Файловая структура приложения.

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

//+------------------------------------------------------------------+
//|                                                SignalMonitor.mq5 |
//|                                Copyright 2019, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
//--- Подключение класса приложения
#include "Program.mqh"
//+------------------------------------------------------------------+
//| Входные параметры эксперта                                       |
//+------------------------------------------------------------------+
input int                  Inp_BaseFont      =  10;                  // Базовый шрифт
input color                Caption           =  C'0,130,225';        // Цвет заголовка
input color                Background        =  clrWhiteSmoke;       // Цвет фона
//---
CProgram program;
ulong tick_counter;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
{
//---
   tick_counter=GetTickCount();
//--- Инициализация переменных класса
   program.OnInitEvent();
   program.m_base_font_size=Inp_BaseFont;
   program.m_background=Background;
   program.m_caption=Caption;
//--- Установим торговую панель
   if(!program.CreateGUI())
   {
      Print(__FUNCTION__," > Не удалось создать графический интерфейс!");
      return(INIT_FAILED);
   }
//--- Инициализация прошла успешно
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   program.OnDeinitEvent(reason);
}
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer(void)
{
   program.OnTimerEvent();
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
{
   program.ChartEvent(id,lparam,dparam,sparam);
   //---
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
   {
      Print("End in ",GetTickCount()-tick_counter," ms");
   }
}
//+------------------------------------------------------------------+

 Как видно из листинга, мы добавили три входных параметра, это:

  • Размер шрифта.
  • Цвет заголовка окон приложения.
  • Цвет заднего фона окон и элементов приложения.

Далее объявляем экземпляр класса CProgram с именем program и переменную tick_counter(она нужна лишь для отображения информации по времени запуска приложения). Далее в методе OnInit() мы инициализируем переменные экземпляра класса, присваивая им значения входных параметров приложения. А также вызываем базовый метод CreateGUI(), который будет запускать само приложение.

Однако, если сейчас скомпилировать открытый нами файл, то мы получим ошибки компиляции, в которых будет сказано, что никаких переменных m_base_font_size, m_background, m_caption и метода CreateGUI() в классе CProgram не найдено. Поэтому, откроем теперь файл Program.mqh и приступим к реализации класса CProgram, и первым делом добавим требуемые в предыдущем файле переменные и метод, а также другие необходимые методы для начальной корректной работы приложения. После всех дополнений класс CProgram будет выглядеть так:

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//---
   int               m_base_font_size;
//---
   string            m_base_font;
//---
   color             m_background;
   color             m_caption;
public:
   CProgram(void);
   ~CProgram(void);
   //--- Инициализация/деинициализация
   void              OnInitEvent(void);
   void              OnDeinitEvent(const int reason);
   //--- Таймер
   void              OnTimerEvent(void);
   //--- Обработчик события графика
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- Создаёт графический интерфейс программы
   bool              CreateGUI(void);
};

А реализация метода, создающего интерфейс, пока пуста:

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс программы                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//---

//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
{
   m_base_font="Trebuchet MS";
}

Итак, теперь приступим к созданию первого окна приложения. Для этого в самом классе объявляем новую переменную m_step_window, являющаяся экземпляром класса CWindow. А также метод, который будет создавать наше первое окно и назовем его CreateStepWindow(). В листинге нашего класса это будет выглядеть так:

class CProgram : public CWndEvents
{
public:
//--- Окна приложения
   CWindow           m_step_window;
...
protected:
   //--- Формы
   bool              CreateStepWindow(const string caption_text);

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

#include "Program.mqh"
//+------------------------------------------------------------------+
//| Создаёт форму для выбора символов                                |
//+------------------------------------------------------------------+
bool CProgram::CreateStepWindow(const string text)
{
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_step_window);
//--- Свойства
   m_step_window.XSize(600);
   m_step_window.YSize(200);
//--- Координаты
   int x=int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_step_window.XSize())/2;
   int y=10;
   m_step_window.CaptionHeight(22);
   m_step_window.IsMovable(true);
   m_step_window.CaptionColor(m_caption);
   m_step_window.CaptionColorLocked(m_caption);
   m_step_window.CaptionColorHover(m_caption);
   m_step_window.BackColor(m_background);
   m_step_window.FontSize(m_base_font_size);
   m_step_window.Font(m_base_font);
//--- Создание формы
   if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
   //---
   return(true);
}
//+------------------------------------------------------------------+

Также не забываем дополнить метод CreateGUI():

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс программы                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Шаг 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   return(true);
}
//+------------------------------------------------------------------+

Если последовательность действий выполнена правильно, то после компиляции файла SignalMonitor.mq5 и его запуске в терминале вы увидите созданную форму:

Рис.2 Первое окно приложения

Первыми элементами созданного окна будет группа кнопок, роль которых состоит в быстром выборе предустановленных наборов символов из терминала, а именно: forex.all, forex.crosses, forex.major. Для начала в файле Program.mqh добавим массив экземпляров класса CButton размерностью три, а также универсальный метод CreateSymbolSet() для создания кнопок:

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//--- Окна приложения
   CWindow           m_step_window;
//--- Простые кнопки
   CButton           m_currency_set[3];
...
   //--- Кнопки
   bool              CreateSymbolSet(CButton &button,string text,const int x_gap,const int y_gap);

Теперь перейдем в файл StepWindow.mqh и добавим реализацию созданного выше метода.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolSet(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'220,225,235';
   color pressed=C'55,160,250';
//--- Сохраним указатель на окно
   button.MainPointer(m_step_window);
//--- Установим свойства перед созданием
   button.TwoState(true);
   button.XSize(80);
   button.YSize(30);
   button.LabelXGap(19);
   button.LabelYGap(2);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrBlack);
   button.LabelColorPressed(clrWhite);
   button.IsCenterText(true);
//--- Создадим элемент управления
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}
//+------------------------------------------------------------------+

Осталось добавить в наше окно три кнопки данным методом с разными значениями координат и текстовых меток в базовый метод CreateStepWindow() самого окна после создания формы:

...
//--- Создание формы
   if(!m_step_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//---
   if(!CreateSymbolSet(m_currency_set[0],"ALL",10,30))
      return(false);
   if(!CreateSymbolSet(m_currency_set[1],"Major",10+100,30))
      return(false);
   if(!CreateSymbolSet(m_currency_set[2],"Crosses",10+2*(100),30))
      return(false);
...

После компиляции будет следующий результат:

Рис.3 Добавление кнопок быстрого выбора групп символов.

Далее добавим поле ввода для названия своей выбранной группы символов, которую можно будет сохранять и загружать с помощью двух кнопок Save и Load. Для этого добавим экземпляр класса для создания поля ввода CTextEdit и еще два экземпляра класса для создания кнопок CButton. Так как кнопки сохранения и загрузки будут отличны лишь в названиях, то создадим один универсальный метод CreateButton1(), а для поля ввода будет в класс CProgram добавим CreateEditValue():

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
public:
//--- Окна приложения
   CWindow           m_step_window;
//--- Простые кнопки
   CButton           m_currency_set[3];
   CButton           m_load_button;
   CButton           m_save_button;
   //--- Поля ввода
   CTextEdit         m_text_edit;
...
   bool              CreateButton1(CButton &button,string text,const int x_gap,const int y_gap);
   //--- Поле ввода
   bool              CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap);

Вернемся в файл StepWindow.mqh и добавим в конце файла реализацию созданных методов.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateEditValue(CTextEdit &text_edit,const int x_gap,const int y_gap)
{
//--- Сохраним указатель на главный элемент
   text_edit.MainPointer(m_step_window);
//--- Свойства
   text_edit.XSize(110);
   text_edit.YSize(24);
   text_edit.Font(m_base_font);
   text_edit.FontSize(m_base_font_size);
   text_edit.GetTextBoxPointer().XGap(1);
   text_edit.GetTextBoxPointer().XSize(110);
   text_edit.GetTextBoxPointer().DefaultTextColor(clrSilver);
   text_edit.GetTextBoxPointer().DefaultText("Template name");
//--- Создадим элемент управления
   if(!text_edit.CreateTextEdit("",x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,text_edit);
   return(true);
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateButton1(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'70,180,70';
   color pressed=C'70,170,70';
//--- Сохраним указатель на окно
   button.MainPointer(m_step_window);
//--- Установим свойства перед созданием
   button.XSize(80);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Создадим элемент управления
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}

 И после этого вернемся в метод CreateStepWindow() и в нем добавим обе кнопки и поле ввода в окно приложения.

//---
   if(!CreateEditValue(m_text_edit,300,m_step_window.CaptionHeight()+10))
      return(false);
//---
   if(!CreateButton1(m_load_button,"Load(L)",m_step_window.XSize()-2*(80+10),m_step_window.CaptionHeight()+10))
      return(false);
   if(!CreateButton1(m_save_button,"Save(S)",m_step_window.XSize()-(80+10),m_step_window.CaptionHeight()+10))
      return(false);

Снова компилируем файл SignalMonitor.mq5 и получаем следующую картину:

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

Теперь приступим к визуализации и возможности выбора всех доступных для выбранного счета символов в терминале MetaTrader 5. Здесь следует учесть, что при выводе всех доступных символов высоты окна приложения не будет достаточным, поэтому стоит подумать над возможностью автоматического изменения размера высоты окна под необходимый объем данных. Добавление общего количества символов будет происходить по тому же принципу — добавляем массив экземпляров класса для создания чекбоксов CCheckBox и универсальных метод для их создания, так как они будут отличны лишь в названии.

...
   //--- Чекбоксы
   CCheckBox         m_checkbox[];
...
   //--- Чек-боксы
   bool              CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text);

Но, как видно, размерность массива m_checkbox[] не указана, потому что заранее неизвестно, какое число доступных символов присутствует на выбранном счете в терминале. Поэтому создадим в приватной секции класса CProgram две переменные, которым присвоим значения общего числа доступных символов и число символов, выбранных в данный момент в Обзоре рынка.

private:
//---
   int               m_symbol_total;
   int               m_all_symbols;

В конструкторе класса присвоим им необходимые значение и установим соответствующую размерность массиву m_checkbox[]:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
{
   m_base_font="Trebuchet MS";
   m_symbol_total=SymbolsTotal(true);
   m_all_symbols=SymbolsTotal(false);
   ArrayResize(m_checkbox,m_all_symbols);
}

Добавим в конец файла StepWindow.mqh реализацию созданного выше метода:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateCheckBox(CCheckBox &checkbox,const int x_gap,const int y_gap,const string text)
{
//--- Сохраним указатель на главный элемент
   checkbox.MainPointer(m_step_window);
//--- Свойства
   checkbox.GreenCheckBox(true);
   checkbox.IsPressed(false);
   checkbox.Font(m_base_font);
   checkbox.FontSize(m_base_font_size);
   checkbox.BackColor(m_background);
   checkbox.LabelColorHover(C'55,160,250');
//--- Создадим элемент управления
   if(!checkbox.CreateCheckBox(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,checkbox);
   return(true);
}

И в метод CreateStepWindow() добавим чекбоксы. На листинге ниже сделано так, чтобы весь список доступных символов в ширину был 7 столбцов, а также учитываем ранее сказанное изменение высоты окна приложения под число полученных строк.

   //--- Чекбоксы
   int k=0;
   for(int j=0; j<=MathCeil(m_all_symbols/7); j++)
   {
      for(int i=0; i<7; i++)
      {
         if(k<m_all_symbols)
            if(!CreateCheckBox(m_checkbox[k],10+80*i,m_step_window.CaptionHeight()+70+j*25,SymbolName(k,false)))
               return(false);
         k++;
      }
   }
   m_step_window.ChangeWindowHeight(m_checkbox[m_all_symbols-1].YGap()+30+30);

Компилируем полученные дополнения:

Рис.5 Добавляем чекбоксы со всеми доступными символами.

И последний элемент этой части приложения — это кнопки навигации для перехода между шагами настройки. Добавить их достаточно просто: добавляем два экземпляра класса CButton с именами m_next_button и m_back_button, а метод для их создания возьмём из уже созданного CreateButton1(). Переходим к методу создания окна CreateStepWindow() и в нем добавляем:

//---
   if(!CreateButton1(m_back_button,"Back",m_step_window.XSize()-2*(80+10),m_step_window.YSize()-(30+10)))
      return(false);
   if(!CreateButton1(m_next_button,"Next",m_step_window.XSize()-(80+10),m_step_window.YSize()-(30+10)))
      return(false);

Остается только настроить работу кнопок, с помощью которых выбираются предустановленные наборы символов. Для этого переходим в файл Program.mqh и в нем находим метод OnEvent(), и в нем добавляем следующий код:

//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Событие нажатия на кнопке
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
   {
      //--- All
      if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
      {
         m_currency_set[1].IsPressed(false);
         m_currency_set[2].IsPressed(false);
         m_currency_set[1].Update(true);
         m_currency_set[2].Update(true);
         //---
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(true);
            m_checkbox[i].Update(true);
         }
      }
      //--- Majors
      else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
      {
         m_currency_set[0].IsPressed(false);
         m_currency_set[2].IsPressed(false);
         m_currency_set[0].Update(true);
         m_currency_set[2].Update(true);
         //---
         string pairs[4]= {"EURUSD","GBPUSD","USDCHF","USDJPY"};
         //--- Очистим выбор
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
         //---
         for(int i=0; i<m_all_symbols; i++)
         {
            for(int j=0; j<4; j++)
               if(m_checkbox[i].LabelText()==pairs[j])
               {
                  m_checkbox[i].IsPressed(true);
                  m_checkbox[i].Update(true);
               }
         }
      }
      //--- Crosses
      else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
      {
         m_currency_set[0].IsPressed(false);
         m_currency_set[1].IsPressed(false);
         m_currency_set[0].Update(true);
         m_currency_set[1].Update(true);
         //---
         string pairs[20]=
         {
            "EURUSD","GBPUSD","USDCHF","USDJPY","USDCAD","AUDUSD","AUDNZD","AUDCAD","AUDCHF","AUDJPY",
            "CHFJPY","EURGBP","EURAUD","EURCHF","EURJPY","EURCAD","EURNZD","GBPCHF","GBPJPY","CADCHF"
         };
         //--- Очистим выбор
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
         //---
         for(int i=0; i<m_all_symbols; i++)
         {
            for(int j=0; j<20; j++)
               if(m_checkbox[i].LabelText()==pairs[j])
               {
                  m_checkbox[i].IsPressed(true);
                  m_checkbox[i].Update(true);
               }
         }
      }
      //---
      if((lparam==m_currency_set[0].Id() && !m_currency_set[0].IsPressed())      ||
            (lparam==m_currency_set[1].Id() && !m_currency_set[1].IsPressed())   ||
            (lparam==m_currency_set[2].Id() && !m_currency_set[2].IsPressed())
        )
      {
         //--- Очистим выбор
         for(int i=0; i<m_all_symbols; i++)
         {
            m_checkbox[i].IsPressed(false);
            m_checkbox[i].Update(true);
         }
      }
   }
}

Суть данной реализации в следующем:

  • При нажатии на кнопку ALL выбираются все символы.
  • При нажатии на кнопку Major, снимается предыдущий выбор и устанавливается набор символов как в терминала forex.major.
  • При нажатии на кнопку Crosses, снимается предыдущий выбор и устанавливается набор символов как терминала crosses.major.
  • При отжатых трех кнопках выбор полностью снимается.
<d

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

Рис.6 Реализация базового взаимодействия элементов.

Для завершения визуальной реализации осталось два небольших дополнения: как видно на рис.5, там присутствует созданная нами кнопка Back(Назад), но учитывая, что это шаг №1, ее быть недолжно, поэтому нужно ее скрыть и отображать только на шагах 2 и 3. Поэтому в метод CreateGUI() добавим такую строчку с кодом:

bool CProgram::CreateGUI(void)
{
//--- Шаг 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   return(true);
}

И второй момент: необходимо следить за выбором пользователя и не допускать перехода на шаг №2 при отсутствии выбора хотя бы одного из доступных символов. Переход между шагами осуществляется кнопками Back(Назад) и Next(Вперед), поэтому для решения задачи введем в приватную секцию класса CProgram три новых метода, которые буду обрабатывать информацию, выбранную на каждом из трех шагов и тем самым производить первоначальную настройку приложения. А также добавим переменную m_current_step, чтобы при нажатии на кнопки навигации Назад/Вперед приложение знало, на каком шаге настроек мы находимся.

private:
//---
   int               m_symbol_total;
   int               m_all_symbols;
   int               m_current_step;
   //---
   void              ToStep_1(void);
   void              ToStep_2(void);
   void              ToStep_3(void);

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

      //--- Навигация
      if(lparam==m_back_button.Id())
      {
         //--- Возврат на Шаг 1
         if(m_current_step==2)
            ToStep_1();
         //--- Возврат на Шаг 2
         else if(m_current_step==3)
            ToStep_2();
      }
      //--- Переход на Шаг 2
      if(lparam==m_next_button.Id())
      {
         //--- Переход на Шаг 2
         if(m_current_step==1)
            ToStep_2();
         //--- Переход на Шаг 3
         else if(m_current_step==2)
            ToStep_3();
      }

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

function 'CProgram::ToStep_1' must have a body Program.mqh 60 22

Поэтому, чтобы это исправить в файле Program.mqh создадим реализацию этих классов, но в случае с методами ToStep_1() и ToStep_3() пока что оставим ее пустой. Их мы заполним чуть позже, сейчас нас интересует метод перехода на второй шаг ToStep_2(). И в нем добавим проверку на выбор хотя бы одного символа:

//+------------------------------------------------------------------+
//| Переход на шаг 1                                                 |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//---
}
//+------------------------------------------------------------------+
//| Переход на шаг 2                                                 |
//+------------------------------------------------------------------+
void CProgram::ToStep_2(void)
{
//--- Проверка на выбор хотя бы одного символа
   int cnt=0;
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   if(cnt<1)
   {
      MessageBox("No symbols selected!","Warning");
      return;
   }
}
//+------------------------------------------------------------------+
//| Переход на шаг 3                                                 |
//+------------------------------------------------------------------+
void CProgram::ToStep_3(void)
{
//---
}

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


Второй шаг настройки: Таймфреймы

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

  • Группа кнопок для быстрого выбора таймфреймов
  • Список таймфреймов в виде чекбоксов.
  • Наличие кнопки Back(Назад) для возможности вернуться на Шаг 1.

Чтобы не плодить множество схожих по функционалу объектов, мы возьмем уже существующие из визуальной реализации Шага 1 и переделаем его под выбор таймфреймов. Перейдем в тело метода ToStep_2(), который мы только что редактировали и начнем его дополнять. Первым делом запомним выбор символов, отмеченных на Шаге 1 и отобразим их в Обзоре рынка терминала MetaTrader 5:

//--- Установка в Обзоре рынка выбранных символов
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
         SymbolSelect(m_checkbox[i].LabelText(),true);
      else
         SymbolSelect(m_checkbox[i].LabelText(),false);
   }

Далее трансформируем интерфейс первого шага во второй:

//--- Меняем заголовок
   m_step_window.LabelText("Signal Monitor Step 2: Choose Timeframes");
   m_step_window.Update(true);
//--- Скрываем элементы Шага 1
   for(int i=0; i<m_all_symbols; i++)
   {
      m_checkbox[i].IsLocked(false);
      m_checkbox[i].IsPressed(false);
      m_checkbox[i].Hide();
   }
   string names[3]= {"All","Junior","Senior"};
//--- Меняем названия кнопок наборов
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].IsPressed(false);
      if(m_current_step==3)
         m_currency_set[i].Show();
      m_currency_set[i].Update(true);
   }
//--- Скрываем блок работы с шаблонами
   m_text_edit.Hide();
   m_load_button.Hide();
   m_save_button.Hide();
//--- Показываем все таймфреймы
   string timeframe_names[21]=
   {
      "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<21; i++)
   {
      m_checkbox[i].LabelText(timeframe_names[i]);
      m_checkbox[i].Show();
      m_checkbox[i].Update(true);
   }
//--- Показываем кнопку Назад
   m_back_button.Show();
//---
   m_current_step=2;

По комментариям листинга выше, всё достаточно просто реализуется и в конце преобразования переменной m_current_step присваиваем значение второго шага настройки, то есть 2. Теперь необходимо, чтобы измененный интерфейс корректно отображал выбор наборов таймфреймов All, Junior, Senior. Для этого необходимо перейти в файл Program.mqh и в методе OnEvent() изменить код в секции События "нажатие кнопки". Потому что как объект, кнопки быстрого выбора наборов одинаковы что для первого шага, что для второго, лишь имеют разные названия. Поэтому при событии нажатия на них нужно уточнить на какой шаге настройки мы находимся:

 //--- Шаг 1
      if(m_current_step==1)
      {
       ...
      }
      //--- Шаг 2
      else if(m_current_step==2)
      {
         //--- All
         if(lparam==m_currency_set[0].Id() && m_currency_set[0].IsPressed())
         {
            m_currency_set[1].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[1].Update(true);
            m_currency_set[2].Update(true);
            //---
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(true);
               m_checkbox[i].Update(true);
            }
         }
         //--- Junior Timeframes
         else if(lparam==m_currency_set[1].Id() && m_currency_set[1].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[2].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[2].Update(true);
            //---
            string pairs[11]=
            {
               "M1","M2","M3","M4","M5","M6","M10","M12","M15","M20","M30"
            };
            //--- Очистим выбор
            for(int i=0; i<21; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<21; i++)
            {
               for(int j=0; j<11; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }
         //--- Senior Timeframes
         else if(lparam==m_currency_set[2].Id() && m_currency_set[2].IsPressed())
         {
            m_currency_set[0].IsPressed(false);
            m_currency_set[1].IsPressed(false);
            m_currency_set[0].Update(true);
            m_currency_set[1].Update(true);
            //---
            string pairs[10]=
            {
               "H1","H2","H3","H4","H6","H8","H12","D1","W1","MN"
            };
            //--- Очистим выбор
            for(int i=0; i<m_all_symbols; i++)
            {
               m_checkbox[i].IsPressed(false);
               m_checkbox[i].Update(true);
            }
            //---
            for(int i=0; i<m_all_symbols; i++)
            {
               for(int j=0; j<10; j++)
                  if(m_checkbox[i].LabelText()==pairs[j])
                  {
                     m_checkbox[i].IsPressed(true);
                     m_checkbox[i].Update(true);
                  }
            }
         }

Последний элемент интерфейса второго шага настройки, который нужно реализовать — это кнопка Back возврата на Шаг 1. За это отвечает созданный, но еще пустой метод ToStep_1(). В нем нужно будет вернуть прежний интерфейс и установить старую обработку событий по нажатию кнопок выбора наборов.

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_1(void)
{
//--- Меняем заголовок
   m_step_window.LabelText("Signal Monitor Step 1: Choose Symbols");
   m_step_window.Update(true);
//--- Скрываем кнопку Назад
   m_back_button.Hide();
//--- Очищаем выбор
   for(int i=0; i<21; i++)
   {
      m_checkbox[i].IsPressed(false);
      m_checkbox[i].Update(true);
   }
//--- Показываем элементы Шага 1
   for(int i=0; i<m_all_symbols; i++)
   {
      m_checkbox[i].Show();
      m_checkbox[i].LabelText(SymbolName(i,false));
      m_checkbox[i].Update(true);
   }
   string names[3]= {"All","Majors","Crosses"};
//--- Меняем названия кнопок наборов
   for(int i=0; i<3; i++)
   {
      m_currency_set[i].IsPressed(false);
      m_currency_set[i].LabelText(names[i]);
      m_currency_set[i].Update(true);
   }
//--- Показываем блок работы с шаблонами
   m_text_edit.Show();
   m_load_button.Show();
   m_save_button.Show();
//--- Устанавливаем текущий шаг настройки
   m_current_step=1;
}

Теперь скомпилируем проект, и если всё верно добавили, то результат будет как на рис.7 ниже.

Рис.7 Реализация второго шага настроек приложения.

Третий шаг настройки: Добавление сигналов

Следующим этапом будет третий шаг настроек — интерфейс добавления сигналов. Он достаточно прост и состоит из кнопки добавления сигналов и заголовка списка добавленных сигналов. Перейдем в файл Program.mqh и объявим в классе СProgram две новые переменные:

   CButton           m_add_signal;
   //---
   CTextLabel        m_signal_header;

И реализующие их методы:

   bool              CreateIconButton(CButton &button,string text,const int x_gap,const int y_gap);
   //--- Текстовая метка
   bool              CreateLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);

Теперь в конце файла StepWindow.mqh добавим их реализацию.

//+------------------------------------------------------------------+
//| Создаёт кнопку с картинкой                                       |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp"
bool CProgram::CreateIconButton(CButton &button,string text,const int x_gap,const int y_gap)
{
//---
   color baseclr=C'70,180,70';
   color pressed=C'70,170,70';
//--- Сохраним указатель на окно
   button.MainPointer(m_step_window);
//--- Установим свойства перед созданием
   button.XSize(110);
   button.YSize(30);
   button.Font(m_base_font);
   button.FontSize(m_base_font_size);
   button.IconXGap(3);
   button.IconYGap(7);
   button.IconFile("Images\\EasyAndFastGUI\\Icons\\bmp16\\plus.bmp");
   button.BackColor(baseclr);
   button.BackColorHover(baseclr);
   button.BackColorPressed(pressed);
   button.BorderColor(baseclr);
   button.BorderColorHover(baseclr);
   button.BorderColorPressed(pressed);
   button.LabelColor(clrWhite);
   button.LabelColorPressed(clrWhite);
   button.LabelColorHover(clrWhite);
   button.IsCenterText(true);
//--- Создадим элемент управления
   if(!button.CreateButton(text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,button);
   return(true);
}
//+------------------------------------------------------------------+
//| Создаёт текстовую метку                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Сохраним указатель на окно
   text_label.MainPointer(m_step_window);
//---
   text_label.Font(m_base_font);
   text_label.FontSize(m_base_font_size);
   text_label.XSize(120);
   text_label.BackColor(m_background);
//--- Создание кнопки
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+

И дополним метод CreateStepWindow(), чтобы они были созданы при запуске приложения.

//---
   if(!CreateIconButton(m_add_signal,"Add Signal",10,30))
      return(false);
   if(!CreateLabel(m_signal_header,10,30+30+10,"Signal List"))
      return(false);   

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

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс программы                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Шаг 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   return(true);
}

Теперь реализуем ранее добавленный метод ToStep_3(), который очистит визуализацию предыдущего шага и отобразит созданные нами элементы:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToStep_3(void)
{
//--- Проверка на выбор хотя бы одного таймфрейма
   int cnt=0;
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   if(cnt<1)
   {
      MessageBox("No timeframes selected!","Warning");
      return;
   }
//---
   m_step_window.LabelText("Signal Monitor Step 3: Create Signals");
   m_step_window.Update(true);
   m_next_button.LabelText("Create");
   m_next_button.Update(true);
//--- Скрываем элементы шага 2
   for(int i=0; i<21; i++)
   {
      if(i<3)
         m_currency_set[i].Hide();
      m_checkbox[i].Hide();
   }
//---
   m_add_signal.Show();
   m_signal_header.Show();
//---
   m_current_step=3;
}

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

Рис.8 Реализация второго шага настроек приложения.

Окно создания и редактирования торговых сигналов

Вся визуальная составляющая для работы с торговыми сигналами будет находится в файле SetWindow.mqh, поэтому откроем его. Сейчас в нем только подключенный файл Program.mqh с помощью командной строки #include. Для начала необходимо создать отдельное окно, которое будет базовым для всех остальных элементов создания и настройки. Поэтому переходим в Program.mqh и в самом классе объявляем переменную m_set_window, являющуюся экземпляром класса CWindow, и добавим метод создания окна CreateSetWindow():

   CWindow           m_set_window;

   bool              CreateSetWindow(const string caption_text);

После этого возвращаемся в файл SetWindow.mqh и реализуем созданный метод.

//+------------------------------------------------------------------+
//| Создает окно создания и редактирования торговых сигналов         |
//+------------------------------------------------------------------+
bool CProgram::CreateSetWindow(const string text)
{
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_set_window);
//--- Свойства
   m_set_window.XSize(568);
   m_set_window.YSize(555);
//--- Координаты
   int x=int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/2;
   int y=30;
//---
   m_set_window.CaptionHeight(22);
   m_set_window.IsMovable(true);
   m_set_window.CaptionColor(m_caption);
   m_set_window.CaptionColorLocked(m_caption);
   m_set_window.CaptionColorHover(m_caption);
   m_set_window.BackColor(m_background);
   m_set_window.FontSize(m_base_font_size);
   m_set_window.Font(m_base_font);
   m_set_window.WindowType(W_DIALOG);
//--- Создание формы
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
   return(true);
}
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс программы                          |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
{
//--- Шаг 1-3
   if(!CreateStepWindow("Signal Monitor Step 1: Choose Symbols"))
      return(false);
//--- Окно создания и редактирования
   if(!CreateSetWindow("Signal Monitor Edit Signal"))
      return(false);
//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   m_back_button.Hide();
   m_add_signal.Hide();
   m_signal_header.Hide();
   return(true);
}

В метод OnEvent() в событие по нажатию кнопки:

      //--- Нажатие на кпопку Добавить сигнал
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
      }

Компилируем проект и смотрим результат: при переходе на шаг 3 и нажатии на кнопку Add Signal открывается диалоговое окно для создания и редактирования.

Рис.9 Реализация окна создания и редактирования торгового сигнала.

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

   //--- Выпадающее меню
   CComboBox         m_indicator_type;
   //--- Создает выпадающее меню
   bool              CreateIndicatorType(const int x_gap,const int y_gap);

Реализация метода будет находится в том же файле, что и созданное до этого окно. 

//+------------------------------------------------------------------+
//| Создает выпадающее меню с выбором типа индикатора                |
//+------------------------------------------------------------------+
bool CProgram::CreateIndicatorType(const int x_gap,const int y_gap)
{
//--- Передать объект панели
   m_indicator_type.MainPointer(m_set_window);
//--- Массив значений пунктов в списке
   string pattern_names[7]=
   {
      "ATR","CCI","DeMarker","Force Ind","WPR","RSI","Momentum"
   };
//--- Установим свойства перед созданием
   m_indicator_type.XSize(200);
   m_indicator_type.YSize(26);
   m_indicator_type.LabelYGap(4);
   m_indicator_type.ItemsTotal(7);
   m_indicator_type.Font(m_base_font);
   m_indicator_type.FontSize(m_base_font_size);
   m_indicator_type.BackColor(m_background);
   m_indicator_type.GetButtonPointer().Font(m_base_font);
   m_indicator_type.GetButtonPointer().FontSize(m_base_font_size);
   m_indicator_type.GetButtonPointer().BackColor(clrWhite);
   m_indicator_type.GetButtonPointer().XGap(100);
   m_indicator_type.GetButtonPointer().XSize(100);
   m_indicator_type.GetListViewPointer().Font(m_base_font);
   m_indicator_type.GetListViewPointer().FontSize(m_base_font_size);
   m_indicator_type.GetListViewPointer().ItemYSize(26);
//--- Сохраним значения пунктов в список комбо-бокса
   for(int i=0; i<7; i++)
      m_indicator_type.SetValue(i,pattern_names[i]);
//--- Получим указатель списка
   CListView *lv=m_indicator_type.GetListViewPointer();
//--- Установим свойства списка
   lv.LightsHover(true);
   m_indicator_type.SelectItem(5);
//--- Создадим элемент управления
   if(!m_indicator_type.CreateComboBox("Indicator Type",x_gap,y_gap))
      return(false);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(1,m_indicator_type);
   return(true);
}

Единственное дополнение в том, что в конце тела метода создания окна CreateSetWindow() вызываем метод создания выбора типа индикатора CreateIndicatorType().

...
//--- Создание формы
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Тип индикатора
   if(!CreateIndicatorType(10,22+10))
      return(false);

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

Рис.10 Элемент выбора типа индикатора.

Думаю, подробное рассмотрение добавления каждого элемента интерфейса достаточно очевидно, поэтому далее рассмотрим наборы элементов, сгруппированные по двум разделам Настройки индикатора(Indicator Settings) и Настройки сигнала(Signal Settings). Все отобранные индикаторы из стандартного набора имеют общие настройки, такие как период(Period) и применяемая цена(Applied Price). Поэтому для первого раздела нам понадобится текстовая метка, поле ввода периода и выпадающее меню выбора применяемой для расчета значений индикатора цены. Добавим необходимые переменные и методы для их создания в класс CProgram.

//--- Текстовая метка
   CTextLabel        m_set_header[5];
//--- Поля ввода
   CTextEdit         m_period_edit;
//--- Выпадающее меню
   CComboBox         m_applied_price;
...
   bool              CreateSetLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);
   bool              CreatePeriodEdit(const int x_gap,const int y_gap);
   bool              CreateAppliedPrice(const int x_gap,const int y_gap);

Реализуем добавленные методы и, как в случае с типом индикатора, вызовем методы в конце тела метода CreateSetWindow(). Теперь предстоит добавить механизм, при котором, выбирая какой-либо из типов индикатора, созданные элементы меняли бы набор предлагаемых настроек. Для этого в методе OnEvent() добавим секцию с событием по нажатию пункта выпадающего меню и настроим для каждого индикатора свой набор настроек:

//--- Выбор пункта в списке комбобокса
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
   {
      int index=m_indicator_type.GetListViewPointer().SelectedItemIndex();
      switch(index)
      {
      case  0:
         m_period_edit.LabelText("ATR Period");
         m_applied_price.Hide();
         break;
      case  1:
         m_period_edit.LabelText("CCI Period");
         m_applied_price.Show();
         break;
      case  2:
         m_period_edit.LabelText("DeMarker Period");
         m_applied_price.Hide();
         break;
      case  3:
         m_period_edit.LabelText("Force Index Period");
         m_applied_price.Show();
         break;
      case  4:
         m_period_edit.LabelText("WPR Period");
         m_applied_price.Hide();
         break;
      case  5:
         m_period_edit.LabelText("RSI Period");
         m_applied_price.Show();
         break;
      case  6:
         m_period_edit.LabelText("Momentum Period");
         m_applied_price.Hide();
         break;
      default:
         m_period_edit.LabelText("RSI Period");
         m_applied_price.Hide();
         break;
      }
      m_period_edit.Update(true);
   }

Скомпилируем проект и посмотрим что получилось:

Рис.11 Реализация настроек индикатора.

Далее перейдем ко второму разделу редактирования сигнала. Он состоит из заголовка и восьми настроек:

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

Для добавление заголовка этого раздела достаточно добавить в конце тела метода CreateSetWindow() такой код, потому что ранее мы уже создали метод для визуализации заголовка, и он вполне подходит для создания еще одного, но с другими значениями аргументов:

//--- Настройки сигнала
   if(!CreateSetLabel(m_set_header[1],10,22+10+4*(25+10),"2.Signal Settings"))
      return(false);

Правило сигнала состоит из двух элементов, это выпадающее меню и поле ввода числового значения. Поэтому добавляем экземпляры классов и реализующие их методы в класс CProgram:

CTextEdit         m_rule_value;
CComboBox         m_rule_type;
...
bool              CreateRuleValue(const int x_gap,const int y_gap);
bool              CreateRule(const int x_gap,const int y_gap);

Добавляем их реализацию в SetWindow.mqh и в теле метода CreateSetWindow() их вызываем.

   //--- Настройки условий
   if(!CreateRuleValue(130,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(10,22+10+5*(25+10)))
      return(false);

Далее по списку выше аналогично добавляем каждую из настроек, и в результате полная реализация метода CreateSetWindow() будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//| Создает окно создания и редактирования торговых сигналов         |
//+------------------------------------------------------------------+
bool CProgram::CreateSetWindow(const string text)
{
//--- Добавим указатель окна в массив окон
   CWndContainer::AddWindow(m_set_window);
//--- Свойства
   m_set_window.XSize(568);
   m_set_window.YSize(575);
//--- Координаты
   int x=int(ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)-m_set_window.XSize())/2;
   int y=30;
//---
   m_set_window.CaptionHeight(22);
   m_set_window.IsMovable(true);
   m_set_window.CaptionColor(m_caption);
   m_set_window.CaptionColorLocked(m_caption);
   m_set_window.CaptionColorHover(m_caption);
   m_set_window.BackColor(m_background);
   m_set_window.FontSize(m_base_font_size);
   m_set_window.Font(m_base_font);
   m_set_window.WindowType(W_DIALOG);
//--- Создание формы
   if(!m_set_window.CreateWindow(m_chart_id,m_subwin,text,x,y))
      return(false);
//--- Тип индикатора
   if(!CreateIndicatorType(10,22+10))
      return(false);
//--- Настройки выбранного индикатора
   if(!CreateSetLabel(m_set_header[0],10,22+10+26+10,"1.Indicator Settings"))
      return(false);
   if(!CreatePeriodEdit(10,22+10+2*(25+10)))
      return(false);
   if(!CreateAppliedPrice(10,22+10+3*(25+10)))
      return(false);
//--- Настройки сигнала
   if(!CreateSetLabel(m_set_header[1],10,22+10+4*(25+10),"2.Signal Settings"))
      return(false);
//--- Настройки условий
   if(!CreateRuleValue(130,22+10+5*(25+10)))
      return(false);
   if(!CreateRule(10,22+10+5*(25+10)))
      return(false);
//--- Настройки отображения метки
   if(!CreateSetLabel(m_set_header[2],10,22+10+6*(25+10),"Label"))
      return(false);
   if(!CreateButton2(m_label_button[0],"Value",100,22+7+6*(25+10)))
      return(false);
   if(!CreateButton2(m_label_button[1],"Text",100+80,22+7+6*(25+10)))
      return(false);
//--- Настройки отображения цвета метки
   if(!CreateColorButton(m_color_button[0],10,22+10+7*(25+10),"Label Color"))
      return(false);
   if(!CreateTextBox(180+80+10,22+7+6*(25+10)))
      return(false);
//---
   if(!CreateColorButton(m_color_button[1],25,22+10+8*(25+10),""))
      return(false);
   if(!CreateSetCheckBox(m_set_param[0],10,22+10+8*(25+10),"Use Background"))
      return(false);
   if(!CreateColorButton(m_color_button[2],25,22+10+9*(25+10),""))
      return(false);
   if(!CreateSetCheckBox(m_set_param[1],10,22+10+9*(25+10),"Use Border"))
      return(false);
   if(!CreateColorButton(m_color_button[3],25,22+10+10*(25+10),""))
      return(false);
   if(!CreateSetCheckBox(m_set_param[2],10,22+10+10*(25+10),"Use Tooltip"))
      return(false);
   if(!CreateTooltipText(240,22+10+10*(25+10)))
      return(false);
   if(!CreateSetCheckBox(m_set_param[3],10,22+10+11*(25+10),"Use Image"))
      return(false);
   if(!CreateImageSlider(125,22+10+11*(25+10)))
      return(false);
//--- Выбор таймфреймов
   if(!CreateSetLabel(m_set_header[4],10,22+10+12*(25+10),"Timeframes"))
      return(false);
//---
   y=22+10+13*(25+10);
   int k=0;
   for(int i=0; i<21; i++)
   {
      if(i==11)
      {
         y=22+20+14*(25+10);
         k=0;
      }
      if(!CreateTfButton(m_tf_button[i],40*k+10,y))
         return(false);
      k++;
   }
   return(true);
}

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

Рис.12 Реализация элементов интерфейса окна редактирования сигналов.

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

  • Кнопки таймфреймов должны показывать лишь то количество, которое было выбрано на Шаге 2.
  • При выборе кнопки Значение(Value) должна отжиматься кнопка Текст(Text) и исчезать поле ввода текстовой метки.
  • При нажатии на кнопки выбора цвета должно открываться окно с цветовой палитрой.
  • При снятии галочек кнопки вызова палитры,поле ввода подсказки и выбор графической метки должно становится неактивным.

Для реализации задачи вывода выбранных таймфреймов создадим в нашем базовом классе в приватной секции метод RebulidTimeframes() и реализуем его:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::RebuildTimeframes(void)
{
//--- Считаем количество выбранных таймфреймов
   int cnt=0;
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
         cnt++;
   }
   ArrayResize(m_timeframes,cnt);
   cnt=0;
//--- Запоминаем выбранные таймфреймы в массив
   for(int i=0; i<21; i++)
   {
      if(m_checkbox[i].IsPressed())
      {
         m_timeframes[cnt]=m_checkbox[i].LabelText();
         cnt++;
      }
   }
//---
   for(int i=0; i<cnt; i++)
      m_tf_button[i].IsLocked(false);
//---
   for(int i=0; i<cnt; i++)
   {
      m_tf_button[i].LabelText(m_timeframes[i]);
      m_tf_button[i].Update(true);
   }
   //---
   for(int i=cnt; i<21; i++)
      m_tf_button[i].IsLocked(true);
}

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

      //--- Нажатие на кпопку Добавить сигнал
      if(lparam==m_add_signal.Id())
      {
         m_set_window.OpenWindow();
         if(m_set_window.IsAvailable())
            RebuildTimeframes();
      }

Перейдем к следующему моменту, когда настроим взаимодействие кнопок Value и Text. Для этого в методе OnEvent() в событии по нажатию кнопки добавим следующий код:

//---
      if(lparam==m_label_button[0].Id())
      {
         if(m_label_button[0].IsPressed())
         {
            m_label_button[1].IsPressed(false);
            m_label_button[1].Update(true);
         }
         m_text_box.Hide();
      }
      if(lparam==m_label_button[1].Id())
      {
         if(m_label_button[1].IsPressed())
         {
            m_label_button[0].IsPressed(false);
            m_label_button[0].Update(true);
         }
         m_text_box.Show();
      }

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

      //---
      for(int i=0; i<4; i++)
      {
         if(lparam==m_color_button[i].Id())
         {
            m_color_picker.ColorButtonPointer(m_color_button[i]);
            return;
         }
      }

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

//--- Нажатие на чекбоксе
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
   {
      //---
      for(int i=0; i<3; i++)
      {
         if(lparam==m_set_param[i].Id())
         {
            m_color_button[i+1].IsLocked(!m_set_param[i].IsPressed());
            if(m_set_param[2].IsPressed())
               m_tooltip_text.Show();
            else
               m_tooltip_text.Hide();
         }
      }
      //---
      if(lparam==m_set_param[3].Id())
         m_pictures_slider.IsLocked(!m_set_param[3].IsPressed());
   }

Итак, теперь снова скомпилируем проект и посмотрим что получилось.

Рис.13 Реализации взаимодействия элементов интерфейса окна редактирования торговых сигналов.


Монитор торговых сигналов

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

  • Создать строки с текстовыми метками выбранных на первом шаге символами для работы.
  • Создать столбцы заголовки с текстовыми метками выбранных на втором шаге таймфреймами.
  • Изменить размеры окна под созданные элементы по строками и столбцам. Своего рода авторазмер.

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

   CTextLabel        m_timeframe_label[];
   CTextLabel        m_symbol_label[];
   bool              CreateTimeframeLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);
   bool              CreateSymbolLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text);

Теперь реализуем созданные методы в файле MainWindow.mqh:

//+------------------------------------------------------------------+
//| Создаёт текстовую метку                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateTimeframeLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Сохраним указатель на окно
   text_label.MainPointer(m_step_window);
//---
   text_label.Font(m_base_font);
   text_label.FontSize(m_base_font_size);
   text_label.XSize(40);
   text_label.BackColor(m_background);
//--- Создание кнопки
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}
//+------------------------------------------------------------------+
//| Создаёт текстовую метку                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateSymbolLabel(CTextLabel &text_label,const int x_gap,const int y_gap,string label_text)
{
//--- Сохраним указатель на окно
   text_label.MainPointer(m_step_window);
//---
   text_label.Font(m_base_font);
   text_label.FontSize(m_base_font_size);
   text_label.XSize(100);
   text_label.BackColor(m_background);
//--- Создание кнопки
   if(!text_label.CreateTextLabel(label_text,x_gap,y_gap))
      return(false);
//--- Добавим указатель на элемент в базу
   CWndContainer::AddToElementsArray(0,text_label);
   return(true);
}

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

   int               m_total_signals;
   string            m_symbols[];
   void              ToMonitor(void);
   void              AutoResize(const int x_size,const int y_size);

Переменная m_total_signals необходима для проверки создания хотя бы одного торгового сигнала, прежде чем создавать окно монитора, массивов m_symbols[] будет содержать в себе выбор символов из первого шага настройки. Метод ToMonitor() будет осуществлять построение интерфейса монитора, а AutoResize() настроит размеры окна под созданные элементы. Теперь реализуем объявленные методы:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::ToMonitor(void)
{
//--- Проверка на наличие хотя бы одного сигнала
   if(m_total_signals<1)
   {
      MessageBox("No signals created!","Warning");
      return;
   }
//--- Скрываем шаг 3
   m_add_signal.Hide();
   m_signal_header.Hide();
   m_back_button.Hide();
   m_next_button.Hide();
//--- Меняем заголовок окна
   m_step_window.LabelText("Signal Monitor");
   m_step_window.Update(true);
//--- Символы
   int sy=ArraySize(m_symbols);
   ArrayResize(m_symbol_label,sy);
   for(int i=0; i<sy; i++)
   {
      if(!CreateSymbolLabel(m_symbol_label[i],5,m_step_window.CaptionHeight()+25+i*25,m_symbols[i]))
         return;
      m_symbol_label[i].Update(true);
   }
//--- Таймфреймы
   int tf=ArraySize(m_timeframes);
   ArrayResize(m_timeframe_label,tf);
//---
   for(int i=0; i<tf; i++)
   {
      if(!CreateTimeframeLabel(m_timeframe_label[i],110+50*i,m_step_window.CaptionHeight()+3,m_timeframes[i]))
         return;
      m_timeframe_label[i].Update(true);
   }
//--- Изменяем размеры окна
   AutoResize(m_timeframe_label[tf-1].XGap()+m_timeframe_label[tf-1].XSize()+5,m_symbol_label[sy-1].YGap()+m_symbol_label[sy-1].YSize()+5);
}

В листинге видно, что в разделе Символы используются данные из массива m_symbols, но нигде эти данные не собираются и не подготавливаются. Это следует исправить. Перейдем к методу ToStep_2() и после проверки на выбор хотя бы одного символа запомним выбор символов на первом шаге в наш массив:

//--- Считаем количество выбранных символов
   ArrayResize(m_symbols,cnt);
   cnt=0;
//--- Запоминаем выбранные таймфреймы в массив
   for(int i=0; i<m_all_symbols; i++)
   {
      if(m_checkbox[i].IsPressed())
      {
         m_symbols[cnt]=m_checkbox[i].LabelText();
         cnt++;
      }
   }

Теперь создаем метод авторазмера окна. 

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CProgram::AutoResize(const int x_size,const int y_size)
{
   m_step_window.ChangeWindowWidth(x_size);
   m_step_window.ChangeWindowHeight(y_size);
}

Прежде чем проверить проект на работоспособность, необходимо переменной m_total_signals присвоить ноль в конструкторе класса CProgram. И второй важный момент — это дополнение в методе OnEvent() в разделе события по нажатию кнопки. 

      //--- Навигация
      if(lparam==m_back_button.Id())
      {
         //--- Переход назад
         if(m_current_step==2)
            ToStep_1();
         //--- Возврат на Шаг 2
         else if(m_current_step==3)
            ToStep_2();
      }
      //--- Переход вперед
      if(lparam==m_next_button.Id())
      {
         //--- Переход на Шаг 2
         if(m_current_step==1)
            ToStep_2();
         //--- Переход на Шаг 3
         else if(m_current_step==2)
            ToStep_3();
         //--- Переход в Монитор
         else if(m_current_step==3)
            ToMonitor();
      }

Здесь добавляем вызов созданного метода ToMonitor() по нажатию кнопки перехода вперед. На шаге 3 она будет иметь название Create(Создать). Теперь компилируем проект и запускаем приложение:

  • На первом шаге выбираем Crosses.
  • На втором шаге возьмем набор Senior.
  • На третьем нажмем на кнопку Add Signal. 
  • После этого закрываем окно создания сигнала и нажимаем на Create.

рис.14 Базовая настройка монитора.

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

Заключение

В конце статьи приложен архив со всеми перечисленными файлами, отсортированными по папкам. Поэтому для корректной работы достаточно положить папку MQL5 в корень терминала. Для того чтобы найти корень терминала, в котором находится папка MQL5, нужно в MetaTrader 5 нажать комбинацию клавиш   Ctrl+Shift+D или воспользоваться контекстным меню, как показано на рис.15 ниже.


Рис.15 Поиск папки MQL5 в корне терминала MetaTrader 5

Прикрепленные файлы |
MQL5.zip (1644.31 KB)
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIII): Отложенные торговые запросы - закрытие позиций по условиям Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIII): Отложенные торговые запросы - закрытие позиций по условиям
Продолжаем работу над функционалом библиотеки для реализации торговли при помощи отложенных запросов. У нас уже реализована отправка торговых запросов по условию на открытие позиций и установку отложенных ордеров. Сегодня создадим возможность полного, частичного и встречного закрытия позиций по условию.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXII): Отложенные торговые запросы - установка ордеров по условиям Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXII): Отложенные торговые запросы - установка ордеров по условиям
Продолжаем создавать функционал, позволяющий производить торговлю при помощи отложенных запросов. В этой статье мы реализуем возможность устанавливать отложенные ордеры по условию.
Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIV): Отложенные торговые запросы - удаление ордеров, модификация ордеров и позиций по условиям Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIV): Отложенные торговые запросы - удаление ордеров, модификация ордеров и позиций по условиям
В этой статье мы завершим описание концепции работы с отложенными торговыми запросами и создадим функционал для удаления отложенных ордеров и модификации ордеров и позиций по условиям. Таким образом у нас будет в наличии весь функционал, при помощи которого можно будет впоследствии создавать несложные пользовательские стратегии, а вернее — некоторую логику поведения советника при наступлении заданных пользователем условий.
Применение OLAP в трейдинге (Часть 3): анализ котировок в целях выработки торговых стратегий Применение OLAP в трейдинге (Часть 3): анализ котировок в целях выработки торговых стратегий
В данной статье мы продолжим рассматривать технологию OLAP в применении к трейдингу, расширяя функционал, представленный в первых двух статьях. На этот раз оперативному анализу подвергнутся котировки. Показано выдвижение и проверка гипотез о торговых стратегиях на основе агрегированных показателей истории. Представлены эксперты для исследований побаровых закономерностей и адаптивной торговли.