Как быстро добавить панель управления к индикатору и советнику

Vladimir Karputov | 17 февраля, 2016

Зачем нужна графическая панель

Ваша MQL4/MQL5-программа — индикатор или советник — может быть одной из лучших и полностью выполнять возложенные на нее задачи. Но вы всегда можете немного улучшить ее. Как правило, в 99% случаев для любого изменения входных параметров программы пользователю нужно заходить в ее настройки. Хотите обойтись без этого?

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

В этой статье я покажу, как самому добавить простую панель к вашей MQL4/MQL5-программе. Вы узнаете, как научить программу читать входные параметры и реагировать на изменение их значений.

 

1. Соединяем индикатор и панель


1.1. Индикатор

Индикатор "NewBar.mq5" выполняет одно действие: при появлении нового бара печатает сообщение в журнал экспертов терминала. Код индикатора представлен ниже:

//+------------------------------------------------------------------+
//|                                                       NewBar.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "The indicator identifies a new bar"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   static datetime prev_time;
//--- revert access to array time[] - do it like in timeseries 
   ArraySetAsSeries(time,true);
//--- first calculation or number of bars was changed
   if(prev_calculated==0)// first calculation
     {
      prev_time=time[0];
      return(rates_total);
     }
//---
   if(time[0]>prev_time)
      Print("New bar!");
//---
   prev_time=time[0];
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Расскажу немного подробнее о том, как работает индикатор "NewBar.mq5".

В функции OnCalculate() объявляется статическая переменная "prev_time" — в этой переменной сохраняется время открытия "time[0]". В следующем заходе сравнивается время открытия "time[0]" с переменной "prev_time" — то есть происходит сравнение времени открытия "time[0]" на текущем тике и время открытия "time[0]" на предыдущем тике. В случае выполнения условия

if(time[0]>prev_time)

считаем, что обнаружен новый бар.

На следующем примере детально разберем, как индикатор "NewBar.mq5" обнаруживает новый бар:

New bar

Рис. 1. Процесс обнаружения нового бара в индикаторе

Рассмотрим 10 тиков на очень спокойном рынке.

Тики с 1 по 3 включительно: время открытия бара с индексом "0" (time[0]) равно времени, которое сохранено в статической переменной prev_time, а значит, нового бара нет.

Тик №4: этот тик пришел на новом баре. При входе в функцию OnCalculate() в time[0] будет время открытия бара (2015.12.01 00:02:00), а переменная prev_time еще хранит время из предыдущего тика (2015.12.01 00:01:00). Поэтому при проверке условия time[0]>prev_time мы обнаружим новый бар. Перед выходом из OnCalculate() в переменной prev_time будет записано время из time[0] (2015.12.01 00:02:00).

Тики с 5 по 8 включительно: время открытия бара с индексом "0" (time[0]) равно времени, которое сохранено в статической переменной prev_time, а значит, нового бара нет.

Тик №9: этот тик пришел на новом баре. При входе в функцию OnCalculate() в time[0] будет время открытия бара (2015.12.01 00:03:00), а переменная prev_time еще хранит время из предыдущего тика (2015.12.01 00:02:00). Поэтому при проверке условия time[0]>prev_time мы обнаружим новый бар. Перед выходом из OnCalculate() в переменной prev_time будет записано время из time[0] (2015.12.01 00:03:00).

Тик 10: время открытия бара с индексом "0" (time[0]) равно времени, которое сохранено в статической переменной prev_time, а значит, нового бара нет.


1.2. Панель

Все параметры построения панели: количество, размеры и координаты элементов управления сосредоточены в одном включаемом файле "PanelDialog.mqh". Файл "PanelDialog.mqh" — это класс реализации панели.

Панель имеет такой вид:

Panel

Рис. 2. Панель

Код включаемого файла "PanelDialog.mqh" представлен ниже:

//+------------------------------------------------------------------+
//|                                                  PanelDialog.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <Controls\Dialog.mqh>
#include <Controls\CheckGroup.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT                         (11)      // indent from left (with allowance for border width)
#define INDENT_TOP                          (11)      // indent from top (with allowance for border width)
#define INDENT_BOTTOM                       (11)      // indent from bottom (with allowance for border width)
//--- for buttons
#define BUTTON_WIDTH                        (100)     // size by X coordinate
//+------------------------------------------------------------------+
//| Class CControlsDialog                                            |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CControlsDialog : public CAppDialog
  {
private:
   CCheckGroup       m_check_group;                   // CCheckGroup object

public:
                     CControlsDialog(void);
                    ~CControlsDialog(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);
   //--- handlers of the dependent controls events
   void              OnChangeCheckGroup(void);
  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CControlsDialog)
ON_EVENT(ON_CHANGE,m_check_group,OnChangeCheckGroup)
EVENT_MAP_END(CAppDialog)
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CControlsDialog::CControlsDialog(void)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CControlsDialog::~CControlsDialog(void)
  {
  }
//+------------------------------------------------------------------+
//| Create                                                           |
//+------------------------------------------------------------------+
bool CControlsDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
//--- create dependent controls
   if(!CreateCheckGroup())
      return(false);
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Create the "CheckGroup" element                                  |
//+------------------------------------------------------------------+
bool CControlsDialog::CreateCheckGroup(void)
  {
//--- coordinates
   int x1=INDENT_LEFT;
   int y1=INDENT_TOP;
   int x2=x1+BUTTON_WIDTH;
   int y2=ClientAreaHeight()-INDENT_BOTTOM;
//--- create
   if(!m_check_group.Create(m_chart_id,m_name+"CheckGroup",m_subwin,x1,y1,x2,y2))
      return(false);
   if(!Add(m_check_group))
      return(false);
   m_check_group.Alignment(WND_ALIGN_HEIGHT,0,y1,0,INDENT_BOTTOM);
//--- fill out with strings
   if(!m_check_group.AddItem("Mail",1<<0))
      return(false);
   if(!m_check_group.AddItem("Push",1<<1))
      return(false);
   if(!m_check_group.AddItem("Alert",1<<2))
      return(false);
   Comment(__FUNCTION__+" : Value="+IntegerToString(m_check_group.Value()));
//--- succeed
   return(true);
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CControlsDialog::OnChangeCheckGroup(void)
  {
   Comment(__FUNCTION__+" : Value="+IntegerToString(m_check_group.Value()));
  }
//+------------------------------------------------------------------+

Как видите, в классе нашей панели нет методов установки и чтения свойств состояния переключателей с независимой фиксацией.

Наша задача: сделать индикатор "NewBar.mq5" основным файлом и добавить входные параметры — например, с помощью каких методов ("Mail", "Push" или "Alert") сообщать пользователю о том, что найден новый бар. Кроме того, мы должны добавить во включаемый файл "PanelDialog.mqh" методы установки и чтения свойств состояния переключателей с независимой фиксацией "Mail", "Push" и "Alert".


1.3. Изменяем индикатор

Примечание: все вносимые изменения будут отмечаться цветом.

Сначала нужно подключить включаемый файл "PanelDialog.mqh":

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()

Затем добавим входные параметры:

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//--- input parameters
input bool     bln_mail=false;      // Notify by email
input bool     bln_push=false;      // Notify by push
input bool     bln_alert=true;      // Notify by alert
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()

Скомпилируем индикатор (F7 в MetaEditor) и проверим отображение входных параметров в терминале:

Input parameters

Рис. 3. Входные параметры индикатора


1.4. Изменяем панель

В панель нужно добавить методы установки и чтения свойств состояния переключателей с независимой фиксацией "Mail", "Push" и "Alert".

Добавим в класс панели новые методы:

class CControlsDialog : public CAppDialog
  {
private:
   CCheckGroup       m_check_group;                   // CCheckGroup object

public:
                     CControlsDialog(void);
                    ~CControlsDialog(void);
   //--- create
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   //--- chart event handler
   virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- set check for element
   virtual bool      SetCheck(const int idx,const int value);
   //--- get check for element
   virtual int       GetCheck(const int idx) const;

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

Реализация этих методов:

//+------------------------------------------------------------------+
//| Set check for element                                            |
//+------------------------------------------------------------------+
bool CControlsDialog::SetCheck(const int idx,const bool check)
  {
   return(m_check_group.Check(idx,check));
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)
  {
   return(m_check_group.Check(idx));
  }


1.5. Заключительный этап соединения индикатора и панели

В индикаторе "NewBar.mq5", в области объявлений глобальных переменных объявим переменную класса нашей панели:

#property indicator_chart_window
#property indicator_plots 0
#include "PanelDialog.mqh"
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CControlsDialog ExtDialog;
//--- input parameters
input bool     bln_mail=false;      // Notify by email
input bool     bln_push=false;      // Notify by push
input bool     bln_alert=true;      // Notify by alert

и добавим в самый конец индикатора функцию OnChartEvent():

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }

В функции OnInit() индикатора "NewBar.mq5", создадим панель и программно нажмем чекбоксы в соответствии с входными параметрами:

int OnInit()
  {
//--- indicator buffers mapping
//--- create application dialog
   if(!ExtDialog.Create(0,"Notification",0,50,50,180,160))
      return(INIT_FAILED);
//--- run application
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//---
   ExtDialog.SetCheck(0,bln_mail);
   ExtDialog.SetCheck(1,bln_push);
   ExtDialog.SetCheck(2,bln_alert);
//---
   return(INIT_SUCCEEDED);
  }

На этом соединение индикатора и панели закончено. В классе панели нами реализованы метод установки состояния чекбокса нажатый/отжатый (SetCheck) и метод получения состояния чекбокса (GetCheck).

 

2. Соединяем советник и панель


2.1. Советник

За основу взят советник из стандартной поставки ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5.


2.2. Панель

Вид панели "PanelDialog2.mqh" после внесения окончательных изменений:

Panel number two

Рис. 4. Панель номер два

Что мы получим после объединения советника "MACD Sample.mq5" и панели "PanelDialog2.mqh"? На текущем таймфрейме, на котором будет работать советник, можно будет оперативно менять параметры советника ("Lots", "Trailing Stop Level (in pips)" и другие), а также параметры оповещения о торговых событиях советника ("Mail", "Push", "Alert").

Измененные параметры советника ("Lots", "Trailing Stop Level (in pips)" и другие) применяются после щелчка на кнопке "Применить изменения". Изменение параметров оповещения о торговых событиях советника ("Mail", "Push", "Alert") применяются автоматически — щелкать на кнопке "Применить изменения" не нужно.


2.3. Советнику и панели нужно общаться

Communication EA and panels

Рис. 5. Общение советника и панели

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


2.4. Шаг первый. Внесение изменений в советник

План действий: возьмите советник из стандартной поставки ...\MQL5\Experts\Examples\MACD\MACD Sample.mq5 и скопируйте его в свою папку. Например, можно создать папку "Notification" и в нее скопировать советник:

Create a new folder

Рис. 6. Создание новой папки


В области глобальных переменных советника (не путайте их с глобальными переменными терминала) объявим новые переменные, которые будут отвечать за способ отправки уведомления о торговых действиях советника. Обратите внимание, что эти переменные, как и другие внешние переменные, имеют приставку "Inp":

#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//--- input parameters
input bool     InpMail=false;          // Notify by email
input bool     InpPush=false;          // Notify by push
input bool     InpAlert=true;          // Notify by alert
//---
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =50;  // Take Profit (in pips)

Чуть ниже добавим дубликаты всех внешних переменных советника. Дубликаты будут иметь приставку "Ext":

input int    InpMACDCloseLevel=2;   // MACD close level (in pips)
input int    InpMATrendPeriod =26;  // MA trend period
//--- ext variables
bool           ExtMail;
bool           ExtPush;
bool           ExtAlert;

double         ExtLots;
int            ExtTakeProfit;
int            ExtTrailingStop;
int            ExtMACDOpenLevel;
int            ExtMACDCloseLevel;
int            ExtMATrendPeriod;
//---
int ExtTimeOut=10; // time out in seconds between trade operations
//+------------------------------------------------------------------+
//| MACD Sample expert class                                         |
//+------------------------------------------------------------------+

В OnInit() пропишем копирование значений из внешних переменных советника в значения переменных-дубликатов:

//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
   ExtMail=InpMail;
   ExtPush=InpPush;
   ExtAlert=InpAlert;

   ExtLots=InpLots;
   ExtTakeProfit=InpTakeProfit;
   ExtTrailingStop=InpTrailingStop;
   ExtMACDOpenLevel=InpMACDOpenLevel;
   ExtMACDCloseLevel=InpMACDCloseLevel;
   ExtMATrendPeriod=InpMATrendPeriod;
//--- create all necessary objects
   if(!ExtExpert.Init())

На данном этапе в функциях советника CSampleExpert::InitIndicators, CSampleExpert::InitCheckParameters и CSampleExpert::Init используются внешние переменные советника с приставкой "Inp". Нам нужно в этих функциях заменить внешние переменные на их дубликаты (дубликаты у нас с приставкой "Ext"). Я предлагаю сделать это довольно оригинальным методом:


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


2.5. Шаг второй. Внесение изменений в панель

Панель, изображенная на рис. 4, — это заготовка. В ней еще нет ни функций для "общения" с советником, ни функций обработки вводимых данных. Файл заготовки панели "PanelDialog2Original.mqh" тоже скопируйте в папку "Notification".

Добавим в класс панели внутренние переменные, в которых потом будем хранить состояние всех введенных данных. Обратите внимание на переменную "mModification" — о ней будет сказано в пункте 2.7.

private:
   //--- get check for element
   virtual int       GetCheck(const int idx);
   //---
   bool              mMail;
   bool              mPush;
   bool              mAlert_;
   double            mLots;               // Lots
   int               mTakeProfit;         // Take Profit (in pips)
   int               mTrailingStop;       // Trailing Stop Level (in pips)
   int               mMACDOpenLevel;      // MACD open level (in pips)
   int               mMACDCloseLevel;     // MACD close level (in pips)
   int               mMATrendPeriod;      // MA trend period
   //---
   bool              mModification;       // Values have changed
  };
//+------------------------------------------------------------------+
//| Event Handling                                                   |

Сразу за этим в конструкторе класса панели инициализируем внутренние переменные:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CControlsDialog::CControlsDialog(void) : mMail(false),
                                         mPush(false),
                                         mAlert_(true),
                                         mLots(0.1),
                                         mTakeProfit(50),
                                         mTrailingStop(30),
                                         mMACDOpenLevel(3),
                                         mMACDCloseLevel(2),
                                         mMATrendPeriod(26),
                                         mModification(false)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |

В функцию CControlsDialog::Create добавим установку группы элементов переключателей в соответствии со внутренними переменными:

if(!CreateButtonOK())
      return(false);

//---
   SetCheck(0,mMail);
   SetCheck(1,mPush);
   SetCheck(2,mAlert_);

//--- succeed
   return(true);
  }

 

2.6. Шаг третий. Внесение изменений в советник

До сих пор советник и панель были двумя отдельными, независимыми друг от друга файлами. Свяжем их и объявим переменную класса нашей панели "ExtDialog":

#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include "PanelDialog2Original.mqh"
//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CControlsDialog ExtDialog;
//--- input parameters
input bool     InpMail=false;          // Notify by email
input bool     InpPush=false;          // Notify by push

Чтобы панель заработала и стала видна, ее нужно создать и запустить на выполнение. Также необходимо добавить функцию OnChartEvent(), которая является обработчиком события ChartEvent, и функцию OnDeinit(). OnInit() в советнике приобретет такой вид:

int OnInit(void)
  {
   ExtMail=InpMail;
   ExtPush=InpPush;
   ExtAlert=InpAlert;

   ExtLots=InpLots;
   ExtTakeProfit=InpTakeProfit;
   ExtTrailingStop=InpTrailingStop;
   ExtMACDOpenLevel=InpMACDOpenLevel;
   ExtMACDCloseLevel=InpMACDCloseLevel;
   ExtMATrendPeriod=InpMATrendPeriod;
//--- create all necessary objects
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- create application dialog
   if(!ExtDialog.Create(0,"Notification",0,100,100,360,380))
      return(INIT_FAILED);
//--- run application
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//--- succeed
   return(INIT_SUCCEEDED);
  }

В OnDeinit() уничтожаем нашу панель, а саму функцию OnDeinit() пропишем сразу после OnInit():

//--- succeed
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 
   Comment("");
//--- destroy dialog
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| Expert new tick handling function                                |
//+------------------------------------------------------------------+
void OnTick(void)

Функцию OnChartEvent() добавим в самый конец советника (после функции OnTick):

//--- change limit time by timeout in seconds if processed
         if(ExtExpert.Processing())
            limit_time=TimeCurrent()+ExtTimeOut;
        }
     }
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

Теперь советник можно cкомпилировать и проверить на графике. Советник будет запускаться вместе с панелью:

EA and panel

Рис. 7. Советник вместе с панелью


2.7. Шаг четвертый. Внесение изменений в панель. Большая интеграция

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

Добавим метод Initialization() — этот метод принимает параметры и инициализирует этими параметрами внутренние переменные панели. Объявление:

virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
      //--- initialization
   virtual bool      Initialization(const bool Mail,const bool Push,const bool Alert_,
                                    const double Lots,const int TakeProfit,
                                    const int  TrailingStop,const int MACDOpenLevel,
                                    const int  MACDCloseLevel,const int MATrendPeriod);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

Само тело метода (вставим его перед CControlsDialog::GetCheck):

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
bool CControlsDialog::Initialization(const bool Mail,const bool Push,const bool Alert_,
                                     const double Lots,const int TakeProfit,
                                     const int  TrailingStop,const int MACDOpenLevel,
                                     const int  MACDCloseLevel,const int MATrendPeriod)
  {
   mMail=Mail;
   mPush=Push;
   mAlert_=Alert_;

   mLots=Lots;
   mTakeProfit=TakeProfit;
   mTrailingStop=TrailingStop;
   mMACDOpenLevel=MACDOpenLevel;
   mMACDCloseLevel=MACDCloseLevel;
   mMATrendPeriod=MATrendPeriod;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

Так как внутренние переменные панели проинициализированы данными, то теперь нужно правильно заполнить элементы управления панели — поля ввода. Поскольку полей ввода у нас шесть, то я приведу пример на базе m_edit1. Строка, в которой присваивался текст, была такая:

...
   if(!m_edit1.Text("Edit1"))
...

а стала такая:

...
   if(!m_edit1.Text(DoubleToString(mLots,2)))
...

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

Следующий метод — GetValues() — отвечает за возврат значений внутренних переменных:

virtual bool      Initialization(const bool Mail,const bool Push,const bool Alert_,
                                    const double Lots,const int TakeProfit,
                                    const int  TrailingStop,const int MACDOpenLevel,
                                    const int  MACDCloseLevel,const int MATrendPeriod);
   //--- get values
   virtual void      GetValues(bool &Mail,bool &Push,bool &Alert_,
                               double &Lots,int &TakeProfit,
                               int &TrailingStop,int &MACDOpenLevel,
                               int &MACDCloseLevel,int &MATrendPeriod);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

Его тело вставим после CControlsDialog::Initialization()):

//+------------------------------------------------------------------+
//| Get values                                                       |
//+------------------------------------------------------------------+
void CControlsDialog::GetValues(bool &Mail,bool &Push,bool &Alert_,
                                double &Lots,int &TakeProfit,
                                int &TrailingStop,int &MACDOpenLevel,
                                int &MACDCloseLevel,int &MATrendPeriod)
  {
   Mail=mMail;
   Push=mPush;
   Alert_=mAlert_;

   Lots=mLots;
   TakeProfit=mTakeProfit;
   TrailingStop=mTrailingStop;
   MACDOpenLevel=mMACDOpenLevel;
   MACDCloseLevel=mMACDCloseLevel;
   MATrendPeriod=mMATrendPeriod;
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

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

virtual void      GetValues(bool &Mail,bool &Push,bool &Alert_,
                               double &Lots,int &TakeProfit,
                               int &TrailingStop,int &MACDOpenLevel,
                               int &MACDCloseLevel,int &MATrendPeriod);   //--- send notifications
   virtual void      Notifications(const string text);

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

Его тело вставим после CControlsDialog::GetValues()):

//+------------------------------------------------------------------+
//|  Send notifications                                              |
//+------------------------------------------------------------------+
void CControlsDialog::Notifications(const string text)
  {
   int i=m_check_group.ControlsTotal();
   if(GetCheck(0))
      SendMail(" ",text);
   if(GetCheck(1))
      SendNotification(text);
   if(GetCheck(2))
      Alert(text);
  }
//+------------------------------------------------------------------+
//| Get check for element                                            |
//+------------------------------------------------------------------+
int CControlsDialog::GetCheck(const int idx)

Чтобы запомнить, производились ли изменения параметров в панели, введена внутренняя переменная — флаг "mModification" (о ней упоминалось ранее, в пункте 2.5.).

virtual void      Notifications(const string text);
   //---
   virtual bool      Modification(void) const { return(mModification);          }
   virtual void      Modification(bool value) { mModification=value;            }

protected:
   //--- create dependent controls
   bool              CreateCheckGroup(void);

Контроль за изменениями будем проводить в "CControlsDialog::OnClickButtonOK" — обработчике нажатия на кнопку "Применить изменения":

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CControlsDialog::OnClickButtonOK(void)
  {
//--- verifying changes
   if(m_check_group.Check(0)!=mMail)
      mModification=true;
   if(m_check_group.Check(1)!=mPush)
      mModification=true;
   if(m_check_group.Check(2)!=mAlert_)
      mModification=true;

   if(StringToDouble(m_edit1.Text())!=mLots)
     {
      mLots=StringToDouble(m_edit1.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit2.Text())!=mTakeProfit)
     {
      mTakeProfit=(int)StringToDouble(m_edit2.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit3.Text())!=mTrailingStop)
     {
      mTrailingStop=(int)StringToDouble(m_edit3.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit4.Text())!=mMACDOpenLevel)
     {
      mMACDOpenLevel=(int)StringToDouble(m_edit4.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit5.Text())!=mMACDCloseLevel)
     {
      mMACDCloseLevel=(int)StringToDouble(m_edit5.Text());
      mModification=true;
     }
   if(StringToInteger(m_edit6.Text())!=mMATrendPeriod)
     {
      mMATrendPeriod=(int)StringToDouble(m_edit6.Text());
      mModification=true;
     }
  }

Также панель проверяет введенные данные в обработчиках:

void              OnChangeCheckGroup(void);
   void              OnChangeEdit1(void);
   void              OnChangeEdit2(void);
   void              OnChangeEdit3(void);
   void              OnChangeEdit4(void);
   void              OnChangeEdit5(void);
   void              OnChangeEdit6(void);
   void              OnClickButtonOK(void);

Их описание я пропущу.

2.8. Шаг пятый. Внесение изменений в советник. Последнее редактирование

На данный момент панель не работает в тестере стратегий, поэтому нужно защититься — ввести внутреннюю переменную, а именно — флаг "bool_tester".

//---
int ExtTimeOut=10; // time out in seconds between trade operations
bool           bool_tester=false;      // true - mode tester
//+------------------------------------------------------------------+
//| MACD Sample expert class                                         |
//+------------------------------------------------------------------+
class CSampleExpert

Вносим изменения в OnInit() — защищаемся от запуска в тестере стратегий, а также перед визуализацией панели инициализируем ее параметры:

//--- create all necessary objects
   if(!ExtExpert.Init())
      return(INIT_FAILED);
//--- 
   if(!MQLInfoInteger(MQL_TESTER))
     {
      bool_tester=false;
      //---
      ExtDialog.Initialization(ExtMail,ExtPush,ExtAlert,
                               ExtLots,ExtTakeProfit,ExtTrailingStop,
                               ExtMACDOpenLevel,ExtMACDCloseLevel,ExtMATrendPeriod);
      //--- create application dialog
      if(!ExtDialog.Create(0,"Notification",0,100,100,360,380))
         return(INIT_FAILED);
      //--- run application
      if(!ExtDialog.Run())
         return(INIT_FAILED);
     }
   else
      bool_tester=true;
//--- secceed
   return(INIT_SUCCEEDED);
  }

В OnChartEvent() проверяем, были ли изменены параметры в панели. Если они изменены, значит, необходимо запустить инициализацию советника с новыми параметрами:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   ExtDialog.ChartEvent(id,lparam,dparam,sparam);
// Дальше опрашиваем переменную bool в панели: изменялись ли параметры
// Если параметры изменились - значит, опрашиваем параметры панели и вызываем
// CSampleExpert::Init(void)
   if(ExtDialog.Modification())
     {
      ExtDialog.GetValues(ExtMail,ExtPush,ExtAlert,
                          ExtLots,ExtTakeProfit,ExtTrailingStop,
                          ExtMACDOpenLevel,ExtMACDCloseLevel,ExtMATrendPeriod);
      if(ExtExpert.Init())
        {
         ExtDialog.Modification(false);
         Print("Параметры изменены, ",ExtLots,", ",ExtTakeProfit,", ",ExtTrailingStop,", ",
               ExtMACDOpenLevel,", ",ExtMACDCloseLevel,", ",ExtMATrendPeriod);
        }
      else
        {
         ExtDialog.Modification(false);
         Print("Ошибка изменения параметров");
        }
     }
  }
//+------------------------------------------------------------------+

 

Заключение

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

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

К статье приложены файлы: