English 中文 Español Deutsch 日本語 Português
Универсальный канал с графическим интерфейсом

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

MetaTrader 5Примеры | 24 января 2017, 11:35
8 758 0
Dmitry Fedoseev
Dmitry Fedoseev

Содержание

Введение

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

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

Несмотря на схожесть с универсальным осциллятором, будут и серьезные принципиальные отличия. Все индикаторы каналов представляют собой три линии: центральную, верхнюю и нижнюю. Центральная линия по принципу своего построения идентична скользящей средней, и в большинстве случаев для построения канала используется именно скользящая средняя. Верхняя и нижняя линия располагаются на одинаковом расстоянии от центральной линии. Это расстояние может определяться просто в пунктах, в процентах от цены (индикатор Envelopes), может использоваться значение стандартной девиации (полосы Боллинджера), может — значение индикатора ATR (канал Кельтнера). Значит, канальный индикатор будет построен с использованием двух независимых блоков:

  1. Блок расчета центральной линии
  2. Блок определения ширины канала (или построения границ)

Существуют каналы несколько другого типа, в частности, канал Дончиана (ценовой канал). Обычно его создание начинается с построения крайних линий (ценового диапазона), и уже после этого вычисляется значение центральной линии (на середине диапазона). Но и этот канал можно построить по вышеизложенной системе: сначала построить центральную линию, определяемую как середина ценового диапазона, а затем достроить границы на расстоянии половины ценового диапазона. Конечно, вычислений для этого потребуется больше, чем при обычном построении. Но поскольку главная цель этой статьи — создание универсального индикатора, можно допустить некоторые исключения, тем более, что данный подход увеличит количество возможных комбинаций центральной линии и границ. Например, можно будет получить индикатор с центральной линией как у ценового канала, но границы которого расположены на расстоянии стандартной девиации, как у полос Боллинджера и т.п. 

Типы центральной линии

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

Таблица 1. Типы центральной линии

Стандартная
функция
Название Параметры
iAMA Adaptive Moving Average 1. int ama_period — период AMA 
2. int fast_ma_period — период быстрой скользящей
3. int slow_ma_period — период медленной скользящей
4. int ama_shift — смещение индикатора по горизонтали
5. ENUM_APPLIED_PRICE  applied_price — тип цены или handle 
iDEMA Double Exponential Moving Average 1. int ma_period — период усреднения
2. int ma_shift — смещение индикатора по горизонтали
3. ENUM_APPLIED_PRICE  applied_price — тип цены
iFrAMA Fractal Adaptive Moving Average 1. int ma_period — период усреднения
2. int ma_shift — смещение индикатора по горизонтали
3. ENUM_APPLIED_PRICE  applied_price — тип цены
iMA Moving Average 1. int ma_period — период усреднения
2. int ma_shift — смещение индикатора по горизонтали  
3. ENUM_MA_METHOD ma_method — тип сглаживания
4. ENUM_APPLIED_PRICE applied_price — тип цены
iTEMA Triple Exponential Moving Average 1. int ma_period — период усреднения
2. int ma_shift — смещение индикатора по горизонтали  
3. ENUM_APPLIED_PRICE  applied_price — тип цены
iVIDyA Variable Index Dynamic Average 1. int cmo_period — период Chande Momentum 
2. int ema_period — период фактора сглаживания  
3. int ma_shift — смещение индикатора по горизонтали 
4. ENUM_APPLIED_PRICE  applied_price — тип цены
- центральная линия ценового канала 1. int period

На основании анализа колонки "Параметры" из таблицы 1, получим необходимый минимальный набор параметров (таблица 2).

Таблица 2. Универсальный набор параметров для расчета центральной линии канала 

Тип Название
int period1
int period2
int period3
int shift
ENUM_MA_METHOD ma_method 
ENUM_APPLIED_PRICE  price
Параметры центральной линии в окне свойств индикатора будут иметь префиксы "c_". 

Типы границ

Так же, как с центральной линией канала, определимся с вариантами расчета границ канала (таблица 3).

Таблица 3. Варианты расчета ширины канала 

Стандартная
функция
Название  Параметры
iATR Average True Range 1. int ma_period — период усреднения
iStdDev  Standard Deviation 1. int ma_period — период усреднения
2. int ma_shift — смещение индикатора по горизонтали
3. ENUM_MA_METHOD — тип сглаживания
4. ENUM_APPLIED_PRICE applied_price — тип цены 
в пунктах  int width — ширина в пунктах 
в процентах (как у Envelopes)  double width — ширина в процентах от цены   
как у ценового канала  double width — коэффициент масштабирования относительно действительной ширины ценового канала

На основании колонки "Параметры" из табл. 3 получим необходимый набор параметров (таблица 4).

Таблица 4. Универсальный набор параметров для расчета ширины канала

Тип Название
int period
int  shift 
ENUM_MA_METHOD  ma_method  
ENUM_APPLIED_PRICE  price
double  width  

При расчете в пунктах нужна переменная типа int, но ее нет в таблице 4, так как вместо нее можно использовать переменную типа double. Таким образом сокращается общее количество переменных в окне свойств.

Параметры расчета границ в окне свойств индикатора будут иметь префиксы "w_". 

Классы центральной линии

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

В папке MQL5/Include создадим папку UniChannel, скопируем в нее файл CUniOsc.mqh (из папки Include/UniOsc) и переименуем его в CUniChannel.mqh. Оставим в файле базовый класс (COscUni), дочерний класс Calculate1 (полное имя COscUni_Calculate1) и его дочерний класс COscUni_ATR, остальные классы удалим.

Переименуем классы: заменим фрагмент "COscUni" на "CChannelUni". Для замены удобно пользоваться функцией редактора (Главное меню — Правка — Поиск и замена — Заменить), но использовать не кнопку "Заменить все", а выполнять замены по одной, чтобы проконтролировать процесс и быть уверенным, что все замены выполнены только там, где надо.

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

class CChannelUniWidth{
   protected:
      int m_handle;      // хэндл индикатора
      string m_name;     // имя индикатора
      string m_label1;   // название буфера 1      
      string m_help;     // небольшая справка по параметрам индикатора
      double m_width;    // ширина канала
   public:
  
      // конструктор
      void CChannelUniWidth(){
         m_handle=INVALID_HANDLE;
      }
      
      // деструктор
      void ~CChannelUniWidth(){
         if(m_handle!=INVALID_HANDLE){
            IndicatorRelease(m_handle);
         }
      }
  
      // основной метод, вызываемый из функции OnCalculate() индикатора
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & bufferCentral[],
                     double & bufferUpper[],
                     double & bufferLower[],
      ){
         return(rates_total);
      }
      
      // получение хэндла загруженного индикатора
      int Handle(){
         return(m_handle);
      }
      
      // метод проверки хэндла, чтобы узнать, загрузился ли индикатор  
      bool CheckHandle(){
         return(m_handle!=INVALID_HANDLE);
      }
      
      // получение имени индикатора
      string Name(){
         return(m_name);
      }    

      // получение текста надписи для буферов
      string Label1(){
         return(m_label1);
      }
      
      // получение подсказки по параметрам
      string Help(){
         return(m_help);
      }
};

Из класса Calculate можно удалить всё, касающееся второго буфера, в результате в классе останется один метод Calculate:

class CChannelUni_Calculate1:public CChannelUni{
   public:
      // основной метод, вызываемый из функции OnCalculate() индикатора
      // первые два параметра аналогичны первым двум параметрам
      // функции OnCalculate() индикатора
      // третий параметр - индикаторный буфер для центральной линии
      int Calculate( const int rates_total,    
                     const int prev_calculated,
                     double & buffer0[]
      ){
        
         // определение количества копируемых элементов
        
         int cnt;
        
         if(prev_calculated==0){
            cnt=rates_total;
         }
         else{
            cnt=rates_total-prev_calculated+1;
         }  
        
         // копирование данных в индикаторный буфер
         if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
            return(0);
         }
        
         return(rates_total);
      }
};

Напишем дочерний класс, использующий индикатор iMA. Имеющийся в файле класс CChannelUni_ATR переименуем в CChannelUni_MA, заменим в нем вызываемый индикатор и удалим лишнее. В результате получается следующий класс:

class CChannelUni_MA:public CChannelUni_Calculate1{
   public:
   // конструктор
   // первые два параметра, одинаковые для всех дочерних классов
   // затем параметры загружаемого индикатора
   void CChannelUni_MA( bool use_default,
                        bool keep_previous,
                        int & ma_period,
                        int & ma_shift,
                        long & ma_method,
                        long & ma_price){
      if(use_default){ // выбрано использование значений по умолчанию
         if(keep_previous){
            // ранее использованные параметры не менять
            if(ma_period==-1)ma_period=14;
            if(ma_shift==-1)ma_shift=0;
            if(ma_method==-1)ma_method=MODE_SMA;
            if(ma_price==-1)ma_price=PRICE_CLOSE;
         }
         else{
            ma_period=14;
            ma_shift=0;
            ma_method=MODE_SMA;
            ma_price=PRICE_CLOSE;            
         }      
      }    
      
      // загрузка индикатора
      m_handle=iMA(Symbol(),Period(),ma_period,ma_shift,(ENUM_MA_METHOD)ma_method,(ENUM_APPLIED_PRICE)ma_price);
      
      // формирование строки с именем индикатора
      m_name=StringFormat( "iMA(%i,%i,%s,%s)",
                           ma_period,
                           ma_shift,        
                           EnumToString((ENUM_MA_METHOD)ma_method),              
                           EnumToString((ENUM_APPLIED_PRICE)ma_price)
                        );

      
      // строка для названия буфера
      m_label1=m_name;

      // подсказка по параметрам
      m_help=StringFormat( "ma_period - c_Period1(%i), "+
                           "ma_shift - c_Shift(%i), "+
                           "ma_method - c_Method(%s)"+
                           "ma_price - c_Price(%s)",
                           ma_period,
                           ma_shift,
                           EnumToString((ENUM_MA_METHOD)ma_method),
                           EnumToString((ENUM_APPLIED_PRICE)ma_price)
                           );
   }
};

Рассмотрим подробнее формирование строк в переменных m_name и mlabel1. Имя индикатора (переменная m_name) у индикаторов, располагающихся в подокне, становится видимым в левом верхнем углу подокна. Поскольку канал будет отображаться на графике цены, его имя будет невидимо, поэтому переменной m_label присвоим точно такое же подробное название как переменной m_name, чтобы при наведении мыши на центральный канал во всплывающей подсказке были видимы все его параметры. 

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

  1. Создать дочерний класс типа Calculate и выполнить расчеты в нем
  2. Написать дополнительный индикатор и вызывать его через функцию iCustom
Оба варианта имеют свое право на существование. В первом случае сокращается количество файлов, от которых зависит создаваемый в статье индикатор, но потребуется повторное выполнение одних и тех же расчетов (сначала определение границ канала для расчета средней линии, потом опять определение границ канала для определения ширины канала). Во втором случае дублирования расчетов не будет, к тому же получаем дополнительный независимый и полноценный индикатор ценового канала, который можно будет использовать самостоятельно.      

В приложении к статье находится файл CUniChannel.mqh с дочерними классами для всех остальных индикаторов и индикатор iPriceChannel. У индикатора iPriceChannel данные центральной линии расположены в буфере 0. Если кто-то будет для себя дорабатывать класс под какой-то другой индикатор, требуемые данные у которого располагаются не в нулевом буфере, тогда надо будет создать еще один дочерний класс Calculate или же в базовом классе создать переменную для индекса буфера и в конструкторе дочернего класса устанавливать ей нужное значение.   

Классы расчета ширины и построения канала

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

Сохраним файл CUniChannel.mqh с именем CUniChannelWidth.mqh и внесем в него изменения. Сначала удалим все дочерние классы, оставив только базовый класс и класс Calculate. Переименуем класс из CChannelUni в CChannelUniWidth (не забываем про конструктор, деструктор и имя родителя у дочернего класса, которые тоже надо изменить). Получается следующий класс:

class CChannelUniWidth{
   protected:
      int m_handle;           // хэндл индикатора
      string m_name;          // имя индикатора
      string m_label1;        // название буфера 1      
      string m_help;          // небольшая справка по параметрам индикатора
      double m_width;         // ширина канала
   public:
  
      // конструктор
      void CChannelUniWidth(){
         m_handle=INVALID_HANDLE;
      }
      
      // деструктор
      void ~CChannelUniWidth(){
         if(m_handle!=INVALID_HANDLE){
            IndicatorRelease(m_handle);
         }
      }
  
      // основной метод, вызываемый из функции OnCalculate() индикатора
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & bufferCentral[],
                     double & bufferUpper[],
                     double & bufferLower[],
      ){
         return(rates_total);
      }
      
      // получение хэндла загруженного индикатора
      int Handle(){
         return(m_handle);
      }
      
      // метод проверки хэндла, чтобы узнать, загрузился ли индикатор  
      bool CheckHandle(){
         return(m_handle!=INVALID_HANDLE);
      }
      
      // получение имени индикатора
      string Name(){
         return(m_name);
      }    

      // получение текста надписи для буферов
      string Label1(){
         return(m_label1);
      }
      
      // получение подсказки по параметрам
      string Help(){
         return(m_help);
      }
};

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

class CChannelUni_Calculate_ATR:public CChannelUniWidth{
   public:
      // конструктор
      // первые два параметра стандартны для всех дочерних классов 
      // затем идут параметры загружаемого индикатора
      // последний параметр - ширина канала
      void CChannelUni_Calculate_ATR(bool use_default,
                                     bool keep_previous,
                                     int & ma_period,
                                     double & ch_width){
         if(use_default){ // выбрано использование значений по умолчанию
            if(keep_previous){ // ранее использованные параметры не менять
               if(ma_period==-1)ma_period=14;
               if(ch_width==-1)ch_width=2;
            }
            else{
               ma_period=14;
               ch_width=2;
            }      
         } 
         
         // сохранение параметра ширины для использования в методе расчета  
         m_width=ch_width; 
         // загрузка индикатора
         m_handle=iATR(Symbol(),Period(),ma_period);
         // формирование строки с именем индикатора
         m_name=StringFormat("ATR(%i)",ma_period);
         // строка с именем буферов
         m_label1=m_name;
         // подсказка по параметрам 
         m_help=StringFormat("ma_period - Period1(%i)",ma_period); // подсказка   
      }   
      
      // основной метод, вызываемый из функции OnCalculate() индикатора
      // первые два параметра соответствуют первым двум параметрам
      // функции OnCalculate()
      // затем передаются индикаторные буферы 
      int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & bufferCentral[],
                     double & bufferUpper[],
                     double & bufferLower[],
      ){
      
         // определение начала расчета
         
         int start;
         
         if(prev_calculated==0){
            start=0
         }
         else
            start=prev_calculated-1;
         }  

         // основной цикл расчета и заполнения буферов 

         for(int i=start;i<rates_total;i++){
            
            // получение данных индикатора для обсчитываемого бара
            double tmp[1];
            if(CopyBuffer(m_handle,0,rates_total-i-1,1,tmp)<=0){
               return(0);
            }
            
            // умножение на параметр ширины
            tmp[0]*=m_width;   

            // расчет значений верхней и нижней границ
            bufferUpper[i]=bufferCentral[i]+tmp[0];
            bufferLower[i]=bufferCentral[i]-tmp[0];

         }   
         
         return(rates_total);
      }
};

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

Некоторые варианты расчета ширины канала не требуют использования дополнительных индикаторов — в частности, при ширине, задаваемой в пунктах или как у индикатора Envelope. В этом случае переменной m_handle базового класса присвоим значение 0 (отличающееся от значения INVALID_HANDLE).

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

Создание индикатора универсального канала

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

В редакторе создаем новый пользовательский индикатор с именем iUniChannel. При создании индикатора в мастере MQL выбираем функции: OnCalculate(...,open,high,low,close), OnTimer, OnChartEvent, создаем три буфера типа Line.

Для выбора типа центральной линии и типа канала необходимо создать два перечисления. Перечисления будут располагаться в файле UniChannelDefines.mqh. Создаем перечисления в соответствии с таблицами 1 и 3:

// перечисление типов центральной линии
enum ECType{
   UniCh_C_AMA,
   UniCh_C_DEMA,
   UniCh_C_FrAMA,
   UniCh_C_MA,
   UniCh_C_TEMA,
   UniCh_C_VIDyA,
   UniCh_C_PrCh
};

// перечисление типов границ
enum EWType{
   UniCh_W_ATR,
   UniCh_W_StdDev,
   UniCh_W_Points,
   UniCh_W_Percents,
   UniCh_W_PrCh
};

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

#include <UniChannel/UniChannelDefines.mqh>
#include <UniChannel/CUniChannel.mqh>
#include <UniChannel/CUniChannelWidth.mqh>

Объявляем две внешних переменных для выбора типов центральной линии и ширины канала и переменные для параметров в соответствии с таблицами 2 и 4:

// параметры центральной линии
input ECType               CentralType   =  UniCh_C_MA;
input int                  c_Period1     =  5;
input int                  c_Period2     =  10;
input int                  c_Period3     =  15;
input int                  c_Shift       =  0;
input ENUM_MA_METHOD       c_Method      =  MODE_SMA;
input ENUM_APPLIED_PRICE   c_Price       =  PRICE_CLOSE;
// параметры границ
input EWType               WidthType     =  UniCh_W_StdDev;
input int                  w_Period      =  20;
input int                  w_Shift       =  0;
input ENUM_MA_METHOD       w_Method      =  MODE_SMA;
input ENUM_APPLIED_PRICE   w_Price       =  PRICE_CLOSE;
input double               w_Width       =  2.0;

Объявим две переменные, которые пока будут внутренними, но в версии с графическими интерфейсом будут выведены в окно свойств:

bool                 UseDefault  =  false;
bool                 KeepPrev    =  false;

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

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

ECType               _CentralType;
int                  _ma_Period1;
int                  _ma_Period2;
int                  _ma_Period3;
int                  _ma_Shift;
long                 _ma_Method;
long                 _ma_Price;
EWType               _WidthType;
int                  _w_Period;
int                  _w_Shift;
long                 _w_Method;
long                 _w_Price;
double               _w_Width;

Затем напишем функцию подготовки параметров:

void PrepareParameters(){

   _CentralType=CentralType;
   _WidthType=WidthType;
  
   if(UseDefault && KeepPrev){
      _c_Period1=-1;
      _c_Period2=-1;
      _c_Period3=-1;
      _c_Shift=0;
      _c_Method=-1;
      _c_Price=-1;
      _w_Period=-1;
      _w_Shift=0;
      _w_Method=-1;
      _w_Price=-1;
      _w_Width=-1;
   }
   else{  
      _c_Period1=c_Period1;
      _c_Period2=c_Period2;
      _c_Period3=c_Period3;
      _c_Shift=c_Shift;
      _c_Method=c_Method;
      _c_Price=c_Price;
      _w_Period=w_Period;
      _w_Shift=w_Shift;
      _w_Method=w_Method;
      _w_Price=w_Price;
      _w_Width=w_Width;
   }
}

Заметьте, при выполнении условия  UseDefault && KeepPrev, всем переменным присваивается значение -1, а переменным Shift значение 0, потому что значения этих переменных не устанавливаются из объектов индикаторов, а только из интерфейса пользователя (окна свойств индикатора или графического интерфейса).   

После подготовки параметров можно создавать объекты для расчета центральной линии и канала. В универсальном осцилляторе для этого была функция LoadOscillator(). Здесь будет две функции: LoadCentral() и LoadWidth(), но сначала объявим переменные-указатели:

CChannelUni * central;
CChannelUniWidth * width;

У некоторых индикаторов имеется параметр смещения по горизонтали (shift), но у некоторых индикаторов его нет, хотя смещать можно все индикаторы. Поэтому объявим дополнительную переменную shift0 со значением 0, и в конструкторы классов передавать будем ее. Смещение же будет выполняться за счет смещения индикаторных буферов.

Функция LoadCentral():

void LoadCentral(){
   switch(_CentralType){ // в зависимости от выбранного типа создается соответствующий класс
      case UniCh_C_AMA:
         central=new CChannelUni_AMA(  UseDefault,
                                       KeepPrev,
                                       _c_Period1,
                                       _c_Period2,
                                       _c_Period3,
                                       shift0,
                                       _c_Price);
      break;
      case UniCh_C_DEMA:
         central=new CChannelUni_DEMA( UseDefault,
                                       KeepPrev,
                                       _c_Period1,
                                       shift0,
                                       _c_Price);
      break;
      case UniCh_C_FrAMA:
         central=new CChannelUni_FrAMA(UseDefault,
                                       KeepPrev,
                                       _c_Period1,
                                       shift0,
                                       _c_Price);
      break;
      case UniCh_C_MA:
         central=new CChannelUni_MA(   UseDefault,
                                       KeepPrev,
                                       _c_Period1,
                                       shift0,
                                       _c_Method,
                                       _c_Price);
      break;
      case UniCh_C_TEMA:
         central=new CChannelUni_TEMA( UseDefault,
                                       KeepPrev,
                                       _c_Period1,
                                       shift0,
                                       _c_Price);
      break;
      case UniCh_C_VIDyA:
         central=new CChannelUni_VIDyA(UseDefault,
                                       KeepPrev,
                                       _c_Period1,
                                       _c_Period2,
                                       shift0,
                                       _c_Price);
      break;
      case UniCh_C_PrCh:
         central=new CChannelUni_PriceChannel(  UseDefault,
                                                KeepPrev,
                                                _c_Period1);
      break;
   }
}

У одного из вариантов расчета ширина канала (класс CChannelUni_Calculate_InPoints) есть параметр, измеряющийся в пунктах, а в классе предусмотрена адаптация значения этого параметра в соответствии с количеством знаком после запятой у котировок. Для работы функции адаптации при создании объекта в конструктор класса необходимо передать множитель параметра. На котировках с 2-мя и 4-мя знаками значение множителя будет равно 1, а на 3- и 5-значных котировках — 10. Во внешних параметрах объявим переменную Auto5Digits типа bool:

input bool                 Auto5Digits   =  true;

Если Auto5Digits равно true, то будет выполняться коррекция параметра, если false, то значение будет использоваться как есть. Чуть ниже Auto5Digits объявим еще одну переменную для множителя:

int mult;

В самом начале функции OnInit() вычислим значение mult:

   if(Auto5Digits && (Digits()==3 || Digits()==5)){
      mult=10; // будем умножать параметры, измеряющиеся в пунктах, на 10
   }
   else{
      mult=1; // параметры, измеряющиеся в пунктах, остаются без изменения
   }

Теперь напишем функцию LoadWidth():

void LoadWidth(){
   switch(_WidthType){ // в зависимости от выбранного типа создается соответствующий класс
      case UniCh_W_ATR:
         width=new CChannelUni_Calculate_ATR(UseDefault,KeepPrev,_w_Period,_w_Width);
      break;
      case UniCh_W_StdDev:
         width=new CChannelUni_Calculate_StdDev(UseDefault,KeepPrev,_w_Period,shift0,_w_Method,_w_Price,_w_Width);
      break;
      case UniCh_W_Points:
         width=new CChannelUni_Calculate_InPoints(UseDefault,KeepPrev,_w_Width,mult);
      break;
      case UniCh_W_Percents:
         width=new CChannelUni_Calculate_Envelopes(UseDefault,KeepPrev,_w_Width);
      break;
      case UniCh_W_PrCh:
         width=new CChannelUni_Calculate_PriceChannel(UseDefault,KeepPrev,_w_Period,_w_Width);
      break;
   }
}

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

Print("Central line parameters matching:",central.Help());
Print("Width parameters matching:",width.Help());  

Установка надписей и смещений для буферов будет выполняться в функции SetStyles():

void SetStyles(){

   // названия буферов
   PlotIndexSetString(0,PLOT_LABEL,"Central: "+central.Label1());
   PlotIndexSetString(1,PLOT_LABEL,"Upper: "+width.Label1());  
   PlotIndexSetString(2,PLOT_LABEL,"Lower: "+width.Label1());  
  
   // смещение буферов
   PlotIndexSetInteger(0,PLOT_SHIFT,_c_Shift);
   PlotIndexSetInteger(1,PLOT_SHIFT,_w_Shift);
   PlotIndexSetInteger(2,PLOT_SHIFT,_w_Shift);

}

В итоге получим следующую функцию OnInit():

int OnInit(){
  
   // подготовка множителя для коррекция параметра, измеряющегося в пунктах
   if(Auto5Digits && (Digits()==3 || Digits()==5)){
      mult=10;
   }
   else{
      mult=1;
   }
  
   // подготовка параметров
   PrepareParameters();
  
   // загрузка индикатора центральной линии
   LoadCentral();
  
   // проверка успешности загрузки центральной линии
   if(!central.CheckHandle()){
      Alert("Central line error "+central.Name());
      return(INIT_FAILED);
   }    
  
   // загрузка индикатора расчета ширины
   LoadWidth();
  
   // проверка успешности загрузки индикатора ширины
   if(!width.CheckHandle()){
      Alert("Width error "+width.Name());
      return(INIT_FAILED);
   }      

   // вывод подсказок по параметрам
   Print("Central line parameters matching: "+central.Help());
   Print("Width parameters matching: "+width.Help());  
  
   // установка имени
   ShortName="iUniChannel";  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);  
  
   // стандартная часть функции OnInit
   SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,Label2Buffer,INDICATOR_DATA);
   SetIndexBuffer(2,Label3Buffer,INDICATOR_DATA);
  
   // Установка надписей и смещений буферов
   SetStyles();

   return(INIT_SUCCEEDED);
}

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

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

enum ELockTo{
   LockTo_Off,
   LockTo_Period1,
   LockTo_Period2,
   LockTo_Period3
};

При выборе вариант LockTo_Off периоды регулируются раздельно, а в остальных случаях значение параметра w_Period равно соответствующему периоду центральной линии. Объявим переменную типа ELockTo сразу после переменной  w_Period:

input ELockTo              w_LockPeriod  =  LockTo_Off;

Доработаем функцию PrepareParameters(), добавим в самый низ функции следующий код:

switch(w_LockPeriod){ // в зависимости от типа блокировки
   case LockTo_Period1:
      _w_Period=_c_Period1;
   break;
   case LockTo_Period2:
      _w_Period=_c_Period2;      
   break;
   case LockTo_Period3:
      _w_Period=_c_Period3;      
   break;
}

Другое неудобство состоит в том, что информационные сообщения о соответствии параметров выводятся во вкладку "Эксперты" в одну строку, и на узких экранах часть строки выходит за край. Сделаем доработку для вывода строки в столбик. Вместо функции Print используем свою функцию PrintColl(). В эту функцию передаются два параметра: заголовок и строка с подсказкой. В функции строка с подсказкой разделяется и выводитcя по частям:

void PrintColl(string caption,string message){
   Print(caption); // вывод заголовка
   string res[];
   // разбиваем сообщение
   int cnt=StringSplit(message,',',res);
   // вывод сообщения по частям
   for(int i=0;i<cnt;i++){
      StringTrimLeft(res[i]);
      Print(res[i]);
   }
}

Соответственно, в функции OnInit() изменятся две строки вывода подсказок:

PrintColl("Central line parameters matching:",central.Help());
PrintColl("Width parameters matching:",width.Help());  

Теперь индикатор готов полностью,  имя файла в приложении — "iUniChanhel". Приступим к созданию графического интерфейса.   

Создание классов графического интерфейса

Графический интерфейс будет создаваться на основе графического интерфейса универсального осциллятора. Скопируем файл UniOsc/UniOscGUI.mqh в папку UniChannel и переименуем его в UniChannelGUI.mqh. Графический интерфейс универсального канала будет значительно отличаться от интерфейса универсального осциллятора, так что нам предстоит серьезно поработать.

Основное отличие состоит в том, что у универсального канала выполняется независимый выбор двух индикаторов (центральной линии и границ), поэтому должно быть два главных списка выбора типа индикатора. После первого списка должны располагаться элементы управления параметрами центральной линии, затем располагается второй список и элементы управления параметрами границ. Значит, второй список не имеет фиксированных координат, они должны вычисляться. Кроме двух списков для выбора типов, на форме всегда должны присутствовать два поля для ввода значений смещения, их координаты тоже не фиксированные. Еще один момент, которому нужно уделить внимание — это список для выбора варианта, соответствующего параметру w_LockPeriod. Во всех случаях, когда нам нужно отображать поле ввода параметра w_Period в группе элементов управления параметрами ширины, необходимо отображать дополнительный выпадающий список.

Сначала в файле UniChannelGUI.mqh выполним общие изменения:

1. Путь к файлу с перечислениями:

#include <UniOsc/UniOscDefines.mqh>

необходимо заменить на следующую строку:

#include <UniChannel/UniChannelDefines.mqh>

2. Добавим массив со значениями перечисления ELockTo:

ELockTo e_lockto[]={LockTo_Off,LockTo_Period1,LockTo_Period2,LockTo_Period3};

3. Массивы с перечислениями ENUM_APPLIED_VOLUME и ENUM_STO_PRICE удаляем.

Теперь приступим к изменению класса  CUniOscControls.  

Класс элементов управления центральной линии

1. Класс CUniOscControls переименуем в CUniChannelCentralControls.

2. В классе удалим объявление переменных m_volume и m_sto_price. Соответственно, удаляем все, что связано с этими элементами управления из методов SetPointers(), Hide(), Events().

3. Добавим переменную m_last_y, в ней будет фиксироваться координата Y последнего элемента управления группы. Добавим метод для получения значения этой переменной — GetLastY(). Метод FormHeight() становится ненужным, его удаляем, но вместо него добавляем метод ControlsCount(), который будет возвращать количество элементов управления в дочернем классе. Это количество потребуется для расчета высоты формы.

В результате получаем следующий родительский класс:

class CUniChannelCentralControls{
   protected:
      CSpinInputBox * m_value1; // для периода 1
      CSpinInputBox * m_value2; // для периода 2
      CSpinInputBox * m_value3; // для периода 3
      CComBox * m_price;        // для цены
      CComBox * m_method;       // для метода
      int m_last_y;             // позиция Y последнего элемента управления

   public:
  
   // получение позиции Y последнего элемента управления
   int GetLastY(){
      return(m_last_y);
   }
  
   // метод для передачи в объект указателей на объекты
   void SetPointers(CSpinInputBox & value1,
                        CSpinInputBox & value2,      
                        CSpinInputBox & value3,
                        CComBox & price,
                        CComBox & method){
      m_value1=GetPointer(value1);
      m_value2=GetPointer(value2);      
      m_value3=GetPointer(value3);            
      m_price=GetPointer(price);
      m_method=GetPointer(method);
   }
  
   // скрытие группы элементов управления
   void Hide(){
      m_value1.Hide();
      m_value2.Hide();
      m_value3.Hide();
      m_price.Hide();
      m_method.Hide();
   }
  
   // обработка событий
   int Event(int id,long lparam,double dparam,string sparam){
      int e1=m_value1.Event(id,lparam,dparam,sparam);
      int e2=m_value2.Event(id,lparam,dparam,sparam);
      int e3=m_value3.Event(id,lparam,dparam,sparam);
      int e4=m_price.Event(id,lparam,dparam,sparam);
      int e5=m_method.Event(id,lparam,dparam,sparam);
      if(e1!=0 || e2!=0 || e3!=0 || e4!=0 || e5!=0){
         return(1);
      }
      return(0);
   }
  
   // метод инициализации элементов управления (для изменения надписей)
   virtual void InitControls(){
   }  
  
   // отображение группы элементов управления
   virtual void Show(int x,int y){
   }  
  
   // получение количества элементов управления в группе
   virtual int ControlsCount(){
      return(0);
   }      
};

Изменим дочерний класс CUniOscControls_ATR:

1. Переименуем его в CUniChannelCentralControls_AMA, это будет класс для индикатора AMA.

2. В соответствии со столбцом "Параметры" таблицы 1, инициализируем элементы управления в методе InitControls(), а в методе Show() вызываем методы Show() всех элементов управления. Переменной m_last_y присваиваем значение последнего элемента управления.

3. Метод FormHeight() удаляем, вместо него добавляем метод ControlsCount().

Получаем такой класс:

class CUniChannelCentralControls_AMA:public CUniChannelCentralControls{
   void InitControls(){
      // инициализация элементов управления
      m_value1.Init("c_value1",SPIN_BOX_WIDTH,1," ama_period");
      m_value2.Init("c_value2",SPIN_BOX_WIDTH,1," fast_ma_period");      
      m_value3.Init("c_value3",SPIN_BOX_WIDTH,1," slow_ma_period");      
   }
  
   // отображение
   void Show(int x,int y){
      m_value1.Show(x,y);
      y+=20;
      m_value2.Show(x,y);
      y+=20;
      m_value3.Show(x,y);
      y+=20;
      m_price.Show(x,y);
      m_last_y=y;
   }
  
   // получение количества элементов в группе
   int ControlsCount(){
      return(4);
   }
};

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

Классы элементов управления расчета ширины

На основе полученного класса CUniChannelCentralControls создаем класс для управления параметрами ширины канала. Делаем копию класса CUniChannelCentralControls, переименовываем ее в CUniChannelWidthControls. В этом классе потребуется два поля ввода (период и ширина), два стандартных перечисления для типа усреднения и цены, а также перечисление параметра w_LockPeriod. В результате изменений получим следующий класс:

class CUniChannelWidthControls{
   protected:
      CSpinInputBox * m_value1; // для периода
      CSpinInputBox * m_value2; // для ширины    
      CComBox * m_price;        // для цены
      CComBox * m_method;       // для метода
      CComBox * m_lockto;       // для типа блокировки
      int m_last_y;             // позиция Y последнего элемента управления

   public:
  
   // получение позиции Y последнего элемента управления
   int GetLastY(){
      return(m_last_y);
   }
  
   // метод для передачи в объект указателей на объекты
   void SetPointers(CSpinInputBox & value1,
                        CSpinInputBox & value2,      
                        CComBox & price,
                        CComBox & method,
                        CComBox & lockto){
      m_value1=GetPointer(value1);
      m_value2=GetPointer(value2);      
      m_price=GetPointer(price);
      m_method=GetPointer(method);
      m_lockto=GetPointer(lockto);      
   }
  
   // скрытие группы элементов управления
   void Hide(){
      m_value1.Hide();
      m_value2.Hide();
      m_price.Hide();
      m_method.Hide();
   }
  
   // обработка событий
   int Event(int id,long lparam,double dparam,string sparam){
      int e1=m_value1.Event(id,lparam,dparam,sparam);
      int e2=m_value2.Event(id,lparam,dparam,sparam);
      int e4=m_price.Event(id,lparam,dparam,sparam);
      int e5=m_method.Event(id,lparam,dparam,sparam);
      int e6=m_lockto.Event(id,lparam,dparam,sparam);      
      if(e1!=0 || e2!=0 || e4!=0 || e5!=0 || e6){
         return(1);
      }
      return(0);
   }
  
   // метод инициализации элементов управления (для изменения надписей)
   virtual void InitControls(){
   }  
  
   // отображение группы элементов управления
   virtual void Show(int x,int y){
   }  
  
   // получение количества элементов управления в группе
   virtual int ControlsCount(){
      return(0);
   }    
};

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

class CUniChannelWidthControls_ATR:public CUniChannelWidthControls{
   void InitControls(){
      // инициализация элемента управления
      m_value1.Init("w_value1",SPIN_BOX_WIDTH,1," period");
   }
  
   // отображение группы элементов управления
   void Show(int x,int y){
      m_value1.Show(x,y);
      y+=20;
      m_lockto.Show(x,y);
      m_last_y=y;
   }  
  
   // получение количества элементов управления в группе
   int ControlsCount(){
      return(2);
   }    
};

Аналогично созданы классы для остальных вариантов расчета ширины канала. 

Теперь в файле UniChannelGUI.mqh находится два базовых класса элементов управления, множество их дочерних классов и класс формы. С последним нам предстоит поработать. Из-за значительного размера файла, дальнейшая работа в нем может быть неудобна, поэтому вынесем классы элементов управления в другие файлы. Создаем файл UniChannel/CUniChannelCentralControls.mqh и переносим в него класс CUniChannelCentralControls и все его дочерние классы и подключим дополнительные файлы: 

#include <IncGUI_v4.mqh>
#include <UniChannel/UniChannelDefines.mqh>

Определение констант FORM_WIDTH, SPIN_BOX_WIDTH, COMBO_BOX_WIDTH перенесем в файл UniChannelDefines.mqh. После этого файл CUniChannelCentralControls можно скомпилировать, чтобы проверить ошибки. Также перенесем в отдельный файл класс CUniChannelWidthControls. После этого будет удобно работать над классом формы. 

Класс формы

Подключим к файлу UniChannelGUI.mqh два только что созданных файла:

#include <UniChannel/CUniChannelCentralControls.mqh>
#include <UniChannel/CUniChannelWidthControls.mqh>

Переименуем класс CUniOscForm в CUniChannelForm. В секции public удалим переменную-указатель типа CUniOscControls, вместо нее объявим две других переменных-указателя: CUniChannelCentralControls и CUniChannelWidthControls, определимся с другими элементами управления класса формы. В итоге в секции public располагаем следующие переменные:

CComBox           m_c_cmb_main;  // список выбора центральной линии
CSpinInputBox     m_c_value1;    // поле ввода периода 1
CSpinInputBox     m_c_value2;    // поле ввода периода 2
CSpinInputBox     m_c_value3;    // поле ввода периода 3
CComBox           m_c_price;     // список выбора цены
CComBox           m_c_method;    // список выбора метода
CSpinInputBox     m_c_shift;     // поле ввода смещения

CComBox           m_w_cmb_main;  // список выбора границ
CSpinInputBox     m_w_value1;    // поле ввода периода
CSpinInputBox     m_w_value2;    // поле ввода ширины  
CComBox           m_w_price;     // список выбора цены
CComBox           m_w_method;    // список выбора метода
CComBox           m_w_lockto;    // список выбора варианта блокировки    
CSpinInputBox     m_w_shift;     // поле ввода смещения          

// группа управления элементами центральной линии
CUniChannelCentralControls * m_central_controls;
// группа управления элементами границ
CUniChannelWidthControls * m_width_controls;  

В методе  MainProperties() изменим значения переменных m_Name и m_Caption, остальные переменные остаются без изменений:

void MainProperties(){
      m_Name         =  "UniChannelForm";
      m_Width        =  FORM_WIDTH;
      m_Height       =  150;
      m_Type         =  0;
      m_Caption      =  "UniChannel";
      m_Movable      =  true;
      m_Resizable    =  true;
      m_CloseButton  =  true;
}

В методе OnInitEvent() вызываем методы Init() всех элементов управления, у которых не меняются надписи (набор элементов управления соответствующий выбранному индикатору) и заполняем выпадающие списки:

void OnInitEvent(){

   // инициализация элементов управления, не входящих в группы
  
   m_c_cmb_main.Init("cb_c_main",COMBO_BOX_WIDTH," select central");
   m_w_cmb_main.Init("cb_w_main",COMBO_BOX_WIDTH," select bands");

   m_c_price.Init("c_price",COMBO_BOX_WIDTH," price");
   m_c_method.Init("c_method",COMBO_BOX_WIDTH," method");
   m_c_shift.Init("c_shift",COMBO_BOX_WIDTH,1," shift");    
  
   m_w_price.Init("w_price",COMBO_BOX_WIDTH," price");
   m_w_method.Init("w_method",COMBO_BOX_WIDTH," method");
   m_w_shift.Init("w_shift",COMBO_BOX_WIDTH,1," shift");    
  
   m_w_lockto.Init("cb_w_lockto",COMBO_BOX_WIDTH," lock period");
   m_w_value2.Init("w_value2",SPIN_BOX_WIDTH,0.001," width");
  
   // заполнение выпадающих списков
  
   for(int i=0;i<ArraySize(e_price);i++){
      m_c_price.AddItem(EnumToString(e_price[i]));
      m_w_price.AddItem(EnumToString(e_price[i]));
   }
   for(int i=0;i<ArraySize(e_method);i++){
      m_c_method.AddItem(EnumToString(e_method[i]));
      m_w_method.AddItem(EnumToString(e_method[i]));
   }            
   for(int i=0;i<ArraySize(e_lockto);i++){
      m_w_lockto.AddItem(EnumToString(e_lockto[i]));            
   }
  
   // разрешение ввода смещений с клавиатуры            
   m_c_shift.SetReadOnly(false);
   m_w_shift.SetReadOnly(false);                        
}

В методах OnShowEvent() отображаем элементы управления, при этом, после отображения групп элементов, получаем координату Y и в соответствии с ней отображаем следующие элементы управления:

void OnShowEvent(int aLeft, int aTop){
   m_c_cmb_main.Show(aLeft+10,aTop+10);        // список выбора типа центральной линии
   m_central_controls.Show(aLeft+10,aTop+30);  // группа элементов управления параметрами центральной линии
   int m_y=m_central_controls.GetLastY();      // получение координаты последнего элемента управления
   m_c_shift.Show(aLeft+10,m_y+20);            // поле ввода параметра смещения
   m_w_cmb_main.Show(aLeft+10,m_y+40);         // список выбора канала
   m_width_controls.Show(aLeft+10,m_y+60);     // группа элементов управления параметрами канала
   m_y=m_width_controls.GetLastY();            // получение координаты последнего элемента управления
   m_w_value2.Show(aLeft+10,m_y+20);           // поле ввода ширины
   m_w_shift.Show(aLeft+10,m_y+40);            // поле ввода параметра смещения
}

В методе OnHideEvent() скрываем элементы управления:

void OnHideEvent(){
   m_c_cmb_main.Hide();       // список выбора типа центральной линии    
   m_central_controls.Hide(); // группа элементов управления параметрами центральной линии
   m_c_shift.Hide();          // поле ввода параметра смещения
   m_w_cmb_main.Hide();       // список выбора канала
   m_width_controls.Hide();   // группа элементов управления параметрами канала
   m_w_shift.Hide();          // поле ввода параметра смещения
   m_w_lockto.Hide();         // выбор типа блокировки периода
   m_width_controls.Hide();   // поле ввода ширины
}

Внесем изменения в метод SetValues(). Меняем набор параметров метода, в методе всем элементам управления устанавливаем значения, соответствующие этим параметрам:

void SetValues(int c_value1,
               int c_value2,
               int c_value3,
               long c_method,
               long c_price,
               long c_shift,                    
               int w_value1,
               int w_value2,
               long w_method,
               long w_price,
               long w_lockto,
               long w_shift  
){

   // поля ввода параметров центральной линии
   m_c_value1.SetValue(c_value1);
   m_c_value2.SetValue(c_value2);      
   m_c_value3.SetValue(c_value3);
   m_c_shift.SetValue(c_shift);        

   // поля ввода параметров канала
   m_w_value1.SetValue(w_value1);
   m_w_value2.SetValue(w_value2);        
   m_w_shift.SetValue(w_shift);            
  
   // отображение выбранных типов в списках выбора методов сглаживания
   for(int i=0;i<ArraySize(e_method);i++){
      if(c_method==e_method[i]){
         m_c_method.SetSelectedIndex(i);
      }
      if(w_method==e_method[i]){
         m_w_method.SetSelectedIndex(i);
      }            
   }
  
   // отображение выбранных типов в списках выбора типа цены
   for(int i=0;i<ArraySize(e_price);i++){
      if(c_price==e_price[i]){
         m_c_price.SetSelectedIndex(i);
      }
      if(w_price==e_price[i]){
         m_w_price.SetSelectedIndex(i);
      }            
   }

   // отображение выбранного типа блокировки периода канала
   for(int i=0;i<ArraySize(e_lockto);i++){
      if(w_lockto==e_lockto[i]){
         m_w_lockto.SetSelectedIndex(i);
         break;
      }
   }                    
}

Вместо метода SetType() создадим два метода: SetCentralType() — для установки типа центральной линии и SetWidthType() — для установки типа границ. В конце каждого метода после создания объектов элементам управления устанавливаются свойства, позволяющие вводить значения с клавиатуры. Также устанавливаем минимально возможные значения и вызываем приватный метод расчета высоты формы:  

Метод SetCentralType():

void SetCentralType(long type){
   // если объект уже создавался ранее, то удаляем его
   if(CheckPointer(m_central_controls)==POINTER_DYNAMIC){
      delete(m_central_controls);
      m_central_controls=NULL;
   }
   switch((ECType)type){ // в зависимости от выбранного типа создаем объект
      case UniCh_C_AMA:
         m_central_controls=new CUniChannelCentralControls_AMA();
      break;
      case UniCh_C_DEMA:
         m_central_controls=new CUniChannelCentralControls_DEMA();            
      break;
      case UniCh_C_FrAMA:
         m_central_controls=new CUniChannelCentralControls_FrAMA();            
      break;
      case UniCh_C_MA:
         m_central_controls=new CUniChannelCentralControls_MA();            
      break;
      case UniCh_C_TEMA:
         m_central_controls=new CUniChannelCentralControls_TEMA();            
      break;
      case UniCh_C_VIDyA:
         m_central_controls=new CUniChannelCentralControls_VIDyA();            
      break;
      case UniCh_C_PrCh:
         m_central_controls=new CUniChannelCentralControls_PrCh();            
      break;
   }    
  
   // передача указателей на объекты элементов управления
   m_central_controls.SetPointers(m_c_value1,m_c_value2,m_c_value3,m_c_price,m_c_method);
   // инициализация элементов управления групп
   m_central_controls.InitControls();
  
   // разрешение ввода с клавиатуры
   m_c_value1.SetReadOnly(false);
   m_c_value2.SetReadOnly(false);
   m_c_value3.SetReadOnly(false);
  
   // установка минимально допустимых значений
   m_c_value1.SetMinValue(1);        
   m_c_value2.SetMinValue(1);
   m_c_value3.SetMinValue(1);            
  
   // расчет высоты формы
   this.SolveHeight();

}

 Метод SetWidthType():

void SetWidthType(long type){
   // если объект уже создавался ранее, то удаляем его
   if(CheckPointer(m_width_controls)==POINTER_DYNAMIC){
      delete(m_width_controls);
      m_width_controls=NULL;
   }
   switch((EWType)type){ // в зависимости от выбранного типа создаем объект
      case UniCh_W_ATR:
         m_width_controls=new CUniChannelWidthControls_ATR();
      break;
      case UniCh_W_StdDev:
         m_width_controls=new CUniChannelWidthControls_StdDev();            
      break;
      case UniCh_W_Points:
         m_width_controls=new CUniChannelWidthControls_InPoints();            
      break;
      case UniCh_W_Percents:
         m_width_controls=new CUniChannelWidthControls_Envelopes();            
      break;
      case UniCh_W_PrCh:
         m_width_controls=new CUniChannelWidthControls_PrCh();                        
      break;
   }    

   // передача указателей на объекты элементов управления
   m_width_controls.SetPointers(m_w_value1,m_w_value2,m_w_price,m_w_method);
   // инициализация элементов управления групп
   m_width_controls.InitControls();
  
   // установка минимально допустимых значений
   m_w_value1.SetReadOnly(false);
   m_w_value2.SetReadOnly(false);
  
   // установка минимально допустимых значений
   m_w_value1.SetMinValue(1);        
   m_w_value2.SetMinValue(0);
  
   // расчет высоты формы
   this.SolveHeight();
              
}

В конце методов SetCentralType() и SetWidthType()  вызывается метод расчета высоты формы SolveHeight():

void SolveHeight(){
   // если существуют оба объекта (центральной линии и ширины)
   if(CheckPointer(m_central_controls)==POINTER_DYNAMIC && CheckPointer(m_width_controls)==POINTER_DYNAMIC){
      m_Height=(m_width_controls.ControlsCount()+m_central_controls.ControlsCount()+6)*20+10;
   }      
}  

Приступим к соединению индикатора и графического интерфейса.  

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

Сохраняем индикатор iUniChannel с именем iUniChannelGUI. По аналогии с индикатором iUniOscGUI добавляем в самый верх его окна свойств внешний параметр UseGUI. Следом располагаем переменные UseDefault, KeepPrev, ставим им значения по умолчанию true и выводим их в окно свойств:

input bool                 UseGUI        =  true;
input bool                 UseDefault    =  true;
input bool                 KeepPrev      =  true;

Подключаем файл с графическим интерфейсом (там же, где подключаются файлы с классами индикаторов):

#include <UniChannel/UniChannelGUI.mqh>

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

// массив с типами центральной линии
ECType ctype[]={
   UniCh_C_AMA,
   UniCh_C_DEMA,
   UniCh_C_FrAMA,
   UniCh_C_MA,
   UniCh_C_TEMA,
   UniCh_C_VIDyA,
   UniCh_C_PrCh
};

// массив с типами границ
EWType wtype[]={
   UniCh_W_ATR,
   UniCh_W_StdDev,
   UniCh_W_Points,
   UniCh_W_Percents,
   UniCh_W_PrCh
};

Тут же добавим переменную-указатель на класс формы:

CUniChannelForm * frm;

В функции OnInit(), в самом конце, выполним создание объекта графического интерфейса:

if(UseGUI){
  
   // создание и инициализация объекта формы
   frm=new CUniChannelForm();
   frm.Init();
  
   // вспомогательные переменные
   int ind1=0;
   int ind2=0;
  
   // поиск выбранного типа центральной линии в массиве типов центральной линии
   for(int i=0;i<ArraySize(ctype);i++){        
      frm.m_c_cmb_main.AddItem(EnumToString(ctype[i]));
      if(ctype[i]==_CentralType){
         ind1=i;
      }
   }
  
   // поиск выбранного типа границ канала в массиве типов границ
   for(int i=0;i<ArraySize(wtype);i++){        
      frm.m_w_cmb_main.AddItem(EnumToString(wtype[i]));
      if(wtype[i]==_WidthType){
         ind2=i;
      }
   }      
  
   // отображение в списке выбранного типа центральной линии
   frm.m_c_cmb_main.SetSelectedIndex(ind1);      
   // подготовка соответствующих типу элементов управления
   frm.SetCentralType(_CentralType);
  
   // отображение в списке выбранного типа границ
   frm.m_w_cmb_main.SetSelectedIndex(ind2);      
   frm.SetWidthType(_WidthType);      
  
   // установка значений
   frm.SetValues(
                  _c_Period1,
                  _c_Period2,
                  _c_Period3,
                  _c_Method,
                  _c_Price,
                  _c_Shift,
                  _w_Period,
                  _w_Width,
                  _w_Method,
                  _w_Price,
                  _w_LockPeriod,
                  _w_Shift
   );
  
   // установка свойств формы
   frm.SetSubWindow(0);
   frm.SetPos(10,30);
   // отображение формы
   frm.Show();
}  

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


Рис. 1. Форма с элементами управления универсального канала

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

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // события формы
   if(frm.Event(id,lparam,dparam,sparam)==1){
      EventForm();
   }
  
   // выбор типа центральной линии
   if(frm.m_c_cmb_main.Event(id,lparam,dparam,sparam)==1){
      EventCentralTypeChange();
   }  
  
   // выбор типа границ
   if(frm.m_w_cmb_main.Event(id,lparam,dparam,sparam)==1){
      EventWidthTypeChange();
   }  
  
   // изменение параметров центральной линии
   if(frm.m_central_controls.Event(id,lparam,dparam,sparam)==1){
      EventCentralParametersChange();
   }  
  
   // изменение параметров границ
   if(frm.m_width_controls.Event(id,lparam,dparam,sparam)==1){
      EventWidthParametersChange();

   }  

   // изменение параметров смещения
   if(frm.m_c_shift.Event(id,lparam,dparam,sparam)!=0 ||
      frm.m_w_shift.Event(id,lparam,dparam,sparam)
   ){
      EventShift();
   }    
}

Рассмотрим все эти функции. Функция EventForm():

void EventForm(){      
   int win=ChartWindowFind(0,ShortName);  // определение подокна индикатора  
   ChartIndicatorDelete(0,win,ShortName); // удаление индикатора
   ChartRedraw();
}  

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

Функция EventCentralTypeChange():

void EventCentralTypeChange(){    
   // получение нового типа в переменную
   _CentralType=ctype[frm.m_c_cmb_main.SelectedIndex()];
  
   // удаление старого объекта и создание нового
   delete(central);
   LoadCentral(true);
  
   // проверка, как загрузился индикатор
   if(!central.CheckHandle()){
      Alert("Ошибка загрузки индикатора "+central.Name());
   }

   // установка смещений и названий буферов
   SetStyles();

   // установка в списке нового типа
   frm.SetCentralType(ctype[frm.m_c_cmb_main.SelectedIndex()]);
   // обновление значений параметров на форме
   frm.SetValues(
                  _c_Period1,
                  _c_Period2,
                  _c_Period3,
                  _c_Method,
                  _c_Price,
                  _c_Shift,
                  _w_Period,
                  _w_Width,
                  _w_Method,
                  _w_Price,
                  _w_LockPeriod,
                  _w_Shift
   );
   // обновление формы
   frm.Refresh();
  
   // запуск таймера для пересчета индикаторов
   EventSetMillisecondTimer(100);
}

В этой функции выполняется смена типа индикатора центральной линии. Сначала выполняется получение типа выбранного индикатора, удаление старого объекта, создание нового. При создании нового объекта некоторые его параметры могут измениться (из-за функции UseDefault), поэтому вызывается метод SetValues() для установки новых значений элементам управления, отображение формы обновляется (метод Refresh()). В конце запускается таймер для выполнения пересчета индикатора.      

Функция EventWidthTypeChange() аналогична функции EventCentralTypeChange(), не будем рассматривать ее подробно. 

В функциях EventCentralParametersChange() и EventWidthParametersChange() обеспечивается реакция индикаторов на изменения значений параметров. Эти две функции по своей основной функциональности идентичны друг другу. Однако изменение параметров требует уделить внимание блокировке периодов, выполнить коррекцию параметров в соответствии с ней, поэтому функции имеют свои уникальные различия и будут рассмотрены обе.

void EventCentralParametersChange(){          
  
   // переменная, указывающая на необходимость перезагрузить индикатор границ
   bool dolock=false;
  
   // изменение значения периода 1
   if((int)frm.m_c_value1.Value()>0){
      // присвоение переменной значения, полученного от элемента управления
      _c_Period1=(int)frm.m_c_value1.Value();
      // если период 1 связан с периодом индикатора ширины
      if(_w_LockPeriod==LockTo_Period1){
         // переменной с периодом индикатора ширины, присваиваем значение периода 1
         _w_Period=_c_Period1;
         // отображаем его на форме
         frm.m_w_value1.SetValue(_w_Period);
         // указываем, что второй индикатор надо перезагрузить
         dolock=true;
      }
   }
  
   // изменение значения периода 2 аналогично изменению периода 1
   if((int)frm.m_c_value2.Value()>0){
      _c_Period2=(int)frm.m_c_value2.Value();
      if(_w_LockPeriod==LockTo_Period2){
         _w_Period=_c_Period2;
         frm.m_w_value1.SetValue(_w_Period);
         dolock=true;
      }        
   }
  
   // изменение значения периода 3 аналогично изменению периода 1
   if((int)frm.m_c_value3.Value()>0){
      _c_Period3=(int)frm.m_c_value3.Value();
      if(_w_LockPeriod==LockTo_Period3){
         _w_Period=_c_Period3;
         frm.m_w_value1.SetValue(_w_Period);
         dolock=true;
      }        
   }
  
   // изменения метода
   if(frm.m_c_method.SelectedIndex()!=-1){
      _c_Method=e_method[frm.m_c_method.SelectedIndex()];
   }
  
   // изменение цены
   if(frm.m_c_price.SelectedIndex()!=-1){
      _c_Price=e_price[frm.m_c_price.SelectedIndex()];
   }
  
   // удаление старого объекта и создание нового
   delete(central);
   LoadCentral(false);
   if(!central.CheckHandle()){
      Alert("Ошибка загрузки индикатора "+central.Name());
   }  

   // удаление и создание нового объекта второго индикатора
   if(dolock){
      delete(width);
      LoadWidth(false);
      if(!width.CheckHandle()){
         Alert("Ошибка загрузки индикатора "+width.Name());
      }  
   }  

   // установка смещений и названий буферов
   SetStyles();

   // запуск таймера для пересчета индикатора
   EventSetMillisecondTimer(100);
}  

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

void EventWidthParametersChange(){  
      
   // переменная, указывающая на необходимость перезагрузить индикатор центральной линии
   bool dolock=false;

   // изменение периода
   if((int)frm.m_w_value1.Value()>0){
      // присвоение переменной значения от элемента управления
      _w_Period=(int)frm.m_w_value1.Value();
      // выполнение блокировки
      // параметр ширины связан с первым периодом центральной линии
      if(_w_LockPeriod==LockTo_Period1){
         // присвоение нового значения переменной индикатора центральной линии
         _c_Period1=_w_Period;
         // обновление значения на форме
         frm.m_c_value1.SetValue(_c_Period1);
         // указываем о необходимости перезагрузить индикатор ширины
         dolock=true;
      }
      else if(_w_LockPeriod==LockTo_Period2){ // если блокировка с периодом 2
         _c_Period2=_w_Period;
         frm.m_c_value2.SetValue(_c_Period2);
         dolock=true;
      }
      else if(_w_LockPeriod==LockTo_Period3){ // если блокировка с периодом 3
         _c_Period3=_w_Period;
         frm.m_c_value3.SetValue(_c_Period3);
         dolock=true;
      }
   }
  
   // изменение параметра ширины канала
   if((double)frm.m_w_value2.Value()>0){
      _w_Width=(double)frm.m_w_value2.Value();
   }      
  
   // изменение метода
   if(frm.m_w_method.SelectedIndex()!=-1){
      _w_Method=e_method[frm.m_w_method.SelectedIndex()];
   }
  
   // изменение цены
   if(frm.m_w_price.SelectedIndex()!=-1){
      _w_Price=e_price[frm.m_w_price.SelectedIndex()];
   }
  
   // событие изменения в списке выбора типа блокировки периодов
   if(frm.m_w_lockto.SelectedIndex()>=0){
      // присвоение переменной значения из элемента управления
      _w_LockPeriod=e_lockto[frm.m_w_lockto.SelectedIndex()];
      // если выбрана блокировка с каким-то из периодов,
      // то копируется его значение и обновляется на форме  
      if(_w_LockPeriod==LockTo_Period1){
         _w_Period=_c_Period1;
         frm.m_w_value1.SetValue(_w_Period);
      }
      else if(_w_LockPeriod==LockTo_Period2){
         _w_Period=_c_Period2;
         frm.m_w_value1.SetValue(_w_Period);
      }
      else if(_w_LockPeriod==LockTo_Period3){
         _w_Period=_c_Period3;
         frm.m_w_value1.SetValue(_w_Period);
      }
   }      

   // удаление старого объекта и создание нового
   delete(width);
   LoadWidth(false);
   if(!width.CheckHandle()){
      Alert("Ошибка загрузки индикатора "+width.Name());
   }
  
   // удаление и создание нового объекта второго индикатора
   if(dolock){
      delete(central);
      LoadCentral(false);
      if(!central.CheckHandle()){
         Alert("Ошибка загрузки индикатора "+central.Name());
      }
   }

   // установка смещений и названий буферов
   SetStyles();

   // запуск таймера для пересчета индикатора
   EventSetMillisecondTimer(100);      
}  

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

Обработка события изменения значений смещения довольно простая:

void EventShift(){     
   // получение в переменных новых значений 
   _c_Shift=(int)frm.m_c_shift.Value();
   _w_Shift=(int)frm.m_w_shift.Value();
   // установка новых стилей
   SetStyles();
   // обновление графика
   ChartRedraw();
}

Переменным присваиваются значения из элементов управления, вызывается функция SetStyles() и обновляется график.

На этом индикатор с графическим интерфейсом практически готов.

В процессе тестирования индикатора у него был выявлен один недостаток. Когда внешний параметр UseDefault включен и используется блокировка периода, то блокировка не действовала. Это связано с тем, что при загрузке второго индикатора (индикатора ширины) в его конструкторе выполняется изменение параметров. Для исправления этой ошибки пришлось немного доработать некоторые дочерние классы индикаторов ширины. В конструкторы классов CChannelUni_Calculate_ATR, CChannelUni_Calculate_StdDev и CChannelUni_Calculate_PriceChannel добавлен необязательный параметр locked со значением по умолчанию false (если параметр не передается в класс, то все работает без изменения). При установке locked=true и use_default=true параметры периода в конструкторе не изменяются (при условии locked=true). Рассмотрим фрагмент класса CChannelUni_Calculate_ATR:

if(use_default){
   if(keep_previous){
      if(ma_period==-1 && !locked)ma_period=14// изменение
      if(ch_width==-1)ch_width=2;
   }
   else{
      if(!locked)ma_period=14// изменение
      ch_width=2;
   }      
}

Переменной ma_period присваивается значение по умолчанию, только если переменная locked равна false. Соответственно выполнена доработка функции LoadWidth(). В начале функции вычисляется значение Locked:

bool Locked=(w_LockPeriod!=LockTo_Off);

Затем эта переменная передается в конструкторы классов при создании объектов.

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

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

void SaveOrDeleteParameters(const int reason){
   // если это не смена графика, то удаляем графические объекты 
   if(reason!=REASON_CHARTCHANGE){
      ObjectDelete(0,"_CentralType");
      ObjectDelete(0,"_c_Period1");
      ObjectDelete(0,"_c_Period2");
      ObjectDelete(0,"_c_Period3");
      ObjectDelete(0,"_c_Shift");
      ObjectDelete(0,"_c_Method");
      ObjectDelete(0,"_c_Price");
      ObjectDelete(0,"_WidthType");
      ObjectDelete(0,"_w_Period");
      ObjectDelete(0,"_w_LockPeriod");
      ObjectDelete(0,"_w_Shift");
      ObjectDelete(0,"_w_Method");
      ObjectDelete(0,"_w_Price");
      ObjectDelete(0,"_w_Width");      
   }
   else// при смене графика создаем графические объекты со значениями параметров
      SaveParameter("_CentralType",(string)_CentralType);
      SaveParameter("_c_Period1",(string)_c_Period1);
      SaveParameter("_c_Period2",(string)_c_Period2);
      SaveParameter("_c_Period3",(string)_c_Period3);
      SaveParameter("_c_Shift",(string)_c_Shift);
      SaveParameter("_c_Method",(string)_c_Method);
      SaveParameter("_c_Price",(string)_c_Price);
      SaveParameter("_WidthType",(string)_WidthType);
      SaveParameter("_w_Period",(string)_w_Period);
      SaveParameter("_w_LockPeriod",(string)_w_LockPeriod);
      SaveParameter("_w_Shift",(string)_w_Shift);
      SaveParameter("_w_Method",(string)_w_Method);
      SaveParameter("_w_Price",(string)_w_Price);
      SaveParameter("_w_Width",(string)_w_Width);        
   }
}

// вспомогательная функция для сохранения одного параметра в графическом объекте
void SaveParameter(string name,string value){
   if(ObjectFind(0,name)==-1){
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,0);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,-30);
   }
   ObjectSetString(0,name,OBJPROP_TEXT,value);
}

В функции OnInit(), сразу после вызова функции PrepareParameters(), вызовем функцию LoadSavedParameters():

bool LoadSavedParameters(){
   // если существуют все объекты с параметрами 
   if(ObjectFind(0,"_CentralType")==0 &&
      ObjectFind(0,"_c_Period1")==0 &&
      ObjectFind(0,"_c_Period2")==0 &&
      ObjectFind(0,"_c_Period3")==0 &&
      ObjectFind(0,"_c_Shift")==0 &&
      ObjectFind(0,"_c_Method")==0 &&
      ObjectFind(0,"_c_Price")==0 &&
      ObjectFind(0,"_WidthType")==0 &&
      ObjectFind(0,"_w_Period")==0 &&
      ObjectFind(0,"_w_LockPeriod")==0 &&
      ObjectFind(0,"_w_Shift")==0 &&
      ObjectFind(0,"_w_Method")==0 &&
      ObjectFind(0,"_w_Price")==0 &&
      ObjectFind(0,"_w_Width")==0
   ){
      // получение значений из графических объектов
      _CentralType=(ECType)ObjectGetString(0,"_CentralType",OBJPROP_TEXT);
      _c_Period1=(int)ObjectGetString(0,"_c_Period1",OBJPROP_TEXT);
      _c_Period2=(int)ObjectGetString(0,"_c_Period2",OBJPROP_TEXT);
      _c_Period3=(int)ObjectGetString(0,"_c_Period3",OBJPROP_TEXT);
      _c_Shift=(int)ObjectGetString(0,"_c_Shift",OBJPROP_TEXT);
      _c_Method=(long)ObjectGetString(0,"_c_Method",OBJPROP_TEXT);
      _c_Price=(long)ObjectGetString(0,"_c_Price",OBJPROP_TEXT);
      _WidthType=(EWType)ObjectGetString(0,"_WidthType",OBJPROP_TEXT);
      _w_Period=(int)ObjectGetString(0,"_w_Period",OBJPROP_TEXT);
      _w_LockPeriod=(long)ObjectGetString(0,"_w_LockPeriod",OBJPROP_TEXT);
      _w_Shift=(int)ObjectGetString(0,"_w_Shift",OBJPROP_TEXT);
      _w_Method=(long)ObjectGetString(0,"_w_Method",OBJPROP_TEXT);
      _w_Price=(long)ObjectGetString(0,"_w_Price",OBJPROP_TEXT);
      _w_Width=(double)ObjectGetString(0,"_w_Width",OBJPROP_TEXT);
      return(true);
   }
   else{
      return(false);
   }
}

В функции проверяется, существуют ли эти объекты, и если существуют, то используются их значения, при этом функция вернет true. Если функция вернула true, то функции LoadCentral() и LoadWidth() вызовем с параметром false (чтобы не были установлены параметры по умолчанию). Фрагмент функции OnInit():

bool ChartCange=LoadSavedParameters();
  
LoadCentral(!ChartCange);

Таким же образом вызывается функция LoadWidth():

LoadWidth(!ChartCange);

На этом создание универсального канала полностью завершено. 

Заключение

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

Приложение

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

 

Прикрепленные файлы |
files.zip (95.81 KB)
Графические интерфейсы X: Элемент "Многострочное текстовое поле ввода"  (build 8) Графические интерфейсы X: Элемент "Многострочное текстовое поле ввода" (build 8)
Рассматривается элемент "Многострочное поле ввода". В отличие от графического объекта типа OBJ_EDIT, в представленной версии не будет ограничений на количество вводимых символов. Кроме этого, становится доступен режим, когда поле ввода превращается в простой текстовый редактор, где курсор можно перемещать мышью или клавишами.
ZUP - зигзаг универсальный с паттернами Песавенто. Графический интерфейс ZUP - зигзаг универсальный с паттернами Песавенто. Графический интерфейс
За 10 лет, прошедших с момента выхода первой версии платформы ZUP, произошло множество изменений и улучшений. В результате получилась уникальная графическая надстройка к MetaTrader 4, позволяющая быстро и комфортно проводить анализ рыночной информации. В статье рассказывается как работать с графическим интерфейсом индикаторной платформы ZUP.
Визуализируй это! Графическая библиотека в MQL5 как аналог plot из R Визуализируй это! Графическая библиотека в MQL5 как аналог plot из R
При исследовании и изучении закономерностей важную роль играет визуальное отображение с помощью графиков. В популярных среди научного сообщества языках программирования, таких как R и Python, для визуализации предназначена специальная функция plot. С её помощью можно рисовать линии, точечные распределения и гистограммы для наглядного представления закономерностей. В MQL5 вы можете делать всё то же самое с помощью класса CGraphics.
Как построить и протестировать стратегию бинарных опционов в Тестере Стратегий MetaTrader 4 Как построить и протестировать стратегию бинарных опционов в Тестере Стратегий MetaTrader 4
Руководство по построению стратегии бинарных опционов и ее тестированию в Тестере Стратегий MetaTrader 4 с использованием утилиты Binary-Options-Strategy-Tester из Маркета на MQL5.com.