English 中文 Español Deutsch 日本語 Português
Как быстро добавить панель управления к индикатору и советнику

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

MetaTrader 5Примеры | 17 февраля 2016, 16:16
10 840 32
Vladimir Karputov
Vladimir Karputov

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

Ваша 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().

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

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

  • NewBarOriginal.mq5 — изначальный файл индикатора.
  • PanelDialogOriginal.mqh — изначальный файл панели.
  • NewBar.mq5 — измененный файл индикатора.
  • PanelDialog.mqh — измененный файл панели.
  • PanelDialog2Original.mqh — изначальный файл второй панели.
  • PanelDialog2.mqh — измененный файл второй панели.
  • MACD Sample.mq5 — измененный файл советника.
Прикрепленные файлы |
newbar.mq5 (3.24 KB)
paneldialog.mqh (6.39 KB)
newbaroriginal.mq5 (2.05 KB)
macd_sample.mq5 (24.16 KB)
paneldialog2.mqh (28.49 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (32)
Subscribers
Subscribers | 8 дек. 2016 в 16:51
Vladimir Karputov:
Запретить никак, но можно прописать в функции обработки клика проверку флага - если флаг true - значит обрабатываем нажатия, а если флаг false - значит выходим из функции.
вообщем никак нельзя сделать панель не активной ?
Vladimir Karputov
Vladimir Karputov | 8 дек. 2016 в 16:54
Subscribers:
это уже сделал... не все ровно не то... например когда у меня закрывается много ордеров по нажатию кнопки на панели хочу что-бы панелька полностью не отвечала, а то если кликнуть (в это время) на кнопку бай, то ивент все-ровно будет, и будет вход в обработчик ивента после того когда отработает закрытие

Сделайте правильно - обработку запретного флага сразу по входу в функцию.

Хотя... Минутку... 

Добавлено:

Нет. Нужно просто обрабатывать нажатие в зависимости от запрещающего флага. 

Subscribers
Subscribers | 8 дек. 2016 в 17:35
Vladimir Karputov:

Сделайте правильно - обработку запретного флага сразу по входу в функцию.

Хотя... Минутку... 

Добавлено:

Нет. Нужно просто обрабатывать нажатие в зависимости от запрещающего флага. 

я Вас понял, спасибо 
Yuriy Zaytsev
Yuriy Zaytsev | 28 февр. 2017 в 08:37
Vladimir Karputov:
  1. При ближайшем обновлении стандартная библиотека будет восстановлена - соответственно будут восстановлен файл Defines.mqh.
  2. Править стандартную библиотеку - это не есть хорошо.
В теории - можно   Defines.mqh - просто внутрь проекта уложить т.е. не обращаться к файлу

#include <Controls\Label.mqh>
#include <Controls\Panel.mqh>
#include <Controls\Edit.mqh>
// #include <Controls\Defines.mqh>
#include <Controls\Button.mqh>
Но  метод , который ниже - красивей.



Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Почему съезжает панель при обновлении настроек эксперта?

Andrey Khatimlianskii, 2016.03.10 13:17

Примерно так:

// ПЕРЕД подключением Dialog.mqh!
#include <Controls\Defines.mqh>

//--- Забываем старые цвета
#undef  CONTROLS_DIALOG_COLOR_BORDER_LIGHT
#undef  CONTROLS_DIALOG_COLOR_BORDER_DARK
#undef  CONTROLS_DIALOG_COLOR_BG
#undef  CONTROLS_DIALOG_COLOR_CAPTION_TEXT
#undef  CONTROLS_DIALOG_COLOR_CLIENT_BG
#undef  CONTROLS_DIALOG_COLOR_CLIENT_BORDER

//--- Устанавливаем новые цвета
#define CONTROLS_DIALOG_COLOR_BORDER_LIGHT  clrWhite            // Dialog border color (outside)
#define CONTROLS_DIALOG_COLOR_BORDER_DARK   C'0xB6,0xB6,0xB6'   // Dialog border color (inside)
#define CONTROLS_DIALOG_COLOR_BG            clrLightGreen       // Dialog background (under the caption and around the client area)
#define CONTROLS_DIALOG_COLOR_CAPTION_TEXT  C'0x28,0x29,0x3B'   // Dialog caption text color
#define CONTROLS_DIALOG_COLOR_CLIENT_BG     clrAliceBlue        // Client area background color
#define CONTROLS_DIALOG_COLOR_CLIENT_BORDER C'0xC8,0xC8,0xC8'   // Client area color

// Теперь подключаем
#include <Controls\Dialog.mqh>
Sergei Kiriakov
Sergei Kiriakov | 11 дек. 2023 в 07:37
у меня кнопки на панели самопроизвольно нажимаются когда я над ними мышкой просто вожу не кликая, мля странное всё это какое-то.
Графические интерфейсы II: Элементы "Разделительная линия" и "Контекстное меню" (Глава 2) Графические интерфейсы II: Элементы "Разделительная линия" и "Контекстное меню" (Глава 2)
В этой статье мы создадим элемент «Разделительная линия». Его тоже можно будет использовать не только как независимый элемент интерфейса, но и как часть многих других элементов. После этого у нас будет всё необходимое для разработки класса контекстного меню, которое тоже будет подробно рассмотрено в этой статье. Кроме этого, вносятся необходимые дополнения в класс, который является базой для хранения указателей на все элементы графического интерфейса приложения.
Создание ручных торговых стратегий с использованием нечеткой логики Создание ручных торговых стратегий с использованием нечеткой логики
В статье рассматривается возможность улучшения ручных торговых стратегий с помощью теории нечетких множеств. В качестве примера пошагово описан поиск стратегии и подбор ее параметров, а затем — применение нечеткой логики для размытия слишком формальных критериев входа в рынок. Таким образом, после модификации стратегии мы получаем гибкие условия открытия позиции, более оптимально реагирующие на рыночную ситуацию.
Метод площадей Метод площадей
Торговая система "Метод площадей" работает на необычной интерпретации показаний осциллятора RSI. В настоящей статье приводится индикатор, который визуализирует метод площадей, и советник, торгующий по этой системе. Статья дополнена подробными результатами тестирования советника для различных символов, таймфреймов и значений площади.
Графические интерфейсы II: Элемент "Пункт меню" (Глава 1) Графические интерфейсы II: Элемент "Пункт меню" (Глава 1)
В второй части серии будет показан процесс разработки таких элементов интерфейса, как главное меню и контекстное меню. Также затронем тему рисования элементов и для этого создадим специальный класс. Очень широко будет освещен такой вопрос, как управление событиями программы, в том числе и пользовательскими.