English Русский 中文 Deutsch 日本語 Português
Canal universal con interfaz gráfica

Canal universal con interfaz gráfica

MetaTrader 5Ejemplos | 24 febrero 2017, 09:02
1 421 0
Dmitry Fedoseev
Dmitry Fedoseev

Contenido

Introducción

Ya se escribió en su momento un artículo sobre la creación de un oscilador universal con interfaz gráfica. Como resultado, obtuvimos un indicador muy interesante, cómodo y útil, que simplificaba y aceleraba significativamente el análisis de gráficos. Aparte de los osciladores, existen otros tipos de indicadores de análisis técnico que despiertan en nosotros un interés semejante al de los osciladores. Se trata de los indicadores de tendencia, los indicadores de volatilidad, volúmenes y otros que, a su vez, también pueden dividirse en diferentes categorías. En este artículo analizaremos la creación de un indicador de canal universal.

El artículo del oscilador universal resultó bastante complejo, y se mostró conveniente para los programadores experimentados más que para los principiantes. Puesto que el tema de este artículo se aproxima en cierto grado al de la creación de un oscilador universal, para no hablar por duplicado sobre cuestiones generales ya analizadas, crearemos el canal universal basándonos en el oscilador universal. De esta forma, los programadores principantes también podrán crear sus indicadores universales realizando ciertas mejoras, sin sumergirse del todo en los sutiles matices que abarca la creación de indicadores universales con interfaz gráfica.

A pesar de las semejanzas con el oscilador universal, también nos encontraremos con diferencias significativas. Todos los indicadores de canales están constituidos por tres líneas: una central, una superior y otra inferior. Según su principio de construcción la línea central es idéntica a una media móvil, y en la mayoría de los casos para construir el canal se usa precisamente una media móvil. Las líneas superior e inferior se ubican a la misma distancia de la central. Esta distancia se puede definir simplemente en puntos, en tanto por ciento del precio (indicador Envelopes), se pueden usar valores de desviación estándar (franjas de Bollinger) o los valores del indicador ATR (canal de Keltner). Esto significa que el indicador inicial se construirá usando dos bloques independientes:

  1. El bloque de cálculo de la línea central
  2. El bloque de definición de la amplitud del canal (o de construcción de los límites)

Existen canales de un tipo un poco distinto, en concreto, el canal de Donchian (canal de precio). Su creación comienza normalmente con la construcción de las líneas de los límites (diapasón de precio), y ya después de esto se calcula el valor de la línea central (en la mitad del diapasón). Pero este canal se puede construir según el sistema expuesto más arriba: primero se construye la línea central, definida como la mitad del diapasón de precio, y a continuación se acaban de construir los límites a una distancia igual a la mitad del diapasón de precio. Por supuesto, para ello necesitaremos más de lo requerido durante la construcción simple. Puesto que el principal objetivo de este artículo es crear un indicador universal, podemos tolerar ciertas excepciones, tanto más, cuanto este enfoque aumenta el número de combinaciones posibles de la línea central y los límites. Por ejemplo, podremos obtener un indicador con una línea central como la del diapasón de precio, pero cuyos límites estén ubicados a la distancia de la desviación estándar, como sucede con las frnajas de Bollinger, etcétera. 

Tipos de línea central

Para la línea central se usarán diferentes medias móviles. Vamos a ver qué tipos hay y cuántos parámetros tienen. Todas las variantes de la línea central que serán usadas en el indicador se muestran en el recuadro 1.

Recuadro 1. Tipos de línea central

Estándar
función
Nombre Parámetros
iAMA Adaptive Moving Average 1 int ama_period — periodo AMA 
2. int fast_ma_period — periodo de la media rápida
3. int slow_ma_period — periodo de la media lenta
4. int ama_shift — desplazamiento del indicador en horizontal
5. ENUM_APPLIED_PRICE  applied_price — tipo de precio o handle 
iDEMA Double Exponential Moving Average 1 int ma_period — periodo de promediación
2. int ma_shift — desplazamiento del indicador en horizontal
3. ENUM_APPLIED_PRICE  applied_price — tipo de precio
iFrAMA Fractal Adaptive Moving Average 1 int ma_period — periodo de promediación
2. int ma_shift — desplazamiento del indicador en horizontal
3. ENUM_APPLIED_PRICE  applied_price — tipo de precio
iMA Moving Average 1 int ma_period — periodo de promediación
2. int ma_shift — desplazamiento del indicador en horizontal  
3. ENUM_MA_METHOD ma_method — tipo de suavizado
4. ENUM_APPLIED_PRICE applied_price — tipo de precio
iTEMA Triple Exponential Moving Average 1 int ma_period — periodo de promediación
2. int ma_shift — desplazamiento del indicador en horizontal  
3. ENUM_APPLIED_PRICE  applied_price — tipo de precio
iVIDyA Variable Index Dynamic Average 1 int cmo_period — periodo Chande Momentum 
2. int ema_period — periodo del factor de suavizado  
3. int ma_shift — desplazamiento del indicador en horizontal 
4. ENUM_APPLIED_PRICE  applied_price — tipo de precio
- línea central del canal de precio 1 int period

Basándonos en el análisis de la columna "Parámetros" del recuadro 1, obtenemos el conjunto mínimo de parámetros necesarios (recuadro 2).

Recuadro 2. Conjunto universal de parámetros para calcular la línea central del canal 

Tipo Nombre
int period1
int period2
int period3
int shift
ENUM_MA_METHOD ma_method 
ENUM_APPLIED_PRICE  price
Los parámetros de la línea central en la ventana de propiedades del indicador tendrán los prefijos "c_". 

Tipos de límites

Asimismo, al igual que sucede con la línea central, aclararemos las variantes de cálculo de los límites del canal (recuadro 3).

Recuadro 3. Variantes de cálculo de la amplitud del canal 

Estándar
función
Nombre  Parámetros
iATR Average True Range 1 int ma_period — periodo de promediación
iStdDev  Standard Deviation 1 int ma_period — periodo de promediación
2. int ma_shift — desplazamiento del indicador en horizontal
3. ENUM_MA_METHOD — tipo de suavizado
4. ENUM_APPLIED_PRICE applied_price — tipo de precio 
en puntos  int width — amplitud en puntos 
en tanto por ciento (como en Envelopes)  double width — amplitud en tanto por ciento del precio   
como en el canal de precio  double width — coeficiente de escalado con respecto a la amplitud real del canal de precio

Basándonos en la columna "Parámetros" del recuadro 3, obtenemos el conjunto de parámetros necesario (recuadro 4).

Recuadro 4. Conjunto universal de parámetros para calcular la amplitud del canal

Tipo Nombre
int period
int  shift 
ENUM_MA_METHOD  ma_method  
ENUM_APPLIED_PRICE  price
double  width 

Al calcular en puntos, es necesaria la variable int, pero no está en el recuadro 4, ya que en su lugar se puede usar una variable del tipo double. De esta forma, se acorta el número total de variables en la ventana de propiedades.

Los parámetros de cálculo de los límites en la ventana de propiedades del indicador tendrán los prefijos "w_". 

Clases de la línea central

La clase básica de la línea central es idéntica en cuanto a su principio y conjunto de métodos a la clase CUniOsc del artículo sobre el oscilador universal con interfaz gráfica, por eso la tomaremos como base y la modificaremos un poco.

En la carpeta MQL5/Include crearemos la carpeta UniChannel, copiaremos en ella el archivo CUniOsc.mqh (de la carpeta Include/UniOsc) y la renombraremos como CUniChannel.mqh. Dejaremos en el archivo la clase básica (COscUni), la clase derivada Calculate1 (nombre completo COscUni_Calculate1) y su clase derivada COscUni_ATR, el resto de las clases las eliminaremos.

Renombramos las clases: sustituimos el fragmento "COscUni" por "CChannelUni". En la sustitución es cómodo usar la función del editor (Menú principal — Editar — Buscar y reemplazar — Reemplazar), pero no utilizando el botón "Reemplazar todo", sino reemplazando uno a uno, para controlar el proceso y estar seguros de que todos los reemplazos se realizan en el lugar necesario.

La clase de la línea central siempre dibuja una línea continua, por eso muchos métodos de la clase básica no son necesarios. Después de eliminar los métodos innecesarios, solo queda la siguiente clase básica:

class CChannelUniWidth{
   protected:
      int m_handle;      // manejador del indicador
      string m_name;     // nombre del indicador
      string m_label1;   // nombre del búfer 1      
      string m_help;     // pequeña guía de los parámetros del indicador
      double m_width;    // amplitud del canal
   public:
  
      // constructor
      void CChannelUniWidth(){
         m_handle=INVALID_HANDLE;
      }
      
      // destructor
      void ~CChannelUniWidth(){
         if(m_handle!=INVALID_HANDLE){
            IndicatorRelease(m_handle);
         }
      }
  
      // método principal, llamado desde la función OnCalculate() del indicador
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & bufferCentral[],
                     double & bufferUpper[],
                     double & bufferLower[],
      ){
         return(rates_total);
      }
      
      // obteniendo el manejador del indicador cargado
      int Handle(){
         return(m_handle);
      }
      
      // método de comprobación del manejador, para saber si se ha cargado el indicador  
      bool CheckHandle(){
         return(m_handle!=INVALID_HANDLE);
      }
      
      // obteniendo el nombre del indicador
      string Name(){
         return(m_name);
      }    

      // obteniendo el texto del rótulo para los búferes
      string Label1(){
         return(m_label1);
      }
      
      // obteniendo las sugerencias de los parámetros
      string Help(){
         return(m_help);
      }
};

Desde la clase Calculate se puede eliminar todo lo que respecta al segundo búfer, como resultado, en la clase se quedará solo el método Calculate:

class CChannelUni_Calculate1:public CChannelUni{
   public:
      // método principal, llamado desde la función OnCalculate() del indicador
      // los primeros dos parámetros son análogos a los dos primeros parámetros
      // de la función OnCalculate() del indicador
      // el tercer parámetro es el búfer de indicador para la línea central
      int Calculate( const int rates_total,    
                     const int prev_calculated,
                     double & buffer0[]
      ){
        
         // definiendo el número de elementos a copiar
        
         int cnt;
        
         if(prev_calculated==0){
            cnt=rates_total;
         }
         else{
            cnt=rates_total-prev_calculated+1;
         }  
        
         // copiando los datos al búfer de indicador
         if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
            return(0);
         }
        
         return(rates_total);
      }
};

Escribiremos la clase derivada que usa el indicador iMA. La clase en el archivo CChannelUni_ATR la renombramos a CChannelUni_MA, sustituimos en ella el indicador llamado y eliminamos los elementos sobrantes. Como resultado, obtenemos la siguiente clase:

class CChannelUni_MA:public CChannelUni_Calculate1{
   public:
   // constructor
   // los dos primeros parámetros, idénticos para todas las clases derivadas
   // a continuación, los parámetros del indicador cargado
   void CChannelUni_MA( bool use_default,
                        bool keep_previous,
                        int & ma_period,
                        int & ma_shift,
                        long & ma_method,
                        long & ma_price){
      if(use_default){ // se ha elegido el uso de valores por defecto
         if(keep_previous){
            // no cambiar los parámetros usados con anterioridad
            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;            
         }      
      }    
      
      // carga del indicador
      m_handle=iMA(Symbol(),Period(),ma_period,ma_shift,(ENUM_MA_METHOD)ma_method,(ENUM_APPLIED_PRICE)ma_price);
      
      // formando la línea con el nombre del indicador
      m_name=StringFormat( "iMA(%i,%i,%s,%s)",
                           ma_period,
                           ma_shift,        
                           EnumToString((ENUM_MA_METHOD)ma_method),              
                           EnumToString((ENUM_APPLIED_PRICE)ma_price)
                        );

      
      // línea para el nombre del búfer
      m_label1=m_name;

      // sugerencia sobre los parámetros
      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)
                           );
   }
};

Analicemos en mayor profundidad la formación de las líneas en las variables m_name y mlabel1. El nombre del indicador (variable m_name) de los indcadores ubicados en la subventana se hace visible en la esquina superior izquierda de la subventana. Puesto que el canal se representará en el gráfico de precios, su nombre será visible, por eso asignaremos a la variable m_label exactamente el mismo nombre que a la variable m_name, para que al colocar el cursor sobre el canal central, en la sugerencia emergente se vean todos sus parámetros. 

Asimismo, como ocurre con la clase para el indicador iMA, se crean también las clases para todos los demás indicadores estándar. La excepción será el canal de precio. Puesto que el canal de precio no está presente entre los indicadores estándar del terminal, es imprescindible calcularlo. Aquí tenemos dos opciones:

  1. Crear una clase derivada del tipo Calculate y realizar los cálculos en ella
  2. Escribir un indicador adicional y llamarlo a través de la función iCustom
Ambas opciones tienen su razón de ser. En el primer caso, se reduce el número de archivos de los que depende el indicador creado en el artículo, pero será necesario ejecutar de nuevo los mismos cálculos (primero definir los límites del canal para el cálculo de la línea media, después la definición de los límites del canal para determinar la amplitud del mismo). En el segundo caso, no habrá duplicación de cálculos, además, obtenemos un indicador adicional, independiente y completo, que podremos usar por nostros mismos.      

En los anexos al artículo se encuentra el archivo CUniChannel.mqh con las clases derivadas para todos los demás indicadores y el indicador iPriceChannel. En el indicador iPriceChannel los datos de la línea central se ubican en el búfer 0. Si alguien va a trabajar de forma adicional con la clase para algún otro indicador, cuyos datos necesarios no se ubiquen el búfer 0, entonces tendrá que crear otra clase derivada Calculate, o crear en la clase básica una variable para el índice del búfer y establecer en el constructor de la clase derivada el valor que necesite.   

Clases de cálculo de la amplitud y construcción del canal

Como base de la clase básica tomaremos de nuevo CUniChannel. Al método Calculate de la clase se le transmitirá el búfer de indicador con los valores de la línea central, dos búferes para los límites del canal, que se rellenarán con los valores calculados en el método. Aquí, a diferencia de CUniChannel, para cada variante del cálculo de los límites existirán sus propias clases derivadas Calculate, que cargarán los indicadores, y en las que se formarán los nombres del indicador y de los búferes. También será necesario perfeccionar la clase básica: añadir la variable para la amplitud del canal, el valor de la variable se establecerá a través del constructor de la clase básica.

Guardamos el archivo CUniChannel.mqh con el nombre CUniChannelWidth.mqh e introducimos los cambios en él. Primero eliminamos todas las clases derivadas, dejando solo la clase básica Calculate. Renombramos la clase de CChannelUni a CChannelUniWidth (no olvidemos el constructor, el destructor y el nombre de la clase padre de la clase derivada, que también debemos modificar). Obtenemos la clase siguiente:

class CChannelUniWidth{
   protected:
      int m_handle;           // manejador del indicador
      string m_name;          // nombre del indicador
      string m_label1;        // nombre del búfer 1      
      string m_help;          // pequeña guía de los parámetros del indicador
      double m_width;         // amplitud del canal
   public:
  
      // constructor
      void CChannelUniWidth(){
         m_handle=INVALID_HANDLE;
      }
      
      // destructor
      void ~CChannelUniWidth(){
         if(m_handle!=INVALID_HANDLE){
            IndicatorRelease(m_handle);
         }
      }
  
      // método principal, llamado desde la función OnCalculate() del indicador
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & bufferCentral[],
                     double & bufferUpper[],
                     double & bufferLower[],
      ){
         return(rates_total);
      }
      
      // obteniendo el manejador del indicador cargado
      int Handle(){
         return(m_handle);
      }
      
      // método de comprobación del manejador, para saber si se ha cargado el indicador  
      bool CheckHandle(){
         return(m_handle!=INVALID_HANDLE);
      }
      
      // obteniendo el nombre del indicador
      string Name(){
         return(m_name);
      }    

      // obteniendo el texto del rótulo para los búferes
      string Label1(){
         return(m_label1);
      }
      
      // obteniendo las sugerencias de los parámetros
      string Help(){
         return(m_help);
      }
};

La clase CChannelUni_Calculate la renombramos a CChannelUni_Calculate_ATR y le añadimos un constructor. El constructor se puede tomar de la clase COscUni_ATR del oscilador universal, pero deberemos renombrarla y añadir el parámetro de amplitud. Necesitaremos otros cambios: es necesario añadir la formación de nombres del indicador y los búferes. Con respecto a la clase para el cálculo de los límites basada en el indicador ATR tendrá el aspecto siguiente:

class CChannelUni_Calculate_ATR:public CChannelUniWidth{
   public:
      // constructor
      // los primeros dos parámetros son estándar para todas las clases derivadas 
      // después vienen los parámetros del indicador cargado
      // el último parámetro es la amplitud del canal
      void CChannelUni_Calculate_ATR(bool use_default,
                                     bool keep_previous,
                                     int & ma_period,
                                     double & ch_width){
         if(use_default){ // se ha elegido el uso de valores por defecto
            if(keep_previous){ // no cambiar los parámetros usados con anterioridad
               if(ma_period==-1)ma_period=14;
               if(ch_width==-1)ch_width=2;
            }
            else{
               ma_period=14;
               ch_width=2;
            }      
         } 
         
         // guardando el parámetro de amplitud para usarlo en el método de cálculo  
         m_width=ch_width; 
         // carga del indicador
         m_handle=iATR(Symbol(),Period(),ma_period);
         // formando la línea con el nombre del indicador
         m_name=StringFormat("ATR(%i)",ma_period);
         // línea con el nombre del búfer
         m_label1=m_name;
         // sugerencia sobre los parámetros 
         m_help=StringFormat("ma_period - Period1(%i)",ma_period); // sugerencia   
      }   
      
      // método principal, llamado desde la función OnCalculate() del indicador
      // los primeros dos parámetros corresponden a los dos primeros parámetros
      // función OnCalculate()
      // a continuación, se transmiten los búferes de indicador 
      int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & bufferCentral[],
                     double & bufferUpper[],
                     double & bufferLower[],
      ){
      
         // definiendo el comienzo del cálculo
         
         int start;
         
         if(prev_calculated==0){
            start=0
         }
         else
            start=prev_calculated-1;
         }  

         // ciclo principal de cálculo y rellenado de búferes 

         for(int i=start;i<rates_total;i++){
            
            // obteniendo los datos del indicador para la barra calculada
            double tmp[1];
            if(CopyBuffer(m_handle,0,rates_total-i-1,1,tmp)<=0){
               return(0);
            }
            
            // multiplicamos por el parámetro de amplitud
            tmp[0]*=m_width;   

            // calculamos los valores del límite superior e inferior
            bufferUpper[i]=bufferCentral[i]+tmp[0];
            bufferLower[i]=bufferCentral[i]-tmp[0];

         }   
         
         return(rates_total);
      }
};

Preste atención a que el valor del indicador ATR se copia dentro del ciclo principal solo para una barra. Esta variante, por supuesto, es sustancialmente más lenta que el copiado de una serie de valores al búfer. Sin embargo, con este enfoque se ahorra un búfer completo, y la pérdida de velocidad podría corregirse fijando manualmente el indicador al gráfico. Pero un retraso de varias décimas de segundo no será relevante para el usuario. En el simulador, al inicio de la prueba en el gráfico hay una pequeña cantidad de barras, por eso la pérdida de tiempo en la copia de datos para cada barra por separado no será significativa.

Ciertas variantes de cálculo de la amplitud del canal no exigen del uso de indicadores adicionales, en particular, al usar una magnitud establecida en puntos o como en el indicador Envelope. En este caso, a la variable m_handle de la clase básica le asignamos el valor 0 (que se diferencia del valor INVALID_HANDLE).

En los anexos al artículo se encuentra completamente listo el archivo CUniChannelWidth.mqh con las clases derivadas para todas las variantes del cálculo del canal.   

Creando el indicador del canal universal

Ahora, podemos crear el indicador del canal universal ubicando las clases creadas más arriba, pero sin interfaz gráfica, por el momento.

En el editor creamos un nuevo indicador de usuario con el nombre iUniChannel. Al crear un indicador en el editor MQL, elegimos las funciones: OnCalculate(...,open,high,low,close), OnTimer, OnChartEvent, creamos tres búferes del tipo Line.

Para elegir el tipo de la línea central y el tipo del canal es necesario crear dos enumeraciones. Estas se ubicarán en el archivo UniChannelDefines.mqh. Creamos las enumeraciones de acuerdo con los recuadros 1 y 3:

// enumeración de los tipos de línea central
enum ECType{
   UniCh_C_AMA,
   UniCh_C_DEMA,
   UniCh_C_FrAMA,
   UniCh_C_MA,
   UniCh_C_TEMA,
   UniCh_C_VIDyA,
   UniCh_C_PrCh
};

// enumeración de los tipos de los límites
enum EWType{
   UniCh_W_ATR,
   UniCh_W_StdDev,
   UniCh_W_Points,
   UniCh_W_Percents,
   UniCh_W_PrCh
};

La enumeración de los tipos de línea central tiene el nombre ECType, y la enumeración de los tipos de la amplitud del canal tiene el nombre EWType. Conectamos al indicador el archivo con la enumeración y los dos archivos creados anteriormente con las clases:

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

Declaramos las dos variables externas para elegir los tipos de la línea central y la amplitud del canal y las variables para los parámetros de acuerdo con los recuadros 2 y 4:

// parámetros de la línea central
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;
// parámetros de los límites
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;

Declaramos dos variables, que por ahora serán internas, pero en la versión con la interfaz gráfica se mostrarán en la ventana de propiedades:

bool                 UseDefault  =  false;
bool                 KeepPrev    =  false;

La asignación de estas variables se describe en el artículo sobre el oscilador universal: con la variable UseDefault se activa un modo con el que cada indicador elegido de nuevo se carga con los parámetros por defecto, con la variable KeepPrev se activa el modo de guardado de los valores de los parámetros al cambiar los indicadores. En la versión del indicador sin interfaz gráfica, el indicador se carga con los parámetros de la ventana de propiedades, por eso el valor UseDefault es igual a false. Para la variable KeepPrev también se establece false, puesto que la interfaz gráfica por ahora está ausente y no hay ninguna conmuntación de los indicadores. 

Al inicializar el indicador es necesario preparar los parámetros. Asimismo, al igual que en el oscilador universal, ejecutamos la preparación de los parámetros en una función aparte con el nombre PrepareParameters(), pero primero hacemos una copia de todos los parámetros externos:

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;

A continuación, escribimos la función de preparación de los parámetros:

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;
   }
}

Noten que al cumplirse la condición  UseDefault && KeepPrev, a todas las variables se les asigna el valor -1, y a las variables Shift el valor 0, porque los valores de estas variables no se establecen desde los objetos de los indicadores, sino solo desde la interfaz de usuario (de la ventana de propiedades o de la interfaz gráfica).   

Después de preparar los parámetros se pueden crear los objetos para el cálculo de la línea central y del canal. En el oscilador universal para esto se tenía la función LoadOscillator(). Aquí tendremos dos funciones: LoadCentral() y LoadWidth(), pero primero declararemos las variables-puntero:

CChannelUni * central;
CChannelUniWidth * width;

En ciertos indicadores existe un parámetro de desplazamiento en horizontal (shift), pero en otros indicadores no existe, aunque se pueden desplazar todos los indicadores. Por eso, declararemos la variable adicional shift0 con el valor 0, y la transmitiremos a los constructores de las clases. El desplazamiento se ejecutará a cuenta del desplazamiento de los búferes de indicador.

Función LoadCentral():

void LoadCentral(){
   switch(_CentralType){ // dependiendo del tipo elegido, se crea la clase correspondiente
      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;
   }
}

En una de las variantes de cálculo de la amplitud del canal (clase CChannelUni_Calculate_InPoints) existe un parámetro que se mide en puntos, mientras que en la clase se prevé la adaptación del valor de este parámetro de acuerdo con el signo de cantidad tras la coma en las cotizaciones. Para que la función de adaptación funcione, al crear un objeto se debe transmitir al constructor de la clase el multiplicador del parámetro. En las cotizaciones con 2 y 4 dígitos el valor del multiplicador será igual a 1, y para las de 3 y 5 dígitos, igual 10. En los parámetros externos declaramos la variable Auto5Digits del tipo bool:

input bool                 Auto5Digits   =  true;

Si Auto5Digits es igual a true, se ejecutará la corrección del parámetro, si es false, el valor se usará como está. Un poco más abajo de Auto5Digits declaramos otra variable para el multiplicador:

int mult;

Al principio de la función OnInit() calculamos el valor mult:

   if(Auto5Digits && (Digits()==3 || Digits()==5)){
      mult=10; // multiplicaremos los parámetros medidos en puntos por 10
   }
   else{
      mult=1; // los parámetros que se midan en puntos se quedan sin cambios
   }

Ahora escribimos la función LoadWidth():

void LoadWidth(){
   switch(_WidthType){ // dependiendo del tipo elegido, se crea la clase correspondiente
      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;
   }
}

Después de crear cada uno de los objetos (de la línea central y la amplitud), comprobamos si se han creado con éxito. Si los objetos han sido creados, establecemos el nombre corto del indicador y a través de la función Print() mostramos la sugerencia de los parámetros: 

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

El ajuste de los rótulos y el desplazamiento para los búferes se ejecutarán en la función SetStyles():

void SetStyles(){

   // nombres de los búferes
   PlotIndexSetString(0,PLOT_LABEL,"Central: "+central.Label1());
   PlotIndexSetString(1,PLOT_LABEL,"Upper: "+width.Label1());  
   PlotIndexSetString(2,PLOT_LABEL,"Lower: "+width.Label1());  
  
   // desplazamiento de los búferes
   PlotIndexSetInteger(0,PLOT_SHIFT,_c_Shift);
   PlotIndexSetInteger(1,PLOT_SHIFT,_w_Shift);
   PlotIndexSetInteger(2,PLOT_SHIFT,_w_Shift);

}

En total, obtenemos la siguiente función OnInit():

int OnInit(){
  
   // preparando el multiplicador para la corrección del parámetro, medido en puntos
   if(Auto5Digits && (Digits()==3 || Digits()==5)){
      mult=10;
   }
   else{
      mult=1;
   }
  
   // preparando los parámetros
   PrepareParameters();
  
   // carga del indicador de la línea central
   LoadCentral();
  
   // comprobando si la carga de la línea central ha tenido éxito
   if(!central.CheckHandle()){
      Alert("Central line error "+central.Name());
      return(INIT_FAILED);
   }    
  
   // carga del indicador del cálculo de la amplitud
   LoadWidth();
  
   // comprobando si la carga del indicador de amplitud ha tenido éxito
   if(!width.CheckHandle()){
      Alert("Width error "+width.Name());
      return(INIT_FAILED);
   }      

   // mostrar las sugerencias de los parámetros
   Print("Central line parameters matching: "+central.Help());
   Print("Width parameters matching: "+width.Help());  
  
   // estableciendo el nombre
   ShortName="iUniChannel";  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);  
  
   // parte estándar de la función OnInit
   SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,Label2Buffer,INDICATOR_DATA);
   SetIndexBuffer(2,Label3Buffer,INDICATOR_DATA);
  
   // Estableciendo los rótulos y los desplazamientos de los búferes
   SetStyles();

   return(INIT_SUCCEEDED);
}

Una vez hecho esto, podemos considerar que el indicador sin interfaz gráfica está completamente listo. Con su ayuda se puede poner a prueba el funcionamiento de todas las clases y parámetros, y después de ello proceder a la creación de la interfzaz gráfica.

Durante la simulación del indicador se han detectado varias peculiaridades incómodas. Una de las características incómodas consiste en el control por separado del periodo de la línea central y del periodo de cálculo de la amplitud del canal. En sí, este tipo de control, por supuesto, amplía las posibilidades del indicador, pero en ciertos casos puede ser necesario el control simultáneo de ambos periodos con la ayuda de un solo parámetro. Vamos a mejorar un poco este elemento de forma que el periodo de la amplitud del canal sea igual a uno de los tres periodos de la línea central. La elección de una de las cuatro variantes se realizará a través de una enumeración (se ubica en el archivo UniChannelDefines.mqh):

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

Al elegir la variante LockTo_Off, los periodos se regulan por separado, y en el resto de los casos, el valor del parámetro w_Period es igual al periodo correspondiente de la línea central. Declaramos una variable del tipo ELockTo justo después de la variable  w_Period:

input ELockTo              w_LockPeriod  =  LockTo_Off;

Mejoramos la función PrepareParameters() añadiéndole al final del todo el siguiente código:

switch(w_LockPeriod){ // dependiendo del tipo de bloqueo
   case LockTo_Period1:
      _w_Period=_c_Period1;
   break;
   case LockTo_Period2:
      _w_Period=_c_Period2;      
   break;
   case LockTo_Period3:
      _w_Period=_c_Period3;      
   break;
}

Otra incomodidad consiste en que los mensajes informativos sobre la correspondencia de los parámetros se muestran en una línea en la pestaña "Expertos", y en las pantallas estrechas, parte de la línea se sale de los límites. Vamos a realizar unas modificaciones para mostrar la línea en forma de columna. En lugar de la función Print, utilizaremos nuestra propia función PrintColl(). A esta función se le transmiten dos parámetros: el encabezamiento y la línea con la sugerencia. En la función, la línea con la sugerencia se divide y se muestra por partes:

void PrintColl(string caption,string message){
   Print(caption); // muestra del encabezamiento
   string res[];
   // dividimos el mensaje
   int cnt=StringSplit(message,',',res);
   // mostrar el mensaje por partes
   for(int i=0;i<cnt;i++){
      StringTrimLeft(res[i]);
      Print(res[i]);
   }
}

Por consisguiente, en la función OnInit() se cambian dos líneas de muestra de sugerencias:

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

Ahora el indicador está completamente listo,  el nombre del archivo está en la aplicación — "iUniChanhel". Vamos a proceder a la creación de la interfaz.   

Creando las clases de la interfaz gráfica

La interfaz gráfica se creará usando como base la interfaz gráfica del oscilador universal. Copiamos el archivo UniOsc/UniOscGUI.mqh en la carpeta UniChannel y lo renombramos a UniChannelGUI.mqh. La interfaz gráfica del canal universal se diferenciará sustancialmente de la interfaz del oscilador universal, así que tendremos que trabajar bastante.

La principal diferencia reside en que en el canal universal se realiza la elección independiente de los dos indicadores (el de la línea central y el de los indicadores), por eso deberá haber dos listas principales de elección del tipo de indicador. Después de la primera lista, deberán ubicarse los elementos de control de los parámetros de la línea central, y a continuación ubicarse la segunda lista y los elementos de control de los parámetros de los límites. Eso significa que la segunda lista no tiene coordenadas fijas, estas deberán ser calculadas. Aparte de las dos listas para elegir los tipos, en el formulario siempre deberán existir dos campos para introducir los valores de desplazamiento, sus coordenadas tampoco son fijas. Otro momento al que hay que prestar atención es la lista para elegir la variante que corresponde al parámetro w_LockPeriod. En todos los casos en los que hay que representar el campo de edición del parámetro w_Period, en el grupo de elementos de control de los parámetros de amplitud es necesario representar una lista desplegable adicional.

Primero en el archivo UniChannelGUI.mqh ejecutamos los cambios generales:

1 La ruta al archivo con las enumeraciones:

#include <UniOsc/UniOscDefines.mqh>

deberemos cambiarla a la siguiente línea:

#include <UniChannel/UniChannelDefines.mqh>

2. Añadimos la matriz con los valores de la enumeración ELockTo:

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

3. Eliminamos las matrices con las enumeraciones ENUM_APPLIED_VOLUME y ENUM_STO_PRICE.

Ahora procedemos a la modificación de la clase  CUniOscControls.  

Clase de los elementos de control de la línea central

1 La clase CUniOscControls la renombramos a CUniChannelCentralControls.

2. En la clase eliminamos la declaración de las variables m_volume y m_sto_price. Por consiguiente, eliminamos todo lo relacionado con estos elementos de control de los métodos SetPointers(), Hide(), Events().

3. Añadimos la variable m_last_y, en ella se fijará la coordenada Y del último elemento de control del grupo. Añadimos el método para obtener el valor de esta variable — GetLastY(). El método FormHeight() se hace innecesario, así que lo eliminamos, pero en su lugar añadimos el método ControlsCount(), que retornará el número de elementos de control en la clase derivada. Esta cantidad será necesaria a la hora de calcular la altura del formulario.

Como resultado, obtenemos la siguiente clase padre:

class CUniChannelCentralControls{
   protected:
      CSpinInputBox * m_value1; // para el periodo 1
      CSpinInputBox * m_value2; // para el periodo 2
      CSpinInputBox * m_value3; // para el periodo 3
      CComBox * m_price;        // para el precio
      CComBox * m_method;       // para el método
      int m_last_y;             // posición Y del último elemento de control

   public:
  
   // obtener la posición Y del último elemento de control
   int GetLastY(){
      return(m_last_y);
   }
  
   // método para transmitir al objeto los punteros al objeto
   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);
   }
  
   // grupos ocultos de elementos de control
   void Hide(){
      m_value1.Hide();
      m_value2.Hide();
      m_value3.Hide();
      m_price.Hide();
      m_method.Hide();
   }
  
   // procesando los eventos
   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);
   }
  
   // método de inicialización de los elementos de control (para cambiar los rótulos)
   virtual void InitControls(){
   }  
  
   // representación del grupo de elementos de control
   virtual void Show(int x,int y){
   }  
  
   // obteniendo el número de elementos de control en el grupo
   virtual int ControlsCount(){
      return(0);
   }      
};

Cambiamos la clase derivada CUniOscControls_ATR:

1 La renombramos a CUniChannelCentralControls_AMA, esta será la clase para el indicador AMA.

2. De acuerdo con la columna "Parámetros" del recuadro 1, inicializamos los elementos de control en el método InitControls(), y en el método Show() llamamos los métodos Show() de todos los elementos de control. A la variable m_last_y le asignamos el valor del último elemento de control.

3. Eliminamos el método FormHeight(), en su lugar añadimos el método ControlsCount().

Obtenemos esta clase:

class CUniChannelCentralControls_AMA:public CUniChannelCentralControls{
   void InitControls(){
      // inicializando los elementos de control
      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");      
   }
  
   // representación
   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;
   }
  
   // obteniendo el número de elementos en el grupo
   int ControlsCount(){
      return(4);
   }
};

De forma análoga creamos las clases para todos los demás indicadores de la línea central y eliminamos todas las clases derivadas del oscilador.

Clases de elementos de control del cálculo de la amplitud

Basándonos en la clase CUniChannelCentralControls obtenida, creamos la clase para controlar los parámetros de la amplitud del canal. Hacemos una copia de la clase CUniChannelCentralControls, la renombramos a CUniChannelWidthControls. En esta clase serán necesarios dos campos de edición (periodo y amplitud), dos enumeraciones estándar para el tipo de promediación y el precio, así como la enumeración del parámetro w_LockPeriod. Como resultado de los cambios, obtenemos la clase siguiente:

class CUniChannelWidthControls{
   protected:
      CSpinInputBox * m_value1; // para el periodo
      CSpinInputBox * m_value2; // para la amplitud    
      CComBox * m_price;        // para el precio
      CComBox * m_method;       // para el método
      CComBox * m_lockto;       // para el tipo de bloqueo
      int m_last_y;             // posición Y del último elemento de control

   public:
  
   // obtener la posición Y del último elemento de control
   int GetLastY(){
      return(m_last_y);
   }
  
   // método para transmitir al objeto los punteros al objeto
   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);      
   }
  
   // grupos ocultos de elementos de control
   void Hide(){
      m_value1.Hide();
      m_value2.Hide();
      m_price.Hide();
      m_method.Hide();
   }
  
   // procesando los eventos
   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);
   }
  
   // método de inicialización de los elementos de control (para cambiar los rótulos)
   virtual void InitControls(){
   }  
  
   // representación del grupo de elementos de control
   virtual void Show(int x,int y){
   }  
  
   // obteniendo el número de elementos de control en el grupo
   virtual int ControlsCount(){
      return(0);
   }    
};

Creamos para ella las clases derivadas. La principipal diferencia de la clase de la línea central reside en que, tras el campo de edición del perido, es necesario crear una lista desplegable para el parámetro w_LockPeriod. Obtenemos esta clase para calcular la amplitud de ATR:

class CUniChannelWidthControls_ATR:public CUniChannelWidthControls{
   void InitControls(){
      // inicializando el elemento de control
      m_value1.Init("w_value1",SPIN_BOX_WIDTH,1," period");
   }
  
   // representando los grupos de elementos de control
   void Show(int x,int y){
      m_value1.Show(x,y);
      y+=20;
      m_lockto.Show(x,y);
      m_last_y=y;
   }  
  
   // obteniendo el número de elementos de control en el grupo
   int ControlsCount(){
      return(2);
   }    
};

De forma análoga han sido creadas para el resto de las variantes del cálculo de la amplitud del canal. 

Ahora en el archivo UniChannelGUI.mqh se encuentran dos clases básicas de elementos de control, multitud de sus clases derivadas y la clase del formulario. Con la última tendremos que trabajar un poco. Debido al considerable tamaño del archivo, el trabajo posterior con el mismo puede resultar incómodo, por eso introduciremos las clases de los elementos de control en otros archivos. Creamos el archivo UniChannel/CUniChannelCentralControls.mqh y trasladamos a él la clase CUniChannelCentralControls y todas sus clases derivadas e incluimos los archivos adicionales: 

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

La definición de las constantes FORM_WIDTH, SPIN_BOX_WIDTH, COMBO_BOX_WIDTH la trasladamos al archivo UniChannelDefines.mqh. Después de ello, el archivo CUniChannelCentralControls se puede compilar para comprobar los archivos. De la misma forma trasladaremos la clase CUniChannelWidthControls a un archivo aparte. Después de ello, será cómodo trabajar con la clase del formulario. 

Clase del formulario

Incluimos en el archivo UniChannelGUI.mqh los dos archivos recién creados:

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

Renombramos la clase CUniOscForm a CUniChannelForm. En la sección public eliminamos la variable-puntero del tipo CUniOscControls, en lugar de ella, declaramos otras dos variables-puntero: CUniChannelCentralControls y CUniChannelWidthControls, resolvemos lo concerniente a los otros elementos de control de la clase del formulario. En conclusión, en la sección public ubicamos las siguientes variables:

CComBox           m_c_cmb_main;  // lista de elección de la línea central
CSpinInputBox     m_c_value1;    // campo de edición del periodo 1
CSpinInputBox     m_c_value2;    // campo de edición del periodo 2
CSpinInputBox     m_c_value3;    // campo de edición del periodo 3
CComBox           m_c_price;     // lista de elección del precio
CComBox           m_c_method;    // lista de elección del método
CSpinInputBox     m_c_shift;     // campo de edición del desplazamiento

CComBox           m_w_cmb_main;  // lista de elección de los límites
CSpinInputBox     m_w_value1;    // campo de edición del periodo
CSpinInputBox     m_w_value2;    // campo edición de la amplitud  
CComBox           m_w_price;     // lista de elección del precio
CComBox           m_w_method;    // lista de elección del método
CComBox           m_w_lockto;    // lista de elección de la variedad de bloqueo    
CSpinInputBox     m_w_shift;     // campo de edición del desplazamiento          

// grupo de control de los elementos de la línea central
CUniChannelCentralControls * m_central_controls;
// grupo de control de los elementos de los límites
CUniChannelWidthControls * m_width_controls;  

En el método  MainProperties() cambiamos los valores de las variables m_Name y m_Caption, el resto de las variables permanecerán sin modificaciones:

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;
}

En el método OnInitEvent() llamamos los métodos Init() de todos los elementos de control de los que no se cambian los rótulos (el conjunto de elementos de control correspondiente al indicador elegido) y rellenamos las listas desplegables:

void OnInitEvent(){

   // inicializando los elementos de control que no entran en el grupo
  
   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");
  
   // rellenando las listas desplegables
  
   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]));            
   }
  
   // permitir la edición de desplazamientos desde el teclado            
   m_c_shift.SetReadOnly(false);
   m_w_shift.SetReadOnly(false);                        
}

En los métodos OnShowEvent() representamos los elementos de control, además, después de representar un grupo de elementos, obtenemos la coordenada Y, y de acuerdo con esta, representamos con ella los siguientes elementos de control:

void OnShowEvent(int aLeft, int aTop){
   m_c_cmb_main.Show(aLeft+10,aTop+10);        // lista de elección del tipo de línea central
   m_central_controls.Show(aLeft+10,aTop+30);  // grupo de elementos de control de los parámetros de la línea central
   int m_y=m_central_controls.GetLastY();      // obteniendo las coordenadas del último elemento de control
   m_c_shift.Show(aLeft+10,m_y+20);            // campo de edición del parámetro de desplazamiento
   m_w_cmb_main.Show(aLeft+10,m_y+40);         // lista de elección del canal
   m_width_controls.Show(aLeft+10,m_y+60);     // grupo de elementos de control de los parámetros del canal
   m_y=m_width_controls.GetLastY();            // obteniendo las coordenadas del último elemento de control
   m_w_value2.Show(aLeft+10,m_y+20);           // campo de edición de la amplitud
   m_w_shift.Show(aLeft+10,m_y+40);            // campo de edición del desplazamiento
}

En el método OnHideEvent() ocultamos los elementos de control:

void OnHideEvent(){
   m_c_cmb_main.Hide();       // lista de elección del tipo de línea central    
   m_central_controls.Hide(); // grupo de elementos de control de los parámetros de la línea central
   m_c_shift.Hide();          // campo de edición del desplazamiento
   m_w_cmb_main.Hide();       // lista de elección del canal
   m_width_controls.Hide();   // grupo de elementos de control de los parámetros del canal
   m_w_shift.Hide();          // campo de edición del desplazamiento
   m_w_lockto.Hide();         // elegir el tipo de bloqueo del periodo
   m_width_controls.Hide();   // campo de edición de la amplitud
}

Introducimos los cambios en el métdo SetValues(). Cambiamos el conjunto de parámetros del método, en el método establecemos para todos los elementos de control los valores correspondientes a estos parámetros:

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  
){

   // campo de edición de los parámetros de la línea central
   m_c_value1.SetValue(c_value1);
   m_c_value2.SetValue(c_value2);      
   m_c_value3.SetValue(c_value3);
   m_c_shift.SetValue(c_shift);        

   // campo de edición de los parámetros del canal
   m_w_value1.SetValue(w_value1);
   m_w_value2.SetValue(w_value2);        
   m_w_shift.SetValue(w_shift);            
  
   // representar los tipos elegidos en las listas de elección de los métodos de suavizado
   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);
      }            
   }
  
   // representar los tipos elegidos en las listas de elección del tipo de precio
   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);
      }            
   }

   // representación del tipo elegido de bloqueo del periodo del canal
   for(int i=0;i<ArraySize(e_lockto);i++){
      if(w_lockto==e_lockto[i]){
         m_w_lockto.SetSelectedIndex(i);
         break;
      }
   }                    
}

En lugar del método SetType(), crearemos dos métodos: SetCentralType() para establecer el tipo de línea central y SetWidthType() para establecer el tipo de límites. Al final de cada método, después de crear los objetos, para los elementos de control se establecen las propiedades que permiten introducir valores desde el teclado. Asimismo, indicamos los valores mínimos posibles y llamamos el método privado de cálculo de la altura del formulario:  

Метод SetCentralType():

void SetCentralType(long type){
   // si el objeto ya se ha creado con anterioridad, lo eliminamos
   if(CheckPointer(m_central_controls)==POINTER_DYNAMIC){
      delete(m_central_controls);
      m_central_controls=NULL;
   }
   switch((ECType)type){ // dependiendo del tipo elegido se crea el objeto
      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;
   }    
  
   // transmitir los punteros a los objetos de los elementos de control
   m_central_controls.SetPointers(m_c_value1,m_c_value2,m_c_value3,m_c_price,m_c_method);
   // inicializando los elementos de control de los grupos
   m_central_controls.InitControls();
  
   // permitir la edición desde el teclado
   m_c_value1.SetReadOnly(false);
   m_c_value2.SetReadOnly(false);
   m_c_value3.SetReadOnly(false);
  
   // estableciendo los valores mínimos permitidos
   m_c_value1.SetMinValue(1);        
   m_c_value2.SetMinValue(1);
   m_c_value3.SetMinValue(1);            
  
   // cálculo de la altura del formulario
   this.SolveHeight();

}

 Метод SetWidthType():

void SetWidthType(long type){
   // si el objeto ya se ha creado con anterioridad, lo eliminamos
   if(CheckPointer(m_width_controls)==POINTER_DYNAMIC){
      delete(m_width_controls);
      m_width_controls=NULL;
   }
   switch((EWType)type){ // dependiendo del tipo elegido se crea el objeto
      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;
   }    

   // transmitir los punteros a los objetos de los elementos de control
   m_width_controls.SetPointers(m_w_value1,m_w_value2,m_w_price,m_w_method);
   // inicializando los elementos de control de grupos
   m_width_controls.InitControls();
  
   // estableciendo los valores mínimos permitidos
   m_w_value1.SetReadOnly(false);
   m_w_value2.SetReadOnly(false);
  
   // estableciendo los valores mínimos permitidos
   m_w_value1.SetMinValue(1);        
   m_w_value2.SetMinValue(0);
  
   // cálculo de la altura del formulario
   this.SolveHeight();
              
}

Al final de los métodos SetCentralType() y SetWidthType()  se llama el método de cálculo de la altura del formulario SolveHeight():

void SolveHeight(){
   // si existen ambos objetos (de la línea central y de la amplitud)
   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;
   }      
}  

Procedemos a la conexión del indicador y la interfaz gráfica  

Creando el indicador y la interfaz gráfica

Guardamos el indicador iUniChannel con el nombre iUniChannelGUI. Por analogía con el indicador iUniOscGUI, añadimos a la parte superior de su ventana de propiedades el parámetro UseGUI. Después ubicamos las variables UseDefault, KeepPrev, les asignamos los valores por defecto y los mostramos en la ventana de propiedades:

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

Incluimos el archivo con la interfaz gráfica (en el mismo lugar en que se incluyen los archivos con las clases de los indicadores):

#include <UniChannel/UniChannelGUI.mqh>

En la parte más inferior de OnInit() añadimos el código de carga de la interfaz gráfica, pero antes de hacerlo, necesitaremos las matrices con los tipos de la línea central y de los límites. Las añadiremos debajo de los parámetros externos del indicador:

// matriz con los tipos de la línea central
ECType ctype[]={
   UniCh_C_AMA,
   UniCh_C_DEMA,
   UniCh_C_FrAMA,
   UniCh_C_MA,
   UniCh_C_TEMA,
   UniCh_C_VIDyA,
   UniCh_C_PrCh
};

// matriz con los tipos de los límites
EWType wtype[]={
   UniCh_W_ATR,
   UniCh_W_StdDev,
   UniCh_W_Points,
   UniCh_W_Percents,
   UniCh_W_PrCh
};

Aquí añadimos una variable-puntero a la clase del formulario:

CUniChannelForm * frm;

En la función OnInit(), al final del todo, ejecutamos la creación del objeto de la interfaz gráfica:

if(UseGUI){
  
   // crear e inicializar el objeto del formulario
   frm=new CUniChannelForm();
   frm.Init();
  
   // variables auxiliares
   int ind1=0;
   int ind2=0;
  
   // búsqueda del tipo elegido de línea central en la matriz de tipos de la línea central
   for(int i=0;i<ArraySize(ctype);i++){        
      frm.m_c_cmb_main.AddItem(EnumToString(ctype[i]));
      if(ctype[i]==_CentralType){
         ind1=i;
      }
   }
  
   // búsqueda del tipo elegido de límites del canal en la matriz de tipos de límite
   for(int i=0;i<ArraySize(wtype);i++){        
      frm.m_w_cmb_main.AddItem(EnumToString(wtype[i]));
      if(wtype[i]==_WidthType){
         ind2=i;
      }
   }      
  
   // representación en lista del tipo elegido de línea central
   frm.m_c_cmb_main.SetSelectedIndex(ind1);      
   // preparando los elementos de control correspondientes al tipo
   frm.SetCentralType(_CentralType);
  
   // representación en la lista del tipo elegido de límites
   frm.m_w_cmb_main.SetSelectedIndex(ind2);      
   frm.SetWidthType(_WidthType);      
  
   // establecer valores
   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
   );
  
   // estableciendo las propiedades del formulario
   frm.SetSubWindow(0);
   frm.SetPos(10,30);
   // representando el formulario
   frm.Show();
}  

Aparte de la creación del objeto del formulario, se rellenan las listas para elegir los indicadores y se les asignan las variantes elegidas. Asimismo, establecemos el resto de los valores en los elementos de control.  Después de ello, al colocar el indicador en el gráfico se representará el formulario con los elementos de control (fig. 1).


Fig. 1 Formulario con los elementos de control del canal universal

Los elementos de control se han representado, ahora es necesario proporcionar acciones a los botones del formulario. En la función OnChartEvent() se procesan seis eventos diferentes. El procesamiento de algunos de ellos es bastante complejo y voluminoso, por eso se ha trasladado a funciones aparte:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   // eventos del formulario
   if(frm.Event(id,lparam,dparam,sparam)==1){
      EventForm();
   }
  
   // eligiendo el tipo de línea central
   if(frm.m_c_cmb_main.Event(id,lparam,dparam,sparam)==1){
      EventCentralTypeChange();
   }  
  
   // eligiendo el tipo de límites
   if(frm.m_w_cmb_main.Event(id,lparam,dparam,sparam)==1){
      EventWidthTypeChange();
   }  
  
   // cambiar los parámetros de la línea central
   if(frm.m_central_controls.Event(id,lparam,dparam,sparam)==1){
      EventCentralParametersChange();
   }  
  
   // cambiar los parámetros de los límites
   if(frm.m_width_controls.Event(id,lparam,dparam,sparam)==1){
      EventWidthParametersChange();

   }  

   // cambiar los parámetros de desplazamiento
   if(frm.m_c_shift.Event(id,lparam,dparam,sparam)!=0 ||
      frm.m_w_shift.Event(id,lparam,dparam,sparam)
   ){
      EventShift();
   }    
}

Vamos a analizar todas estas funciones. Función EventForm():

void EventForm(){      
   int win=ChartWindowFind(0,ShortName);  // definir la subventana del indicador  
   ChartIndicatorDelete(0,win,ShortName); // eliminar el indicador
   ChartRedraw();
}  

Esta función se ejecuta al cerrar el formulario con el botón del aspa, además, se produce la búsqueda de la ventana del indicador según su nombre corto y se elimina el indicador. 

Función EventCentralTypeChange():

void EventCentralTypeChange(){    
   // obtener en la variable el nuevo tipo
   _CentralType=ctype[frm.m_c_cmb_main.SelectedIndex()];
  
   // eliminar el objeto antiguo y crear uno nuevo
   delete(central);
   LoadCentral(true);
  
   // comrpobando cómo se ha cargado el indicador
   if(!central.CheckHandle()){
      Alert("Error al cargar el indicador "+central.Name());
   }

   // estableciendo los desplazamientos y los nombres de los búferes
   SetStyles();

   // estableciendo en la lista el nuevo tipo
   frm.SetCentralType(ctype[frm.m_c_cmb_main.SelectedIndex()]);
   // actualizar los valores de los parámetros en el formulario
   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
   );
   // actualizar el formulario
   frm.Refresh();
  
   // iniciar el temporizador para recalcular los indicadores
   EventSetMillisecondTimer(100);
}

En esta función se ejecuta el cambio del tipo de indicador de la línea central. Primero se obtiene el tipo de indicador elegido, se elimina el objeto antiguo y se crea uno nuevo. Al crear un nuevo objeto, algunos de sus parámetros pueden modificarse (debido a la función UseDefault), por eso se llama el método SetValues(), para establecer los nuevos valores para los elementos de control, la representación del formulario se actualiza (método Refresh()). Al final se inicia el temporizador para recalcular el indicador.      

La función EventWidthTypeChange() es análoga a la función EventCentralTypeChange(), no la analizaremos en profundidad. 

En las funciones EventCentralParametersChange() y EventWidthParametersChange() se posibilita la reacción de los indicadores al cambio de los valores de los parámetros. Estas dos funciones son idénticas una a otra por su funcionalidad. Sin embargo, el cambio de parámetros exige prestar especial atención al bloqueo de periodos y la corrección de los parámetros de acuerdo con el mismo, por eso las funciones tienen sus propias diferencias únicas. Ambas serán analizadas.

void EventCentralParametersChange(){          
  
   // variable que indica la necesidad de reiniciar el indicador de límites
   bool dolock=false;
  
   // cambio de valor en el periodo 1
   if((int)frm.m_c_value1.Value()>0){
      // asignar a la variable el valor obtenido del elemento de control
      _c_Period1=(int)frm.m_c_value1.Value();
      // si el periodo 1 está relacionado con el periodo del indicador de amplitud
      if(_w_LockPeriod==LockTo_Period1){
         // asignamos a la variable con el periodo del indicador de amplitud el valor del periodo 1
         _w_Period=_c_Period1;
         // lo representamos en el formulario
         frm.m_w_value1.SetValue(_w_Period);
         // indicamos que hay que reiniciar el segundo indicador
         dolock=true;
      }
   }
  
   // el cambio de valor del periodo 2 es análogo al cambio del periodo 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;
      }        
   }
  
   // el cambio de valor del periodo 3 es análogo al cambio del periodo 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;
      }        
   }
  
   // cambios de método
   if(frm.m_c_method.SelectedIndex()!=-1){
      _c_Method=e_method[frm.m_c_method.SelectedIndex()];
   }
  
   // cambio de precio
   if(frm.m_c_price.SelectedIndex()!=-1){
      _c_Price=e_price[frm.m_c_price.SelectedIndex()];
   }
  
   // eliminar un objeto antiguo y crear uno nuevo
   delete(central);
   LoadCentral(false);
   if(!central.CheckHandle()){
      Alert("Error al cargar el indicador "+central.Name());
   }  

   // eliminar y crear un nuevo objeto del segundo indicador
   if(dolock){
      delete(width);
      LoadWidth(false);
      if(!width.CheckHandle()){
         Alert("Error al cargar el indicador "+width.Name());
      }  
   }  

   // estableciendo los desplazamientos y los nombres de los búferes
   SetStyles();

   // iniciar el temporizador para recalcular el indicador
   EventSetMillisecondTimer(100);
}  

En esta función, al cambiar cualquiera de los tres periodos se comprueba el valor del parámetro de bloqueo, y si se está ejecutando el bloqueo, se cambia el parámetro para el indicador de límites, se actualiza en el formulario y a la variable dolock se le asigna el valor true. Al final se elimina el objeto antiguo del indicador, se crea uno nuevo, y, si la variable dolock es igual a true, se elimina y se crea un objeto de límites. Después de todo esto, se inicia el temporizador, que espera el recálculo de los indicadores.

void EventWidthParametersChange(){  
      
   // variable que indica la necesidad de reiniciar el indicador de la línea central
   bool dolock=false;

   // cambio de periodo
   if((int)frm.m_w_value1.Value()>0){
      // asignar a la variable el valor obtenido del elemento de control
      _w_Period=(int)frm.m_w_value1.Value();
      // ejecutar bloqueo
      // el parámetro de amplitud está relacionado con el primer periodo de la línea central
      if(_w_LockPeriod==LockTo_Period1){
         // asignar un nuevo valor a la variable del indicador de la línea central
         _c_Period1=_w_Period;
         // actualizar el valor en el formulario
         frm.m_c_value1.SetValue(_c_Period1);
         // indicamos la necesidad de reiniciar el indicador de amplitud
         dolock=true;
      }
      else if(_w_LockPeriod==LockTo_Period2){ // si el bloqueo tiene un periodo 2
         _c_Period2=_w_Period;
         frm.m_c_value2.SetValue(_c_Period2);
         dolock=true;
      }
      else if(_w_LockPeriod==LockTo_Period3){ // si el bloqueo tiene un periodo 3
         _c_Period3=_w_Period;
         frm.m_c_value3.SetValue(_c_Period3);
         dolock=true;
      }
   }
  
   // cambiar el parámetro de amplitud del canal
   if((double)frm.m_w_value2.Value()>0){
      _w_Width=(double)frm.m_w_value2.Value();
   }      
  
   // cambio de método
   if(frm.m_w_method.SelectedIndex()!=-1){
      _w_Method=e_method[frm.m_w_method.SelectedIndex()];
   }
  
   // cambio de precio
   if(frm.m_w_price.SelectedIndex()!=-1){
      _w_Price=e_price[frm.m_w_price.SelectedIndex()];
   }
  
   // evento de cambio en la lista de elección del tipo de bloqueo de los periodos
   if(frm.m_w_lockto.SelectedIndex()>=0){
      // asignar a la variable el valor del elemento de control
      _w_LockPeriod=e_lockto[frm.m_w_lockto.SelectedIndex()];
      // si se ha elegido el bloqueo con alguno de los periodos,
      // se copia su valor y se actualiza en el formulario  
      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);
      }
   }      

   // eliminar un objeto antiguo y crear uno nuevo
   delete(width);
   LoadWidth(false);
   if(!width.CheckHandle()){
      Alert("Error al cargar el indicador "+width.Name());
   }
  
   // eliminar y crear un nuevo objeto del segundo indicador
   if(dolock){
      delete(central);
      LoadCentral(false);
      if(!central.CheckHandle()){
         Alert("Error al cargar el indicador "+central.Name());
      }
   }

   // estableciendo los desplazamientos y los nombres de los búferes
   SetStyles();

   // iniciar el temporizador para recalcular el indicador
   EventSetMillisecondTimer(100);      
}  

En esta función, al cambiar el periodo se comprueba el tipo de bloqueo, y, si es necesario, se cambia el periodo del indicador de la línea central. Si tiene lugar el evento de la lista de elección del tipo de bloqueo, a la variable del periodo del indicador se le asigna del valor de la variable del indicador correspondiente de la línea central.

El procesamiento del evento de cambio de valores del desplazamiento es bastante sencillo:

void EventShift(){     
   // obtener nuevos valores en las variables 
   _c_Shift=(int)frm.m_c_shift.Value();
   _w_Shift=(int)frm.m_w_shift.Value();
   // establecer nuevos estilos
   SetStyles();
   // actualizar el gráfico
   ChartRedraw();
}

A las variables se les asignan los valores de los elementos de control, se llaman las funciones SetStyles() y se actualiza el gráfico.

Hecho esto, el indcador con interfaz gráfica se puede considerar casi finalizado.

Durante la simulación del indicador, se ha detectado un defecto en el mismo. Cuando el parámetro externo UseDefault está activado y se usa el bloqueo de periodo, el bloqueo no funcionaba. Esto se debe a que, al cargar el segundo indicador (el indicador de amplitud), en su constructor se ejecuta el cambio de parámetros. Para corregir este error, hemos tenido que modificar un poco algunas clases derivadas de los indicadores de amplitud. Se ha añadido a los constructores de las clases CChannelUni_Calculate_ATR, CChannelUni_Calculate_StdDev y CChannelUni_Calculate_PriceChannel el parámetro opcional locked con el valor por defecto false (si el parámetro no se transmite a la clase, todo funcionará sin cambios). Al establecer locked=true y use_default=true, los parámetros del periodo en el constructor no se cambian (con la condición locked=true). Analizaremos un fragmento de la clase CChannelUni_Calculate_ATR:

if(use_default){
   if(keep_previous){
      if(ma_period==-1 && !locked)ma_period=14// cambio
      if(ch_width==-1)ch_width=2;
   }
   else{
      if(!locked)ma_period=14// cambio
      ch_width=2;
   }      
}

A la variable ma_period se le asigna el valor por defecto solo si la variable locked es igual a false. Así, hemos completado la corrección de la función LoadWidth(). Al inicio de la función se calcula el valor Locked:

bool Locked=(w_LockPeriod!=LockTo_Off);

A continuación, esta variable se transmite a los constructores de las clases al crear los objetos.

Asimismo, como hemos hecho en el oscilador universal, añadiremos la posibilidad de cambiar el esquema de color y posibilitaremos el guardado de parámetros del indicador al cambiar de marco temporal. No vamos a analizar el uso de los esquemas de color, pues ya se estudió al crear el oscilador universal. Vamos a trabajar con el guardado de parámetros.

En la función OnDeinit() del indicador, si el cambio de gráfico es el motivo de la inicialización, crearemos objetos gráficos con los valores de los parámetros. Estos objetos gráficos los crearemos fuera de los límites visibles del gráfico:

void SaveOrDeleteParameters(const int reason){
   // si no se trata del cambio del gráfico, eliminamos los bjetos gráficos 
   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// al cambiar el gráfico, creamos objetos gráficos con los valores de los parámetros
      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);        
   }
}

// función auxiliar para guardar un parámetro en el objeto gráfico
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);
}

En la función OnInit(), justo después de la llamada de PrepareParameters(), llamaremos la función LoadSavedParameters():

bool LoadSavedParameters(){
   // si existen todos los objetos con los parámetros 
   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
   ){
      // obteniendo los valores de los objetos gráficos
      _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);
   }
}

En la función se comprueba si existen estos objetos, y si existen, se usan sus valores, además, la función retorna true. Si la función ha retornado true, llamaremos las funciones LoadCentral() y LoadWidth() con el parámetro false (para que no se establezcan los parámetros por defecto). Fragmento de la función OnInit():

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

De la misma forma se llama la función LoadWidth():

LoadWidth(!ChartCange);

Así, hemos finalizado por completo la creación del canal universal. 

Conclusión

A pesar de que hemos usado una gran cantidad de código ya preparado del oscilador universal, la creación del canal universal ha requerido de un volumen considerable de trabajo adicional. La principal diferencia con respecto al oscilador universal reside en la existencia de dos bloques independientes: el de la línea central y el de los límites. Las complicaciones derivadas de este hecho han aumentado el volumen de trabajo a casi el doble. Asimismo, se ha complicado el agoritmo de cambio de parámetros debido a la función de bloqueo de periodos. También se ha complicado la propia carga de los nuevos indicadores, porque ahora son dos. Además, se ha añadido una nueva posibilidad funcional: el guardado de parámetros al cambiar de marco temporal. Como resultado, hemos obtenido un indicador tan útil y cómodo como el oscilador universal. Aparte, este indicador amplía significativamente las posibilidades de la propia idea del canal, pues ahora se pueden elegir por separado la línea central y el método de construcción de los límites. En definitiva, esto nos da un gran número de diferentes combinaciones posibles. El aumento de la velocidad de uso del indicador gracias a la interfaz gráfica permitirá investigar de forma visual todas estas combinaciones.

Anexos

En los anexos al artículo se encuentra un directorio con todos los archivos necesarios. Todos los archivos están ubicados en carpetas de la misma forma en que deberán ubicarse en el terminal.

 

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/2888

Archivos adjuntos |
files.zip (93 KB)
Interfaces gráficas X: Nuevas posibilidades para la tabla dibujada (build 9) Interfaces gráficas X: Nuevas posibilidades para la tabla dibujada (build 9)
Hasta este momento, el tipo más desarrollado de las tablas de la librería fue el tipo CTable. Esta tabla se reúne de los campos de edición tipo OBJ_EDIT y su desarrollo posterior ya resulta problemático. Por eso, desde el punto de vista de las posibilidades máximas incluso en esta fase del desarrollo de la librería, es mejor desarrollar la tabla dibujada tipo CCanvasTable. Su versión actual es absolutamente inerte, pero a partir de este artículo vamos a tratar de solucionar ese asunto.
Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8) Interfaces gráficas X: Control "Campo de edición del texto multilínea" (build 8)
Se considera el control «Campo de edición multilínea». A diferencia del objeto gráfico OBJ_EDIT, en esta versión no habrá limitación alguna para el número de los caracteres a introducir. Aparte de eso, se hace disponible el modo cuando el campo de edición se convierte en un sencillo editor de texto donde se puede mover el cursor usando el ratón o el teclado.
Modelado 3D en MQL5 Modelado 3D en MQL5
Una serie temporal es un sistema dinámico en el que los valores de una cierta magnitud aleatoria llegan de forma consecutiva: ininterrumpidamente o tras un cierto intervalo temporal. El paso del análisis plano del mercado al análisis con volumen permitirá mirar de una forma nueva a los complejos procesos y manifestaciones que interesan al investigador. En el artículo se describen las funciones de visualización de la representación 3-D de datos bidimensionales.
Incorpore el terminal web MetaTrader 4/5 en sus páginas web, es gratuito, y además podrá ganar dinero con ello Incorpore el terminal web MetaTrader 4/5 en sus páginas web, es gratuito, y además podrá ganar dinero con ello
Los tráders ya conocen bien el terminal web, que permite comerciar en los mercados financieros directamente desde el navegador. Le proponemos que lo incorpore en su página web, es algo totalmente gratuito. Usted tiene visitas a su página, los brókeres se interesan por clientes potenciales, y nosotros proporcionamos una solución web ya lista. Para que todo ello funcione, solo es necesario que incluya un iframe en su página web.