English Русский 中文 Deutsch 日本語 Português
Trabajando con las series temporales en la biblioteca DoEasy (Parte 48): Indicadores de periodo y símbolo múltiples en un búfer en una subventana

Trabajando con las series temporales en la biblioteca DoEasy (Parte 48): Indicadores de periodo y símbolo múltiples en un búfer en una subventana

MetaTrader 5Ejemplos | 24 noviembre 2020, 15:43
512 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el artículo anterior, utilizamos el indicador estándar Accelerator Oscillator para analizar los principios y métodos destinados a mostrar en el gráfico actual los datos de los indicadores estándar calculados con cualquier símbolo/marco temporal. Ahora, vamos a reunir los métodos que también permiten crear otros indicadores estándar. Ya tenemos prácticamente todo listo para ello. Lo que vamos a hacer hoy es más bien una prueba para determinar si hay construcciones idénticas en el código en los métodos, trasladando posteriormente los bloques de código repetidos a métodos aparte. Esto es lo más sencillo que deberemos hacer para trabajar con los indicadores estándar de un búfer: aquellos que utilizan un solo búfer para mostrar los datos.
En los siguientes artículos, definiremos qué podemos mejorar en el código, para así reducirlo y optimizarlo de acuerdo con los métodos mejorados en los artículos actuales y los métodos de trabajo con los indicadores estándar de búferes múltiples que se desarrollarán en el próximo artículo.

Hoy crearemos un ejemplo de indicador personalizado que mostrará en una subventana del gráfico actual uno de los indicadores estándar seleccionados en la configuración (que solo tienen un búfer de dibujado y que muestran sus datos en la subventana del gráfico principal). Para conseguirlo, deberemos mejorar algunos detalles de las clases de la biblioteca. Este será un importante paso preliminar a la hora de crear los métodos para trabajar con el resto de los indicadores estándar.


Mejorando las clases de la biblioteca

En primer lugar, vamos a añadir un nuevo mensaje de la biblioteca al archivo \MQL5\Include\DoEasy\Datas.mqh.

Añadimos el índice del nuevo mensaje:

   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Invalid number of indicator buffers (#property indicator_buffers)
   MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED,      // Reached maximum possible number of indicator buffers
   MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ,            // No buffer object for standard indicator

   MSG_LIB_TEXT_BUFFER_TEXT_STATUS_NONE,              // No drawing

y el texto del mensaje que se corresponde con el índice nuevamente añadido:

   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},
   {"Нет ни одного объекта-буфера для стандартного индикатора","There is no buffer object for the standard indicator"},
   
   {"Нет отрисовки","No drawing"},


Todas las mejoras de hoy se relacionan con la clase de colección de los búferes de indicador en el archivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh, y, como es natural, con la clase CEngine.

En el archivo BuffersCollection.mqh, tenemos que añadir un método para preparar los datos del búfer de cálculo de todos los indicadores estándar creados, para que la biblioteca pueda ocuparse de ello por sí misma al ejecutarse el comando correspondiente del programa. Esto simplificará el código del programa final, pues no necesitaremos buscar y obtener los objetos necesarios.

En la clase pública de la clase, declaramos este método:

//--- Prepare calculated buffer data of (1) the specified standard indicator and (2) all created standard indicators
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
   bool                    PreparingDataAllBuffersStdInd(void);
//--- Clear buffer data of the specified standard indicator by the timeseries index

y lo implementaremos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of all created standard indicators                               |
//+------------------------------------------------------------------+
bool CBuffersCollection::PreparingDataAllBuffersStdInd(void)
  {
   CArrayObj *list=this.GetListBuffersWithID();
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
   bool res=true;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
         continue;
      CSeriesDE *series=this.m_timeseries.GetSeries(buff.Symbol(),buff.Timeframe());
      if(series==NULL)
         continue;
      int used_data=(int)series.AvailableUsedData();
      int copied=this.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),used_data);
      if(copied<used_data)
         res &=false;
     }
   return res;
  }
//+------------------------------------------------------------------+

Cada objeto de búfer usado para calcular el indicador estándar tiene necesariamente un identificador asignado, ya sea de forma automática según el tipo del indicador estándar (ENUM_INDICATOR), o manualmente desde el programa al crear los objetos de búfer necesarios.

Aquí, nosotros obtendremos en primer lugar la lista con todos los objetos de búfer cuyo identificador no es igual a -1.
Si la lista está vacía, mostraremos un mensaje indicando que no hay objetos de búfer creados para los indicadores estándar, y retornaremos false
.
A continuación, en un ciclo por el número total de los objetos de búfer que tienen el indentificador del indicador estándar, obtenemos el siguiente objeto de búfer. Para cada indicador estándar, tenemos como mínimo dos objetos así: uno de cálculo y otro de dibujado. Solo necesitamos los búferes de cálculo, pero en este ciclo no los seleccionaremos, porque después, en el método PreparingDataBufferStdInd() (que analizamos en el artículo anterior, y que mejoraremos un poco más tarde), precisamente se selecciona solo el búfer de cálculo para el posterior trabajo con el mismo. Por eso, no vamos a repetir esta selección aquí.
Ahora, necesitamos determinar cuántas barras tenemos en el símbolo/periodo para el que se ha creado el objeto de búfer obtenido. Necesitamos este valor para determinar cuántos datos tenemos que copiar del indicador estándar al objeto de búfer designado para él. Para ello, obtenemos la serie temporal necesaria según los valores del símbolo y el marco temporal del objeto de búfer, y tomamos de ella el número de datos al que tenemos acceso.
A continuación, llamamos al método PreparingDataBufferStdInd() para copiar el número necesario de datos del manejador del indicador al objeto de búfer de cálculo.
Si se han copiado menos datos de los necesarios, añadiremos al valor de la variable res el valor false. Si aunque sea uno de los objetos de búfer no se copia en la cantidad necesaria, anotaremos en la variable res el valor false, y precisamente desde el método retornaremos el valor de esta variable. El método retornará true si todos los datos de todos los objetos de búfer de cálculo se han copiado con éxito.

Cabe señalar que, por ahora, este método copia todos los datos disponibles del manejador del indicador al búfer calculado. Esto, claro está, supone un lujo inadmisible: copiar grandes cantidades de datos en cada tic, e incluso para más de un indicador. Pero, como en estos momentos estamos creando la funcionalidad necesaria para trabajar con los indicadores estándar, lo dejaremos así por ahora. Aquí lo más importante no es la velocidad, sino la corrección de la lógica. Más tarde, obviamente, nos aseguraremos de que los datos completos se copien solo en las condiciones necesarias (primer inicio, cambios en los datos históricos) y, en los demás casos, solo la cantidad requerida de datos, una o dos barras.

Ya declaramos todos los métodos necesarios para crear los identificadores de los indicadores estándar y los búferes acompañantes en el artículo anterior. Pero aún no los hemos implementado (salvo los dos métodos para crear los indicadores AC y AD). Hoy, implementaremos los métodos necesarios para crear los identificadores de los indicadores estándar y sus objetos de búfer para aquellos que tienen un solo búfer de indicador, y donde el propio indicador dibuja por sí mismo los datos en la subventana principal del gráfico.

Asimismo, haremos que el color de los indicadores creados se corresponda con el color de los indicadores estándar correspondientes por defecto. Incluso teniendo esto en cuenta, podremos establecer nuestros propios colores para estos objetos de búfer. Para lograrlo, solo tenemos que transmitir el índice de color necesario al calcular los búferes en el ciclo principal.

Vamos a analizar los cambios efectuados en el método de creación de búferes para el indicador estándar Accelerator Oscillator:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period AC                              |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iAC(symbol,timeframe);
   int identifier=(id==WRONG_VALUE ? IND_AC : id);
   color array_colors[3]={clrGreen,clrRed,clrGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the histogram buffer from the zero line
      this.CreateHistogram();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetShowData(true);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
      buff.SetColors(array_colors);
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Como el indicador estándar Accelerator Oscillator tiene dos colores para representar sus valores, declaramos una matriz de colores con un tamaño de 3, que inicializaremos de inmediato con tres colores. ¿Por qué con tres? Porque el color con el índice 0 representa los valores ascendentes de la línea del indicador, y el color con el índice 1, los valores descendentes. Pero nosotros necesitamos otro color que represente los valores iguales para dos barras colindantes de la linea del indicador. Y en el Accelerator Oscillator estándar, estas se representan con el color de los valores ascendentes. De ahí que necesitemos tres colores.
Después de crear el objeto de búfer de dibujado, establecemos los colores para su dibujado desde esta matriz.

Al obtener el puntero al último objeto de búfer creado, podría suceder que obtuviéramos erróneamente este puntero. Antes, esta situación no se consideraba, lo cual es potencialmente peligroso: el acceso a un puntero no válido provoca que el programa finalice de forma anormal.
Por eso, en esa situación, tenemos que salir del método retornando el valor INVALID_HANDLE.

Para los indicadores estándar que solo tengan un color de dibujado, estableceremos una matriz de colores que conste solo de un elemento; esto es lo único en que se diferencian los métodos para crear los manejadores de los indicadores estándar de color y sus objetos de búfer respecto a los métodos para crear indicadores estándar monocromos: solo en el tamaño de la matriz de colores.

Por ejemplo, aquí tenemos el método para crear el indicador estándar Average True Range:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period ATR                             |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iATR(symbol,timeframe,ma_period);
   int identifier=(id==WRONG_VALUE ? IND_ATR : id);
   color array_colors[1]={clrLightSeaGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ATR);
      buff.SetShowData(true);
      buff.SetLabel("ATR("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Average True Range");
      buff.SetColors(array_colors);
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ATR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("ATR("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Average True Range");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Hoy, vamos a escribir los métodos solo para los indicadores estándar de un solo búfer que dibujen en la subventana, en concreto:

  • Accelerator Oscillator
  • Accumulation/Distribution
  • Awesome Oscillator
  • Average True Range
  • Bears Power
  • Bulls Power
  • Chaikin Oscillator
  • Commodity Channel Index
  • DeMarker
  • Force Index
  • Momentum
  • Money Flow Index
  • Moving Average of Oscillator
  • On Balance Volume
  • Relative Strength Index
  • Standart Deviation
  • Triple Exponential Average
  • William's Percent Range
  • Volumes

Los métodos para crear los indicadores de símbolo y periodo múltiples que hemos enumerado antes ya han sido creados, por lo que no los analizaremos aquí: son idénticos a los dos métodos que hemos estudiado, y el lector podrá encontrarlos en el archivo adjunto al artículo.

El lector atento ya habrá notado la ausencia de un indicador en la lista anterior, que se dibuja en la subventana y tiene un búfer de dibujado: estamos hablando del indicador Market Facilitation Index. Sí, tiene un búfer y se dibuja en una subventana. Pero, para calcular el color de las columnas del histograma, este necesita un búfer de cálculo adicional: el búfer de indicador de volumen. Por consiguiente, vamos a clasificar este indicador en la sección de indicadores de búfer múltiple en la subventana.

El método PreparingDataBufferStdInd() rellena la matriz del objeto de búfer calculado con los datos del identificador del indicador. Ya analizamos este método en el artículo anterior, pero solo implementamos el rellenando del búfer del indicador AC:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return 0;
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

Y lo que más interesante: para otros indicadores de un solo búfer, deberemos reproducir exactamente las mismas acciones. Y es aquí donde acude en nuestra ayuda el propio operador switch: en él se comparan los valores de la expresión con las constantes en todas las variantes de case, y luego se transmite el control al operador que se corresponde con el valor de la expresión. Cada "case" es finalizado o bien por el operador de finalización break, o bien por el operador de retorno return. Y si no ubicamos ninguno de estos operadores de finalización dentro de "case", después de procesar el valor de la expresión en el caso necesario, el control pasará al siguiente.
Por lo tanto, en lugar de clonar las operaciones necesarias del mismo tipo en todos los casos en los que se deban efectuar exactamente las mismas operaciones, bastará con combinar todos esos casos en uno solo sin un operador de retorno o finalización:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      //--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
 
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

Actuamos exactamente de la misma forma en el método para limpiar el búfer del indicador estándar indicado:

//+------------------------------------------------------------------+
//| Clear buffer data of the specified standard indicator            |
//| by the timeseries index                                          |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   if(list==NULL || list.Total()==0)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      //--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

y en el método para establecer el valor de los datos del búfer del indicador estándar especificado:

//+------------------------------------------------------------------+
//| Set values for the current chart to the specified buffer         |
//| standard indicator by the timeseries index according to          |
//| the buffer object symbol/period                                  |
//+------------------------------------------------------------------+
bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
//--- Get the list of drawn objects with ID
   CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Get the list of calculated buffers with ID
   CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Exit if any of the lists is empty
   if(list_data.Total()==0 || list_calc.Total()==0)
      return false;
//--- Declare the necessary objects and variables
   CBuffer *buffer_data=NULL;
   CBuffer *buffer_calc=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   datetime time_period=0;
   double value0=EMPTY_VALUE, value1=EMPTY_VALUE;

//--- Depending on the standard indicator type
   switch((int)ind_type)
     {
      //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        //--- Get drawn and calculated buffer objects
        buffer_data=list_data.At(0);
        buffer_calc=list_calc.At(0);
        if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(0)==0) return false;
        
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-1) return false;
        //--- Get the value by the index from the indicator buffer
        value0=buffer_calc.GetDataBufferValue(0,index_period);
        if(buffer_calc.Symbol()==::Symbol() && buffer_calc.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
           time_period=::iTime(buffer_calc.Symbol(),buffer_calc.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Get the appropriate current chart bar
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
           num_bars=::PeriodSeconds(buffer_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-1 ? value0 : buffer_data.GetDataBufferValue(0,series_index_start+num_bars));
        //--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
        //--- and set the color of the drawn buffer depending on the value0 and value1 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data.SetBufferValue(0,index,value0);
           buffer_data.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value0>value1 ? 0 : value0<value1 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Ya hemos visto los tres métodos en el artículo anterior: hoy solo hemoscombinado los casos en los que es necesario crear el mismo manejador.
Al final del manejador, debe ubicarse necesariamente un operador de retorno o interrupción, para que el procesamiento del valor switch no pase a otros casos en los que habrá otro manejador.

Ahora, vamos a completar la clase del objeto principal de la biblioteca CEngine en el archivo \MQL5\Include\DoEasy\Engine.mqh.

Ya hemos escrito todos los métodos de creación de los indicadores estándar y los objetos de búfer para ellos en la clase de colección de búferes(algunos métodos aún no los hemos analizado, y por ahora solo retornan INVALID_HANDLE); ahora, tenemos que escribir en la clase CEngine el acceso a estos métodos para el programa.

En la sección pública de la clase, escribimos la llamada de estos métodos:
//--- The methods of creating standard indicators and buffer objects for them
//--- Create the standard Accelerator Oscillator indicator
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);                    }
//--- Create the standard Accumulation/Distribution indicator
   bool                 BufferCreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateAD(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);     }
//--- Create the standard ADX indicator
   bool                 BufferCreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADX(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);        }
//--- Create the standard ADX Wilder indicator
   bool                 BufferCreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADXWilder(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Alligator indicator
   bool                 BufferCreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                              const int jaw_period,const int jaw_shift,
                                              const int teeth_period,const int teeth_shift,
                                              const int lips_period,const int lips_shift,
                                              const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE applied_price,const int id)
                          {
                           return(this.m_buffers.CreateAlligator(symbol,timeframe,
                                                                 jaw_period,jaw_shift,
                                                                 teeth_period,teeth_shift,
                                                                 lips_period,lips_shift,
                                                                 ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Adaprive Moving Average indicator
   bool                 BufferCreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                        const int ama_period,
                                        const int fast_ema_period,
                                        const int slow_ema_period,
                                        const int ama_shift,
                                        const ENUM_APPLIED_PRICE applied_price,
                                        const int id)
                          { 
                           return(this.m_buffers.CreateAMA(symbol,timeframe,
                                                           ama_period,
                                                           fast_ema_period,slow_ema_period,
                                                           ama_shift,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Awesome Oscillator indicator
   bool                 BufferCreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAO(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Create the standard Average True Range indicator
   bool                 BufferCreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateATR(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Bollinger Bands indicator
   bool                 BufferCreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                          const int bands_period,
                                          const int bands_shift,
                                          const double deviation,
                                          const ENUM_APPLIED_PRICE applied_price,
                                          const int id)
                          {
                           return(this.m_buffers.CreateBands(symbol,timeframe,
                                                             bands_period,bands_shift,
                                                             deviation,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Bears Power indicator
   bool                 BufferCreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBearsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Bulls Power indicator
   bool                 BufferCreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBullsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Chaikin Oscillator indicator
   bool                 BufferCreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          {
                           return(this.m_buffers.CreateChaikin(symbol,timeframe,
                                                                fast_ma_period,slow_ma_period,
                                                                ma_method,applied_volume,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Commodity Channel Index indicator
   bool                 BufferCreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateCCI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard DEMA indicator
   bool                 BufferCreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateDEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard DeMarker indicator
   bool                 BufferCreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateDeMarker(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Envelopes indicator
   bool                 BufferCreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id)
                          {
                           return(this.m_buffers.CreateEnvelopes(symbol,timeframe,
                                                                  ma_period,ma_shift,ma_method,
                                                                  applied_price,deviation,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Force Index indicator
   bool                 BufferCreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateForce(symbol,timeframe,ma_period,ma_method,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Fractals indicator
   bool                 BufferCreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateFractals(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Create the standard FrAMA indicator
   bool                 BufferCreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Gator indicator
   bool                 BufferCreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          {
                           return(this.m_buffers.CreateGator(symbol,timeframe,
                                                              jaw_period,jaw_shift,
                                                              teeth_period,teeth_shift,
                                                              lips_period,lips_shift,
                                                              ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Ichimoku indicator
   bool                 BufferCreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id)
                          { return(this.m_buffers.CreateIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b,id)!=INVALID_HANDLE);  }
//--- Create the standard BW MFI indicator
   bool                 BufferCreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateBWMFI(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Momentum indicator
   bool                 BufferCreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMomentum(symbol,timeframe,mom_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Money Flow Index indicator
   bool                 BufferCreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateMFI(symbol,timeframe,ma_period,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Moving Average indicator
   bool                 BufferCreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Moving Average of Oscillator indicator
   bool                 BufferCreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard MACD indicator
   bool                 BufferCreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard On Balance Volume indicator
   bool                 BufferCreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateOBV(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Parabolic SAR indicator
   bool                 BufferCreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id)
                          { return(this.m_buffers.CreateSAR(symbol,timeframe,step,maximum,id)!=INVALID_HANDLE);  }
//--- Create the standard Relative Strength Index indicator
   bool                 BufferCreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateRSI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard RVI indicator
   bool                 BufferCreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateRVI(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the Standard Deviation indicator
   bool                 BufferCreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Stochastic indicator
   bool                 BufferCreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id)
                          { return(this.m_buffers.CreateStochastic(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field,id)!=INVALID_HANDLE);  }
//--- Create the standard TEMA indicator
   bool                 BufferCreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Triple Exponential Average indicator
   bool                 BufferCreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTriX(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard William's Percent Range indicator
   bool                 BufferCreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id)
                          { return(this.m_buffers.CreateWPR(symbol,timeframe,calc_period,id)!=INVALID_HANDLE);  }
//--- Create the standard VIDYA indicator
   bool                 BufferCreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateVIDYA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Volumes indicator
   bool                 BufferCreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateVolumes(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
                          
//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object

Los métodos en la clase de colección retornan el tipo int, en concreto, el manejador del indicador estándar creado. Aquí, implementaremos el retorno del valor con el tipo bool, así resultará más sencillo trabajar con los métodos del programa final. Por consiguiente, estos métodos retornan el resultado de la comparación entre el valor retornado por el método correspondiente de la clase de colección de búfer y el valor INVALID_HANDLE.

En la sección pública de la clase, escribimos dos métodos: el primer método prepara los datos del búfer de cálculo para todos los indicadores estándar creados, retornando el resultado del funcionamiento del método del mismo nombre de la clase de colección de objetos de búfer que hemos analizado anteriormente; el segundo método retorna la descripción de un objeto de búfer según el tipo de indicador estándar y su identificador:

//--- Prepare data of the calculated buffer of all created standard indicators
   bool                 BufferPreparingDataAllBuffersStdInd(void)                         { return this.m_buffers.PreparingDataAllBuffersStdInd();    }

//--- Return the standard indicator buffer description by type and ID
   string               BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id);

//--- Display short description of all indicator buffers of the buffer collection
   void                 BuffersPrintShort(void);

Fuera del cuerpo de la clase, implementamos este método:

//+------------------------------------------------------------------+
//| Return the standard indicator buffer description                 |
//| by type and ID                                                   |
//+------------------------------------------------------------------+
string CEngine::BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id)
  {
   CArrayObj *list=m_buffers.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.Label();
  }
//+------------------------------------------------------------------+

Aquí todo es muy simple: obtenemos la lista de objetos de búfer con el tipo de indicador estándar correspondiente (transmitido al método) y su ID.
En la lista tendremos dos de estos objetos de búfer: uno de dibujado y otro de cálculo. No nos importa cuál de estos objetos aporte los datos: ambos objetos los tienen y son iguales. Por consiguiente, obtenemos el puntero al primer objeto de búfer de la lista y retornamos su descripción.

En la implementación del método OnDoEasyEvent() en el bloque de procesamiento del evento "Nueva barra", corregimos algunos detalles de la obtención de la cantidad de datos copiados:

//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin((int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated()));
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- "Bars skipped" event
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
     }
     
//--- Handling trading events

Antes, en la línea destacada, obteníamos el valor mínimo del número total de datos del objeto de búfer y de los datos calculados del indicador estándar:

int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());

No obstante, como en la biblioteca (y por lo tanto para el programa), el número de datos usado de las series temporales se establece por defecto en 1000 barras, ahora vamos a copiar el valor mínimo de tres fuentes de datos: el número total de datos del objeto de búfer, los datos calculados del indicador estándar y el número de datos predeterminado (1000). Normalmente, el valor 1000 siempre es inferior a los otros dos, y esto resulta más rentable al copiar los datos de forma masiva.

Estas son todas las mejoras de las clases de la biblioteca por hoy.

Prueba

Para poner a prueba la creación de los indicadores estándar de búfer único de símbolo y periodo múltiples dibujados en una subventana, haremos lo siguiente:
creamos un indicador de usuario con la siguiente selección en la configuración:

  1. El símbolo usado en el que se creará el indicador estándar
  2. El periodo de gráfico usado (marco temporal) en el que se creará el indicador estándar
  3. El tipo creado de indicador estándar de un solo búfer de símbolo y periodo múltiples

Los datos del indicador estándar seleccionado se mostrarán en la subventana del gráfico principal del símbolo/periodo actual.

Tomaremos el indicador del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Indicators\TestDoEasy\Part48\ con el nuevo nombre TestDoEasyPart48.mq5.

En los parámetros de entrada del indicador, añadimos un parámetro que servirá para seleccionar el indicador estándar preciso que vamos a representar:

//|                                             TestDoEasyPart48.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
sinput   ENUM_INDICATOR       InpIndType        =  IND_AC;        // Type standard indicator
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // List of used timeframes
CEngine              engine;                                      // CEngine library main object
string               prefix;                                      // Prefix of graphical object names
int                  min_bars;                                    // The minimum number of bars for the indicator calculation
int                  used_symbols_mode;                           // Mode of working with symbols
string               array_used_symbols[];                        // The array for passing used symbols to the library
string               array_used_periods[];                        // The array for passing used timeframes to the library
//+------------------------------------------------------------------+

No todos los indicadores estándar funcionarán con nuestro indicador, solo los que tengan un búfer de construcción, y sean mostrados en una subventana. Creamos en la biblioteca del manejador OnInit() los indicadores estándar que sean adecuados según el tipo, mientras que para el resto de los indicadores, escribimos un mensaje de error:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Initialize DoEasy library
   OnInitDoEasy();
   
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- calculate the number of bars of the current period fitting in the maximum used period
   //--- Use the obtained value if it exceeds 2, otherwise use 2
   int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>2 ? num_bars : 2);

//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel

//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
      case IND_AC       :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_AD       :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                    break;
      case IND_AO       :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_ATR      :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_BEARS    :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_BULLS    :  success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_CHAIKIN  :  success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,3,10,MODE_EMA,VOLUME_TICK,1); break;
      case IND_CCI      :  success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,14,PRICE_TYPICAL,1);              break;
      case IND_DEMARKER :  success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,14,1);                       break;
      case IND_FORCE    :  success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,13,MODE_SMA,VOLUME_TICK,1);     break;
      case IND_MOMENTUM :  success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);           break;
      case IND_MFI      :  success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,14,VOLUME_TICK,1);                break;
      case IND_OSMA     :  success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);          break;
      case IND_OBV      :  success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                   break;
      case IND_RSI      :  success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                break;
      case IND_STDDEV   :  success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,20,0,MODE_SMA,PRICE_CLOSE,1);  break;
      case IND_TRIX     :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);               break;
      case IND_WPR      :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_VOLUMES  :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);               break;
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Check the number of buffers specified in the 'properties' block

Cada uno de los indicadores estándar dibujados en una subventana tiene su propio número de caracteres decimales mostrados. Además, algunos de ellos también dibujan las líneas de nivel. Vamos a crear un conjunto individual para cada uno de los indicadores estándar usados, en el que deberemos indicar el rango decimal de los datos mostrados y definir los niveles, siempre que el indicador estándar los tenga:

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();
//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AC       : digits+=2;   break;
      case IND_AD       : digits=0;    break;
      case IND_AO       : digits+=1;   break;
      case IND_ATR      :              break;
      case IND_BEARS    : digits+=1;   break;
      case IND_BULLS    : digits+=1;   break;
      case IND_CHAIKIN  : digits=0;    break;
      case IND_CCI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_FORCE    : digits+=1;   break;
      case IND_MOMENTUM : digits=2;    break;
      case IND_MFI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_OSMA     : digits+=2;   break;
      case IND_OBV      : digits=0;    break;
      case IND_RSI      :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STDDEV   : digits+=1;   break;
      case IND_TRIX     :              break;
      case IND_WPR      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
      case IND_VOLUMES  : digits=0;    break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetLabelByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

El manejador OnCalculate() ha quedado bastante simplificado en comparación con el anterior indicador de prueba. Esto se debe a que hemos creado los métodos necesarios para trabajar con los indicadores estándar; todo lo que necesitamos es preparar los datos del indicador estándar y mostrar en el ciclo principal los datos calculados del indicador estándar en el gráfico actual:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      engine.EventsHandling();      // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
      engine.BufferPreparingDataAllBuffersStdInd();
     }
   
//--- Prepare data 
//--- Fill in calculated buffers of all created standard indicators with data
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   if(!engine.BufferPreparingDataAllBuffersStdInd())
      return 0;

//--- Calculate the indicator
//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(InpIndType,1,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Esto es todo lo que debíamos hacer para crear el indicador de prueba.
Podrá ver el código completo del indicador en los archivos adjuntos al artículo.

Compilamos el indicador creado, establecemos en los ajustes el símbolo para calcular el indicador estándar GBPUSD y el marco temporal M5, e iniciamos el propio indicador en el gráfico EURUSD, M1:


En la figura no mostramos todos los indicadores posibles, pero sí que representamos lo principal: el indicador dibuja un indicador estándar construido con datos distintos a los datos del gráfico actual. Las líneas de los niveles se dibujan allá donde se necesitan.

¿Qué es lo próximo?

En el próximo artículo, comenzaremos a implementar los métodos para crear indicadores estándar que muestren los datos en la ventana principal del gráfico, así como indicadores que tengan varios búferes de dibujado.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y los archivos del indicador de prueba. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.
Querríamos recordar al lector que en este artículo hemos creado un indicador de prueba en MQL5 para MetaTrader 5.
Los archivos adjuntos han sido diseñados solo para MetaTrader 5, y en MetaTrader 4, la biblioteca en su versión actual no ha sido puesta a prueba.
Después de crear la funcionalidad necesaria para trabajar con los búferes de indicador y poner estos a prueba, trataremos también de implementar algunas cosas de MQL5 en MetaTrader 4.

Volver al contenido

Artículos de esta serie:

Trabajando con las series temporales en la biblioteca DoEasy (Parte 35): El objeto "Barra" y la lista de serie temporal del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 36): El objeto de series temporales de todos los periodos utilizados del símbolo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 37): Colección de series temporales - Base de datos de series temporales según el símbolo y el periodo
Trabajando con las series temporales en la biblioteca DoEasy (Parte 38): Colección de series temporales - Actualización en tiempo real y acceso a los datos desde el programa
Trabajando con las series temporales en la biblioteca DoEasy (Parte 39): Indicadores basados en la biblioteca - Preparación de datos y eventos de la series temporales
Trabajando con las series temporales en la biblioteca DoEasy (Parte 40): Indicadores basados en la biblioteca - actualización de datos en tiempo real
Trabajando con las series temporales en la biblioteca DoEasy (Parte 41): Ejemplo de indicador de símbolo y periodo múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 42): La clase del objeto de búfer de indicador abstracto
Trabajando con las series temporales en la biblioteca DoEasy (Parte 43): Las clases de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 44): Las clases de colección de los objetos de búferes de indicador
Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple
Trabajando con las series temporales en la biblioteca DoEasy (Parte 46): Búferes de indicador de periodo y símbolos múltiples
Trabajando con las series temporales en la biblioteca DoEasy (Parte 47): Indicadores estándar de periodo y símbolo múltiples

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

Archivos adjuntos |
MQL5.zip (3755.32 KB)
Gradient boosting (CatBoost) en las tareas de construcción de sistemas comerciales. Un enfoque ingenuo Gradient boosting (CatBoost) en las tareas de construcción de sistemas comerciales. Un enfoque ingenuo
Entrenamiento del clasificador CatBoost en el lenguaje Python, exportación al formato mql5; análisis de los parámetros del modelo y simulador de estrategias personalizado. Para preparar los datos y entrenar el modelo, se usan el lenguaje de programación Python y la biblioteca MetaTrader5.
Uso de criptografía con aplicaciones externas Uso de criptografía con aplicaciones externas
En el presente artículo, analizaremos la encriptación/desencriptación de objetos en MetaTrader y los programas externos para aclarar las condiciones en las que se obtendrán los mismos resultados con los mismos datos iniciales.
Aprendizaje de máquinas de Yándex (CatBoost) sin estudiar Python y R Aprendizaje de máquinas de Yándex (CatBoost) sin estudiar Python y R
En el artículo, descricribiremos las etapas del proceso de aprendizaje de máquinas usando un ejemplo concreto, y también adjuntaremos un código sobre el mismo. Para obtener los modelos, no necesitaremos conocer ningún lenguaje de programación como Python o R. Los conocimientos requeridos de MQL5 no serán profundos, iguales, por cierto, que los del autor del presente artículo; por eso, esperamos que este artículo sirva de guía para un amplio círculo de lectores que deseen valorar de forma experimental las posibilidades del aprendizaje de máquinas e implementar estas en sus desarrollos.
Enfoque científico sobre el desarrollo de algoritmos comerciales Enfoque científico sobre el desarrollo de algoritmos comerciales
En el presente artículo, estudiaremos con ejemplos la metodología de desarrollo de algoritmos comerciales usando un enfoque científico secuencial sobre el análisis de las posibiles patrones de formación de precio y la construcción de algoritmos comerciales basados en dichas leyes.