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

Artyom Trishkin | 10 noviembre, 2020

Contenido


Concepto

En el artículo de hoy, no comenzaremos con una introdución extensa: todos sabemos lo que supone un indicador estándar del paquete del terminal. También sabemos que estos indicadores representan en el gráfico del símbolo/periodo actual los datos calculados precisamente para ese mismo símbolo/periodo.
Hoy, vamos a implementar (concretamente, comenzaremos a implementar) la posibilidad de crear indicadores que muestren en el gráfico del símbolo/periodo actual los datos de todos los indicadores estándar disponibles en el terminal, calculados para los símbolos/periodos indicados.

En el presente artículo, vamos a analizar la creación de los métodos necesarios para crear un indicador de usuario basado en el indicador estándar AC (Accelerator Oscillator). Todos los métodos funcionarán también para otros indicadores estándar (haciéndoles pequeñas mejoras): los crearemos en próximos artículos.

Para crear e identificar los objetos de búfer para trabajar con los datos de los indicadores estándar, añadiremos nuevas propiedades al objeto de búfer:

Aparte de crear una base para trabajar con los datos de los indicadores estándar, mejoraremos ligeramente la clase de objeto "Barra nueva" y las clases de series temporales para monitorear las barras restantes de la historia y enviar el evento "Barras restamtes" al programa.
En caso de perder la conexión, de entrar en el sistema o salir del mismo en el modo reposo (y otros eventos imprevistos que requieran de tiempo para el restablecimiento), tras reanudar el trabajo, veremos que se ha omitido información en la base de datos de la biblioteca: en este tiempo en el que no ha habido conexión con el servidor se han omitido barras de la historia, y estas no han sido incluidas en la base de datos de la biblioteca. Vamos a crear algunos métodos que se encargarán de monitorear el número de barras omitidas y enviar el evento "Barras omitidas" al programa, de forma que el usuario pueda procesar esta situación en su programa.


Mejorando las clases de la biblioteca

En primer lugar, vamos a añadir al archivo \MQL5\Include\DoEasy\Datas.mqh los datos necesarios para mostrar mensajes.

Incluimos para ello los identificadores de los nuevos mensajes:

   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT,          // Index of the next drawn buffer
   MSG_LIB_TEXT_BUFFER_TEXT_ID,                       // Indicator buffer ID
   MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE,               // Handle of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE,                 // Type of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME,                // Buffer (timeframe) data period
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS,                   // Buffer status
   MSG_LIB_TEXT_BUFFER_TEXT_TYPE,                     // Buffer type
   MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE,                   // Active
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE,               // Arrow code
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT,              // The vertical shift of the arrows
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN,               // The number of initial bars that are not drawn and values in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE,                // Graphical construction type
   MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA,                // Display construction values in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_SHIFT,                    // Indicator graphical construction shift by time axis in bars
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE,               // Line style
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH,               // Line width
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE,               // Arrow size
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM,                // Number of colors
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR,                    // Drawing color
   MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE,              // Empty value for plotting where nothing will be drawn
   MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL,                   // Buffer symbol
   MSG_LIB_TEXT_BUFFER_TEXT_LABEL,                    // Name of the graphical indicator series displayed in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME,                 // Name of an indicator using a buffer
   

Y los mensajes de texto que se corresponden con los identificadores nuevamente añadidos:

   {"Индекс следующего по счёту рисуемого буфера","Index of the next drawable buffer"},
   {"Идентификатор буферов индикатора","Indicator Buffer Id"},
   {"Хэндл индикатора, использующего буфер","Indicator handle that uses buffer"},
   {"Тип индикатора, использующего буфер","Indicator type that uses buffer"},
   {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"},
   {"Статус буфера","Buffer status"},
   {"Тип буфера","Buffer type"},
   {"Активен","Active"},
   {"Код стрелки","Arrow code"},
   {"Смещение стрелок по вертикали","Vertical shift of arrows"},
   {"Количество начальных баров без отрисовки и значений в DataWindow","Number of initial bars without drawing and values in DataWindow"},
   {"Тип графического построения","Type of graphical construction"},
   {"Отображение значений построения в окне DataWindow","Display construction values in DataWindow"},
   {"Сдвиг графического построения индикатора по оси времени в барах","Shift of indicator plotting along time axis in bars"},
   {"Стиль линии отрисовки","Drawing line style "},
   {"Толщина линии отрисовки","Thickness of drawing line"},
   {"Размер значка стрелки","Arrow icon size"},
   {"Количество цветов","Number of colors"},
   {"Цвет отрисовки","Index of buffer containing drawing color"},
   {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"},
   {"Символ буфера","Buffer Symbol"},
   {"Имя индикаторной графической серии, отображаемое в окне DataWindow","Name of indicator graphical series to display in DataWindow"},
   {"Наименование индикатора, использующего буфер","Name of indicator that uses buffer"},
   {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"},
   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},


En el archivo \MQL5\Include\DoEasy\Defines.mqh, añadimos todo lo necesario para las tareas que implementamos hoy.

En el apartado "Macrosustituciones", cambiamos el nombre de la constante que guarda el valor del número de intentos comerciales por defecto por uno más informativo:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG                   ("Russian")                // Country language
#define END_TIME                       (D'31.12.3000 23:59:59')   // End date for account history data requests
#define TIMER_FREQUENCY                (16)                       // Minimal frequency of the library timer in milliseconds
#define TOTAL_TRADE_TRY                (5)                        // Default number of trading attempts
#define IND_COLORS_TOTAL               (64)                       // Maximum possible number of indicator buffer colors
#define IND_BUFFERS_MAX                (512)                      // Maximum possible number of indicator buffers
//--- Standard sounds

Antes, la constante tenía la denominación TOTAL_TRY. Dicha denominación no resulta informativa, y dado que posteriormente podría aparecer alguna constante más que indique el número de intentos comerciales, si añadimos a la denominación de la constante la pertenencia a esta o aquella acción (en este caso, "TRADE", la pertenencia a los intentos comerciales), la denominación será más informativa, y no necesitaremos cambiar el nombre de esta constante al añadir nuevas constantes para otros "números de intentos".

Añadimos el nuevo evento a la enumeración de posibles eventos de las series temporales:

//+------------------------------------------------------------------+
//| List of possible timeseries events                               |
//+------------------------------------------------------------------+
enum ENUM_SERIES_EVENT
  {
   SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE,        // no event
   SERIES_EVENTS_NEW_BAR,                                   // "New bar" event
   SERIES_EVENTS_MISSING_BARS,                              // "Bars skipped" event
  };
#define SERIES_EVENTS_NEXT_CODE  (SERIES_EVENTS_MISSING_BARS+1)   // Code of the next event after the "Bars skipped" event
//+------------------------------------------------------------------+

Por consiguiente, el código del siguiente evento ahora se basará en la nueva constante.

Ya hemos hablado sobre la adición de nuevas propiedades a los objetos de búfer. Vamos a escribir sus enumeraciones para las propiedades de tipo entero y string del objeto de búfer:

//+------------------------------------------------------------------+
//| Buffer integer properties                                        |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_INTEGER
  {
   BUFFER_PROP_INDEX_PLOT = 0,                              // Plotted buffer serial number
   BUFFER_PROP_STATUS,                                      // Buffer status (by drawing style) from the ENUM_BUFFER_STATUS enumeration
   BUFFER_PROP_TYPE,                                        // Buffer type (from the ENUM_BUFFER_TYPE enumeration)
   BUFFER_PROP_TIMEFRAME,                                   // Buffer period data (timeframe)
   BUFFER_PROP_ACTIVE,                                      // Buffer usage flag
   BUFFER_PROP_DRAW_TYPE,                                   // Graphical construction type (from the ENUM_DRAW_TYPE enumeration)
   BUFFER_PROP_ARROW_CODE,                                  // Arrow code for DRAW_ARROW style
   BUFFER_PROP_ARROW_SHIFT,                                 // The vertical shift of the arrows for DRAW_ARROW style
   BUFFER_PROP_LINE_STYLE,                                  // Line style
   BUFFER_PROP_LINE_WIDTH,                                  // Line width
   BUFFER_PROP_DRAW_BEGIN,                                  // The number of initial bars that are not drawn and values in DataWindow
   BUFFER_PROP_SHOW_DATA,                                   // Flag of displaying construction values in DataWindow
   BUFFER_PROP_SHIFT,                                       // Indicator graphical construction shift by time axis in bars
   BUFFER_PROP_COLOR_INDEXES,                               // Number of colors
   BUFFER_PROP_COLOR,                                       // Drawing color
   BUFFER_PROP_INDEX_BASE,                                  // Base data buffer index
   BUFFER_PROP_INDEX_NEXT_BASE,                             // Index of the array to be assigned as the next indicator buffer
   BUFFER_PROP_INDEX_NEXT_PLOT,                             // Index of the next drawn buffer
   BUFFER_PROP_ID,                                          // ID of multiple buffers of a single indicator
   BUFFER_PROP_IND_HANDLE,                                  // Handle of an indicator using a buffer
   BUFFER_PROP_IND_TYPE,                                    // Type of an indicator using a buffer
   BUFFER_PROP_NUM_DATAS,                                   // Number of data buffers
   BUFFER_PROP_INDEX_COLOR,                                 // Color buffer index
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (23)                      // Total number of integer bar properties
#define BUFFER_PROP_INTEGER_SKIP  (2)                       // Number of buffer properties not used in sorting
//+------------------------------------------------------------------+
//| Buffer real properties                                           |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_DOUBLE
  {
   BUFFER_PROP_EMPTY_VALUE = BUFFER_PROP_INTEGER_TOTAL,     // Empty value for plotting where nothing will be drawn
  }; 
#define BUFFER_PROP_DOUBLE_TOTAL  (1)                       // Total number of real buffer properties
#define BUFFER_PROP_DOUBLE_SKIP   (0)                       // Number of buffer properties not used in sorting
//+------------------------------------------------------------------+
//| Buffer string properties                                         |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_STRING
  {
   BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Buffer symbol
   BUFFER_PROP_LABEL,                                       // Name of the graphical indicator series displayed in DataWindow
   BUFFER_PROP_IND_NAME,                                    // Name of an indicator using a buffer
  };
#define BUFFER_PROP_STRING_TOTAL  (3)                       // Total number of string buffer properties
//+------------------------------------------------------------------+

Aumentamos el número total de propiedades de tipo entero de 20 a 23, y de propiedades de tipo string de 2 a 3.

Por consiguiente, ya que hemos añadido nuevas propiedades, también deberemos añadir la posibilidad de clasificar y seleccionar según estas propiedades.
En la enumeración de los posibles criterios de clasificación, añadimos los nuevos tipos de clasificación de los objetos de búfer:

//+------------------------------------------------------------------+
//| Possible buffer sorting criteria                                 |
//+------------------------------------------------------------------+
#define FIRST_BUFFER_DBL_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP)
#define FIRST_BUFFER_STR_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BUFFER_MODE
  {
//--- Sort by integer properties
   SORT_BY_BUFFER_INDEX_PLOT = 0,                           // Sort by the plotted buffer serial number
   SORT_BY_BUFFER_STATUS,                                   // Sort by buffer drawing style (status) from the ENUM_BUFFER_STATUS enumeration
   SORT_BY_BUFFER_TYPE,                                     // Sort by buffer type (from the ENUM_BUFFER_TYPE enumeration)
   SORT_BY_BUFFER_TIMEFRAME,                                // Sort by the buffer data period (timeframe)
   SORT_BY_BUFFER_ACTIVE,                                   // Sort by the buffer usage flag
   SORT_BY_BUFFER_DRAW_TYPE,                                // Sort by graphical construction type (from the ENUM_DRAW_TYPE enumeration)
   SORT_BY_BUFFER_ARROW_CODE,                               // Sort by the arrow code for DRAW_ARROW style
   SORT_BY_BUFFER_ARROW_SHIFT,                              // Sort by the vertical shift of the arrows for DRAW_ARROW style
   SORT_BY_BUFFER_LINE_STYLE,                               // Sort by the line style
   SORT_BY_BUFFER_LINE_WIDTH,                               // Sort by the line width
   SORT_BY_BUFFER_DRAW_BEGIN,                               // Sort by the number of initial bars that are not drawn and values in DataWindow
   SORT_BY_BUFFER_SHOW_DATA,                                // Sort by the flag of displaying construction values in DataWindow
   SORT_BY_BUFFER_SHIFT,                                    // Sort by the indicator graphical construction shift by time axis in bars
   SORT_BY_BUFFER_COLOR_INDEXES,                            // Sort by a number of attempts
   SORT_BY_BUFFER_COLOR,                                    // Sort by the drawing color
   SORT_BY_BUFFER_INDEX_BASE,                               // Sort by the basic data buffer index
   SORT_BY_BUFFER_INDEX_NEXT_BASE,                          // Sort by the index of the array to be assigned as the next indicator buffer
   SORT_BY_BUFFER_INDEX_NEXT_PLOT,                          // Sort by the index of the next drawn buffer
   SORT_BY_BUFFER_ID,                                       // Sort by ID of multiple buffers of a single indicator
   SORT_BY_BUFFER_IND_HANDLE,                               // Sort by handle of an indicator using a buffer
   SORT_BY_BUFFER_IND_TYPE,                                 // Sort by type of an indicator using a buffer
//--- Sort by real properties
   SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP,      // Sort by the empty value for plotting where nothing will be drawn
//--- Sort by string properties
   SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP,           // Sort by the buffer symbol
   SORT_BY_BUFFER_LABEL,                                    // Sort by the name of the graphical indicator series displayed in DataWindow
   SORT_BY_BUFFER_IND_NAME,                                 // Sort by name of an indicator using a buffer
  };
//+------------------------------------------------------------------+


Para registrar las barras omitidas, por ejemplo, al perder y restaurar la conexión, vamos a mejorar ligeramente la clase del objeto "Barra nueva" en el archivo \MQL5\Include\DoEasy\Objects\Series\NewBarObj.mqh. Todo lo que necesitamos hacer es añadir el cálculo del número de barras transcurridas entre dos eventos "Barra nueva". Si el valor calculado es superior a 1, significará que se han omitido barras en la historia, o bien que no hay ninguna en el servidor (por ahora, no vamos a analizar esta situación).

Añadimos a la sección privada las cuatro nuevas variables de miembros de clase para guardar la hora del anterior evento "Barra nueva" para la gestión de tiempo manual y automática, y para guardar el número de segundos y las barras transcurridas entre dos eventos "Barra nueva"

//+------------------------------------------------------------------+
//| "New bar" object class                                           |
//+------------------------------------------------------------------+
class CNewBarObj : public CBaseObj
  {
private:
   string            m_symbol;                                    // Symbol
   ENUM_TIMEFRAMES   m_timeframe;                                 // Timeframe
   datetime          m_new_bar_time;                              // New bar time for auto time management
   datetime          m_prev_time;                                 // Previous time for auto time management
   datetime          m_new_bar_time_manual;                       // New bar time for manual time management
   datetime          m_prev_time_manual;                          // Previous time for manual time management
   datetime          m_prev_new_bar_time;                         // Previous new bar time for auto time management
   datetime          m_prev_new_bar_time_manual;                  // Previous new bar time for manual time management
   long              m_seconds_between;                           // Number of seconds between two "New bar" events
   int               m_bars_between;                              // Number of bars between two "New bar" events
//--- Return the current bar data
   datetime          GetLastBarDate(const datetime time);
public:

En la sección pública de la clase, renombramos los métodos para establecer y retornar el marco temporal del objeto (antes se utilizaba "Period", mientras que "Timeframe" se usaba para guardar el marco temporal de una forma más informativa), y añadimos los métodos para retornar los valores de las variables nuevamente declaradas:

public:
//--- Set (1) symbol and (2) timeframe
   void              SetSymbol(const string symbol)               { this.m_symbol=(symbol==NULL || symbol==""   ? ::Symbol() : symbol);                     }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe){ this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); }
//--- Save the new bar time during the manual time management
   void              SaveNewBarTime(const datetime time)          { this.m_prev_time_manual=this.GetLastBarDate(time);                                      }
//--- Return (1) symbol and (2) timeframe
   string            Symbol(void)                           const { return this.m_symbol;             }
   ENUM_TIMEFRAMES   Timeframe(void)                        const { return this.m_timeframe;          }
//--- Return (1) new bar time, (2) previous new bar time, number of (3) seconds, (4) number of bars between the two last events
   datetime          TimeNewBar(void)                       const { return this.m_new_bar_time;       }
   datetime          TimePrevNewBar(void)                   const { return this.m_prev_new_bar_time;  }
   long              SecondsBetweenNewBars(void)            const { return this.m_seconds_between;    }
   int               BarsBetweenNewBars(void)               const { return this.m_bars_between;       }
//--- Return the new bar opening flag during the time (1) auto, (2) manual management
   bool              IsNewBar(const datetime time);
   bool              IsNewBarManual(const datetime time);
//--- Constructors
                     CNewBarObj(void) : m_symbol(::Symbol()),
                                        m_timeframe((ENUM_TIMEFRAMES)::Period()),
                                        m_prev_time(0),m_new_bar_time(0),
                                        m_prev_time_manual(0),m_new_bar_time_manual(0) {}
                     CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe);
  };
//+------------------------------------------------------------------+

En el constructor paramétrico de la clase, en su lista de inicialización, establecemos los valores de inicialización para el número de segundos y barras,
e inicializamos las demás variables nuevas con cero en el cuerpo de la clase:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),
                                                                              m_timeframe(timeframe),
                                                                              m_seconds_between(0),
                                                                              m_bars_between(0)
  {
   this.m_prev_new_bar_time=this.m_prev_new_bar_time_manual=this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0;
  }
//+------------------------------------------------------------------+

En el método que retorna la bandera de apertura de una nueva barra al gestionar el tiempo automáticamente, precisamente en la situación en la que se ha formado una barra nueva, guardamos la hora de la anterior nueva barra y calculamos el número de segundos y barras entre los dos eventos "Barra nueva":

//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBar(const datetime time)
  {
//--- Get the current bar time
   datetime tm=this.GetLastBarDate(time);
   if(tm<=0)
      return false;
//--- If the previous and current time are equal to zero, this is the first launch
   if(this.m_prev_time+this.m_new_bar_time==0)
     {
      //--- set the new bar opening time,
      //--- set the previous bar time as the current one and return 'false'
      this.m_new_bar_time=this.m_prev_time=tm;
      return false;
     }
//--- If the previous time is less than the current bar open time, this is a new bar
   if(this.m_prev_time>0 && this.m_prev_time<tm)
     {
      this.m_prev_new_bar_time=this.m_prev_time;
      this.m_seconds_between=tm-m_prev_time;
      this.m_bars_between=int(this.m_seconds_between/::PeriodSeconds(this.m_timeframe));
      //--- set the new bar opening time,
      //--- set the previous time as the current one and return 'true'
      this.m_new_bar_time=this.m_prev_time=tm;
      return true;
     }
//--- in other cases, return 'false'
   return false;
  }
//+------------------------------------------------------------------+

En el método que retorna la bandera de una nueva barra con el control manual, no resulta necesario calcular estos datos: los datos de las barras restantes siempre se calcularán de manera automática. Pero en nuestro método, guardaremos la hora de la anterior "barra nueva" con el control manual y corregiremos el error de asignación de la hora de la barra nueva (antes se guardaba en la variable para la gestión automática del tiempo):

//+------------------------------------------------------------------+
//| Return the new bar opening flag during the manual management     |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBarManual(const datetime time)
  {
//--- Get the current bar time
   datetime tm=this.GetLastBarDate(time);
   if(tm<=0)
      return false;
//--- If the previous and current time are equal to zero, this is the first launch
   if(this.m_prev_time_manual+this.m_new_bar_time_manual==0)
     {
      //--- set the new bar opening time,
      //--- set the previous bar time as the current one and return 'false'
      this.m_new_bar_time_manual=this.m_prev_time_manual=tm;
      return false;
     }
//--- If the previous time is less than the current bar open time, this is a new bar
   if(this.m_prev_time_manual>0 && this.m_prev_time_manual<tm)
     {
      this.m_prev_new_bar_time_manual=this.m_prev_time_manual;
      //--- set the new bar opening time and return 'true'
      //--- Save the previous time as the current one from the program using the SaveNewBarTime() method
      //--- Till the previous time is forcibly set as the current one from the program,
      //--- the method returns the new bar flag allowing the completion of all the necessary actions on the new bar.
      this.m_new_bar_time_manual=tm;
      return true;
     }
//--- in other cases, return 'false'
   return false;
  }
//+------------------------------------------------------------------+


Con bastante frecuencia, podemos ver en el diario del terminal entradas de la biblioteca sobre las barras del historial de obtención de errores. Esto ocurre así porque la biblioteca revisa todo el historial, incluso los lugares donde no hay datos históricos sobre un símbolo concreto. Sobre este hecho se muestra una entrada, y después se produce el paso a la siguiente barra del historial. Se ha hecho así para que resulte posible depurar los métodos de la biblioteca al trabajar con las series temporales. Pero cuando no sea en absoluto necesario ver los errores en la obtención de los datos históricos, eliminaremos estas entradas. Para ello, necesitaremos añadir a la clase del objeto "Barra", en el archivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh otro constructor, sin parámetros:

//+------------------------------------------------------------------+
//| Bar class                                                        |
//+------------------------------------------------------------------+
class CBar : public CBaseObj
  {
private:
   MqlDateTime       m_dt_struct;                                 // Date structure
   int               m_digits;                                    // Symbol's digits value
   string            m_period_description;                        // Timeframe string description
   long              m_long_prop[BAR_PROP_INTEGER_TOTAL];         // Integer properties
   double            m_double_prop[BAR_PROP_DOUBLE_TOTAL];        // Real properties
   string            m_string_prop[BAR_PROP_STRING_TOTAL];        // String properties

//--- Return the index of the array the bar's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_BAR_PROP_DOUBLE property)     const { return(int)property-BAR_PROP_INTEGER_TOTAL;                        }
   int               IndexProp(ENUM_BAR_PROP_STRING property)     const { return(int)property-BAR_PROP_INTEGER_TOTAL-BAR_PROP_DOUBLE_TOTAL;  }

//--- Return the bar type (bullish/bearish/zero)
   ENUM_BAR_BODY_TYPE BodyType(void)                              const;
//--- Calculate and return the size of (1) candle, (2) candle body,
//--- (3) upper, (4) lower candle wick,
//--- (5) candle body top and (6) bottom
   double            CandleSize(void)                             const { return(this.High()-this.Low());                                    }
   double            BodySize(void)                               const { return(this.BodyHigh()-this.BodyLow());                            }
   double            ShadowUpSize(void)                           const { return(this.High()-this.BodyHigh());                               }
   double            ShadowDownSize(void)                         const { return(this.BodyLow()-this.Low());                                 }
   double            BodyHigh(void)                               const { return ::fmax(this.Close(),this.Open());                           }
   double            BodyLow(void)                                const { return ::fmin(this.Close(),this.Open());                           }

//--- Return the (1) year and (2) month the bar belongs to, (3) week day,
//--- (4) bar serial number in a year, (5) day, (6) hour, (7) minute,
   int               TimeYear(void)                               const { return this.m_dt_struct.year;                                      }
   int               TimeMonth(void)                              const { return this.m_dt_struct.mon;                                       }
   int               TimeDayOfWeek(void)                          const { return this.m_dt_struct.day_of_week;                               }
   int               TimeDayOfYear(void)                          const { return this.m_dt_struct.day_of_year;                               }
   int               TimeDay(void)                                const { return this.m_dt_struct.day;                                       }
   int               TimeHour(void)                               const { return this.m_dt_struct.hour;                                      }
   int               TimeMinute(void)                             const { return this.m_dt_struct.min;                                       }

public:
//--- Set bar's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_BAR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                              }
   void              SetProperty(ENUM_BAR_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;            }
   void              SetProperty(ENUM_BAR_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;            }
//--- Return (1) integer, (2) real and (3) string bar properties from the properties array
   long              GetProperty(ENUM_BAR_PROP_INTEGER property)  const { return this.m_long_prop[property];                                 }
   double            GetProperty(ENUM_BAR_PROP_DOUBLE property)   const { return this.m_double_prop[this.IndexProp(property)];               }
   string            GetProperty(ENUM_BAR_PROP_STRING property)   const { return this.m_string_prop[this.IndexProp(property)];               }

//--- Return the flag of the bar supporting the property
   virtual bool      SupportProperty(ENUM_BAR_PROP_INTEGER property)    { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_DOUBLE property)     { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_STRING property)     { return true; }
//--- Return itself
   CBar             *GetObject(void)                                    { return &this;}
//--- Set (1) bar symbol, timeframe and time, (2) bar object parameters
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);

//--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CBar objects by all properties (to search for equal bar objects)
   bool              IsEqual(CBar* compared_bar) const;
//--- Constructors
                     CBar(){;} 
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source);
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates);
                     
//+------------------------------------------------------------------+ 

A continuación, al crear la lista de series temporales según los símbolos, utilizaremos precisamente este constructor para crear el nuevo objeto de barra que pertenece a la serie temporal indicada del símbolo. Antes, los constructores paramétricos trataban de tomar de manera autónoma en el historial los datos necesarios del objeto de barra recién creado, y si se daba un error al obtener la historia del constructor, se mostraba una entrada de depuración en el diario. Un constructor simple sin parámetros creará un objeto de barra vacío que deberemos rellenar con datos después de que se haya creado con éxito. Esto tendrá lugar en los métodos de la clase CSeriesDE.

Vamos a echar un vistazo a los cambios que debemos introducir en el listado de esta clase en el archivo \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh.

En la sección pública de la clase, añadimos el método que retorna el puntero al objeto de clase "Barra nueva" que pertenece a la serie temporal de esta clase:

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeframe
   string            m_symbol;                                          // Symbol
   string            m_period_description;                              // Timeframe string description
   datetime          m_firstdate;                                       // The very first date by a period symbol at the moment
   datetime          m_lastbar_date;                                    // Time of opening the last bar by period symbol
   uint              m_amount;                                          // Amount of applied timeseries data
   uint              m_required;                                        // Required amount of applied timeseries data
   uint              m_bars;                                            // Number of bars in history by symbol and timeframe
   bool              m_sync;                                            // Synchronized data flag
   CArrayObj         m_list_series;                                     // Timeseries list
   CNewBarObj        m_new_bar_obj;                                     // "New bar" object
//--- Set the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol
   void              SetServerDate(void)
                       {
                        this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
                        this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
                       }

public:
//--- Return (1) itself, (2) timeseries list, (3) timeseries "New bar" object
   CSeriesDE        *GetObject(void)                                    { return &this;               }
   CArrayObj        *GetList(void)                                      { return &m_list_series;      }
   CNewBarObj       *GetNewBarObj(void)                                 { return &this.m_new_bar_obj; }

//--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition

Como ahora tenemos dos eventos de serie temporal (Barra nueva y Barras omitidas), deberemos mejorar el método de creación y envío de un evento de la serie temporal al gráfico del programa de control. En la declaración del método, escribimos el parámetro de entrada en el que transmitiremos el evento de serie temporal que debemos crear y enviar:

//--- Create and send the timeseries event to the control program chart
   void              SendEvent(ENUM_SERIES_EVENT event);

Y mejoramos el propio método ubicado fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Create and send the timeseries event                             |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CSeriesDE::SendEvent(ENUM_SERIES_EVENT event)
  {
   if(event==SERIES_EVENTS_NEW_BAR)
     {
      int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME);
      CBar *bar=this.m_list_series.At(index);
      if(bar==NULL)
         return;
      ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,bar.Time(),this.Timeframe(),this.Symbol());
     }
   else if(event==SERIES_EVENTS_MISSING_BARS)
     {
      ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_MISSING_BARS,this.m_new_bar_obj.BarsBetweenNewBars(),this.Timeframe(),this.Symbol());
     }
  }
//+------------------------------------------------------------------+

Aquí: dependiendo del valor transmitido al método, creamos el evento necesario y lo enviamos al gráfico del programa de control. Si se crea el evento "Barras omitidas", transmitimos en el valor lparam de la función EventChartCustom() el número de barras omitidas de la historia.

Para evitar mensajes innecesarios en el diario acerca de errores de obtención de los datos históricos, deberemos mejorar el método que retorna un objeto de barra según la hora en la serie temporal:

//+------------------------------------------------------------------+
//| Return the bar object by time in the timeseries                  |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const datetime time)
  {
   CBar *obj=new CBar();
   if(obj==NULL)
      return NULL;
   obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time);
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   int index=this.m_list_series.Search(obj);
   delete obj;
   return this.m_list_series.At(index);
  }
//+------------------------------------------------------------------+

Dado que ahora tenemos un constructor sin parámetros en la clase CBar, para buscar la barra necesaria utilizaremos la creación de un nuevo objeto de barra con la ayuda de este constructor.

Aquí, simplemente creamos un objeto temporal de barra vacío, y luego asignamos a este objeto el símbolo, el marco temporal y la hora de la barra necesarios.
A continuación, todo es muy sencillo: clasificamos la lista de objetos de barra según el tiempo y buscamos en la lista de objetos de barra el objeto cuyos datos coinciden con los que hemos asignado al objeto temporal de barra que hemos creado.
El método Search() retorna el índice del objeto encontrado en la lista, mientras que el método At() retorna el puntero al objeto según su índice. Si el objeto no ha sido encontrado, el índice tendrá el valor -1, en este caso, además, el método At() retornará el valor NULL.

Los eventos de las barras nuevas, y ahora también los eventos de omisión de barras, los detectaremos en los métodos de actualización de todas las series temporales disponibles de la clase CTimeSeriesDE
en el archivo \MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh.

Vamos a mejorar los dos métodos de actualización de las series temporales con bloques de código para determinar los eventos "Barras omitidas":

//+------------------------------------------------------------------+
//| Update a specified timeseries list                               |
//+------------------------------------------------------------------+
void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Reset the timeseries event flag and clear the list of all timeseries events
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the timeseries from the list by its timeframe
   CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable())
      return;
//--- Update the timeseries list
   series_obj.Refresh(data_calculate);
   datetime time=
     (
      this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
      data_calculate.rates.time : 
      series_obj.LastBarDate()
     );
//--- If the timeseries object features the New bar event
   if(series_obj.IsNewBar(time))
     {
      //--- send the "New bar" event to the control program chart
      series_obj.SendEvent(SERIES_EVENTS_NEW_BAR);
      //--- set the values of the first date in history on the server and in the terminal
      this.SetTerminalServerDate();
      //--- add the "New bar" event to the list of timeseries events
      //--- in case of successful addition, set the event flag for the timeseries
      if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol()))
         this.m_is_event=true;
         
      //--- Check skipped bars
      int missing=series_obj.GetNewBarObj().BarsBetweenNewBars();
      if(missing>1)
        {
         //--- send the "Bars skipped" event to the control program chart
         series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS);
         //--- add the "Bars skipped" event to the list of timeseries events
         this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol());
        }
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update all timeseries lists                                      |
//+------------------------------------------------------------------+
void CTimeSeriesDE::RefreshAll(SDataCalculate &data_calculate)
  {
//--- Reset the flags indicating the necessity to set the first date in history on the server and in the terminal
//--- and the timeseries event flag, and clear the list of all timeseries events
   bool upd=false;
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- In the loop by the list of all used timeseries,
   int total=this.m_list_series.Total();
   for(int i=0;i<total;i++) 
     {
      //--- get the next timeseries object by the loop index
      CSeriesDE *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0)
         continue;
      //--- update the timeseries list
      series_obj.Refresh(data_calculate);
      datetime time=
        (
         this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
         data_calculate.rates.time : 
         series_obj.LastBarDate()
        );
      //--- If the timeseries object features the New bar event
      if(series_obj.IsNewBar(time))
        {
         //--- send the "New bar" event to the control program chart,
         series_obj.SendEvent(SERIES_EVENTS_NEW_BAR);
         //--- set the flag indicating the necessity to set the first date in history on the server and in the terminal
         upd=true;
         //--- add the "New bar" event to the list of timeseries events
         //--- in case of successful addition, set the event flag for the timeseries
         if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol()))
            this.m_is_event=true;
            
         //--- Check skipped bars
         int missing=series_obj.GetNewBarObj().BarsBetweenNewBars();
         if(missing>1)
           {
            //--- send the "Bars skipped" event to the control program chart
            series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS);
            //--- add the "Bars skipped" event to the list of timeseries events
            this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol());
           }
        }
     }
//--- if the flag indicating the necessity to set the first date in history on the server and in the terminal is enabled,
//--- set the values of the first date in history on the server and in the terminal
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

Aquí: en el momento en que se determina el evento "Barra nueva", llamamos al método de creación de un nuevo evento de la serie temporal (hemos creado dicho método antes), al que transmitimos el evento "Barra nueva"; a continuación, si tenemos barras omitidas, también creamos este evento.

Ahora, añadimos a la clase de colección de los objetos de todas las series temporales CTimeSeriesCollection (en el archivo \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh, en su sección pública) la declaración del método de nueva creación de todas las series temporales:

//--- (1) Create, (2) re-create a specified timeseries of a specified symbol, (3) re-create all timeseries
   bool                    CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0);
   bool                    ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0);
   bool                    ReCreateSeriesAll(const int rates_total=0,const uint required=0);
//--- Return (1) an empty, (2) partially filled timeseries

Lo implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Re-create all timeseries                                         |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::ReCreateSeriesAll(const int rates_total=0,const uint required=0)
  {
//--- In the loop by all symbol timeseries objects in the collection,
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next symbol timeseries object
      CTimeSeriesDE *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      //--- Get the list of all symbol timeseries
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      //--- In a loop by all symbol timeseries
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         //--- Get the next timeseries
         CSeriesDE *series=list.At(j);
         if(series==NULL)
            continue;
         //--- check timeseries synchronization and re-create it
         if(!series.SyncData(required,rates_total))
            return false;
         if(series.Create(required)==0)
            return false;
        }
     }
   return true;
  }
//+------------------------------------------------------------------+

El método simplemente crea de nuevo todas las series temporales disponibles en la colección. Hasta ahora, dicho método no se utilizaba en ninguna parte, pero podría ser útil en el futuro si necesitamos recrear las colecciones existentes de series temporales. Por ejemplo, podríamos necesitarlo al definir la omisión de un gran número de barras cuando el programa usa muchos símbolos/periodos en su trabajo. En esta situación, resulta más fácil recrear todas las series temporales de la colección llamando a un método, que definir el número de barras omitidas en cada serie temporal y recrear cada una aparte. Además, esto ocurrirá solo cuando se reanude la conexión con el servidor o en una nueva barra.

Ya hemos ejecutado todas las etapas preparatorias; también hemos mejorado el trabajo con las serires temporales y las barras. Ha llegado el momento de crear los métodos encargados de trabajar con los indicadores estándar.


Métodos de trabajo con los indicadores estándar

En primer lugar, mejoraremos la clase del objeto de búfer abstracto en el archivo \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh.

En la sección pública de la clase, añadimos los métodos para establecer y retornar las cuatro nuevas propiedades del objeto de búfer:

//--- Set (1) the arrow code, (2) vertical shift of arrows, (3) symbol, (4) timeframe, (5) buffer activity flag
//--- (6) drawing type, (7) number of initial bars without drawing, (8) flag of displaying construction values in DataWindow,
//--- (9) shift of the indicator graphical construction along the time axis, (10) line style, (11) line width,
//--- (12) total number of colors, (13) one drawing color, (14) color of drawing in the specified color index,
//--- (15) drawing colors from the color array, (16) empty value, (17) name of the graphical series displayed in DataWindow
   virtual void      SetArrowCode(const uchar code)                  { return;                                                               }
   virtual void      SetArrowShift(const int shift)                  { return;                                                               }
   void              SetSymbol(const string symbol)                  { this.SetProperty(BUFFER_PROP_SYMBOL,symbol);                          }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)   { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe);                    }
   void              SetActive(const bool flag)                      { this.SetProperty(BUFFER_PROP_ACTIVE,flag);                            }
   void              SetDrawType(const ENUM_DRAW_TYPE draw_type);
   void              SetDrawBegin(const int value);
   void              SetShowData(const bool flag);
   void              SetShift(const int shift);
   void              SetStyle(const ENUM_LINE_STYLE style);
   void              SetWidth(const int width);
   void              SetColorNumbers(const int number);
   void              SetColor(const color colour);
   void              SetColor(const color colour,const uchar index);
   void              SetColors(const color &array_colors[]);
   void              SetEmptyValue(const double value);
   virtual void      SetLabel(const string label);
   void              SetID(const int id)                             { this.SetProperty(BUFFER_PROP_ID,id);                                  }
   void              SetIndicatorHandle(const int handle)            { this.SetProperty(BUFFER_PROP_IND_HANDLE,handle);                      }
   void              SetIndicatorType(const ENUM_INDICATOR type)     { this.SetProperty(BUFFER_PROP_IND_TYPE,type);                          }
   void              SetIndicatorName(const string name)             { this.SetProperty(BUFFER_PROP_IND_NAME,name);                          }
   
//--- Return (1) the serial number of the drawn buffer, (2) bound array index, (3) color buffer index,
//--- (4) index of the first free bound array, (5) index of the next drawn buffer, (6) buffer data period, (7) buffer status,
//--- (8) buffer type, (9) buffer usage flag, (10) arrow code, (11) arrow shift for DRAW_ARROW style,
//--- (12) number of initial bars that are not drawn and values in DataWindow, (13) graphical construction type,
//--- (14) flag of displaying construction values in DataWindow, (15) indicator graphical construction shift along the time axis,
//--- (16) drawing line style, (17) drawing line width, (18) number of colors, (19) drawing color, number of buffers for construction
//--- (20) set empty value, (21) buffer symbol, (22) name of the indicator graphical series displayed in DataWindow
   int               IndexPlot(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT);                 }
   int               IndexBase(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE);                 }
   int               IndexColor(void)                          const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR);                }
   int               IndexNextBaseBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE);            }
   int               IndexNextPlotBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT);            }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME);      }
   ENUM_BUFFER_STATUS Status(void)                             const { return (ENUM_BUFFER_STATUS)this.GetProperty(BUFFER_PROP_STATUS);      }
   ENUM_BUFFER_TYPE  TypeBuffer(void)                          const { return (ENUM_BUFFER_TYPE)this.GetProperty(BUFFER_PROP_TYPE);          }
   bool              IsActive(void)                            const { return (bool)this.GetProperty(BUFFER_PROP_ACTIVE);                    }
   uchar             ArrowCode(void)                           const { return (uchar)this.GetProperty(BUFFER_PROP_ARROW_CODE);               }
   int               ArrowShift(void)                          const { return (int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT);                }
   int               DrawBegin(void)                           const { return (int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN);                 }
   ENUM_DRAW_TYPE    DrawType(void)                            const { return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE);       }
   bool              IsShowData(void)                          const { return (bool)this.GetProperty(BUFFER_PROP_SHOW_DATA);                 }
   int               Shift(void)                               const { return (int)this.GetProperty(BUFFER_PROP_SHIFT);                      }
   ENUM_LINE_STYLE   LineStyle(void)                           const { return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE);     }
   int               LineWidth(void)                           const { return (int)this.GetProperty(BUFFER_PROP_LINE_WIDTH);                 }
   int               ColorsTotal(void)                         const { return (int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES);              }
   color             Color(void)                               const { return (color)this.GetProperty(BUFFER_PROP_COLOR);                    }
   int               BuffersTotal(void)                        const { return (int)this.GetProperty(BUFFER_PROP_NUM_DATAS);                  }
   double            EmptyValue(void)                          const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE);                     }
   string            Symbol(void)                              const { return this.GetProperty(BUFFER_PROP_SYMBOL);                          }
   string            Label(void)                               const { return this.GetProperty(BUFFER_PROP_LABEL);                           }
   int               ID(void)                                  const { return (int)this.GetProperty(BUFFER_PROP_ID);                         }
   int               IndicatorHandle(void)                     const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE);                 }
   ENUM_INDICATOR    IndicatorType(void)                       const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE);        }
   string            IndicatorName(void)                       const { return this.GetProperty(BUFFER_PROP_IND_NAME);                        }
   int               IndicatorBarsCalculated(void)             const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE));}

En el constructor de la clase, asignamos los valores por defecto a las nuevas propiedades:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 const uint index_plot,
                 const uint index_base_array,
                 const int num_datas,
                 const uchar total_arrays,
                 const int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   this.m_long_prop[BUFFER_PROP_ID]                            = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_HANDLE]                    = INVALID_HANDLE;
   this.m_long_prop[BUFFER_PROP_IND_TYPE]                      = WRONG_VALUE;
   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE      : 
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   : 
      ENUM_DRAW_TYPE(this.Status()+8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                     = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                     = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                        = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                    = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                   = 0;
   this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                    = 0;
   this.m_long_prop[BUFFER_PROP_SHOW_DATA]                     = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
   this.m_long_prop[BUFFER_PROP_SHIFT]                         = 0;
   this.m_long_prop[BUFFER_PROP_LINE_STYLE]                    = STYLE_SOLID;
   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                    = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]                 = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0);
   this.m_long_prop[BUFFER_PROP_COLOR]                         = clrRed;
   this.m_long_prop[BUFFER_PROP_NUM_DATAS]                     = num_datas;
   this.m_long_prop[BUFFER_PROP_INDEX_PLOT]                    = index_plot;
   this.m_long_prop[BUFFER_PROP_INDEX_BASE]                    = index_base_array;
   this.m_long_prop[BUFFER_PROP_INDEX_COLOR]                   = this.GetProperty(BUFFER_PROP_INDEX_BASE)+
                                                                   (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = index_base_array+this.m_total_arrays;
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT]               = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot);
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0);
//--- Save string properties
   this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)]      = ::Symbol();
   this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)]       = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL);
   this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME)]    = NULL;

//--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string

Dichos valores para estas nuevas propiedades pertenecerán a los objetos de búfer que no trabajen con los indicadores estándar. Si creamos un objeto de búfer que pertenezca a un indicador estándar, en el momento de su creación, estos parámetros serán rellenados por la biblioteca, lo cual vamos a hacer a continuación.

En el método que retorna la descripción de una propiedad de tipo entero del búfer, añadimos la muestra de las descripciones para las nuevas propiedades de tipo entero:

//+------------------------------------------------------------------+
//| Return description of a buffer's integer property                |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property)
  {
   return
     (
      property==BUFFER_PROP_INDEX_PLOT    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_STATUS        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==BUFFER_PROP_TYPE          ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeBufferDescription()
         )  :
      property==BUFFER_PROP_TIMEFRAME     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTimeframeDescription()
         )  :
      property==BUFFER_PROP_ACTIVE        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetActiveDescription()
         )  :
      property==BUFFER_PROP_DRAW_TYPE     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetDrawTypeDescription()
         )  :
      property==BUFFER_PROP_ARROW_CODE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ARROW_SHIFT   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_LINE_STYLE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetLineStyleDescription()
         )  :
      property==BUFFER_PROP_LINE_WIDTH    ?  
         (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) :
          CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_DRAW_BEGIN    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_SHOW_DATA     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetShowDataDescription()
         )  :
      property==BUFFER_PROP_SHIFT         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR_INDEXES ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_COLOR   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_BASE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_BASE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_PLOT ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ID ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_HANDLE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_TYPE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_NUM_DATAS     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetColorsDescription()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

En el método que retorna la descripción de una propiedad de tipo string del búfer, añadimos la muestra de las descripciones para la nueva propiedad de tipo string:

//+------------------------------------------------------------------+
//| Return description of a buffer's string property                 |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING property)
  {
   return
     (
      property==BUFFER_PROP_SYMBOL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      property==BUFFER_PROP_LABEL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"")
         )  :
      property==BUFFER_PROP_IND_NAME   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IndicatorName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorName()+"\"")
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Introducimos las correcciones en los métodos que establecen un valor vacío y el nombre de la serie gráfica. Antes, estos valores no se calculaban para el búfer de cálculo. Vamos a hacer que para el búfer de cálculo solo se establezcan los valores en las propiedades del objeto de búfer,
y para el búfer de dibujado
, tanto en las propiedades del objeto, como en las propiedades del propio búfer:

//+------------------------------------------------------------------+
//| Set the "empty" value for construction                           |
//| without drawing                                                  |
//+------------------------------------------------------------------+
void CBuffer::SetEmptyValue(const double value)
  {
   this.SetProperty(BUFFER_PROP_EMPTY_VALUE,value);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,value);
  }
//+------------------------------------------------------------------+
//| Set the indicator graphical series name                          |
//+------------------------------------------------------------------+
void CBuffer::SetLabel(const string label)
  {
   this.SetProperty(BUFFER_PROP_LABEL,label);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,label);
  }
//+------------------------------------------------------------------+

En los métodos que retornan los valores según el índice de la serie temporal, añadimos el control de un valor del índice inferior a cero:

//+------------------------------------------------------------------+
//| Return the value from the specified timeseries index             |
//| of the specified data buffer array                               |
//+------------------------------------------------------------------+
double CBuffer::GetDataBufferValue(const uint buffer_index,const int series_index) const
  {
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(correct_buff_index);
   if(data_total==0 || series_index<0)
      return this.EmptyValue();
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return this.DataBuffer[correct_buff_index].Array[data_index];
  }
//+------------------------------------------------------------------+
//| Return the color index value from the specified timeseries index |
//| of the specified color buffer array                              |
//+------------------------------------------------------------------+
int CBuffer::GetColorBufferValueIndex(const int series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0 || series_index<0)
      return WRONG_VALUE;
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]);
  }
//+------------------------------------------------------------------+
//| Return the color value from the specified timeseries index       |
//| of the specified color buffer array                              |
//+------------------------------------------------------------------+
color CBuffer::GetColorBufferValueColor(const int series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0 || series_index<0)
      return clrNONE;
   int color_index=this.GetColorBufferValueIndex(series_index);
   return(color_index>WRONG_VALUE ? (color)this.ArrayColors[color_index] : clrNONE);
  }
//+------------------------------------------------------------------+

De esta forma, si transmitimos al método un índice incorrecto, simplemente se ejecutará la salida del método retornando un valor "vacío" propio para cada uno de los métodos.

Ahora, vamos a mejorar la clase del objeto de búfer de cálculo en el archivo \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh.

Los métodos que retornan la bandera de soporte de una propiedad de tipo entero o string por parte de un búfer, siempre retornaban false, es decir, el búfer de cálculo no ofrecía soporte a las propiedades de este tipo. Vamos a hacer que ofrezca soporte a cada una de estas propiedades. Así, añadimos en el método que retorna la bandera de soporte de propiedades de tipo entero por parte de un objeto las nuevas propiedades de tipo entero para que el objeto de búfer de cálculo soporte estas:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if(
      property==BUFFER_PROP_INDEX_PLOT       || 
      property==BUFFER_PROP_STATUS           ||  
      property==BUFFER_PROP_TYPE             || 
      property==BUFFER_PROP_INDEX_BASE       || 
      property==BUFFER_PROP_ID               || 
      property==BUFFER_PROP_IND_HANDLE       || 
      property==BUFFER_PROP_IND_TYPE         || 
      property==BUFFER_PROP_INDEX_NEXT_BASE
     ) return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   return true;
  }
//+------------------------------------------------------------------+

Que el objeto de búfer de cálculo soporte todas las propiedades de tipo real y string es una solución rápida y temporal para crear métodos de trabajo con los búferes que funcionen con los indicadores estándar; más tarde, eliminaremos algunos de ellos de la lista de propiedades admitidas.

Organizaremos todo el trabajo con los búferes de indicador para los indicadores estándar en la clase de colección de los búferes de indicador CBuffersCollection
en el archivo \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh.

Hoy, implementaremos la creación y acompañamiento de los búferes de indicador de símbolo periodo múltiples del indicador estándar AC (Accelerator Oscillator). En los próximos artículos, y utilizando ya como base la funcionalidad comprobada, añadiremos la posibilidad de crear y trabajar con otros indicadores estándar.

Todos los objetos de búfer que trabajan con indicadores estándar obtendrán necesariamente un identificador al crearse; gracias a este identificador, podremos encontrar los búferes necesarios y trabajar con ellos.

En la sección pública de la clase, declaramos el método que retorna la lista de objetos de búfer que tienen este identificador:

//+------------------------------------------------------------------+
//| Collection of indicator buffers                                  |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Buffer object list
   CTimeSeriesCollection  *m_timeseries;                 // Pointer to the timeseries collection object
   
//--- Return the index of the (1) last, (2) next drawn and (3) basic buffer
   int                     GetIndexLastPlot(void);
   int                     GetIndexNextPlot(void);
   int                     GetIndexNextBase(void);
//--- Create a new buffer object and place it to the collection list
   bool                    CreateBuffer(ENUM_BUFFER_STATUS status);
//--- Get data of the necessary timeseries and bars for working with a single buffer bar, and return the number of bars
   int                     GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period);

public:
//--- Return (1) itself, (2) timeseries list, (3) indicator buffer list (featuring the ID of belonging to an indicator)
   CBuffersCollection     *GetObject(void)               { return &this;                                       }
   CArrayObj              *GetList(void)                 { return &this.m_list;                                }
   CArrayObj              *GetListBuffersWithID(void);

Lo implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Return the list of indicator buffers                             |
//| (featuring the ID of belonging to an indicator)                  |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBuffersWithID(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Aquí, todo resulta muy sencillo: con la ayuda de la clase CSelect, obtenemos la lista de objetos de búfer en los que el valor del identificador no es igual a -1, y retornamos el puntero a la lista obtenida.

Si obtenemos la lista con éxito, esta contendrá todos los objetos de búfer que tienen un identificador distinto del valor -1. Esto significará que la lista contendrá todos los objetos de búfer creados para trabajar con los indicadores estándar. En este caso, además, podrá tratarse tanto de búferes de dibujado, como de cualquier tipo de búfer del indicador estándar.
Para buscar los objetos de búfer pertenecientes a un indicador específico, deberemos filtrar de forma adicional esta lista según el tipo de indicador estándar, el identificador y el tipo de búfer.

Vamos a añadir a la sección pública de la clase las declaraciones de los métodos para crear objetos de búfer para trabajar con los indicadores estándar:

//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", 
//--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels",
//--- (8) "Display as bars", (9) "Display as candles", calculated buffer
   bool                    CreateArrow(void)             { return this.CreateBuffer(BUFFER_STATUS_ARROW);      }
   bool                    CreateLine(void)              { return this.CreateBuffer(BUFFER_STATUS_LINE);       }
   bool                    CreateSection(void)           { return this.CreateBuffer(BUFFER_STATUS_SECTION);    }
   bool                    CreateHistogram(void)         { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM);  }
   bool                    CreateHistogram2(void)        { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); }
   bool                    CreateZigZag(void)            { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG);     }
   bool                    CreateFilling(void)           { return this.CreateBuffer(BUFFER_STATUS_FILLING);    }
   bool                    CreateBars(void)              { return this.CreateBuffer(BUFFER_STATUS_BARS);       }
   bool                    CreateCandles(void)           { return this.CreateBuffer(BUFFER_STATUS_CANDLES);    }
   bool                    CreateCalculate(void)         { return this.CreateBuffer(BUFFER_STATUS_NONE);       }

//--- Create a multi-symbol multi-period indicator
   int                     CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);
   int                     CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE);
   int                     CreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE);
   int                     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);
   int                     CreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ama_period,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const int ama_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int bands_period,
                                       const int bands_shift,
                                       const double deviation,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id=WRONG_VALUE);
   int                     CreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateGator(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);
   int                     CreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id=WRONG_VALUE);
   int                     CreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id=WRONG_VALUE);
   int                     CreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id=WRONG_VALUE);
   int                     CreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id=WRONG_VALUE);
   int                     CreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);

Para trabajar con cada tipo concreto de indicador estándar, utilizaremos nuestro propio método para crear el indicador correspondiente y los objetos de búfer que necesita.

Como ejemplo, hoy trabajaremos con el indicador AC. Implementamos fuera del cuerpo de la clase el método para crear el indicador AC y sus búferes:

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

Como podemos ver, todo es bastante sencillo. Si hemos transmitido el valor -1 como identificador, el identificador será igual al valor de la constante del tipo del indentificador estándar. Al crear el indicador con éxito (su manejador no es igual a INVALID_HANDLE), creamos un objeto de búfer con el tipo de dibujado "Histograma de la línea cero", y con ayuda del método GetLastCreateBuffer(), que retorna un puntero al último búfer creado (veremos el método un poco más tarde), obtenemos el puntero al objeto de búfer del histograma y le asignamos los parámetros que necesita para identificarlo como búfer para dibujar los datos del indicador estándar AC.
A continuación, vamos a hacer lo mismo para el búfer de cálculo. En el búfer de cálculo, registraremos los datos del indicador AC obtenidos al acceder a su manejador. En nuestro caso, el manejador del indicador está registrado en las propiedades del objeto de búfer, tanto del búfer de dibujado, como del búfer de cálculo, es decir, podemos obtener cualquiera de estos objetos de búfer y acceder al indicador según el manejador registrado en estos objetos, y trabajar después con el indicador.

Vamos a añadir la implementación del método para crear el indicador AD con los objetos de búfer que necesita, simplemente para ver las diferencias en la implementación de los métodos para los indicadores estándar de distinto tipo:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period AD                              |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iAD(symbol,timeframe,applied_volume);
   int identifier=(id==WRONG_VALUE ? IND_AD : id);
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      CBuffer *buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AD);
      buff.SetShowData(true);
      buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accumulation/Distribution");
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AD);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accumulation/Distribution");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Las diferencias son pequeñas: se relacionan con el tipo de búfer de dibujado, el tipo de indicador estándar, la denominación de la serie gráfica y el nombre del indicador. Otros tipos de indicadores estándar tendrán (si fuera necesario) un número diferente de objetos de búfer de dibujado y de calculado para trabajar con el indicador estándar.

Justo después de declarar los métodos para crear los indicadores estándar, añadimos las declaraciones de los demás métodos necesarios:

//--- Prepare calculated buffer data of the specified standard indicator
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
//--- Clear buffer data of the specified standard indicator by the timeseries index
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
//--- Set the values for the current chart to the specified standard indicator buffer 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);

//--- 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
   CBuffer                *GetBufferByLabel(const string plot_label);
   CBuffer                *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe);
   CBuffer                *GetBufferByPlot(const int plot_index);
   CBuffer                *GetBufferByListIndex(const int index_list);
   CBuffer                *GetLastCreateBuffer(void);
   CArrayObj              *GetListBufferByID(const int id);
   CArrayObj              *GetListBufferByIndType(const ENUM_INDICATOR indicator_type);
   CArrayObj              *GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id);

Todos los métodos declarados se describen en los comentarios. Vamos a analizar su implementación fuera del cuerpo de la clase.

Método que retorna el último objeto de búfer creado:

//+------------------------------------------------------------------+
//| Return the last created buffer                                   |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetLastCreateBuffer(void)
  {
   return this.m_list.At(this.m_list.Total()-1);
  }
//+------------------------------------------------------------------+

El método simplemente retorna un puntero al objeto de búfer que se encuentra en último lugar en la lista de objetos de búfer.

Método que retorna la lista de objetos de búfer según su identificador:

//+------------------------------------------------------------------+
//| Return the list of buffers by ID                                 |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByID(const int id)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Obtenemos una lista con los objetos de búfer cuyas propiedades incluyen un identificador igual al valor transmitido al método.
El puntero a la lista obtenida lo retornaremos desde el método.

Método que retorna la lista de objetos de búfer según el tipo del indicador estándar:

//+------------------------------------------------------------------+
//| Return the list of buffers by the standard indicator type        |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByIndType(const ENUM_INDICATOR indicator_type)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_IND_TYPE,indicator_type,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Obtenemos una lista con los objetos de búfer cuyas propiedades incluyen un tipo de indicador estándar igual al valor transmitido al método.
El puntero a la lista obtenida lo retornaremos desde el método.

Método que retorna la lista de objetos de búfer según el tipo del indicador estándar y el identificador:

//+------------------------------------------------------------------+
//| Return the list of buffers by type and ID                        |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id)
  {
   CArrayObj *list=this.GetListBufferByIndType(indicator_type);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_ID,id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Primero, obtenemos la lista de objetos de búfer cuyas propiedades incluyen el tipo de indicador estándar indicado, después, filtramos la lista obtenida según los objetos de búfer cuyas propiedades incluyen el identificador indicado.
El puntero a la lista obtenida como resultado lo retornaremos desde el método.

Método que retorna la lista de objetos de búfer que pertenecen a algún indicador estándar:

//+------------------------------------------------------------------+
//| Return the list of indicator buffers                             |
//| (featuring the ID of belonging to an indicator)                  |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBuffersWithID(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

A continuación, obtenemos de la lista de colección de objetos de búfer la lista de objetos cuya propiedad "identificador" no es igual a -1.
El puntero a la lista obtenida lo retornaremos desde el método.

El siguiente eslabón entre el programa y la biblioteca es la clase del objeto principal de la biblioteca CEngine.
Introducimos todas las mejoras necesarias en el archivo de la clase \MQL5\Include\DoEasy\Engine.mqh.

Luego, añadimos a la sección pública de la clase el metodo para recrear todas las series temporales:

//--- Re-create (1) the specified timeseries of the specified symbol, (2) all collection timeseries
   bool                 SeriesReCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0)
                          { return this.m_time_series.ReCreateSeries(symbol,timeframe,rates_total,required);      }
   bool                 SeriesReCreateAll(const int rates_total=0,const uint required=0)
                          { return this.m_time_series.ReCreateSeriesAll(rates_total,required);                    }

El método simplemente retorna el resultado del funcionamiento del método homónimo de la colección de series temporales que hemos añadido más arriba.

Ahora, añadimos a la sección de la clase el método que retorna el número de barras de la serie temporal indicada:

//--- Return (1) an empty, (2) partially filled timeseries
   CSeriesDE           *SeriesGetSeriesEmpty(void)       { return this.m_time_series.GetSeriesEmpty();            }
   CSeriesDE           *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted();      }
//--- Return the umber of bars of the timeseries of the specified symbol/period
   int                  SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe);

Vamos a implementar este método fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Return the umber of bars of the timeseries of the specified symbol/period |
//+------------------------------------------------------------------+
int CEngine::SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
   if(series==NULL)
      return WRONG_VALUE;
   return (int)series.Bars();
  }
//+------------------------------------------------------------------+

Obtenemos la serie temporal indicada de la clase de colección de las series temporales y retornamos el número de barras de la serie temporal.


Antes, teníamos el método que retorna el último búfer creado:

//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastBuffer(void);

y su implementación:

//+------------------------------------------------------------------+
//| Return the last indicator buffer                                 |
//| in the indicator buffer collection list                          |
//+------------------------------------------------------------------+
CBuffer *CEngine::GetLastBuffer(void)
  {
   CArrayObj *list=this.GetListBuffers();
   if(list==NULL)
      return NULL;
   return list.At(list.Total()-1);
  }
//+------------------------------------------------------------------+

Eliminamos la implementación del método del listado de la clase, y cambiamos su declaración por el nuevo método:

//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastCreateBuffer(void)                                      { return this.m_buffers.GetLastCreateBuffer();        }

El método retorna el resultado del funcionamiento del método homónimo de la clase de colección de búferes que hemos escrito anteriormente.

En la sección pública de la clase, declaramos el método para crear el indicador estándar AC y los búferes para su funcionamiento:

//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", 
//--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels",
//--- (8) "Display as bars", (9) "Display as candles", calculated buffer
   bool                 BufferCreateArrow(void)                                        { return this.m_buffers.CreateArrow();                }
   bool                 BufferCreateLine(void)                                         { return this.m_buffers.CreateLine();                 }
   bool                 BufferCreateSection(void)                                      { return this.m_buffers.CreateSection();              }
   bool                 BufferCreateHistogram(void)                                    { return this.m_buffers.CreateHistogram();            }
   bool                 BufferCreateHistogram2(void)                                   { return this.m_buffers.CreateHistogram2();           }
   bool                 BufferCreateZigZag(void)                                       { return this.m_buffers.CreateZigZag();               }
   bool                 BufferCreateFilling(void)                                      { return this.m_buffers.CreateFilling();              }
   bool                 BufferCreateBars(void)                                         { return this.m_buffers.CreateBars();                 }
   bool                 BufferCreateCandles(void)                                      { return this.m_buffers.CreateCandles();              }
   bool                 BufferCreateCalculate(void)                                    { return this.m_buffers.CreateCalculate();            }

//--- The methods of creating standard indicators and buffer objects for them
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);  }

//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object

El método retorna el resultado del funcionamiento del método homónimo para crear el indicador AC de la clase de colección de búferes que hemos escrito anteriormente. Los métodos para crear los otros indicadores estándar los añadiremos en próximos artículos.

Implementación del método encargado de preparar los datos del búfer de cálculo para un indicador estándar (por el momento, solo para AC):

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

Obtenemos la lista de objetos de búfer según el tipo de indicador e identificador,
dejamos en la lista obtenida solo los búferes de indicador,
obtenemos el primer búfer de cálculo (y el único para AC) de la lista,
rellenamos el búfer de cálculo con la cantidad indicada de datos y
retornamos la cantidad de datos copiados con éxito desde el manejador del indicador al búfer de cálculo
.

Implementación del método encargado de limpiar los datos del búfer de cálculo para un indicador estándar (por el momento, solo para AC) según el índice indicado:

//+------------------------------------------------------------------+
//| Clear buffer data of the specified standard indicator            |
//| by the timeseries index                                          |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
   CArrayObj *list=this.GetListBufferByID(id);
   if(list==NULL)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

El funcionamiento del método es prácticamente igual que en la preparación de los datos. Salvo que, en lugar de copiar los datos del manejador del indicador al búfer calculado, aquí se establece un valor vacío, indicado para el objeto de búfer en el índice especificado en el búfer dibujado.

Implementación del método de rellenado del búfer de dibujado en el gráfico actual con los datos de un indicador estándar de cualquier símbolo/marco temporal (por ahora solo para AC)

//+------------------------------------------------------------------+
//| Set values for the current chart to the specified buffer         |
//| of the 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 with ID
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL)
      return false;
//--- Get the list of drawn objects with ID
   CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Get the list of calculated buffers with ID
   CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Exit if any of the lists is empty
   if(list_data.Total()==0 || list_calc.Total()==0)
      return false;
//--- Declare the necessary objects and variables
   CBuffer *buffer_data=NULL;
   CBuffer *buffer_calc=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   datetime time_period=0;
   double value0=EMPTY_VALUE, value1=EMPTY_VALUE;

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

En los comentarios se describe la lógica completa del método en el cálculo de los datos para AC: esperamos que el lector la comprenda sin dificultades.


Al final del cuerpo de la clase, declaramos los dos métodos para procesar los eventos de la biblioteca:

public:
//--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

//--- Handling DoEasy library events
void                    OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Working with events in the tester
void                    EventsHandling(void);

  };
//+------------------------------------------------------------------+

Antes, para procesar los eventos de la biblioteca en nuestros programas, utilizábamos funciones con el mismo nombre que los métodos recién declarados. Y transferíamos estas funciones de un programa a otro sin ningún cambio. Esto nos impulsa a pensar que estos manejadores se pueden transferir a la biblioteca, obteniendo simplemente en el programa las banderas de los eventos sucedidos (la obtención de las banderas, así como las propias banderas de los eventos y la posibilidad de procesar eventos en nuestros programas, las implementaremos más tarde).

Ya hemos transferido estas funciones del indicador de prueba al listado de la clase CEngine, convirtiéndolas en la implementación de los métodos declarados anteriormente:

//+------------------------------------------------------------------+
//| Handling DoEasy library events                                   |
//+------------------------------------------------------------------+
void CEngine::OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time
   ushort msc=this.EventMSC(lparam);
   ushort reason=this.EventReason(lparam);
   ushort source=this.EventSource(lparam);
   long time=::TimeCurrent()*1000+msc;
   
//--- Handling symbol events
   if(source==COLLECTION_SYMBOLS_ID)
     {
      CSymbol *symbol=this.GetSymbolObjByName(sparam);
      if(symbol==NULL)
         return;
      //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol
      int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits());
      //--- Event text description
      string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx));
      //--- Property change text value
      string value=::DoubleToString(dparam,digits);
      
      //--- Check event reasons and display its description in the journal
      if(reason==BASE_EVENT_REASON_INC)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     }   
     
//--- Handling account events
   else if(source==COLLECTION_ACCOUNT_ID)
     {
      CAccount *account=this.GetAccountCurrent();
      if(account==NULL)
         return;
      //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol
      int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits());
      //--- Event text description
      string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx));
      //--- Property change text value
      string value=::DoubleToString(dparam,digits);
      
      //--- Checking event reasons and handling the increase of funds by a specified value,
      
      //--- Display an event in the journal
      if(reason==BASE_EVENT_REASON_INC)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     } 
     
//--- Handling market watch window events
   else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE)
     {
      //--- Market Watch window event
      string descr=this.GetMWEventDescription((ENUM_MW_EVENT)idx);
      string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam);
      Print(TimeMSCtoString(lparam)," ",descr,name);
     }
     
//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- "Bars skipped" event
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
     }
     
//--- Handling trading events
   else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE)
     {
      //--- Get the list of trading events
      CArrayObj *list=this.GetListAllOrdersEvents();
      if(list==NULL)
         return;
      //--- get the event index shift relative to the end of the list
      //--- in the tester, the shift is passed by the lparam parameter to the event handler
      //--- outside the tester, events are sent one by one and handled in OnChartEvent()
      int shift=(this.IsTester() ? (int)lparam : 0);
      CEvent *event=list.At(list.Total()-1-shift);
      if(event==NULL)
      return;
      //--- Accrue the credit
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Additional charges
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Correction
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Enumerate bonuses
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Additional commissions
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Daily commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Monthly commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Daily agent commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Monthly agent commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Interest rate
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Canceled buy deal
      if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Canceled sell deal
      if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Dividend operations
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Accrual of franked dividend
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Tax charges
      if(event.TypeEvent()==TRADE_EVENT_TAX)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Replenishing account balance
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Withdrawing funds from balance
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      
      //--- Pending order placed
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order removed
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order activated by price
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order partially activated by price
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position opened
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position opened partially
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by an opposite one
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by a new deal (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by partial market order execution (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by a new deal (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by partial execution of a market order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by partial activation of a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position partially closed by an opposite one
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially by StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially by TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- StopLimit order activation
      if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order and StopLoss price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order, StopLoss and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's StopLoss and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position's StopLoss and TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
     }
  }
//+------------------------------------------------------------------+
//| Working with events in the tester                                |
//+------------------------------------------------------------------+
void CEngine::EventsHandling(void)
  {
//--- If a trading event is present
   if(this.IsTradeEvent())
     {
      //--- Number of trading events occurred simultaneously
      int total=this.GetTradeEventsTotal();
      for(int i=0;i<total;i++)
        {
         //--- Get the next event from the list of simultaneously occurred events by index
         CEventBaseObj *event=this.GetTradeEventByIndex(i);
         if(event==NULL)
            continue;
         long   lparam=i;
         double dparam=event.DParam();
         string sparam=event.SParam();
         this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
        }
     }
//--- If there is an account event
   if(this.IsAccountsEvent())
     {
      //--- Get the list of all account events occurred simultaneously
      CArrayObj* list=this.GetListAccountEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- If there is a symbol collection event
   if(this.IsSymbolsEvent())
     {
      //--- Get the list of all symbol events occurred simultaneously
      CArrayObj* list=this.GetListSymbolsEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- If there is a timeseries collection event
   if(this.IsSeriesEvent())
     {
      //--- Get the list of all timeseries events occurred simultaneously
      CArrayObj* list=this.GetListSeriesEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Ya analizamos anteriormente el funcionamiento de estas funciones (que ahora son métodos de la clase CEngine), en los artículos iniciales de la descripción de la biblioteca, al crear los asesores de prueba. Por el listado de los métodos, podemos ver que mostramos (casi) cada evento con un mensaje en el diario. Por consiguiente, podemos crear una lista de banderas de eventos en el ámbito global y establecer las banderas necesarias. En nuestros propios programas, será muy sencillo hacer manejadores de cada una de las banderas ponderadas. Cosa que haremos en lo sucesivo.

De esta forma, nos evitamos escribir estos manejadores en cada uno de nuestros programas.

En la clase, tenemos el manejador del evento calculate, que se llama desde el indicado. Si el valor retornado por el manejador es igual a cero, esto significará que no todas la series temporales utilizadas en el indicador han sido construidas. En este caso, además, el indicador debe salir de OnCalculate() con un código de retorno de 0, lo que significará que debemos esperar el siguiente tick e indicar que no se han calculado los datos.

Como estamos añadiendo trabajo con los indicadores estándar, deberemos verificar de la misma forma que se ha calculado el indicador creado.
Para saber la cantidad de datos calculados, podemos usar la función BarsCalculated(), que retorna la cantidad de datos ya calculada por el indicador. Si los datos aún no se han calculado, la función retornará -1.

Vamos a escribir el método que procesa el evento calculate para comprobar si todos los indicadores estándar creados en la colección de búferes se han calculado correctamente:

//+------------------------------------------------------------------+
//| Calculate event handler                                          |
//+------------------------------------------------------------------+
int CEngine::OnCalculate(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If this is not an indicator, exit
   if(this.m_program!=PROGRAM_INDICATOR)
      return 0;
//--- Re-create empty timeseries
//--- If at least one of the timeseries is not synchronized, return zero
   if(!this.SeriesSync(data_calculate,required))
      return 0;
//--- Update the timeseries of the current symbol (not in the tester) and
//--- return either 0 (in case there are empty timeseries), or rates_total
   if(!this.IsTester())
      this.SeriesRefresh(NULL,data_calculate);
   int res=(this.SeriesGetSeriesEmpty()==NULL ? data_calculate.rates_total : 0);
   
//--- Check the amount of calculated standard indicator data
   CArrayObj *list=m_buffers.GetListBuffersWithID();
   if(list!=NULL)
     {
      //--- In a loop by the number of buffers having an ID
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         //--- get the next calculated buffer using the standard indicator
         CBuffer *buff=list.At(i);
         if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorHandle()==INVALID_HANDLE)
            continue;
         //--- if the indicator data is not calculated yet, return zero
         if(buff.IndicatorBarsCalculated()==WRONG_VALUE)
            return 0;
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

La lógica de procesamiento de los indicadores creados se describe en el listado del método.

Por hoy, el último detalle a pulir dentro de las mejoras de la biblioteca será la adición del periodo del gráfico actual a la lista de marcos temporales utilizados.
En el archivo de funciones de servicio de la biblioteca E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Services\DELib.mqh, tenemos una función que prepara la lista de marcos temporales utilizados. Y si en los ajustes del programa no se ha indicado el periodo del gráfico actual, la biblioteca no creará su serie temporal. Y para trabajar, necesitamos constantemente conocer esta.

Añadimos a la función CreateUsedTimeframesArray() un bloque de código para introducir el periodo del gráfico actual en la lista de marcos temporales utilizados:

//+------------------------------------------------------------------+
//| Prepare the array of timeframes for the timeseries collection    |
//+------------------------------------------------------------------+
bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[])
  {
//--- If working with the current chart period, fill the array with the current timeframe description string
   if(mode_used_periods==TIMEFRAMES_MODE_CURRENT)
     {
      ArrayResize(used_periods_array,1,21);
      used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
      return true;
     }
//--- If working with a predefined set of chart periods (from the defined_used_periods string)
   else if(mode_used_periods==TIMEFRAMES_MODE_LIST)
     {
      //--- Set comma as a separator (defined in the Datas.mqh file, page 11)
      string separator=INPUT_SEPARATOR;
      //--- Fill in the array of parameters from the string with predefined timeframes
      int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array);
      //--- if nothing is found, display the appropriate message (working with the current period is selected automatically)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         //--- Set the current period to the array
         ArrayResize(used_periods_array,1,21);
         used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         return false;
        }
     }
//--- If working with the full list of timeframes, fill in the array with strings describing all timeframes
   else
     {
      ArrayResize(used_periods_array,21,21);
      for(int i=0;i<21;i++)
         used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1)));
     }

//--- Add the current chart timeframe to the list of used periods
   bool f=false;
   for(int i=0;i<ArraySize(used_periods_array);i++)
     {
      if(used_periods_array[i]==TimeframeDescription((ENUM_TIMEFRAMES)Period()))
        {
         f=true;
         break;
        }
     }
   //--- If the list of used periods features no timeframe of the current chart
   if(!f)
     {
      //--- Increase the array of used periods by 1 and add the current chart period to it
      ArrayResize(used_periods_array,ArraySize(used_periods_array)+1);
      used_periods_array[ArraySize(used_periods_array)-1]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
     }
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+

Con esto, damos por finalizadas las mejoras de las clases de la biblioteca.

Vamos a poner a prueba la creación del indicador estándar de símbolo y periodo múltiples Accelerator Oscillator.

Prueba

Para la prueba, tomaremos el indicador del artículo anterior
y lo guardaremos en la carpeta \MQL5\Indicators\TestDoEasy\Part47\ con el nuevo nombre TestDoEasyPart47.mq5.

En los ajustes del indicador creado, establecemos qué símbolo y marco temporal se van a utilizar para calcular el indicador estándar AcceleratorOscillator. Estos datos serán representados por nuestro indicador en el subventana del gráfico actual.

El encabezado del indicador será el siguiente:

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

//--- classes

//--- enums

//--- defines

//--- structures

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

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

Indicamos en los ajustes solo el símbolo y el periodo del gráfico de este símbolo según los cuales se calculará el indicador AC.

En el manejador OnInit(), creamos el indicador estándar AC con los parámetros indicados en los parámetros de entrada del indicador,
un identificador igual a 1
, y los búferes para trabajar con él:

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

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

//--- Create the button panel

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

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing AO
   engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);

//--- Check the number of buffers specified in the 'properties' block
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Create the color array and set non-default colors to all buffers within the collection
   color array_colors[]={clrGreen,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();
//--- Set the short name for the indicator and bit depth
   IndicatorSetString(INDICATOR_SHORTNAME,"AC("+InpUsedSymbols+","+TimeframeDescription(InpPeriod)+")");
   IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+2);
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

En OnCalculate(), primero preparamos los datos del búfer de cálculo del indicador AC, y después, en el ciclo principal del indicador, rellenamos los datos del búfer de dibujado en el gráfico actual con los datos del búfer de cálculo del indicador AC:

//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      engine.EventsHandling();      // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
     }
   
//--- Prepare data 
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   
//--- Fill in the calculated buffer with AO data
   CArrayObj *list=engine.GetBuffersCollection().GetListBuffersWithID();
   if(list!=NULL)
     {
      for(int i=0;i<list.Total();i++)
        {
         CBuffer *buff=list.At(i);
         if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
            continue;
         CSeriesDE *series=engine.SeriesGetSeries(buff.Symbol(),buff.Timeframe());
         if(series==NULL)
            return 0;
         ulong used_data=series.AvailableUsedData();
         int copied=engine.GetBuffersCollection().PreparingDataBufferStdInd(IND_AC,1,(int)used_data);
         if(copied<(int)used_data)
            return 0;
        }
     }

//--- Calculate the indicator
   CBar *bar=NULL;         // Bar object for defining the candle direction
   uchar color_index=0;    // Color index to be set for the buffer depending on the candle direction
   
//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(IND_AC,1,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Esto es todo lo que tenemos que hacer para calcular y representar el indicador estándar AC en el gráfico actual, calculando dicho indicador con cualquier símbolo/marco temporal.

En el próximo artículo, mejoraremos el bloque con la preparación de los datos del indicador estándar (en la presente implementación no resulta óptimo, solo lo hemos hecho para comprobar el concepto), y también lo trasladaremos a la biblioteca.

Podrá ver el código completo del indicador en los archivos adjuntos al artículo.

Vamos a compilar el indicador e iniciarlo en el gráfico EURUSD, M1, estableciendo previamente en los ajustes del indicador el símbolo GBPUSD y el marco temporal M5, lo cual indica que en el gráfico de minutos actual EURUSD se representarán los datos del indicador AC calculado en el símbolo GBPUSD con un marco temporal M5:

A modo de comparación, abriremos a su lado un gráfico GBPUSD, M5 con el indicador estándar AC en el mismo.

¿Qué es lo próximo?

En el próximo artículo, continuaremos creando indicadores estándar de periodo y símbolo múltiples.

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

Volver al contenido

Artículos de esta serie:

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