Trabajando con las series temporales en la biblioteca DoEasy (Parte 50): Indicadores estándar de período y símbolo múltiples con desplazamiento

7 diciembre 2020, 12:24
Artyom Trishkin
0
428

Contenido


Concepto

En varios artículos anteriores, íbamos creando paso a paso la funcionalidad para la visualización de los indicadores estándar en el gráfico del símbolo/período en el modo del indicador de período y símbolo múltiples. Todavía no todos los indicadores estándar pueden ser representados en este modo, pero hoy vamos a hacer una digresión del tema respecto al traspaso de los indicadores estándar a modos múltiples.
No, no vamos a cambiar de tarea. Todo estará dentro del margen de nuestro tema, pero vamos a ver. Si intentamos crear un indicador de período múltiple con el desplazamiento de su línea en el gráfico del símbolo actual, no lo conseguiremos. La razón es que hasta ahora, para el desplazamiento de la línea del indicador de período múltiple, se usaba un valor predefinido que se establecía en el constructor de la clase del objeto de búfer, siendo éste igual a cero. Y si establecemos el desplazamiento de la línea del indicador de período múltiple igual al valor del desplazamiento de la línea del indicador estándar, pues tampoco conseguiremos nada en este caso: la línea se desplazará, pero solamente dentro del margen del número de las barras del desplazamiento de la línea del indicador estándar.
La razón es la siguiente. Para representar la línea del indicador de período múltiple de forma correcta, tenemos que calcular cuántas barras del gráfico actual caben en una barra del período del gráfico en el que está calculado el indicador estándar, y entonces desplazar la línea a este número de barras. En otras palabras, si el indicador estándar está calculado en el gráfico H4 y nosotros queremos visualizarlo en el gráfico H1, tenemos que dibujar una barra del gráfico H4 en cuatro barras del gráfico H1. Lo mismo se refiere al desplazamiento: es decir, si la línea del indicador estándar está desplazada a una barra en el gráfico H4, tenemos que moverla a cuatro barras en el gráfico H1.

Y eso no es todo. Al rellenar la matriz de búfer de cálculo en el objeto de búfer, también tenemos que tomar en cuenta el desplazamiento de la línea del indicador estándar. Este desplazamiento nos debe indicar el punto de partida para calcular la cantidad de barras a copiar de la matriz fuente del indicador estándar a la matriz receptora del objeto de búfer que hemos creado.

Después de implementar observando las reglas arriba mencionadas, podremos visualizar el indicador estándar en modo de período múltiple con desplazamiento de la línea.

Además, me gustaría señalar que no todos los indicadores estándar se ha logrado convertir tan fácilmente y a la primera en el modo de período múltiple con desplazamiento de la línea. Por ahora, no se consigue representar en el modo de período múltiple aquellos indicadores cuyas líneas se dibujan desde el principio con desplazamiento (Gator Oscillator y Ichimoku Kinko Hyo), según ha sido ideado por sus autores. Por el contrario, el indicador Alligator se ha sometido a la conversión sin ningún problema. Creo que eso está relacionado con el hecho de que en el indicador Alligator a cada línea que se dibuja se le establece su propio desplazamiento, las mostramos con un desplazamiento ya calculado consiguiendo así una representación correcta, mientras que los indicadores Gator y Ichimoku utilizan las líneas desplazadas originalmente para sus cálculos. En cualquier caso, encontraremos el porqué y los implementaremos como indicadores de período y símbolo múltiples. Mientras tanto, seguiremos desarrollando la funcionalidad de la biblioteca, para no perder tiempo con la búsqueda de las razones (serán dos tareas paralelas). Además, todo se pondrá más interesante bastante pronto. Se trata de la inclusión de los datos de los indicadores estándar en los objetos de barra de la clase de la serie temporal, lo cual nos permitirá buscar rápidamente cualquier combinación de indicadores en cualquier símbolo/período del gráfico.

Aparte de implementar la visualización de los indicadores estándar con desplazamiento de la línea, hoy vamos a crear los métodos comunes para la preparación y visualización de los indicadores estándar en los modos múltiples. Por el momento, para cada indicador estándar tenemos implementados sus propios métodos de su visualización. Gracias al desarrollo gradual de los indicadores de periodo y símbolo múltiples, hemos podido observar con claridad qué es lo que ha sido absolutamente igual en todos los métodos separados. Entonces, hoy vamos a implementar lo que se puede hacer en un método universal que trabaje con cada uno de los indicadores estándar. Eso simplificará y reducirá considerablemente el tamaño del código de la clase de colección de los búferes de indicador.

Y, finalmente, hoy del indicador final será eliminado el cálculo y la definición de los niveles del indicador estándar en la subventana, y del rango decimal de los datos de las líneas de los indicadores estándar, que se muestran en la ventana de datos del terminal. Guardaremos este cálculo en el archivo de las funciones de servicio de la biblioteca, será invocado del indicador de prueba. Eso simplificará el código final haciéndolo más claro. Por otra parte, eso librará al usuario final de la biblioteca de la rutina.


Mejorando las clases de la biblioteca

Para poder procesar diferentes indicadores usando el mismo método, en la enumeración de los nombres de las líneas de cada uno de los indicadores tenemos que hacer que los valores numéricos de las líneas de diferentes indicadores coincidan. Es que podemos relacionar cada línea de cualquier indicador con tres siguientes tipos:

  1. Linea superior, o bien línea principal, o bien Jaws,
  2. Línea inferior, o bien línea de señal, o bien Teeth y +DI,
  3. Línea media, o bien Lips y -DI

Aquí por ahora no se consideran las líneas del indicador Ichimoku. Eso será más tarde, cuando vamos a convertirlo en un indicador de períodos múltiples.

Si ahora todas las líneas van a corresponder entre sí de esta manera, podremos hacer fácilmente que los manejadores de cada línea del indicador no sean diferentes, sino se encuentren en el mismo método.

Abrimos el archivo \MQL5\Include\DoEasy\Defines.mqh e introducimos cambios necesarios en la enumeración de los tipos de las líneas de indicadores estándar:

//+------------------------------------------------------------------+
//| Líneas del indicador                                             |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_LINE_MODE
  {
   INDICATOR_LINE_MODE_MAIN      =  0,                      // Línea principal
   INDICATOR_LINE_MODE_SIGNAL    =  1,                      // Línea de señal
   INDICATOR_LINE_MODE_UPPER     =  0,                      // Línea superior
   INDICATOR_LINE_MODE_LOWER     =  1,                      // Línea inferior
   INDICATOR_LINE_MODE_MIDDLE    =  2,                      // Línea media
   INDICATOR_LINE_MODE_JAWS      =  0,                      // Línea Jaws
   INDICATOR_LINE_MODE_TEETH     =  1,                      // Línea Teeth
   INDICATOR_LINE_MODE_LIPS      =  2,                      // Línea Lips
   INDICATOR_LINE_MODE_DI_PLUS   =  1,                      // Línea +DI
   INDICATOR_LINE_MODE_DI_MINUS  =  2,                      // Línea -DI
  };
//+------------------------------------------------------------------+

Puesto que necesitamos copiar los datos de la matriz fuente del indicador estándar a la matriz receptora del objeto del búfer de cálculo con desplazamiento establecido para la línea de indicador, cambiamos el método que establece el desplazamiento de la construcción gráfica del indicador (ahora contiene una verificación que detecta el búfer de cálculo; en este caso, salimos del método y los valores no se establecen). Este método se encuentra en el archivo \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh de la clase del objeto del búfer abstracto. Simplemente, eliminamos esta verificación del método:

//+-----------------------------------------------------------------------+
//| Establece el desplazamiento de la construcción gráfica del indicador  |
//+-----------------------------------------------------------------------+
void CBuffer::SetShift(const int shift)
  {
   if(this.TypeBuffer()==BUFFER_TYPE_CALCULATE)
      return;
   this.SetProperty(BUFFER_PROP_SHIFT,shift);
   ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+

Ahora, este método tendrá el aspecto siguiente:

//+----------------------------------------------------------------------+
//| Establece el desplazamiento de la construcción gráfica del indicador |
//+----------------------------------------------------------------------+
void CBuffer::SetShift(const int shift)
  {
   this.SetProperty(BUFFER_PROP_SHIFT,shift);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetInteger((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+

Primero, definimos un valor del desplazamiento para cualquier búfer. Luego, si el búfer no es de cálculo, establecemos el desplazamiento de la línea dibujada para él.

En el archivo \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh del objeto del búfer de cálculo, tenemos que definir el punto inicial (barra) a partir del cual los datos empiezan a copiarse del búfer del indicador estándar. Para eso, en el método del copiado de datos del indicador especificado a la matriz del objeto de búfer, hay que indicar un valor del inicio para el copiado de datos con el signo menos:

//+-------------------------------------------------------------------------------+
//| Copia los datos del indicador especificado a la matriz del objeto de búfer    |
//+-------------------------------------------------------------------------------+
int CBufferCalculate::FillAsSeries(const int indicator_handle,const int buffer_num,const int start_pos,const int count)
  {
   return ::CopyBuffer(indicator_handle,buffer_num,-start_pos,count,this.DataBuffer[0].Array);
  }
//+------------------------------------------------------------------+

Aquí todo es bastante simple: vamos a pasar al método el valor del desplazamiento de la línea del indicador como la barra inicial para el copiado de datos. Si el desplazamiento es positivo, los datos se copian con un margen negativo; si el desplazamiento es negativo, los datos se copian con un margen positivo. De esta manera, siempre pasamos al inicio correcto de datos necesarios de la matriz fuente.

Ahora, en el archivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh de la clase de colección de los objetos de búfer, vamos a mejorar todos los métodos de la creación de los indicadores estándar que admiten el desplazamiento de las líneas del indicador.

Vamos a ver un ejemplo del método para la creación del objeto del indicador estándar Alligator:

//+------------------------------------------------------------------+
//| Crea Alligator de período y símbolo múltiples                    |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAlligator(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=WRONG_VALUE)
  {
//--- Calculamos y definimos el número de barras 
   int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(PERIOD_CURRENT);
   int shift_jaw=jaw_shift*num_bars;
   int shift_teeth=teeth_shift*num_bars;
   int shift_lips=lips_shift*num_bars;
//--- Create the indicator handle and set the default ID
   int handle=::iAlligator(symbol,timeframe,jaw_period,shift_jaw,teeth_period,shift_teeth,lips_period,shift_lips,ma_method,applied_price);
   int identifier=(id==WRONG_VALUE ? IND_ALLIGATOR : id);
   color array_colors[1]={clrBlue};
   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 Jaws line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_jaw);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+")");
      buff.SetColors(array_colors);
      
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary Teeth line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_teeth);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(string)teeth_period+")");
      array_colors[0]=clrRed;
      buff.SetColors(array_colors);
      
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary Lips line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_lips);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetShowData(true);
      buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
      buff.SetIndicatorName("Alligator");
      buff.SetIndicatorShortName("Alligator("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+","+(string)teeth_period+","+(string)lips_period+")");
      buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(string)lips_period+")");
      array_colors[0]=clrLime;
      buff.SetColors(array_colors);
      
      //--- Create a calculated Jaws line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary Jaws line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_jaw);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_JAWS);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Jaws("+symbol+","+TimeframeDescription(timeframe)+": "+(string)jaw_period+")");
      
      //--- Create a calculated Teeth line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary Teeth line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_teeth);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_TEETH);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Teeth("+symbol+","+TimeframeDescription(timeframe)+": "+(string)teeth_period+")");
      
      //--- Create a calculated Lips line buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary Lips line parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetShift(shift_lips);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ALLIGATOR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLineMode(INDICATOR_LINE_MODE_LIPS);
      buff.SetIndicatorName("Alligator");
      buff.SetLabel("Lips("+symbol+","+TimeframeDescription(timeframe)+": "+(string)lips_period+")");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Vamos a ver que tenemos aquí.

En primer lugar, vamos a contar cuántas barras del gráfico actual van a caber en el período del gráfico en el que se calcula el indicador.
A continuación, calculamos el número de las barras del desplazamiento, multiplicando los valores del desplazamiento de tres líneas del indicador pasados en el método por el número calculado de las barras.
Al crear el manejador del indicador, indicamos los valores calculados anteriormente como parámetros que definen el número de las barras del desplazamiento de cada línea, en vez de los valores que han sido pasados en el método. Los objetos de búfer creados (tanto dibujados, como de cálculo) reciben los valores de desplazamientos calculados al principio. Establecemos sus propios valores de la línea correspondiente del indicador (Jaws, Teeth y Lips) para cada pareja de búferes (dibujado-de cálculo).
Así, el objeto del indicador estándar y de sus búferes está completamente preparado para el dibujado de los datos con desplazamiento de la línea de indicador.
Todas las demás acciones definidas en el método de la creación del objeto del indicador estándar Alligator fueron consideradas en los artículos anteriores, cuando describíamos los métodos de la creación de los objetos de otros indicadores estándar.

Las modificaciones descritas arriba y los complementos para calcular el desplazamiento de la línea de indicador ya figuran en todos los métodos de la creación de indicadores estándar que contienen el parámetro que define el número de las barras del desplazamiento de la línea:
CreateAlligator()
, CreateAMA(), CreateBands(), CreateDEMA(), CreateEnvelopes(), CreateFrAMA(), CreateMA(), CreateStdDev(), CreateTEMA() y CreateVIDYA().

Las modificaciones en cada método son prácticamente idénticas a las que han sido consideradas antes. Por tanto, no hay ninguna necesidad de examinarlas aquí.
Se puede estudiar el código completo de la clase de la colección de los objetos de búfer en los archivos adjuntos al artículo.

Si tenemos creados varios objetos de búfer de los indicadores estándar, pero no los usamos todos al mismo tiempo, en el gráfico van a visualizarse los artefactos de los búferes de indicador que no se usan en este momento. Para evitar esta situación, primero, tenemos que rellenar todas las matrices de los búferes de indicador con sus valores vacíos, y sólo luego, calcular los necesarios en este momento. Para este propósito, vamos a crear el método que vacía todos los búferes de uno de los objetos de búfer de los indicadores estándar según el índice especificado de la serie temporal.

Declaramos el método en la sección pública de la clase:

//--- Clear buffer data (1) of the specified standard indicator and (2) all created standard indicators by the timeseries index
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
   void                    ClearDataAllBuffersStdInd(int series_index);
//--- Set the values for the current chart to the buffers of the specified standard indicator by the timeseries index according to the buffer object period/symbol

Escribimos su implementación fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Clear the calculated buffer data of all created                  |
//| standard indicators by the timeseries index                      |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataAllBuffersStdInd(int series_index)
  {
   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;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_CALCULATE || buff.IndicatorType()==WRONG_VALUE)
         continue;
      this.ClearDataBufferStdInd(buff.IndicatorType(),buff.ID(),series_index);
     }
  }
//+------------------------------------------------------------------+

Aquí:
Obtenemos la lista de todos los objetos de búfer para los que ha sido definido un identificador del indicador.
En un ciclo por la lista obtenida,
obtenemos el siguiente objeto de búfer.
Si el objeto no ha sido obtenido por alguna razón, o se trata de un búfer de cálculo, o el tipo del indicador estándar no ha sido definido para él
(puesto que el identificador puede ser asignado no sólo a un indicador estándar, sino también a cualquier objeto de búfer), entonces omitimos este objeto.
Finalmente, llamamos al método de la limpieza del búfer del indicador estándar especificado. Lo analizamos en el artículo anterior.

Vamos a mejorar los métodos para la preparación y la limpieza de las matrices de los búferes de cálculo que guardan los datos de los búferes de indicadores estándar. Antes, en estos métodos, fueron creados los bloques del código para agrupar las acciones del mismo tipo para los indicadores que tenían los nombres/designaciones iguales de sus líneas. Ahora, después de atribuir los mismos valores numéricos a todas las líneas del mismo tipo (hemos hablado de eso al principio del artículo) en la enumeración de los tipos de las líneas de indicadores estándar, podemos simplificar estos métodos reduciendo todo a tres bloques de códigos: para una, dos y tres líneas de indicadores estándar.

Este es el método que prepara los datos del búfer de cálculo del indicador estándar indicado:

//+------------------------------------------------------------------+
//| 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_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list_ind==NULL || list_ind.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copied=WRONG_VALUE;
   int idx0=0,idx1=1,idx2=2;
   switch((int)std_ind)
     {
   //--- Single-buffer standard indicators
      case IND_AC          :
      case IND_AD          :
      case IND_AMA         :
      case IND_AO          :
      case IND_ATR         :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_BWMFI       :
      case IND_CCI         :
      case IND_CHAIKIN     :
      case IND_DEMA        :
      case IND_DEMARKER    :
      case IND_FORCE       :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_MFI         :
      case IND_MOMENTUM    :
      case IND_OBV         :
      case IND_OSMA        :
      case IND_RSI         :
      case IND_SAR         :
      case IND_STDDEV      :
      case IND_TEMA        :
      case IND_TRIX        :
      case IND_VIDYA       :
      case IND_VOLUMES     :
      case IND_WPR         :
      
        buffer=list_ind.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),0,buffer.Shift(),total_copy);
        return copied;
      
   //--- Multi-buffer standard indicators
      case IND_ENVELOPES   :
      case IND_FRACTALS    :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        idx0=0;
        idx1=1;
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_ALLIGATOR   :
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
        if(std_ind==IND_BANDS)
          {
           idx0=1;
           idx1=2;
           idx2=0;
          }
        else
          {
           idx0=0;
           idx1=1;
           idx2=2;
          }
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx0,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx1,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return 0;
        copied=buffer.FillAsSeries(buffer.IndicatorHandle(),idx2,buffer.Shift(),total_copy);
        if(copied<total_copy) return 0;
        return copied;
      
      case IND_GATOR       :
      case IND_ICHIMOKU    :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

En el bloque del código para tres búferes del indicador, hemos añadido la verificación del procesamiento de líneas del indicador Bollinger Bands, porque la indexación de las matrices de sus búferes se diferencia de la indexación de los búferes para todos los demás indicadores estándar de tres búferes.. Por ahora, no hay procesamiento de los indicadores Gator Oscillator y Ichimoku Kinko Hyo por las razones descritas al principio del artículo.

Método que limpia los datos del búfer del indicador estándar indicado según el índice de la serie temporal:

//+------------------------------------------------------------------+
//| 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_ind=this.GetListBufferByTypeID(std_ind,id);
   CArrayObj *list0=NULL,*list1=NULL,*list2=NULL;
   if(list_ind==NULL || list_ind.Total()==0)
      return;
   list_ind=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list_ind.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
   //--- Single-buffer standard indicators
      case IND_AC          :
      case IND_AD          :
      case IND_AMA         :
      case IND_AO          :
      case IND_ATR         :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_BWMFI       :
      case IND_CCI         :
      case IND_CHAIKIN     :
      case IND_DEMA        :
      case IND_DEMARKER    :
      case IND_FORCE       :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_MFI         :
      case IND_MOMENTUM    :
      case IND_OBV         :
      case IND_OSMA        :
      case IND_RSI         :
      case IND_SAR         :
      case IND_STDDEV      :
      case IND_TEMA        :
      case IND_TRIX        :
      case IND_VIDYA       :
      case IND_VOLUMES     :
      case IND_WPR         :
        buffer=list_ind.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
   //--- Multi-buffer standard indicators
      case IND_ENVELOPES   :
      case IND_FRACTALS    :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
      case IND_GATOR       :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;

      case IND_ALLIGATOR   :
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
        list0=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer=list0.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list1=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer=list1.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        
        list2=CSelect::ByBufferProperty(list_ind,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer=list2.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Aquí, todos los indicadores estándar están distribuidos de la manera igual por los bloques correspondientes del código: los indicadores estándar de uno, dos y tres búferes. Aquí, ya nos no importa la secuencia de la limpieza de los datos de búferes, por eso, Bollinger Bands y Gator Oscillator ya están incluidos en los bloques del código del procesamiento de indicadores de dos y tres búferes. Por ahora, no vamos a procesar Ichimoku de ningún modo.

Y ya que unificamos el procesamiento de los indicadores estándar de uno, dos y tres búferes, ahora podemos reducir considerablemente el código del método que define los valores para el gráfico actual en los búferes del indicador estándar indicado según el índice de la serie temporal de acuerdo con el símbolo/período del objeto de búfer. Este es el código completo antes de su transformación:

//+------------------------------------------------------------------+
//| Set values for the current chart to the buffers of the specified |
//| 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_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   CBuffer *buffer_calc2=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   uchar clr=color_index;
   long vol0=0,vol1=0;
   datetime time_period=0,time_shift=0;
   double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
   double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
   double value20=EMPTY_VALUE, value21=EMPTY_VALUE;

//--- Depending on the standard indicator type
   switch((int)ind_type)
     {
   //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        //--- Get drawn and calculated buffer objects
        buffer_data0=list_data.At(0);
        buffer_calc0=list_calc.At(0);
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time on the indicator buffer chart period
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Calculate the bar shift relative to the indicator line shift direction
           //index_period+=buffer_data0.Shift();
           //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
           time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period); // -buffer_calc0.Shift()+index_shift
           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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars+(num_bars*buffer_calc0.Shift())));
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           if(ind_type!=IND_BWMFI)
              clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           else
             {
              vol0=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
              vol1=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period+1);
              clr=
                (
                 value00>value01 && vol0>vol1 ? 0 :
                 value00<value01 && vol0<vol1 ? 1 :
                 value00>value01 && vol0<vol1 ? 2 :
                 value00<value01 && vol0>vol1 ? 3 : 4
                );
             }
           buffer_data0.SetBufferColorIndex(index,clr);
          }
        return true;
      
   //--- Multi-buffer standard indicators
      case IND_ADX   :
      case IND_ADXW  :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_PLUS,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_DI_MINUS,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data2.SetBufferValue(2,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_BANDS    :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MIDDLE,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(0,index,value10);
           buffer_data2.SetBufferValue(0,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_MAIN,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SIGNAL,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ALLIGATOR:
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_JAWS,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TEETH,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LIPS,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(0,index,value10);
           buffer_data2.SetBufferValue(0,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_GATOR    :
        //--- Get drawn and calculated buffer objects
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_data1=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_UPPER,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_LOWER,EQUAL);
        buffer_calc1=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
           return false;
        //--- Get the value by the index from the indicator buffer
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
        if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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_calc0.Symbol(),buffer_calc0.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_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10<value11 ? 0 : value10>value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Los bloques del código iguales están marcados con el color en el listado del método. Es evidente que podemos sacar todo este procesamiento homologable a un método, lo cual nos permitirá reducir el código del método.

En la sección pública de la clase, vamos a declarar el método privado que prepara los datos del indicador estándar indicado para colocar los valores en el gráfico actual del símbolo (trasladaremos a este método todo el procesamiento homologable desde el método analizado antes del artículo anterior):

//--- Clear buffer data (1) of the specified standard indicator and (2) all created standard indicators by the timeseries index
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
   void                    ClearDataAllBuffersStdInd(int series_index);
//--- Set the values for the current chart to the buffers of the specified standard indicator by the timeseries index according to the buffer object period/symbol
   bool                    SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE);
private:
//--- Prepare data of the specified standard indicator for setting values on the current symbol chart
   int                     PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                                  CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                                  const ENUM_INDICATOR ind_type,
                                                  const int series_index,
                                                  const datetime series_time,
                                                  int &index_period,
                                                  int &num_bars,
                                                  double &value00,
                                                  double &value01,
                                                  double &value10,
                                                  double &value11,
                                                  double &value20,
                                                  double &value21);
public:

//--- Return the buffer (1) by the graphical series name, (2) by timeframe,
//--- (3) by Plot index, (4) by object index in the collection list, (5) the last created one,
//--- list of buffers (6) by ID, (7) by standard indicator type, (8) by type and ID

Vamos a transmitir al método los punteros a los objetos de búfer y los datos de la serie temporal, así como las variables por referencia cuyos valores deberán ser devueltos del método para su procesamiento posterior en el método que estamos reduciendo.

Vamos a escribir la implementación de este método fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Prepare data of the specified standard indicator                 |
//| for setting values on the current symbol chart                   |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingSetDataStdInd(CBuffer *buffer_data0,CBuffer *buffer_data1,CBuffer *buffer_data2,
                                               CBuffer *buffer_calc0,CBuffer *buffer_calc1,CBuffer *buffer_calc2,
                                               const ENUM_INDICATOR ind_type,
                                               const int series_index,
                                               const datetime series_time,
                                               int &index_period,
                                               int &num_bars,
                                               double &value00,
                                               double &value01,
                                               double &value10,
                                               double &value11,
                                               double &value20,
                                               double &value21)
  {
     //--- Find the bar index corresponding to the current bar start time
     index_period=::iBarShift(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),series_time,true);
     if(index_period==WRONG_VALUE || index_period>buffer_calc0.GetDataTotal()-1)
        return WRONG_VALUE;
     
     //--- Get the value by the index from the indicator buffer
     if(buffer_calc0!=NULL)
        value00=buffer_calc0.GetDataBufferValue(0,index_period);
     if(buffer_calc1!=NULL)
        value10=buffer_calc1.GetDataBufferValue(0,index_period);
     if(buffer_calc2!=NULL)
        value20=buffer_calc2.GetDataBufferValue(0,index_period);
     
     int series_index_start=series_index;
     //--- The current chart requires no calculation of the number of handled bars since there is only one bar
     if(buffer_calc0.Symbol()==::Symbol() && buffer_calc0.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
        datetime time_period=::iTime(buffer_calc0.Symbol(),buffer_calc0.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 WRONG_VALUE;
        //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
        num_bars=::PeriodSeconds(buffer_calc0.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
        if(num_bars==0) num_bars=1;
       }
     //--- Set values to calculate colors
     if(buffer_calc0!=NULL)
        value01=(series_index_start+num_bars>buffer_data0.GetDataTotal()-1 ? value00 : buffer_data0.GetDataBufferValue(0,series_index_start+num_bars));
     if(buffer_calc1!=NULL)
        value11=(series_index_start+num_bars>buffer_data1.GetDataTotal()-1 ? value10 : buffer_data1.GetDataBufferValue(0,series_index_start+num_bars));
     if(buffer_calc2!=NULL)
        value21=(series_index_start+num_bars>buffer_data2.GetDataTotal()-1 ? value20 : buffer_data2.GetDataBufferValue(0,series_index_start+num_bars));
   
   return series_index_start;
  }
//+------------------------------------------------------------------+

Por lo general, aquí todo está claro —hemos trasladado el bloque repetido del código de un método a otro nuevo. Puesto que en el método reducido se requiere usar muchos datos que se calculan dentro de este método privado, simplemente se puede transferir a nuevo método las variables por referencia. Entonces, todos los cambios de los valores de estas variables estarán disponibles en el método que realiza la llamada. Aparte del establecimiento de los valores para las variables por referencia, este método devuelve el índice de la barra a partir de la cual es necesario rellenar cíclicamente los datos del búfer en el gráfico actual en el método que realiza la llamada. Si el procesamiento de los datos ha fallado, el método devuelve el valor -1.

Ahora veamos en cuánto se ha reducido el método que define los valores para el gráfico actual en los búferes del indicador estándar indicado según el índice de la serie temporal de acuerdo con el símbolo/período del objeto de búfer:

//+------------------------------------------------------------------+
//| Set values for the current chart to the buffers of the specified |
//| 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_data0=NULL;
   CBuffer *buffer_data1=NULL;
   CBuffer *buffer_data2=NULL;
   CBuffer *buffer_calc0=NULL;
   CBuffer *buffer_calc1=NULL;
   CBuffer *buffer_calc2=NULL;
   double value00=EMPTY_VALUE, value01=EMPTY_VALUE;
   double value10=EMPTY_VALUE, value11=EMPTY_VALUE;
   double value20=EMPTY_VALUE, value21=EMPTY_VALUE;
   long vol0=0,vol1=0;
   int series_index_start=series_index,index_period=0, index=0,num_bars=1;
   uchar clr=0;
//--- Depending on the standard indicator type

   switch((int)ind_type)
     {
   //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AMA      :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_BWMFI    :
      case IND_CCI      :
      case IND_CHAIKIN  :
      case IND_DEMA     :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_FRAMA    :
      case IND_MA       :
      case IND_MFI      :
      case IND_MOMENTUM :
      case IND_OBV      :
      case IND_OSMA     :
      case IND_RSI      :
      case IND_SAR      :
      case IND_STDDEV   :
      case IND_TEMA     :
      case IND_TRIX     :
      case IND_VIDYA    :
      case IND_VOLUMES  :
      case IND_WPR      :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                                  ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           if(ind_type!=IND_BWMFI)
              clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           else
             {
              vol0=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period);
              vol1=::iVolume(buffer_calc0.Symbol(),buffer_calc0.Timeframe(),index_period+1);
              clr=
                (
                 value00>value01 && vol0>vol1 ? 0 :
                 value00<value01 && vol0<vol1 ? 1 :
                 value00>value01 && vol0<vol1 ? 2 :
                 value00<value01 && vol0>vol1 ? 3 : 4
                );
             }
           buffer_data0.SetBufferColorIndex(index,clr);
          }
        return true;
      
   //--- Multi-buffer standard indicators
      case IND_ENVELOPES :
      case IND_FRACTALS  :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
           
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
           
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                                  ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ADX         :
      case IND_ADXW        :
      case IND_BANDS       :
      case IND_MACD        :
      case IND_RVI         :
      case IND_STOCHASTIC  :
      case IND_ALLIGATOR   :
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_data0=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_data1=list.At(0);
        list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer_data2=list.At(0);
        
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL);
        buffer_calc0=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL);
        buffer_calc1=list.At(0);
        list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,2,EQUAL);
        buffer_calc2=list.At(0);
        
        if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0)
           return false;
        if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0)
           return false;
        if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0)
           return false;
        
        series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_calc0,buffer_calc1,buffer_calc2,
                                                  ind_type,series_index,series_time,index_period,num_bars,value00,value01,value10,value11,value20,value21);
        if(series_index_start==WRONG_VALUE)
           return false;
        //--- 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 value00 and value01 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data0.SetBufferValue(0,index,value00);
           buffer_data1.SetBufferValue(1,index,value10);
           buffer_data2.SetBufferValue(2,index,value20);
           buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index);
           buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index);
           buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_GATOR    :
      case IND_ICHIMOKU :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Ahora la llamada al método y la verificación del resultado devuelto de él sustituyen todos los cálculos que se repetían antes de bloque a bloque . Por ahora, los indicadores Gator Oscillator y Ichimoku Kinko Hyo no se procesan aquí por las razones mencionadas antes.

Prueba

Para la prueba, vamos a usar el indicador de prueba del artículo anterior
. Lo guardaremos en la nueva carpeta \MQL5\Indicators\TestDoEasy\Part50\ con el nuevo nombre TestDoEasyPart50.mq5.

Añadimos el desplazamiento de la línea de indicador a los parámetros externos del indicador:

//--- 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   int                  InpShift          =  0;             // Indicator line shift
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

Tenemos que comprobar cómo funciona el desplazamiento de las líneas de indicadores. Todos los indicadores estándar que permiten representar su línea con desplazamiento, muestran sus datos en la ventana principal del gráfico (a excepción del indicador Gator Oscillator que se visualiza en una subventana, pero todavía no está implementado). Por eso, en el manejador OnInit(), vamos a incluir el bloque del código para crear objetos de indicadores estándar que funcionan en la ventana principal:

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
//--- Single-buffer standard indicators in the main window
      case IND_AMA         :  success=engine.BufferCreateAMA(InpUsedSymbols,InpPeriod,9,2,30,InpShift,PRICE_CLOSE,1);                  break;
      case IND_DEMA        :  success=engine.BufferCreateDEMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                     break;
      case IND_FRAMA       :  success=engine.BufferCreateFrAMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                    break;
      case IND_MA          :  success=engine.BufferCreateMA(InpUsedSymbols,InpPeriod,10,InpShift,MODE_SMA,PRICE_CLOSE,1);              break;
      case IND_SAR         :  success=engine.BufferCreateSAR(InpUsedSymbols,InpPeriod,0.02,0.2,1);                                     break;
      case IND_TEMA        :  success=engine.BufferCreateTEMA(InpUsedSymbols,InpPeriod,14,InpShift,PRICE_CLOSE,1);                     break;
      case IND_VIDYA       :  success=engine.BufferCreateVIDYA(InpUsedSymbols,InpPeriod,9,12,InpShift,PRICE_CLOSE,1);                  break;
      
//--- Multi-buffer standard indicators in a subwindow
      case IND_ALLIGATOR   :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN,1);    break;
      case IND_BANDS       :  success=engine.BufferCreateBands(InpUsedSymbols,InpPeriod,20,InpShift,2.0,PRICE_CLOSE,1);                break;
      case IND_ENVELOPES   :  success=engine.BufferCreateEnvelopes(InpUsedSymbols,InpPeriod,14,InpShift,MODE_SMA,PRICE_CLOSE,0.1,1);   break;
      case IND_FRACTALS    :  success=engine.BufferCreateFractals(InpUsedSymbols,InpPeriod,1);                                         break;
      
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }

El parámetro externo que establece el desplazamiento de la línea se transmite a los métodos de creación de los objetos de indicadores estándar. Para el indicador Alligator, el desplazamiento se define para cada una de sus tres líneas. Estos valores ya fueron «soldados rígidamente» en la lógica de la idea del indicador por su autor.

En los indicadores de prueba anteriores había un bloque del código que establecía y definía los niveles para los indicadores en la subventana y el rango decimal para los datos del indicador en la ventana de datos del terminal:

//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    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_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        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_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);

Quitamos este bloque del código del indicador final. Para eso, vamos a escribir una nueva función en el archivo de las funciones de servicio de la biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh y trasladamos este bloque del código a ella:

//+------------------------------------------------------------------+
//| Set standard indicator's decimal capacity and levels             |
//+------------------------------------------------------------------+
void SetIndicatorLevels(const string symbol,const ENUM_INDICATOR ind_type)
  {
   int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   switch(ind_type)
     {
      case IND_AD          :
      case IND_CHAIKIN     :
      case IND_OBV         :
      case IND_VOLUMES     : digits=0;    break;
      
      case IND_AO          :
      case IND_BEARS       :
      case IND_BULLS       :
      case IND_FORCE       :
      case IND_STDDEV      :
      case IND_AMA         :
      case IND_DEMA        :
      case IND_FRAMA       :
      case IND_MA          :
      case IND_TEMA        :
      case IND_VIDYA       :
      case IND_BANDS       :
      case IND_ENVELOPES   :
      case IND_MACD        : digits+=1;   break;
      
      case IND_AC          :
      case IND_OSMA        : digits+=2;   break;
      
      case IND_MOMENTUM    : digits=2;    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_MFI         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        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_STOCHASTIC  :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        digits=2;
        break;
      case IND_WPR         :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
     
      case IND_ATR         :              break;
      case IND_SAR         :              break;
      case IND_TRIX        :              break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
  }
//+------------------------------------------------------------------+

Ahora, al final del manejador OnInit() del indicador será suficiente sólo llamar a esta función, con lo cual el código se hace más simple y claro:

//--- Set the indicator short name, digital capacity and levels
   string label=engine.BufferGetIndicatorShortNameByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   SetIndicatorLevels(InpUsedSymbols,InpIndType);

//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Dejamos el manejador OnCalculate() sin modificaciones. El código completo del indicador de prueba está disponible en los archivos adjuntos al artículo.

Vamos a compilar el indicador e iniciallo en el gráfico EURUSD H1, estableciendo previamente el uso del símbolo EURUSD H4 en los ajustes. Definimos el desplazamiento de la línea de indicador de 4 barras y seleccionamos el indicador Bollinger Bands. Luego, seleccionamos el indicador Alligator en los ajustes.


Como podemos observar, Bollinger Bands se visualiza correctamente con el desplazamiento especificado de 4 barras, mientras que Alligator no reacciona al desplazamiento de 4 barras —tiene los valores predefinidos durante su creación en el código OnInit() que son iguales a estos valores del indicador estándar:

//--- Multi-buffer standard indicators in a subwindow
      case IND_ALLIGATOR   :  success=engine.BufferCreateAlligator(InpUsedSymbols,InpPeriod,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN,1);    break;

Entonces, Alligator también visualiza correctamente sus líneas con el desplazamiento estándar de sus líneas.


¿Qué es lo próximo?

En el próximo artículo, continuaremos desarrollando los métodos de la biblioteca para trabajar con indicadores estándar en el modo de símbolo y período múltiples.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo 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 sólo 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 ponerla 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
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 49): Indicadores estándar de periodo, símbolo y búfer múltiples

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

Archivos adjuntos |
MQL5.zip (3757.38 KB)
¿Qué son las tendencias y cómo es la estructura de los mercados: de tendencia o plana? ¿Qué son las tendencias y cómo es la estructura de los mercados: de tendencia o plana?

Los tráders hablan con frecuencia sobre tendencias y mercado plano (flat), pero muchos de ellos no entienden correctamente qué es en realidad una tendencia o un flat, y son muy pocos los capaces de explicar estos conceptos. Alrededor de estos conceptos básicos, se ha ido formando un conjunto de prejuicios y confusiones que pervive a día de hoy. Y todo a pesar de que, para ganar dinero, es necesario comprender su sentido matemático y lógico. En este artículo, veremos con detalle qué es una tendencia, qué es el mercado plano, y cómo es la estructura de los mercados: de tendencia, plana, o de otro tipo. Asimismo, analizaremos cómo deberá ser una estrategia para ganar dinero en un mercado de tendencia, cómo deberá ser una estrategia para ganar dinero durante un mercado plano.

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.

Trabajando con las series temporales en la biblioteca DoEasy (Parte 51): Indicadores estándar compuestos de período y símbolo múltiples Trabajando con las series temporales en la biblioteca DoEasy (Parte 51): Indicadores estándar compuestos de período y símbolo múltiples

En este artículo, vamos a finalizar el desarrollo de indicadores estándar de período y símbolo múltiples. A base del indicador Ichimoku Kinko Hyo, vamos a analizar la creación de los indicadores personalizados de composición compleja que disponen de los búferes dibujados auxiliares para la visualización de los datos en el gráfico.

Trabajando con las series temporales en la biblioteca DoEasy (Parte 49): Indicadores estándar de período, símbolo y búfer múltiples Trabajando con las series temporales en la biblioteca DoEasy (Parte 49): Indicadores estándar de período, símbolo y búfer múltiples

En el presente artículo, vamos a mejorar las clases de la biblioteca para tener la posibilidad de crear los indicadores estándar de período y símbolo múltiples que requieren varios búferes de indicador para visualizar sus datos.