English Русский 中文 Deutsch 日本語 Português
preview
Preparación de indicadores de símbolo/periodo múltiple

Preparación de indicadores de símbolo/periodo múltiple

MetaTrader 5Indicadores | 16 mayo 2024, 16:17
147 0
Artyom Trishkin
Artyom Trishkin

Contenido


Introducción

Continuando con el tema de la creación de plantillas para indicadores técnicos en asesores e indicadores, iniciado con el artículo sobre la creación de paneles informativos, y continuado en otros tres artículos sobre osciladores, indicadores de volumen y Bill Williams, y también osciladores de tendencia, hoy iniciaremos el tema de la creación de indicadores de símbolo/periodo múltiple. Estos son indicadores que funcionan en el gráfico actual, pero se calculan a partir de datos de otros símbolos y/o de otros marcos temporales del gráfico. Crearemos una clase básica de indicador múltiple y un conjunto de clases por tipos de todos los indicadores estándar. También se creará una clase para el indicador personalizado que nos dará la posibilidad de "convertir" cualquier indicador en un indicador de símbolo y periodo múltiple. Para todas las clases de indicadores crearemos una clase de colección común, en la que se colocarán los indicadores creados en el programa, así podremos utilizar los métodos de la colección para acceder a cualquiera de los indicadores creados para obtener sus datos. En general, el objetivo será: implementar una herramienta cómoda para crear indicadores múltiples y trabajar con sus datos.


Principios básicos

Para comprender bien la lógica de trabajo con los indicadores, intentaremos entender un poco cómo está dispuesto todo. El indicador consta de dos partes: la parte calculada y la parte dibujada. Cada una de estas partes no sabe nada de la otra. Al crear un indicador, el subsistema del terminal buscará la presencia de dicho indicador en el gráfico, es decir, buscará el mismo nombre y los mismos parámetros. Si un indicador de este tipo ya está adjunto a un gráfico o se ha creado para él de forma programática, se utilizará el manejador del indicador existente en lugar de crear uno nuevo. La parte dibujada del indicador recibirá los datos a dibujar de la parte de cálculo según su manejador. Puede darse el caso de que se acceda a una parte de cálculo desde varias dibujadas al mismo tiempo.

En el búfer de la parte de cálculo, los datos del indicador calculado se almacenarán en un array cuya disposición de datos irá desde el presente hacia el pasado. Los datos del índice 0 del array-búfer se corresponden con los datos actuales del gráfico:


Cada celda del array almacenará los datos de la barra correspondiente a la barra de la serie temporal del símbolo/periodo para la que está diseñado el indicador. En consecuencia, para obtener datos de la búfer de la parte de cálculo del indicador y mostrarlos en el gráfico de otro símbolo/marco temporal, deberemos calcular el número de barra en el gráfico correspondiente a la hora de la barra en el array de búfer de la parte de cálculo. También se deberán escribir los datos obtenidos en el búfer de la parte dibujada del indicador, de modo que todas las barras del gráfico actual que caigan en la hora de apertura de la barra en la búfer de la parte de cálculo se escriban en las celdas correspondientes del búfer dibujado.

Por ejemplo, una barra en un gráfico de cinco minutos se corresponderá con cinco barras en un gráfico de minutos. Todas estas cinco barras del gráfico de minutos deberán rellenarse con el valor de la barra correspondiente del gráfico de cinco minutos. Un algoritmo similar se utiliza para dibujar los datos de los periodos inferiores de un gráfico en un gráfico con un periodo superior. En este caso, todas las barras de las celdas del búfer de la parte de cálculo que se correspondan con la hora de la barra en el gráfico del marco temporal superior, se dibujarán en una barra del búfer dibujado.

Aquí habrá naturalmente alguna suposición en las lecturas, ya que al final solo se dibujarán en esta barra los datos de la última barra del marco temporal inferior, que caerá en la barra correspondiente del marco temporal superior. Todo dependerá de la dirección de recepción de datos desde el búfer de la parte de cálculo del marco temporal menor: los datos que se reciban en último lugar, se dibujarán en la barra del gráfico del marco temporal mayor.

La función CopyBuffer() sirve para obtener datos del búfer de indicador calculado:

Obtendrá en la array buffer los datos del búfer indicado del indicador especificado en la magnitud indicada.

El recuento de los elementos de los datos copiados (el búfer de indicador con índice buffer_num) desde la posición inicial se realizará desde el presente hacia el pasado, es decir, una posición inicial igual a 0 indicará la barra actual (valor del indicador de la barra actual).

Al copiar una cantidad de datos que desconocemos de antemano, resultará deseable utilizar un array dinámico como array receptor buffer[], porque la función CopyBuffer() intentará distribuir el tamaño del array receptor para que coincida con el tamaño de los datos copiados. Si el array receptor buffer[] es un búfer de indicador (un array previamente asignado para almacenar los valores de indicadores usando la función SetIndexBufer(), se permitirá el copiado parcial.

Si es necesario realizar un copiado parcial de los valores del indicador a otro array (no al búfer de indicador), para ello deberemos utilizar un array intermedio en el que se copie la cantidad requerida. Y a partir de este array intermedio copiaremos el número necesario de valores a los lugares requeridos del array receptor.

Si es necesario copiar una cantidad predeterminada de datos, será mejor hacerlo en un búfer asignado estáticamente para evitar una sobreasignación innecesaria de memoria.

Independientemente de la propiedad que tenga el array receptor - as_series=true o as_series=false, los datos se copiarán de forma que el elemento más antiguo se encuentre al principio de la memoria física asignada al array. Existen 3 variaciones de la función.

Acceso según la posición inicial y número de elementos necesarios

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // indicator buffer index
   int       start_pos,            // starting point 
   int       count,                // amount to copy
   double    buffer[]              // array the data to be copied to
   );

Acceso según la fecha de inicio y número de elementos necesarios

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // indicator buffer index
   datetime  start_time,           // starting date
   int       count,                // amount to copy
   double    buffer[]              // array the data to be copied to
   );

Acceso según las fechas de inicio y fin del intervalo de tiempo requerido

int  CopyBuffer(
   int       indicator_handle,     // indicator handle
   int       buffer_num,           // indicator buffer index
   datetime  start_time,           // starting date
   datetime  stop_time,            // end time
   double    buffer[]              // array the data to be copied to
   );

Parámetros

indicator_handle

[in]  Manejador del indicador obtenido por la función de indicador correspondiente.

buffer_num

[in]  Número del búfer de indicador.

start_pos

[in] Número del primer elemento a copiar.

count

[in]  Número de elementos a copiar.

start_time

[in] Hora de la barra correspondiente al primer elemento.

stop_time

[in] Hora de la barra correspondiente al último elemento.

buffer[]

[out] Array de tipo double.

Valor retornado

El número de elementos del array copiados o -1 en caso de error.

Observación

Al solicitar datos del indicador, si las series temporales solicitadas aún no se han construido o necesitan descargarse del servidor, la función retornará directamente -1, pero se iniciará el proceso de descarga/construcción.

Al solicitar los datos del asesor o script, se iniciará la descarga desde el servidor, si el terminal no dispone de estos datos localmente, o se iniciará la construcción de las series temporales requeridas, si los datos pueden construirse desde la historia local, pero aún no están listos. La función retornará la cantidad de datos que estarán listos cuando expire el tiempo de espera.

Utilizaremos la primera variante de la función: el acceso según la posición inicial (índice del ciclo) y el número de elementos necesarios.

La estructura de los objetos será la siguiente:

  1. Clase básica del indicador de símbolo/periodo múltiple que contiene la funcionalidad básica común a todos los indicadores;
  2. Clases derivadas del objeto básico que describen cada indicador según su tipo;
  3. Clase de colección de indicadores que permite crear cualquier indicador y añadirlo a la colección. La clase ofrecerá al usuario todas las herramientas necesarias para crear los indicadores y obtener los datos de ellos.

Al trabajar con datos que no pertenezcan a la programación actual, para evitar "liberar" la serie temporal, deberemos acceder a esta serie temporal al menos una vez cada dos minutos. En este caso, sucederá la "retención" de la serie temporal, lo que agilizará el acceso a la misma (no será necesario esperar a que se sincronicen los datos cada vez). En el constructor de la clase crearemos el primer acceso a la serie temporal sobre la que se construirá el indicador, que nos permitirá empezar a cargarla (si no hay acceso a ella localmente). Y además, una vez cada 90 segundos (un minuto y medio) en el temporizador de la clase básica llamaremos a la serie temporal para mantenerla.


Cálculo económico del indicador

Para calcular y mostrar el indicador, deberemos realizar un cálculo económico: para que en la primera ejecución el indicador pueda ser calculado sobre todos los datos históricos, y en los ticks posteriores, solo una o dos barras.

En el manejador OnCalculate() existen constantes predeterminadas que guardan el tamaño de los datos de entrada (serie temporal o array) y la cantidad de datos calculados en la anterior llamada de OnCalculate():

Cálculo basado en un array de datos

int  OnCalculate(
   const int        rates_total,       // price[] array size
   const int        prev_calculated,   // number of processed bars at the previous call
   const int        begin,             // index number in the price[] array meaningful data starts from
   const double&    price[]            // array of values for calculation
   );

Cálculos basados en series temporales del marco temporal actual

int  OnCalculate(
   const int        rates_total,       // size of input timeseries
   const int        prev_calculated,   // number of processed bars at the previous call
   const datetime&  time{},            // Time array
   const double&    open[],            // Open array
   const double&    high[],            // High array
   const double&    low[],             // Low array
   const double&    close[],           // Close array
   const long&      tick_volume[],     // Tick Volume array
   const long&      volume[],          // Real Volume array
   const int&       spread[]           // Spread array
   );

La disponibilidad de estos datos nos permite realizar un cálculo económico rápido del indicador. Por ejemplo, una posible forma de hacerlo sería calcular el valor límite:

//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      //--- If the indicator has any buffers, initialize them here with empty values set for these buffers
     }

En la primera ejecución del indicador tendremos el tamaño de la serie temporal (rates_total) y la cantidad de datos calculados en la llamada anterior (prev_calculated). El valor de las barras calculadas previamente en la primera ejecución será cero: el indicador aún no se ha calculado. Así, el valor límite será superior a 1 (igual al número de barras disponibles menos cero). Con este valor especificaremos un valor de límite igual a rates_total-1: esto será la historia completa disponible para el cálculo. En este caso, además, deberemos borrar primero todos los datos extraídos anteriormente en los búferes de indicador, por ejemplo,

ArrayInitialize(Buffer,EMPTY_VALUE);

A continuación, el ciclo principal calculará toda la historia, ya que el ciclo se construirá desde el valor límite hasta cero inclusive:

   for(int i=limit;i>=0;i--)
     {
      // Calculating indicator at each bar of the loop (i)
     }  

Tenemos que considerar que con este método de cálculo, todos los arrays utilizados en los cálculos y el propio búfer de indicador deberán tener la bandera de indexación como las series temporales:

ArraySetAsSeries(array,true);

Si el límite calculado es igual a 1, indicará la apertura de una nueva barra en el gráfico: el indicador contará la primera barra y la barra cero de la serie temporal, el ciclo de 1 a 0 inclusive.

Si el límite calculado es 0, indicará que se está trabajando en el tick actual: el indicador contará solo la barra cero de la serie temporal, el ciclo de 0 a 0 inclusive.

Si necesitamos un cálculo partiendo de la barra cero hacia la profundidad de los datos históricos (para no tener que desplegar arrays y búferes de series temporales), entonces el diseño del ciclo será inverso:

   int i=fmax(0, prev_calculated-1);
   for(;i<rates_total;i++)
     {
      // Calculating indicator at each bar of the loop (i)
     }   

Vale, el cálculo económico en el gráfico actual es fácil de hacer. Pero, ¿y si necesitamos datos que no pertenezcan al gráfico actual? ¿Cuándo copiamos el conjunto completo de datos de la parte de cálculo y cuándo copiamos solo la última o las dos últimas barras?

Aquí es donde las funciones Bars() y BarsCalculated() vienen al rescate. Son análogas a las variables constantes predefinidas del indicador rates_total y prev_calculated, pero retornan el número de barras para el símbolo/periodo especificado y la cantidad de datos calculados por el indicador. Como el indicador se basa en el símbolo/periodo especificado durante su creación, la cantidad de datos calculados se referirá a este símbolo/periodo. Obtenemos el indicador según su manejador.

Partiendo del hecho de que podemos calcular para cualquier símbolo/periodo cuántas barras deben copiarse (para no copiar todo el array en cada tick), en la clase básica del indicador haremos exactamente la misma construcción que para el símbolo/periodo actual:

limit=rates_total-prev_calculated;

solo las variables limit, rates_total y prev_calculated serán miembros privados de la clase, y obtendrán los valores de las funciones Bars() y BarsCalculated(). En cada tick se calculará el límite, y si es cero, solo se copiarán los datos de las dos últimas barras de datos (la barra actual y la anterior). Si el límite es igual a 1, indicará que se abre una nueva barra en el símbolo/periodo del indicador, y será necesario aumentar el array en 1, y luego ya copiar los datos del búfer de indicador, que también serán solo dos. Si el límite es superior a uno, el array completo del búfer de la parte de cálculo del indicador se copiará en el array de búfer receptor de la clase, porque se considerará que supone la primera ejecución o un cambio en la historia.

Esta lógica se aplicará al día comercial cuando aparecen los ticks.

Los días festivos, sin embargo, requieren un enfoque diferente: suponen un caso especial. Aquí no hay ticks, la función Bars() muy a menudo retorna cero, y el número de barras calculadas del indicador no es fijo, también será cero. El indicador, si hay algún error en los datos iniciales para el cálculo, deberá retornar cero, para esperar hasta el siguiente tick y actualizar los datos y calcular de nuevo. Pero en los días festivos no hay ticks, salvo en el primer inicio.

En la primera ejecución, el indicador primero borrará los arrays del búfer y luego realizará los cálculos. Sin embargo, los cálculos pueden fallar porque no hay suficientes datos para los mismos, o simplemente porque prev_calculated retorna un valor nulo. Y el indicador saldrá de OnCalculate(), retornando cero de nuevo. En consecuencia, si actualizamos el gráfico con el botón directo del ratón, que supone esencialmente una emulación de ticks, el indicador volverá a ver que se calcula con un error, y reinicializará los búferes, considerando que se trata de la primera ejecución. Y así una y otra vez, eliminando los datos de los búferes que se acaban de dibujar en el gráfico.... Pero hay una salida a este problema.

Si en la primera ejecución no se ha podido calcular el indicador de una vez, podremos esperar al siguiente tick durante 20 - 30 segundos y emular el tick de forma programática. Esto podrá hacerse utilizando la función ChartSetSymbolPeriod(). Si lo llamamos especificando el símbolo/periodo actual del gráfico, provocará el recálculo de los indicadores adjuntos al gráfico. Así, será posible calcular el indicador en el gráfico incluso en ausencia de ticks.

Veinte segundos de espera serán suficientes para cargar la historia necesaria por símbolo/periodo del indicador y calcularla. No obstante, necesitaremos una bandera que informe de que el indicador ya ha sido calculado (porque prev_calculated retorna cero), y si no establecemos la bandera de éxito de cálculo, el indicador borrará sus búferes nuevamente. Por ello, para entender que el indicador se ha calculado correctamente, solo tendremos que ver que el número de barras calculadas en el objeto de indicador es igual al número de barras de su símbolo/periodo. Si Bars() retorna cero, entonces podremos averiguar el número disponible de barras del símbolo/periodo requerido de otra manera (no olvide que estamos hablando de indicadores de símbolo/periodo múltiple, que se calculan en otro programa, un asesor o indicador ejecutado en el gráfico actual). En la función SeriesInfoInteger( ), podremos obtener la fecha de inicio de la historia disponible según el símbolo/periodo y la fecha de fin de dicha historia:

SeriesInfoInteger(symbol,period,SERIES_FIRSTDATE);      // The very first date for a period and symbol at the moment
SeriesInfoInteger(symbol,period,SERIES_LASTBAR_DATE);   // Time of opening the last bar for the period and symbol

A partir de estas dos fechas y del periodo del gráfico de series temporales, podremos calcular fácilmente el número de barras disponibles, incluso si Bars(symbol,period) o SeriesInfoInteger(symbol,period,SERIES_BARS_COUNT) han retornado cero.

Una vez se hayan obtenido todos los datos y se haya calculado correctamente el indicador, se activará la bandera de éxito del cálculo. Dicha bandera se comprobará en la condición cuando limit > 1 (en este caso deberemos inicializar los arrays del búfer de indicador con un valor "vacío"). En la primera ejecución se reiniciará la bandera de éxito, se inicializarán los arrays y se intentará calcular el indicador en el símbolo/periodo no actual del gráfico. Si el cálculo falla, se esperarán 20 segundos en el temporizador del siguiente tick.

Si se trata de un festivo, no habrá tick, y al cabo de 20 segundos se enviará una orden para establecer el gráfico en el símbolo y periodo actuales, emulando un tick. Cuando el indicador se reinicie en el temporizador (después de 20 segundos), los datos ya deberían estar cargados y el indicador debería calcularse sin errores. En este caso, además, se establecerá la bandera de éxito del cálculo. En la siguiente ejecución del temporizador, se comprobará esta bandera y, si está activada, no se emulará el tick. En total hay tres intentos de dibujar los búferes de indicador. Después de tres intentos, la bandera de éxito se activará forzosamente y se detendrán los intentos de emular el tick. Si el indicador no se ha podido calcular durante tres ticks emulados, entonces ahora solo se podrá manualmente: o bien (botón directo del ratón --> Actualizar), o bien cambiando el marco temporal del gráfico hacia adelante y hacia atrás para pasar por todo el proceso de emulación de ticks con nueva carga de datos.

Esa es la teoría. Pasemos ahora a la práctica: la creación de clases de indicador múltiple.


Clase básica del indicador MSTF

En la carpeta del terminal \MQL5\Include\IndMSTF\, crearemos un nuevo archivo IndMSTF.mqh de la clase CIndMSTF. La clase deberá heredarse del objeto básico de la Biblioteca Estándar CObject. El archivo del objeto básico deberá estar conectado al archivo de la nueva clase creada:

//+------------------------------------------------------------------+
//|                                                      IndMSTF.mqh |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:

protected:

public:
//--- Constructor/destructor
                     CIndMSTF();
                    ~CIndMSTF();
  };

Antes de añadir los métodos a las distintas secciones de la clase, añadiremos algunas macrosustituciones, enumeraciones y la estructura del búfer de indicador.

Necesitaremos un temporizador de segundos que registre dos intervalos temporales:

  • 90 segundos, tras lo cual accederemos a la serie temporal de los indicadores que se ha calculado no sobre el símbolo/periodo actual,
  • 20 segundos, tras lo cual emularemos un tick para dibujar el indicador los días festivos.
Luego escribiremos as macrosustituciones para especificar los tiempos de espera de estos dos intervalos temporales:
//--- includes
#include <Object.mqh>
//--- defines
#define TIMER_COUNT_1   (90)  // Timer 1 size. Must not be more than two minutes (120)
#define TIMER_COUNT_2   (20)  // Timer 2 size. Too small values quickly trigger tick emulation, which is not desirable in an active market

Los distintos indicadores estándar en el terminal de cliente pertenecerán a categorías diferentes. Para poder clasificar los indicadores creados en categorías o crear listas de indicadores pertenecientes a una categoría, escribiremos una enumeración con las diferentes categorías de indicadores:

//--- defines
#define TIMER_COUNT_1   (90)  // Timer 1 size. Must not be more than two minutes (120)
#define TIMER_COUNT_2   (20)  // Timer 2 size. Too small values quickly trigger tick emulation, which is not desirable in an active market
//--- enums
enum ENUM_IND_CATEGORY        // Indicator categories
  {
   IND_CATEGORY_NONE,         // Not set
   IND_CATEGORY_TREND,        // Trend
   IND_CATEGORY_OSCILLATOR,   // Oscillators
   IND_CATEGORY_VOLUME,       // Volume
   IND_CATEGORY_WILLIAMS,     // Bill Williams
   IND_CATEGORY_CUSTOM,       // Custom
  };


Clasificación, búsqueda y filtrado

Cada objeto de indicador tendrá propiedades mediante las cuales podrá encontrar el indicador necesario. Para comprender que los indicadores son similares entre sí, deberemos comparar sus propiedades clave: símbolo, periodo del gráfico y valores de todos los parámetros de entrada. Si al menos un valor de las propiedades comparadas es distinto, los indicadores no serán idénticos. Si los indicadores son iguales, no se creará uno nuevo, sino que se retornará un puntero al creado anteriormente con los mismos parámetros. Esto en lo que respecta a la recopilación de indicadores. En cuanto a las propiedades, tendremos que crear una enumeración que contenga las constantes de algunas propiedades que se puedan establecer en un indicador creado con éxito, y luego usarlas para encontrarlo:

enum ENUM_COMPARE_MODE        // Comparison mode
  {
   // By default, the comparison mode is set to zero, which compares all properties
   COMPARE_MODE_HANDLE=1,     // Compare by handle
   COMPARE_MODE_SYMBOL,       // Compare by symbol
   COMPARE_MODE_TIMEFRAME,    // Compare by chart period
   COMPARE_MODE_ID,           // Compare by ID
   COMPARE_MODE_DESCRIPTION,  // Compare by custom description
   COMPARE_MODE_CATEGORY,     // Compare by category
  };

Cada indicador creado con éxito tendrá un manejador, y según este manejador podremos acceder a él. Se trata de un número único asignado a la parte de cálculo del indicador. Los valores de los manejadores de los indicadores creados comenzarán por 10 y para cada valor subsiguiente aumentarán en 1.

Las demás propiedades no son únicas y podrán ser compartidas por distintos indicadores. La búsqueda conforme a estas solo se encuentra aquí por comodidad. Por ejemplo, podremos establecer una descripción única para un indicador y luego acceder a él según esta descripción.

Ya hemos analizado las descripciones de los estados de las líneas de indicador en los artículos anteriores sobre indicadores. También usaremos esta enumeración aquí:

enum ENUM_LINE_STATE          // Indicator line state
  {
   LINE_STATE_NONE,           // Undefined
   LINE_STATE_UP,             // Upward
   LINE_STATE_DOWN,           // Downward
   LINE_STATE_TURN_UP,        // Upward reversal
   LINE_STATE_TURN_DOWN,      // Downward reversal
   LINE_STATE_STOP_UP,        // Upward stop
   LINE_STATE_STOP_DOWN,      // Downward stop
   LINE_STATE_ABOVE,          // Above value
   LINE_STATE_BELOW,          // Below value
   LINE_STATE_CROSS_UP,       // Upward value crossing
   LINE_STATE_CROSS_DOWN,     // Downward value crossing
   LINE_STATE_TOUCH_BELOW,    // Touching value from below
   LINE_STATE_TOUCH_ABOVE,    // Touching value from above
   LINE_STATE_EQUALS,         // Equal to value
  };

Podrá leer más información sobre la enumeración en el artículo sobre indicadores de oscilador en la sección de parámetros del indicador ATR.

Cada indicador señalará el resultado de su cálculo con un código de error:

enum ENUM_ERR_TYPE            // Indicator calculation error type
  {
   ERR_TYPE_NO_ERROR,         // No error
   ERR_TYPE_NO_CYNC,          // Data not synchronized
   ERR_TYPE_NO_DATA,          // Data not loaded
   ERR_TYPE_NO_CALC,          // Calculation not completed
  };

Partiendo de este código será posible determinar externamente qué acción será necesaria para tratar el error.

Búfer de indicador

Aquí tendremos que decidir qué búferes pertenecen a qué.

  1. Búfer de la parte de cálculo. Al crear un indicador, se creará en la memoria. Esta será la parte de cálculo del indicador. Dispone de sus propios búferes, gestionados por el subsistema terminal. Podremos acceder a la parte de cálculo mediante el manejador retornado tras crear correctamente el indicador. Un búfer de indicador creado y calculado correctamente contendrá siempre los datos correspondientes a la serie temporal sobre la que se ha calculado el indicador. Los datos de este búfer se clasificarán de forma que el índice cero se corresponda con la barra actual de la serie temporal sobre la que se calcula el indicador.
    La función CopyBuffer() se utilizará para copiar datos del búfer de la parte de cálculo del indicador.

  2. Búfer del objeto de indicador. Cada uno de los objetos de clase de indicador múltiple creados tendrá sus propios arrays-búferes según el número de búferes de este indicador. Estas arrays contendrán los datos del búfer de la parte de cálculo. Los búferes del objeto de indicador se gestionarán dentro del objeto de clase: se inicializarán, aumentarán de tamaño según el tamaño de la serie temporal sobre la que se cree el indicador y se actualizarán con cada nuevo tick. Al copiar los datos en el array del objeto de indicador desde el búfer de la parte de cálculo utilizando CopyBufer(), los datos se clasificarán de forma que la barra actual se sitúe al final del array (ArraySize()-1).

  3. Búfer de la parte dibujada del indicador. Cada objeto de indicador podrá crearse en un asesor o en un indicador personalizado. Al crear indicadores múltiples en asesores, para calcular los indicadores, deberemos llamar al método que calcula el indicador, y para obtener los datos calculados, tendremos que hacer referencia al índice requerido del búfer del objeto de indicador. Pero en un indicador personalizado, deberemos extraer los datos de los indicadores múltiples creados dentro de él en el gráfico. Aquí es donde entra en juego otro búfer: el búfer dibujado. Es el búfer asignado como búfer dibujado en el indicador personalizado. Los datos almacenados en los búferes de los objetos de indicador se mostrarán en él. Para mostrar líneas en el gráfico, bastará con llamar desde el indicador personalizado al método de la clase de colección de indicadores que calcula los indicadores, y luego, si el cálculo tiene éxito, al método que colocará los datos del búfer del objeto de indicador en el búfer dibujado del indicador personalizado.

En un objeto de indicador, un búfer estará representado por una estructura que contendrá tanto el propio array dinámico como los controles de dicho array:

//--- struct
struct SBuffer                // Structure of the indicator buffer
  {
   double            array[];    // Indicator buffer array
   double            init_value; // Initializing value
   int               shift;      // Horizontal shift of the buffer
   string            descript;   // Buffer description
   //--- (1) Sets, (2) returns the initializing value,
   void              SetInitValue(const double value) { init_value=value;                             }
   double            InitValue(void)                  { return init_value;                            }
   //--- (1) Sets, (2) returns the buffer offset
   void              SetShift(const int value)        { shift=value;                                  }
   int               Shift(void)                      { return shift;                                 }
//--- (1) Resizes the buffer array, (2) returns the size of the buffer array,
//--- (3) initializes the array with the set "empty" value
   bool              BuffResize(const int new_size)   { return(ArrayResize(array,new_size)==new_size);}
   uint              BufferSize(void)                 { return array.Size();                          }
   int               InitBuffer(void)                 { return ArrayInitialize(array,init_value);     }
  };

Algunos valores establecidos externamente, por ejemplo, al crear un indicador, deberán guardarse en algún lugar para saber cuáles eran. Lo más fácil será escribirlos directamente en la estructura. Eso es exactamente lo que haremos aquí: el valor "vacío" del búfer establecido por el programa de llamada se almacenará en la estructura del búfer en la variable init_value. El desplazamiento de la línea del indicador, que se establecerá al crear la parte de cálculo del indicador, también será conveniente guardarlo aquí, para poder conocerlo posteriormente dentro del objeto de clase, se guardará en la variable shift. Aquí también se almacenará la descripción del búfer, que se establecerá automáticamente al crear la parte de cálculo del indicador para que se corresponda con el nombre del búfer en el mismo indicador estándar. Esta descripción podrá modificarse.

Las funciones que retornan las descripciones del estado de las líneas de indicador y de los errores durante el cálculo de los indicadores solo serán necesarias para mostrar en el registro o en el panel informativo los estados de las líneas de indicador y los errores durante su inicialización y cálculo:

//+------------------------------------------------------------------+
//| Return the indicator line state description                      |
//+------------------------------------------------------------------+
string BufferLineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_NONE       :  return "None";
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_BELOW      :  return "Below level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }
//+------------------------------------------------------------------+
//| Return error description in indicator calculation                |
//+------------------------------------------------------------------+
string TypeErrorcDescription(ENUM_ERR_TYPE error_type)
  {
   switch(error_type)
     {
      case ERR_TYPE_NO_CYNC   :  return "Data is not synchronized";
      case ERR_TYPE_NO_DATA   :  return "Data not loaded";
      case ERR_TYPE_NO_CALC   :  return "Calculation not completed";
      default                 :  return "No error";
     }
  }

Todos los preparativos están ya hechos. Vamos a tratar directamente la clase del objeto de indicador de símbolo/periodo múltiple.

En el cuerpo de la clase escribiremos todas las variables y métodos privados, protegidos y públicos necesarios para el funcionamiento de la clase, y luego consideraremos su finalidad e implementación:

//+------------------------------------------------------------------+
//| Base class of the multi-symbol multi-period indicator            |
//+------------------------------------------------------------------+
class CIndMSTF : public CObject
  {
private:
   ENUM_PROGRAM_TYPE m_program;           // Program type
   ENUM_INDICATOR    m_type;              // Indicator type
   ENUM_TIMEFRAMES   m_timeframe;         // Chart timeframe
   string            m_symbol;            // Chart symbol
   int               m_handle;            // Indicator handle
   int               m_id;                // Identifier
   bool              m_success;           // Successful calculation flag
   ENUM_ERR_TYPE     m_type_err;          // Calculation error type
   string            m_description;       // Custom description of the indicator
   string            m_name;              // Indicator name
   string            m_parameters;        // Description of indicator parameters

protected:
   ENUM_IND_CATEGORY m_category;          // Indicator category
   MqlParam          m_param[];           // Array of indicator parameters
   string            m_title;             // Title (indicator name + description of parameters)
   SBuffer           m_buffers[];         // Indicator buffers
   int               m_digits;            // Digits in indicator values
   int               m_limit;             // Number of bars required to calculate the indicator on the current tick
   int               m_rates_total;       // Number of available bars for indicator calculation
   int               m_prev_calculated;   // Number of calculated bars on the previous indicator call
   
//--- (1) Sets indicator name, (2) description of parameters
   void              SetName(const string name)                      { this.m_name=name;           }
   void              SetParameters(const string str)                 { this.m_parameters=str;      }
   
//--- Resizes the (1) specified, (2) all indicator buffers
   bool              BufferResize(const uint buffer_num,const int new_buff_size);
   bool              BuffersResize(const int new_buff_size);
//--- Initializes (1) the specified, (2) all indicator buffers
   bool              BufferInitialize(const uint buffer_num,const int new_buff_size);
   bool              BuffersInitialize(const int new_buff_size);
   
//--- Returns the flag indicating equality of the structure of one parameter of two objects
   bool              IsEqualParameters(const MqlParam &this_param,const MqlParam &compared_param) const
                       {
                        if(this_param.type==compared_param.type                     && 
                           this_param.integer_value==compared_param.integer_value   && 
                           this_param.string_value==compared_param.string_value     && 
                           ::NormalizeDouble(this_param.double_value-compared_param.double_value,8)==0
                          ) return true;
                        return false;
                       }
//--- Return the result of comparison on one parameter of two objects
   int               CompareParams(const MqlParam &this_param,const MqlParam &compared_param)
                       {
                        if(this.IsEqualParameters(this_param,compared_param))
                           return 0;
                        else if(this_param.type>compared_param.type                 || 
                           this_param.integer_value>compared_param.integer_value    || 
                           this_param.string_value>compared_param.string_value      || 
                           this_param.double_value>compared_param.double_value
                          ) return 1;
                        else if(this_param.type<compared_param.type                 || 
                           this_param.integer_value<compared_param.integer_value    || 
                           this_param.string_value<compared_param.string_value      || 
                           this_param.double_value<compared_param.double_value
                          ) return -1;
                        else
                           return -1;
                       }
   
public:
//--- Creates the calculation part of the indicator, returns the handle
   int               CreateIndicator(void);
//--- (1) Calculates the indicator, (2) fills the passed buffer array (taking into account the chart period/symbol) with data from the indicator calculation buffer of this class
   bool              Calculate(void);
   bool              DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int limit,double &buffer[]);

//--- (1) Sets (2) returns the initializing value for the specified buffer
   void              SetBufferInitValue(const uint buffer_num,const double value);
   double            BufferInitValue(const uint buffer_num) const;

//--- (1) Sets (2) returns the offset value for the specified buffer
   void              SetBufferShift(const uint buffer_num,const int value);
   double            BufferShift(const uint buffer_num) const;

//--- Returns data of the specified buffer (1) as is, (2) relative to the specified symbol/timeframe,
//--- (3) amount of data in the specified buffer, (4) the state of the indicator line as it is in the calculation part buffer,
//--- (5) indicator line state taking into account the chart symbol/period, description of the line state (6) as is in the buffer (7) taking into account the chart symbol/period
   double            GetData(const uint buffer_num,const int index)           const;
   double            GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int index) const;
   uint              DataTotal(const uint buffer_num)                         const;
   ENUM_LINE_STATE   BufferLineState(const uint buffer_num,const int index)   const;
   ENUM_LINE_STATE   BufferLineState(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const uint buffer_num,const int index) const;
   ENUM_LINE_STATE   BufferLineStateRelative(const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);
   ENUM_LINE_STATE   BufferLineStateRelative(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);

//--- Returns (1) success flag, (2) calculation error type
   bool              IsSuccess(void)                           const { return this.m_success;               }
   ENUM_ERR_TYPE     TypeError(void)                           const { return this.m_type_err;              }
   
//--- Sets (1) identifier, (2) Digits, (3) custom description, (4) description of the specified buffer
   void              SetID(const int id)                             { this.m_id=id;                        }
   void              SetDigits(const uint digits)                    { this.m_digits=(int)digits;           }
   void              SetDescription(const string descr)              { this.m_description=descr;            }
   void              SetBufferDescription(const uint buffer_num,const string descr);

//--- Sets the indexing of buffer arrays of the calculation part not as in the timeseries
   void              SetAsSeriesOff(void);
//--- Returns flag of whether the buffer is set as series, (2) historical data for symbol/period is synchronized
   bool              IsSeries(const uint buffer_num) const;
   bool              IsSynchronized(void) const
                       {
                        return (bool)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED);
                       }
   
//--- Returns (1) timeframe, (2) symbol, (3) name, (4) list of parameters, (5) handle, (6) Digits
//--- number of (7) buffers, (8) bars, (9) identifier, (10) description, (11) title, (12) category,
//--- (13) number of parameters, (14) program type, description of (15) category, (16) indicator buffer
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_timeframe;             }
   string            Symbol(void)                              const { return this.m_symbol;                }
   string            Name(void)                                const { return this.m_name;                  }
   string            Parameters(void)                          const { return this.m_parameters;            }
   int               Handle(void)                              const { return this.m_handle;                }
   int               Digits(void)                              const { return this.m_digits;                }
   uint              BuffersTotal(void)                        const { return this.m_buffers.Size();        }
   uint              RatesTotal(void)                          const { return this.m_rates_total;           }
   int               ID(void)                                  const { return this.m_id;                    }
   string            Description(void)                         const { return this.m_description;           }
   string            Title(void)                               const { return this.m_title;                 }
   ENUM_IND_CATEGORY Category(void)                            const { return this.m_category;              }
   uint              ParamsTotal(void)                         const { return this.m_param.Size();          }
   ENUM_PROGRAM_TYPE Program(void)                             const { return this.m_program;               }
   string            CategoryDescription(void);
   string            BufferDescription(const uint buffer_num);

//--- Returns (1) structure of parameters by index from array, (2) flag of indicator program, (3) timeframe description
   MqlParam          GetMqlParam(const int index)              const { return this.m_param[index];          }
   bool              IsIndicator()                 const { return(this.Program()==PROGRAM_INDICATOR);       }
   string            TimeframeDescription(void)    const
                       {
                        return ::StringSubstr(::EnumToString(this.m_timeframe),7);
                       }
//--- Returns amount of calculated data
   int               Calculated(void)  const { return ::BarsCalculated(this.m_handle);                      }
   
//--- Virtual method returning the type of object (indicator)
   virtual int       Type(void)                                const { return this.m_type;                  }
//--- Virtual method for comparing two objects
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CIndMSTF *compared=node;
                        switch(mode)
                          {
                           case COMPARE_MODE_ID          : return(this.ID()>compared.ID()                   ? 1 : this.ID()<compared.ID()                   ? -1 : 0);
                           case COMPARE_MODE_HANDLE      : return(this.Handle()>compared.Handle()           ? 1 : this.Handle()<compared.Handle()           ? -1 : 0);
                           case COMPARE_MODE_CATEGORY    : return(this.Category()>compared.Category()       ? 1 : this.Category()<compared.Category()       ? -1 : 0);
                           case COMPARE_MODE_SYMBOL      : return(this.Symbol()>compared.Symbol()           ? 1 : this.Symbol()<compared.Symbol()           ? -1 : 0);
                           case COMPARE_MODE_TIMEFRAME   : return(this.Timeframe()>compared.Timeframe()     ? 1 : this.Timeframe()<compared.Timeframe()     ? -1 : 0);
                           case COMPARE_MODE_DESCRIPTION : return(this.Description()>compared.Description() ? 1 : this.Description()<compared.Description() ? -1 : 0);
                           //--- Equality of all object parameters
                           default                       : return(this.IsEqualIndicators(compared) ? 0 : -1);
                          }
                       }
//--- Returns the flag of equality of parameters of two indicator objects
   bool              IsEqualIndicators(const CIndMSTF *compared) const
                       {
                        if(this.Type()!=compared.Type() || this.ParamsTotal()!=compared.ParamsTotal())
                           return false;
                        bool res=true;
                        int total=(int)this.ParamsTotal();
                        for(int i=0;i<total;i++)
                           res &=this.IsEqualParameters(this.m_param[i],compared.GetMqlParam(i));
                        res &=(this.Timeframe()==compared.Timeframe());
                        res &=(this.Symbol()==compared.Symbol());
                        return res;
                       }
//--- Timer
   void OnTimer(void);
   
//--- Constructor/destructor
                     CIndMSTF(){}
                     CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe);
                    ~CIndMSTF();
  };

La asignación de cada variable y de los métodos aquí se comentará en el código de la clase. Veamos la implementación de los métodos.

Constructor de clase:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Start the timer
   ::ResetLastError();
   if(!::EventSetTimer(1))
      ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError());
//--- Set properties to the values passed to the constructor or to default values
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_type=type;
   this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   this.m_handle=INVALID_HANDLE;
   this.m_digits=::Digits();
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- Set the size of the buffer structure array to the number of indicator buffers
   ::ResetLastError();
   if(::ArrayResize(this.m_buffers,buffers)!=buffers)
      ::PrintFormat("%s: Buffers ArrayResize failed. Error  %lu"__FUNCTION__,::GetLastError());
//--- Set the "empty" value for each buffer by default (can be changed it later)
   for(int i=0;i<(int)this.m_buffers.Size();i++)
      this.SetBufferInitValue(i,EMPTY_VALUE);
//--- Set initial values of variables involved in resource-efficient calculation of the indicator
   this.m_prev_calculated=0;
   this.m_limit=0;
   this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe);
//--- If the indicator is calculated on non-current chart, request data from the required chart
//--- (the first access to data starts data pumping)
   datetime array[];
   if(symbol!=::Symbol() || timeframe!=::Period())
      ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array);
  }

El tipo de indicador, su número de búferes, el nombre del símbolo y el periodo del gráfico se transmitirán al constructor de la clase. A continuación, se establecerán los valores por defecto de las variables, el tamaño de los arrays del búfer y un valor de inicialización por defecto para las arrays del búfer: un valor "vacío" como EMPTY_VALUE. Si el objeto de indicador no se calcula sobre el gráfico actual, entonces al final del constructor llamaremos la función que inicia la descarga desde el servidor de los datos de la serie temporal sobre la que se calcula el indicador.

Destructor de la clase:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CIndMSTF::~CIndMSTF()
  {
//--- Delete timer
   ::EventKillTimer();
//--- Release handle of the indicator
   ::ResetLastError();
   if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle))
      ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError());
//--- Free up the memory of buffer arrays
   for(int i=0;i<(int)this.BuffersTotal();i++)
      ::ArrayFree(this.m_buffers[i].array);
  }

El destructor de la clase destruye el temporizador, libera el manejador del indicador y la memoria de los arrays del búfer.

Temporizador:

//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CIndMSTF::OnTimer(void)
  {
//--- If the indicator symbol and timeframe match those of the current chart, exit
   if(this.Symbol()==::Symbol() && this.Timeframe()==::Period())
      return;
//--- Declare time counter variable
   static int count1=0;
   static int count2=0;
//--- If the counter of timer 1 has reached the specified value,
   if(count1>=TIMER_COUNT_1)
     {
      //--- call the CopyTime function (timeseries hold) and reset the counter
      datetime array[1];
      ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array);
      count1=0;
     }
//--- If the counter of timer 2 has reached the specified value
   if(count2>=TIMER_COUNT_2)
     {
      static int count=0;
      //--- if the previous indicator calculation failed, emulate a tick and print the relevant log
      if(!this.m_success)
        {
         if(::ChartSetSymbolPeriod(0,::Symbol(),::Period()))
           {
            count++;
            ::PrintFormat("%s::%s: Tick emulation. Attempt %ld of 3 ...",__FUNCTION__,this.Title(),count);
            if(count>2)
              {
               count=0;
               this.m_success=true;
              }
           }
        }
      //--- reset the counter
      count2=0;
     }
//--- Increase timer counters
   count1++;
   count2++;
  }

En temporizador de la clase se organizarán dos contadores: uno para contener las series temporales del indicador y otro para emular los ticks en los días festivos. Si el indicador se calcula sobre los datos del símbolo/periodo actual del gráfico, no se utilizará el temporizador.

El objeto de indicador en sí no es un indicador como tal, no es más que un envoltorio de la parte de cálculo del indicador, que permite controlarlo, recibir datos de él y transferirlos al programa. En el objeto de indicador múltiple habrá que crear el propio indicador. Para ello se utilizará el método de creación de la parte de cálculo del indicador, que retornará el manejador obtenido durante la creación:

//+------------------------------------------------------------------+
//| Creates an indicator, returns a handle                           |
//+------------------------------------------------------------------+
int CIndMSTF::CreateIndicator(void)
  {
//--- Create indicator name to print to logs
   string name=::StringFormat("%s(%s,%s)",::StringSubstr(::EnumToString(this.m_type),4),this.m_symbol,this.TimeframeDescription());
//--- Create the calculation part of the indicator
   ::ResetLastError();
   this.m_handle=::IndicatorCreate(this.m_symbol,this.m_timeframe,this.m_type,this.m_param.Size(),this.m_param);
//--- If the calculation part is not created, log a message about the failed creation of the indicator
   if(this.m_handle==INVALID_HANDLE)
      ::PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,name,::GetLastError());
//--- If the indicator is created, set non-timeseries indexing of indicator arrays
   else
      this.SetAsSeriesOff();
//--- Return the handle of the created indicator or -1 if unsuccessful
   return this.m_handle;
  }

Los búferes del objeto de indicador son arrays dinámicos dentro de la estructura de búfer de la que hablamos antes, que necesitan cambiar de tamaño para acomodar la cantidad cada vez mayor de nuevos datos. Hay dos métodos para ello: un método para redimensionar el array del búfer especificado y para redimensionar todos los búferes del objeto de indicador a la vez.

Método que redimensiona el búfer de indicador especificado:

//+------------------------------------------------------------------+
//| Resize the specified indicator buffer                            |
//+------------------------------------------------------------------+
bool CIndMSTF::BufferResize(const uint buffer_num,const int new_buff_size)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return 'false'
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return false;
     }
//--- Resize the buffer
   ::ResetLastError();
   bool res=this.m_buffers[buffer_num].BuffResize(new_buff_size);
//--- If unsuccessful, print a message to the log
   if(!res)
      ::PrintFormat("%s::%s: Buffer(%lu) resize failed. Error %lu",__FUNCTION__,this.Title(),buffer_num,::GetLastError());
//--- Return the result of resizing the buffer array
   return res;
  }

El número de búfer se transmitirá al método cuyo array deberá cambiarse por el valor que también se transmite al método. Si el número de búfer se indica incorrectamente, se imprimirá una entrada en el registro y el método retornará false.
Si no se puede cambiar el tamaño de la array, se imprimirá un mensaje en el registro. Como resultado, se retornará el resultado del redimensionamiento del array del búfer.

Método que redimensiona todos los búferes del indicador:

//+------------------------------------------------------------------+
//| Resize all indicator buffers                                     |
//+------------------------------------------------------------------+
bool CIndMSTF::BuffersResize(const int new_buff_size)
  {
//--- In a loop through all indicator buffers, add to the 'res' variable the resizing result of each next buffer
   bool res=true;
   int total=(int)this.BuffersTotal();
   for(int i=0;i<total;i++)
      res &=this.m_buffers[i].BuffResize(new_buff_size);
//--- Return the result of resizing all arrays of indicator buffers
   return res;
  }

Aquí, en un ciclo a través de todos los búferes del objeto de indicador, el resultado del cambio del tamaño del array del siguiente búfer se añadirá a la variable res, cuyo valor se retornará finalmente desde el método.

Los métodos para inicializar los arrays de búferes del objeto de indicador estarán dispuestos de forma similar.

Método que inicializa el búfer indicador especificado:

//+------------------------------------------------------------------+
//| Initialize the specified indicator buffer                        |
//+------------------------------------------------------------------+
bool CIndMSTF::BufferInitialize(const uint buffer_num,const int new_buff_size)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return 'false'
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return false;
     }
//--- resizing the buffer array
   bool res=this.BufferResize(buffer_num,new_buff_size);
//--- If successfully resized, initialize the buffer with the set initializing value
   if(res)
      this.m_buffers[buffer_num].InitBuffer();
//--- Return the result
   return res;
  }

Aquí también se comprobará si se ha especificado correctamente el número del búfer que se va a inicializar. A continuación, el búfer se ajustará al nuevo tamaño y, si tiene éxito, el array se inicializará con el valor establecido para ese búfer.

Método que inicializa todos los búferes del indicador:

//+------------------------------------------------------------------+
//| Initialize all indicator buffers                                 |
//+------------------------------------------------------------------+
bool CIndMSTF::BuffersInitialize(const int new_buff_size)
  {
//--- In a loop through all indicator buffers, add to the 'res' variable the resizing result of each next buffer
//--- If successfully resized, initialize the buffer with the set initializing value
   bool res=true;
   int total=(int)this.BuffersTotal();
   for(int i=0;i<total;i++)
     {
      res &=this.m_buffers[i].BuffResize(new_buff_size);
      if(res)
         this.m_buffers[i].InitBuffer();
     }
//--- Return the overall result
   return res;
  }

En el ciclo a través de todos los búferes del objeto de indicador, el resultado del cambio de tamaño del array del siguiente búfer se añadirá a la variable res. Si el cambio de tamaño se realiza correctamente, el búfer se inicializará con el valor de inicialización establecido para él. El método retornará el estado final de la variable res, que se establecerá en false si al menos uno de los búferes no se ha inicializado correctamente.

Los demás métodos para establecer y retornar los valores del búfer serán idénticos a los comentados anteriormente:

//+------------------------------------------------------------------+
//| Set the initializing value for the specified buffer              |
//+------------------------------------------------------------------+
void CIndMSTF::SetBufferInitValue(const uint buffer_num,const double value)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return;
     }
//--- Set a new initializing value for the specified buffer
   this.m_buffers[buffer_num].SetInitValue(value);
  }
//+------------------------------------------------------------------+
//| Return the initialization value of the specified buffer          |
//+------------------------------------------------------------------+
double CIndMSTF::BufferInitValue(const uint buffer_num) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      //--- If the indicator has buffers, return the initializing value of the first one, otherwise EMPTY_VALUE
      return(this.BuffersTotal()>0 ? this.BufferInitValue(0) : EMPTY_VALUE);
     }
//--- Return the initializing value of the requested buffer
   return this.m_buffers[buffer_num].InitValue();
  }
//+------------------------------------------------------------------+
//| Sets the offset value for the specified buffer                   |
//+------------------------------------------------------------------+
void CIndMSTF::SetBufferShift(const uint buffer_num,const int value)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return;
     }
//--- Set the offset value for the buffer
   this.m_buffers[buffer_num].SetShift(value);
  }
//+------------------------------------------------------------------+
//| Return the offset value of the specified buffer                  |
//+------------------------------------------------------------------+
double CIndMSTF::BufferShift(const uint buffer_num) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      //--- If the indicator has buffers, return the shift value of the very first one, otherwise 0
      return(this.BuffersTotal()>0 ? this.m_buffers[0].Shift() : 0);
     }
//--- Return the offset value of the requested buffer
   return this.m_buffers[buffer_num].Shift();
  }

La lógica de los métodos se detalla en los comentarios del código.

Método para calcular los datos de los indicadores. El indicador calculado (su parte calculada) tiene un búfer que contendrá todos los datos calculados del indicador. Necesitaremos copiar los datos del búfer de la parte de cálculo en los arrays de búfer del objeto de indicador. Deberemos organizar el cálculo económico: el búfer completo deberá copiarse solo en la primera ejecución o al cambiar los datos históricos.

Hemos considerado este cálculo más arriba, y debería organizarse también en el método de cálculo del objeto de indicador. Entonces, en caso de errores, el método retornará false, y el programa que llama reaccionará a ello: saldrá del manejador (OnTick en el asesor y OnCalculate en el indicador) hasta que llegue el siguiente tick. Los errores que requieren el retorno desde el método los consideraremos el inicio de la carga de los datos históricos, la carga de no toda la historia, el cálculo incompleto del indicador y los errores de copiado de datos del búfer de la parte de cálculo al búfer del objeto de indicador. El método escribirá el código de error en una variable, de forma que el programa que realiza la llamada pueda leerlo y procesarlo correctamente.

Método que rellena los búferes del objeto de indicador con los datos del búfer de la parte de cálculo:

//+------------------------------------------------------------------+
//| Fill object buffers with data from the calculation part buffer   |
//+------------------------------------------------------------------+
bool CIndMSTF::Calculate(void)
  {
//--- Set the success flag to true, and the error type to no error
   this.m_success=true;
   this.m_type_err=ERR_TYPE_NO_ERROR;
//--- If the data is not yet synchronized with the trade server,
   if(!this.IsSynchronized())
     {
      //--- Log a message about non-synchronized data,
      ::PrintFormat("%s::%s: Waiting for data to sync...",__FUNCTION__,this.Title());
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_CYNC;
      this.m_success &=false;
      return false;
     }
//--- If the Calculated method returned -1, this means the start of data downloading
   if(this.Calculated()==WRONG_VALUE)
     {
      //--- Log a message about the start of data downloading,
      ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_DATA;
      this.m_success &=false;
      return false;
     }
//--- If the Calculated method returned 0, this means that the indicator has not yet been calculated
   if(this.Calculated()==0)
     {
      //--- Log a message about waiting for the indicator to be calculated,
      ::PrintFormat("%s::%s: Waiting for a new tick and when the indicator will be calculated...",__FUNCTION__,this.Title());
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_CALC;
      this.m_success &=false;
      return false;
     }
//--- Get the number of data bars for the indicator symbol/period
   int bars=::Bars(this.m_symbol,this.m_timeframe);
//--- If the Bars function returned a zero value, which often happens on weekends, calculate the available number of bars
   if(bars==0)
     {
      //--- Get the date of the very first available bar in history for the symbol/period
      datetime firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
      //--- Get the date of the last (current) bar in history for the symbol/period
      datetime lastdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
      //--- Calculate the number of bars between the first and last dates of history
      int sec=::PeriodSeconds(this.m_timeframe);
      ulong date_bars=(((ulong)lastdate-(ulong)firstdate)/(sec>0 ? sec : 1))+1;
      //--- Write to the 'bars' variable the smaller value of the calculated number of bars and the maximum number of bars available in the terminal
      bars=(int)fmin(date_bars,::TerminalInfoInteger(TERMINAL_MAXBARS));
     }
//--- Write the resulting number of available bars to m_rates_total
   if(this.m_rates_total!=bars)
      this.m_rates_total=bars;
//--- If the number of available bars is received, and it is 2 or less,
   if(this.m_rates_total>=0 && this.m_rates_total<3)
     {
      //--- Log a message about the number of available bars being too small
      ::PrintFormat("%s::%s: Not enough data for calculation: %ld bars. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_rates_total);
      //--- set the error type, add 'false' to the error flag and return 'false'
      this.m_type_err=ERR_TYPE_NO_DATA;
      this.m_success &=false;
      return false;
     }

//--- Calculate the number of bars required to calculate the indicator
//--- Either the entire available history, or 1 when a new bar opens, or 0 on the current tick
   this.m_limit=this.m_rates_total-this.m_prev_calculated;
   this.m_prev_calculated=this.Calculated();

//--- Declare an array of size 2 to receive data into it from the indicator's calculation part buffer
//--- We always get two bars: previous and current
   double array[2];
//--- Get the number of indicator buffers
   int total=(int)this.BuffersTotal();
//--- If the calculated m_limit is greater than 1, it means either the first launch or changes in historical data
//--- In this case, a complete recalculation of the indicator is necessary
   if(this.m_limit>1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- resize the indicator buffer array and initialize it to the "empty" value set for this buffer
         this.BufferInitialize(i,this.m_rates_total);
         ::ResetLastError();
         //--- Copy all available historical data from indicator's calculation part array to buffer array of indicator object
         int copied=::CopyBuffer(this.m_handle,i,-this.m_buffers[i].Shift(),this.m_rates_total,this.m_buffers[i].array);
         //--- If not all data is copied
         if(copied!=this.m_rates_total)
           {
            //--- If CopyBuffer returned -1, this means the start of historical data downloading
            //--- print a message about this to the log
            if(copied==WRONG_VALUE)
               ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription());
            //--- In any other case, not all data has been copied yet
            //--- print a message about this to the log
            else
               ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied);
            //--- Write the absence of data to the error type
            this.m_type_err=ERR_TYPE_NO_DATA;
            //--- Add 'false' to the result and return 'false' to exit the method and wait for the next tick
            this.m_success &=false;
            return false;
           }
        }
      //--- If we exited the loop of copying all indicator buffers, then everything was successful - return 'true'
      return true;
     }
//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0)
//--- In this case, it is necessary to calculate two bars - the first and the current
   if(this.m_limit<=1)
     {
      //--- In a loop over the number of indicator buffers
      for(int i=0;i<total;i++)
        {
         //--- If this is the opening of a new bar and resizing the indicator buffer failed,
         if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total))
           {
            //--- add 'false' to the m_success variable and return 'false'
            //--- Here, an error message will be printed to log from the BufferResize method
            this.m_success &=false;
            return false;
           }
         //--- If failed to copy two bars from the indicator's calculation part buffer,
         ::ResetLastError();
         if(::CopyBuffer(this.m_handle,i,-this.m_buffers[i].Shift(),2,array)!=2)
           {
            //--- report this via the log, add 'false' to the m_success variable and return 'false'
            ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError());
            this.m_success &=false;
            return false;
           }
         //--- If got here, it means copying was successful -
         //--- copy data from array[], into which the last two bars were copied, to the indicator object buffer
         this.m_buffers[i].array[this.DataTotal(i)-1]=array[1];
         this.m_buffers[i].array[this.DataTotal(i)-2]=array[0];
        }
      //--- Success
      return true;
     }
//--- Undefined 'limit' option - return 'false'
   return false;
  }

La lógica completa del método se detalla en los comentarios de cada bloque de código. El método debe ser llamado desde el programa y si retorna false, deberá salir de OnTick o OnCalculate antes del siguiente tick.

Si el método ha funcionado sin errores, el búfer del objeto de indicador contendrá datos listos para su uso. Se accederá a ellos a través de los métodos que se exponen a continuación.

Los indicadores tienen métodos especiales para enviar datos desde el búfer del objeto de indicador rellenado con este método a los búferes de los indicadores dibujados. Los métodos envían los datos al búfer para que se dibujen, bien en la misma forma en que están escritos en el búfer del objeto de indicador, bien considerando el símbolo/periodo del gráfico. Estos métodos pueden usarse para mostrar los datos calculados en un objeto de indicador en el gráfico actual.

Método que rellena el array transmitido con los datos del búfer de la clase:

//+------------------------------------------------------------------+
//| Fill the passed array with data from the class buffer            |
//+------------------------------------------------------------------+
bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int limit,double &buffer[])
  {
//--- Set the success flag
   this.m_success=true;
//--- Get the indexing direction of the buffer array passed to the method and,
//--- if non-timeseries indexing, set timeseries indexing
   bool as_series=::ArrayGetAsSeries(buffer);
   if(!as_series)
      ::ArraySetAsSeries(buffer,true);
//--- Set the symbol name and timeframe value passed to the method
   string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to);
   ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to);
   datetime array[2];
//--- If this is the first launch or history changes, initialize the buffer array passed to the method
   if(limit>1 && this.m_limit>1)
     {
      ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num);
      ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num));
     }
//--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart)
   int count=(limit<=1 ? 2 : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit));
//--- In a loop from the zero bar to the value of the loop counter
   for(int i=0;i<count;i++)
     {
      //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array
      if(timeframe==::Period() && this.m_timeframe==::Period())
         buffer[i]=this.GetData(buffer_num,i);
      //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object
      else
        {
         //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to
         ::ResetLastError();
         if(::CopyTime(symbol,timeframe,i,2,array)!=2)
           {
            //--- If there is no data in the terminal, move on
            if(::GetLastError()==4401)
               continue;
            //--- Error in obtaining existing data - return false
            this.m_success &=false;
            return false;
           }
         //--- Using time of bar of current chart timeframe, find corresponding index of bar of class object's chart period
         ::ResetLastError();
         int bar=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
         if(bar==WRONG_VALUE)
           {
            this.m_success &=false;
            continue;
           }
         //--- If this is historical data (not the first or zero bar) -
         //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer
         if(i>1)
            buffer[i]=this.GetData(buffer_num,bar);
         //--- If this is the current (zero) or previous (first) bar
         else
           {
            //--- Get the time of bars 0 and 1 by symbol/timeframe of the class object
            if(::CopyTime(this.m_symbol,this.m_timeframe,0,2,array)!=2)
              {
               this.m_success &=false;
               return false;
              }
            //--- Using time, get indexes of current and previous bars on the chart whose symbol/period was passed to method
            int bar0=::iBarShift(symbol,timeframe,array[1]);
            int bar1=::iBarShift(symbol,timeframe,array[0]);
            if(bar0==WRONG_VALUE || bar1==WRONG_VALUE)
              {
               this.m_success &=false;
               return false;
              }
            //--- If the chart timeframe is lower than the timeframe of the class object,
            if(timeframe<this.m_timeframe)
              {
               //--- in a loop from bar with smaller time to current chart bar, fill the buffer with data from the last 2 cells of the indicator buffer array
               for(int j=bar1;j>=0;j--)
                  buffer[j]=this.GetData(buffer_num,(j>bar0 ? 1 : 0));
              }
            //--- If the chart timeframe is higher than the timeframe of the class object,
            else
              {
               //--- Get the time of the current and previous bars by symbol/timeframe of the current chart
               if(::CopyTime(symbol,timeframe,0,2,array)!=2)
                 {
                  this.m_success &=false;
                  return false;
                 }
               //--- Using time, get indexes of bars in indicator's calculation part buffer, corresponding to time of current and previous bars on the chart
               int bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[1]);
               int bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
               //--- Write into indicator buffer, at indexes 1 and 0, values from corresponding indexes of calculation part buffer
               buffer[1]=this.GetData(buffer_num,bar1);
               buffer[0]=this.GetData(buffer_num,bar0);
              } 
           }
        }
     }
//--- Set initial indexing of the buffer array passed to the method
   ::ArraySetAsSeries(buffer,as_series);
//--- Successful
   return true;
  }

La lógica del método se detalla en el listado. La esencia del método consiste en calcular correctamente qué barras del gráfico actual deberán rellenarse con datos del array del búfer de indicador, calculados en un marco temporal diferente. El último parámetro transmitido al método será un array del búfer dibujado del indicador personalizado, que deberá mostrar el indicador calculado sobre otro símbolo/periodo.

Método que retorna los datos del búfer especificado tal cual:

//+------------------------------------------------------------------+
//| Return the data of the specified buffer as is                    |
//+------------------------------------------------------------------+
double CIndMSTF::GetData(const uint buffer_num,const int index) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to log
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      //--- If the indicator has buffers, return "empty" value of the first one, otherwise EMPTY_VALUE
      return(this.BuffersTotal()>0 ? this.BufferInitValue(0) : EMPTY_VALUE);
     }
//--- If an incorrect index is specified, return the "empty" value of the specified buffer
   if(index<0 || index>(int)this.DataTotal(buffer_num)-1)
      return this.BufferInitValue(buffer_num);
//--- Calculate the real index in the buffer array and return the value at this index
   int n=int(this.DataTotal(buffer_num)-1-index);
   return this.m_buffers[buffer_num].array[n];
  }

El método simplemente retorna los datos del búfer del objeto de indicador según el índice especificado.

Método que retorna los datos del búfer especificados al símbolo/marco temporal indicado:

//+-------------------------------------------------------------------+
//| Returns data from specified buffer for specified symbol/timeframe |
//+-------------------------------------------------------------------+
double CIndMSTF::GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const int index) const
  {
//--- If current symbol/period of the chart is specified
   if(timeframe_to==::Period() && this.m_timeframe==::Period() && symbol_to==::Symbol() && this.m_symbol==::Symbol())
      return this.GetData(buffer_num,index);
//--- Find out which time of this class the current chart timeframe's bar, corresponding to the loop index, belongs to
   datetime array[];
   if(::CopyTime(symbol_to,timeframe_to,index,1,array)!=1)
      return this.BufferInitValue(buffer_num);
//--- Using time of bar of current chart timeframe, find corresponding bar index of bar this class chart period
   int bar=iBarShift(this.m_symbol,this.m_timeframe,array[0]);
//--- If the bar is not found, return the "empty" value set for the buffer
   if(bar==WRONG_VALUE)
      return this.BufferInitValue(buffer_num);
//--- Return value from the indicator object buffer at the found index
   return this.GetData(buffer_num,bar);
  }

El método encuentra el índice de barra de la serie temporal sobre la que se calcula el indicador, correspondiente al símbolo/periodo del gráfico transmitido al método y retorna los datos del búfer del objeto de indicador por el índice encontrado.

Método que retorna el estado de la línea de indicador tal cual:

//+------------------------------------------------------------------+
//| Return the state of the indicator line as is                     |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineState(const uint buffer_num,const int index) const
  {
//--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index
   const double value0=this.GetData(buffer_num,index);
   const double value1=this.GetData(buffer_num,index+1);
   const double value2=this.GetData(buffer_num,index+2);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Line upward reversal (value2>value1 && value0>value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)>0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Line upward direction (value2<=value1 && value0>value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_UP;
//--- Line upward stop (value2<=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Line downward reversal (value2<value1 && value0<value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)<0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Line downward direction (value2>=value1 && value0<value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_DOWN;
//--- Line downward stop (value2>=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Undefined state
   return LINE_STATE_NONE;
  }

El método determina el estado de la línea del objeto de indicador partiendo de los datos de su búfer y retorna el valor encontrado.

Método que retorna el estado de la línea de indicador teniendo en cuenta el símbolo/periodo:

//+------------------------------------------------------------------+
//| Return indicator line state for the specific symbol/period       |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineState(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const uint buffer_num,const int index) const
  {
//--- Determine the chart symbol/period passed to the method
   string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
   ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from);
//--- If we get data from symbol/period equal to current chart, return state from the buffer "as is"
   if(symbol==::Symbol() && symbol==this.m_symbol && timeframe==::Period() && timeframe==this.m_timeframe)
      return this.BufferLineState(buffer_num,index);
//--- Declare variables to search for the required bars on the current chart
   datetime array[1];
   int      bar0=WRONG_VALUE;
   int      bar1=WRONG_VALUE;
   int      bar2=WRONG_VALUE;

//--- Get the time of the first bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index,1,array)!=1)
     {
      ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),7),index,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get index of the first bar in indicator object buffer based on bar opening time on the chart
   bar0=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
   if(bar0==WRONG_VALUE)
     {
      ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),string(array[0]),::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get the time of the second bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index+1,1,array)!=1)
     {
      ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),7),index+1,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get index of the second bar in indicator object buffer based on bar opening time on the chart
   bar1=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
   if(bar1==WRONG_VALUE)
     {
      ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),string(array[0]),::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get the time of the third bar on the chart
   ::ResetLastError();
   if(::CopyTime(symbol,timeframe,index+2,1,array)!=1)
     {
      ::PrintFormat("%s: CopyTime for %s/%s, bar %ld failed. Error %lu",__FUNCTION__,symbol,::StringSubstr(::EnumToString(timeframe),7),index+2,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get index of the third bar in indicator object buffer based on bar opening time on the chart
   bar2=::iBarShift(this.m_symbol,this.m_timeframe,array[0]);
   if(bar2==WRONG_VALUE)
     {
      ::PrintFormat("%s: iBarShift for %s/%s, time %s failed. Error %lu",__FUNCTION__,this.m_symbol,this.TimeframeDescription(),string(array[0]),::GetLastError());
      return LINE_STATE_NONE;
     }
     
//--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index
   const double value0=this.GetData(buffer_num,bar0);
   const double value1=this.GetData(buffer_num,bar1);
   const double value2=this.GetData(buffer_num,bar2);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Line upward reversal (value2>value1 && value0>value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)>0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Line upward direction (value2<=value1 && value0>value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)>0)
      return LINE_STATE_UP;
//--- Line upward stop (value2<=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)<=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Line downward reversal (value2<value1 && value0<value1)
   if(::NormalizeDouble(value2-value1,this.m_digits)<0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Line downward direction (value2>=value1 && value0<value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)<0)
      return LINE_STATE_DOWN;
//--- Line downward stop (value2>=value1 && value0==value1)
   else if(::NormalizeDouble(value2-value1,this.m_digits)>=0 && ::NormalizeDouble(value0-value1,this.m_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Undefined state
   return LINE_STATE_NONE;
  }

El método permite obtener el estado de la línea de objeto de indicador relativa al gráfico actual. Si el objeto de indicador se calcula sobre los datos del marco temporal más antiguo en relación con el gráfico actual, su línea se "estirará" por las barras del gráfico actual. El método permite obtener correctamente el estado de la línea de indicador en la barra especificada del gráfico actual.

Método que retorna el estado de la línea en relación con el nivel especificado:

//+------------------------------------------------------------------+
//| Return the state of the line relative to the specified level     |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Get the values of the indicator line with the shift (0,1) relative to the passed index
   const double value0=this.GetData(buffer_num,index);
   const double value1=this.GetData(buffer_num,index+1);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Define the second level to compare
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- The line is below the level (value1<level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_BELOW;
//--- The line is above the level (value1>level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_ABOVE;
//--- The line crossed the level upwards (value1<=level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<=0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- The line crossed the level downwards (value1>=level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>=0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- The line touched the level from below (value1<level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- The line touched the level from above (value1>level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Line is equal to the level value (value1==level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)==0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_EQUALS;
//--- Undefined state
   return LINE_STATE_NONE;
  }

Método que retorna el estado de la línea relativa al nivel especificado en el símbolo/periodo indicado del gráfico:

//+------------------------------------------------------------------+
//| Return the state of the line relative to the specified level     |
//| on the specified chart symbol/period                             |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CIndMSTF::BufferLineStateRelative(const string symbol_from,const ENUM_TIMEFRAMES timeframes_from,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Determine the chart symbol/period passed to the method
   string symbol=(symbol_from=="" || symbol_from==NULL ? ::Symbol() : symbol_from);
   ENUM_TIMEFRAMES timeframe=(timeframes_from==PERIOD_CURRENT ? ::Period() : timeframes_from);
//--- Get the values of the indicator line with the shift (0,1) relative to the passed index
   const double value0=this.GetDataTo(symbol,timeframe,buffer_num,index);
   const double value1=this.GetDataTo(symbol,timeframe,buffer_num,index+1);
//--- If at least one of the values could not be obtained, return an undefined value 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Define the second level to compare
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- The line is below the level (value1<level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_BELOW;
//--- The line is above the level (value1>level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_ABOVE;
//--- The line crossed the level upwards (value1<=level && value0>level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<=0 && ::NormalizeDouble(value0-level0,this.m_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- The line crossed the level downwards (value1>=level && value0<level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>=0 && ::NormalizeDouble(value0-level0,this.m_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- The line touched the level from below (value1<level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)<0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- The line touched the level from above (value1>level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)>0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Line is equal to the level value (value1==level0 && value0==level0)
   if(::NormalizeDouble(value1-level,this.m_digits)==0 && ::NormalizeDouble(value0-level0,this.m_digits)==0)
      return LINE_STATE_EQUALS;
//--- Undefined state
   return LINE_STATE_NONE;
  }

Podrá leer más sobre los métodos para obtener el estado de las líneas de indicador en el artículo sobre la conexión de osciladores a asesores.

Resto de métodos de la clase:

//+------------------------------------------------------------------+
//| Return category description                                      |
//+------------------------------------------------------------------+
string CIndMSTF::CategoryDescription(void)
  {
//--- Create a category name from ENUM_IND_CATEGORY and return the resulting text
   string category=::StringSubstr(::EnumToString(this.m_category),13);
   if(category.Lower())
      category.SetChar(0,ushort(category.GetChar(0)-0x20));
   return category;
  }
//+------------------------------------------------------------------+
//| Return the description of the indicator buffer                   |
//+------------------------------------------------------------------+
string CIndMSTF::BufferDescription(const uint buffer_num)
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return empty string
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return "";
     }
//--- If indicator has buffers, return description of the specified buffer, otherwise description of the indicator
   return(this.BuffersTotal()>0 ? this.m_buffers[buffer_num].descript : this.m_title);
  }
//+------------------------------------------------------------------+
//| Set indicator buffer description                                 |
//+------------------------------------------------------------------+
void CIndMSTF::SetBufferDescription(const uint buffer_num,const string descr)
  {
//--- If the indicator has no buffers, exit
   if(this.BuffersTotal()==0)
      return;
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and exit
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return;
     }
//--- Write the description passed to the method into the specified buffer
   this.m_buffers[buffer_num].descript=descr;
  }
//+------------------------------------------------------------------+
//| Disable timeseries indexing of buffer arrays                     |
//+------------------------------------------------------------------+
void CIndMSTF::SetAsSeriesOff(void)
  {
//--- In a loop through all indicator buffers, disable the array as timeseries flag
   for(int i=0;i<(int)this.BuffersTotal();i++)
      ::ArraySetAsSeries(this.m_buffers[i].array,false);
  }
//+------------------------------------------------------------------+
//| Returns the timeseries flag of the given buffer                  |
//+------------------------------------------------------------------+
bool CIndMSTF::IsSeries(const uint buffer_num) const
  {
//--- Validate the buffer number passed to the method and, if the number is incorrect, print a message to the log and return 'false'
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return false;
     }
//--- Return the timeseries flag of the array of the specified buffer
   return (bool)::ArrayGetAsSeries(this.m_buffers[buffer_num].array);
  }
//+------------------------------------------------------------------+
//| Returns the amount of data in the specified buffer               |
//+------------------------------------------------------------------+
uint CIndMSTF::DataTotal(const uint buffer_num) const
  {
//--- Validate the buffer number passed to method and, if number is incorrect, print a message to log and return zero
   if(buffer_num>this.BuffersTotal()-1)
     {
      string buff_limit=(this.BuffersTotal()==1 ? "0" : "0 - "+string(this.BuffersTotal()-1));
      ::PrintFormat("%s: Invalid buffer number passed (%lu). Value must be %s",__FUNCTION__,buffer_num,buff_limit);
      return 0;
     }
//--- Return the array size of the specified buffer
   return this.m_buffers[buffer_num].array.Size();
  }

La clase básica del objeto de indicador de símbolo/periodo múltiple está lista. La clase contiene toda la funcionalidad necesaria para trabajar con indicadores construidos sobre los datos de las series temporales que no pertenecen al gráfico actual.

Para crear diferentes tipos de indicadores técnicos, necesitaremos crear clases derivadas de la clase básica que acabamos de crear. En los constructores de las clases derivadas se especificarán aquellos parámetros y propiedades que resulten inherentes al tipo del indicador creado.


Clases de indicadores por tipo

Las derivadas de la clase básica serán las más sencillas y contendrán únicamente el constructor. El símbolo/periodo del gráfico sobre el que se calcula el indicador, así como los parámetros de entrada propios de este tipo de indicadores, se transmitirán al constructor de la clase. La línea de inicialización del constructor transmitirá los parámetros al constructor de la clase padre.

Ejemplo de clase que crea un objeto de indicador que no contiene ningún parámetro:

//+------------------------------------------------------------------+
//| Accelerator Oscillator indicator class                           |
//+------------------------------------------------------------------+
class CIndAC : public CIndMSTF
  {
public:
//--- Constructor
   CIndAC(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,1,symbol,timeframe)
     {
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("AC");
      this.SetDescription("Accelerator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };

En la línea de inicialización se transmitirán a la clase padre el tipo de indicador, el número de búferes, el símbolo y el periodo del gráfico sobre el que deberá calcularse el indicador. En el cuerpo de la clase se creará una línea con la descripción de los parámetros del indicador. En este caso, si el indicador se crea sobre los datos del gráfico actual, la línea de parámetros estará vacía. De lo contrario, contendrá el símbolo y el periodo del gráfico en la forma, por ejemplo "(EURUSD,H1)". Además, en el cuerpo del constructor se establecerán todos los parámetros inherentes a este tipo de indicador (aquí hablamos del indicador Accelerator Oscillator).

Cada indicador tiene la posibilidad de establecer el número de decimales que se muestran en la ventana de datos y en el gráfico del instrumento. No hay ningún ajuste Digits en el constructor de esta clase, ya que este valor, igual al Digits del símbolo sobre el que se calculará el indicador, se establecerá en el constructor de la clase padre. Si es necesario establecer un Digits diferente para el indicador, se hará en los constructores de las clases de esos indicadores, donde el Digits de los valores diferirá del Digits del símbolo, o se podrá cambiar después de crear el objeto de indicador utilizando el método SetDigits().

Clase de indicador con parámetros:

//+------------------------------------------------------------------+
//| Accumulation/Distribution indicator class                        |
//+------------------------------------------------------------------+
class CIndAD : public CIndMSTF
  {
public:
//--- Constructor
   CIndAD(const string symbol,const ENUM_TIMEFRAMES timeframe,
          const ENUM_APPLIED_VOLUME applied_volume // used volume
         ) : CIndMSTF(IND_AD,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("A/D");
      this.SetDescription("Accumulation/Distribution");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };

Hay parámetros aquí, y deberemos escribirlos en el array de estructura de parámetros de entrada del indicador MqlParam. Y aquí también estableceremos el valor Digits del indicador, que estará establecido para el Accumulation/Distribution estándar.

Lista completa de todas las clases herederas de la clase básica del indicador de símbolo/periodo múltiple:

//+------------------------------------------------------------------+
//| Accelerator Oscillator indicator class                           |
//+------------------------------------------------------------------+
class CIndAC : public CIndMSTF
  {
public:
//--- Constructor
   CIndAC(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AC,1,symbol,timeframe)
     {
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("AC");
      this.SetDescription("Accelerator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Accumulation/Distribution indicator class                        |
//+------------------------------------------------------------------+
class CIndAD : public CIndMSTF
  {
public:
//--- Constructor
   CIndAD(const string symbol,const ENUM_TIMEFRAMES timeframe,
          const ENUM_APPLIED_VOLUME applied_volume // used volume
         ) : CIndMSTF(IND_AD,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("A/D");
      this.SetDescription("Accumulation/Distribution");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Average Directional Movement Index indicator class               |
//+------------------------------------------------------------------+
class CIndADX : public CIndMSTF
  {
public:
//--- Constructor
   CIndADX(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int adx_period                    // averaging period
          ) : CIndMSTF(IND_ADX,3,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - PLUSDI_LINE, 2 - MINUSDI_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(adx_period<1 ? 14 : adx_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),adx_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("ADX");
      this.SetDescription("Average Directional Movement Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=2;
      //--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(PLUSDI_LINE,"+DI");
      this.SetBufferDescription(MINUSDI_LINE,"-DI");
     }
  };
//+------------------------------------------------------------------+
//| Average Directional Movement Index Wilder indicator class        |
//+------------------------------------------------------------------+
class CIndADXW : public CIndMSTF
  {
public:
//--- Constructor
   CIndADXW(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int adx_period                      // averaging period
           ) : CIndMSTF(IND_ADXW,3,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - PLUSDI_LINE, 2 - MINUSDI_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(adx_period<1 ? 14 : adx_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),adx_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("ADX Wilder");
      this.SetDescription("Average Directional Movement Index Wilder");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=2;
      //--- write descriptions of MAIN_LINE, PLUSDI_LINE and MINUSDI_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(PLUSDI_LINE,"+DI");
      this.SetBufferDescription(MINUSDI_LINE,"-DI");
     }
  };
//+------------------------------------------------------------------+
//| Alligator indicator class                                        |
//+------------------------------------------------------------------+
class CIndAlligator : public CIndMSTF
  {
public:
//--- Constructor
   CIndAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                 const int jaw_period,                   // period for calculating jaws
                 const int jaw_shift,                    // horizontal shift of jaws
                 const int teeth_period,                 // period for calculating teeth
                 const int teeth_shift,                  // horizontal shift of teeth
                 const int lips_period,                  // period for calculating lips
                 const int lips_shift,                   // horizontal shift of lips
                 const ENUM_MA_METHOD ma_method,         // smoothing type
                 const ENUM_APPLIED_PRICE applied_price  // price type or handle
                ) : CIndMSTF(IND_ALLIGATOR,3,symbol,timeframe)
     {
      // Buffer indexes: 0 - GATORJAW_LINE, 1 - GATORTEETH_LINE, 2 - GATORLIPS_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,8)==8)
        {
         ::ZeroMemory(this.m_param);
         //--- period for jaw line calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(jaw_period<1 ? 13 : jaw_period);
         //--- horizontal shift of the jaw line
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=jaw_shift;
         //--- period for teeth line calculation
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(teeth_period<1 ? 8 : teeth_period);
         //--- horizontal shift of teeth line
         this.m_param[3].type=TYPE_INT;
         this.m_param[3].integer_value=teeth_shift;
         //--- period for lip line calculation
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=(lips_period<1 ? 5 : lips_period);
         //--- horizontal shift of lips line
         this.m_param[5].type=TYPE_INT;
         this.m_param[5].integer_value=lips_shift;
         //--- smoothing type
         this.m_param[6].type=TYPE_UINT;
         this.m_param[6].integer_value=ma_method;
         //--- price type or handle
         this.m_param[7].type=TYPE_UINT;
         this.m_param[7].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Alligator");
      this.SetDescription("Alligator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write descriptions of GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE line buffers
      this.SetBufferDescription(GATORJAW_LINE,::StringFormat("Jaws(%s%lu)", (current ? "" : symbol_period+":"),jaw_period));
      this.SetBufferDescription(GATORTEETH_LINE,::StringFormat("Teeth(%s%lu)",(current ? "" : symbol_period+":"),teeth_period));
      this.SetBufferDescription(GATORLIPS_LINE,::StringFormat("Lips(%s%lu)", (current ? "" : symbol_period+":"),lips_period));
      //--- Write offsets to buffers GATORJAW_LINE, GATORTEETH_LINE and GATORLIPS_LINE
      this.SetBufferShift(GATORJAW_LINE,jaw_shift);
      this.SetBufferShift(GATORTEETH_LINE,teeth_shift);
      this.SetBufferShift(GATORLIPS_LINE,lips_shift);
     }
  };
//+------------------------------------------------------------------+
//| Adaptive Moving Average indicator class                          |
//+------------------------------------------------------------------+
class CIndAMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ama_period,                   // AMA period
           const int fast_ma_period,               // fast MA period
           const int slow_ma_period,               // slow MA period
           const int ama_shift,                    // horizontal shift of the indicator
           const ENUM_APPLIED_PRICE applied_price  // price type or handle
          ) : CIndMSTF(IND_AMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,5)==5)
        {
         ::ZeroMemory(this.m_param);
         //--- AMA period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ama_period<1 ? 9 : ama_period);
         //--- fast MA period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(fast_ma_period<1 ? 2 : fast_ma_period);
         //--- slow MA period
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(slow_ma_period<1 ? 30 : slow_ma_period);
         //--- horizontal shift of the indicator
         this.m_param[3].type=TYPE_INT;
         this.m_param[3].integer_value=ama_shift;
         //--- price type or handle
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),ama_period,fast_ma_period,slow_ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("AMA");
      this.SetDescription("Adaptive Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ama_shift);
     }
  };
//+------------------------------------------------------------------+
//| Awesome Oscillator indicator class                               |
//+------------------------------------------------------------------+
class CIndAO : public CIndMSTF
  {
public:
//--- Constructor
   CIndAO(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_AO,1,symbol,timeframe)
     {
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("AO");
      this.SetDescription("Awesome Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Average True Range indicator class                               |
//+------------------------------------------------------------------+
class CIndATR : public CIndMSTF
  {
public:
//--- Constructor
   CIndATR(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period                     // averaging period
          ) : CIndMSTF(IND_ATR,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("ATR");
      this.SetDescription("Average True Range");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Bears Power indicator class                                      |
//+------------------------------------------------------------------+
class CIndBears : public CIndMSTF
  {
public:
//--- Constructor
   CIndBears(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int ma_period                      // averaging period
            ) : CIndMSTF(IND_BEARS,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 13 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Bears");
      this.SetDescription("Bears Power");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Bulls Power indicator class                                      |
//+------------------------------------------------------------------+
class CIndBulls : public CIndMSTF
  {
public:
//--- Constructor
   CIndBulls(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int ma_period                      // averaging period
            ) : CIndMSTF(IND_BULLS,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 13 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Bulls");
      this.SetDescription("Bulls Power");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Bollinger Bands® indicator class                                 |
//+------------------------------------------------------------------+
class CIndBands : public CIndMSTF
  {
public:
//--- Constructor
   CIndBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int bands_period,                  // central line calculation period
             const int bands_shift,                   // horizontal shift of the indicator
             const double deviation,                  // number of standard deviations
             const ENUM_APPLIED_PRICE applied_price   // price type or handle
            ) : CIndMSTF(IND_BANDS,3,symbol,timeframe)
     {
      // Buffer indexes: 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- central line calculation period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(bands_period<1 ? 20 : bands_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=bands_shift;
         //--- number of standard deviations
         this.m_param[2].type=TYPE_DOUBLE;
         this.m_param[2].double_value=deviation;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),bands_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Bands");
      this.SetDescription("Bollinger Bands");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Description of line buffers BASE_LINE, UPPER_BAND and LOWER_BAND
      this.SetBufferDescription(BASE_LINE,this.m_title+" Middle");
      this.SetBufferDescription(UPPER_BAND,this.m_title+" Upper");
      this.SetBufferDescription(LOWER_BAND,this.m_title+" Lower");
      //--- Write offsets to the BASE_LINE, UPPER_BAND and LOWER_BAND buffers
      this.SetBufferShift(BASE_LINE,bands_shift);
      this.SetBufferShift(UPPER_BAND,bands_shift);
      this.SetBufferShift(LOWER_BAND,bands_shift);
     }
  };
//+------------------------------------------------------------------+
//| Commodity Channel Index indicator class                          |
//+------------------------------------------------------------------+
class CIndCCI : public CIndMSTF
  {
public:
//--- Constructor
   CIndCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period,                    // averaging period
           const ENUM_APPLIED_PRICE applied_price  // price type or handle
          ) : CIndMSTF(IND_CCI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period<2 ? 2 : ma_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("CCI");
      this.SetDescription("Commodity Channel Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Chaikin Oscillator indicator class                               |
//+------------------------------------------------------------------+
class CIndCHO : public CIndMSTF
  {
public:
//--- Constructor
   CIndCHO(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int fast_ma_period,                  // fast period
           const int slow_ma_period,                  // slow period
           const ENUM_MA_METHOD ma_method,            // smoothing type
           const ENUM_APPLIED_VOLUME applied_volume   // used volume
          ) : CIndMSTF(IND_CHAIKIN,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- fast period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(fast_ma_period<1 ? 3 : fast_ma_period);
         //--- slow period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(slow_ma_period<1 ? 10 : slow_ma_period);
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- used volume
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),slow_ma_period,fast_ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("CHO");
      this.SetDescription("Chaikin Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Double Exponential Moving Average indicator class                |
//+------------------------------------------------------------------+
class CIndDEMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int ma_period,                      // averaging period
            const int ma_shift,                       // horizontal indicator shift
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
          ) : CIndMSTF(IND_DEMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("DEMA");
      this.SetDescription("Double Exponential Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| DeMarker indicator class                                         |
//+------------------------------------------------------------------+
class CIndDeM : public CIndMSTF
  {
public:
//--- Constructor
   CIndDeM(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period                     // averaging period
          ) : CIndMSTF(IND_DEMARKER,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("DeM");
      this.SetDescription("DeMarker");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=3;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Envelopes indicator class                                        |
//+------------------------------------------------------------------+
class CIndEnvelopes : public CIndMSTF
  {
public:
//--- Constructor
   CIndEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                 const int ma_period,                    // middle line calculation period
                 const int ma_shift,                     // horizontal shift of the indicator
                 const ENUM_MA_METHOD ma_method,         // smoothing type
                 const ENUM_APPLIED_PRICE applied_price, // price type or handle
                 const double deviation                  // deviation of envelope borders from the middle line
          ) : CIndMSTF(IND_ENVELOPES,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,5)==5)
        {
         ::ZeroMemory(this.m_param);
         //--- central line calculation period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
         //--- deviation of envelope borders from the muddle line
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].double_value=deviation;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Envelopes");
      this.SetDescription("Envelopes");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Upper");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Lower");
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Force Index indicator class                                      |
//+------------------------------------------------------------------+
class CIndForce : public CIndMSTF
  {
public:
//--- Constructor
   CIndForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int                 ma_period,     // averaging period
             const ENUM_MA_METHOD      ma_method,     // smoothing type
             const ENUM_APPLIED_VOLUME applied_volume // volume type for calculation
            ) : CIndMSTF(IND_FORCE,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 13 : ma_period);
         //--- smoothing type
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=ma_method;
         //--- volume type for calculation
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Force");
      this.SetDescription("Force Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Fractals indicator class                                         |
//+------------------------------------------------------------------+
class CIndFractals : public CIndMSTF
  {
public:
//--- Constructor
   CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("Fractals");
      this.SetDescription("Fractals");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Description of UPPER_LINE and LOWER_LINE line buffers
      this.SetBufferDescription(UPPER_LINE,this.m_title+" Up");
      this.SetBufferDescription(LOWER_LINE,this.m_title+" Down");
     }
  };
//+------------------------------------------------------------------+
//| Fractal Adaptive Moving Average indicator class                  |
//+------------------------------------------------------------------+
class CIndFrAMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int ma_period,                     // averaging period
             const int ma_shift,                      // horizontal shift of the indicator
             const ENUM_APPLIED_PRICE applied_price   // price type or handle
            ) : CIndMSTF(IND_FRAMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("FRAMA");
      this.SetDescription("Fractal Adaptive Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Gator Oscillator indicator class                                 |
//+------------------------------------------------------------------+
class CIndGator : public CIndMSTF
  {
public:
//--- Constructor
   CIndGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int jaw_period,                    // period for jaw line calculation
             const int jaw_shift,                     // horizontal shift of jaw line
             const int teeth_period,                  // period for calculating teeth line
             const int teeth_shift,                   // horizontal shift of teeth line
             const int lips_period,                   // period for calculating lip line
             const int lips_shift,                    // horizontal shift of lip line
             const ENUM_MA_METHOD ma_method,          // smoothing type
             const ENUM_APPLIED_PRICE applied_price   // price type or handle
            ) : CIndMSTF(IND_GATOR,4,symbol,timeframe)
     {
      // Buffer indexes: 0 - UPPER_HISTOGRAM, 1 - color buffer of the upper histogram, 2 - LOWER_HISTOGRAM, 3 - color buffer of the lower histogram
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,8)==8)
        {
         ::ZeroMemory(this.m_param);
         //--- period for jaw line calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(jaw_period<1 ? 13 : jaw_period);
         //--- horizontal shift of the jaw line
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=jaw_shift;
         //--- period for teeth line calculation
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(teeth_period<1 ? 8 : teeth_period);
         //--- horizontal shift of teeth line
         this.m_param[3].type=TYPE_INT;
         this.m_param[3].integer_value=teeth_shift;
         //--- period for lip line calculation
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=(lips_period<1 ? 5 : lips_period);
         //--- horizontal shift of lips line
         this.m_param[5].type=TYPE_INT;
         this.m_param[5].integer_value=lips_shift;
         //--- smoothing type
         this.m_param[6].type=TYPE_UINT;
         this.m_param[6].integer_value=ma_method;
         //--- price type or handle
         this.m_param[7].type=TYPE_UINT;
         this.m_param[7].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),jaw_period,teeth_period,lips_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Gator");
      this.SetDescription("Gator Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      this.m_digits=::Digits()+1;
      //--- Description of line buffers UPPER_HISTOGRAM, upper histogram color buffer, LOWER_HISTOGRAM and lower histogram color buffer
      this.SetBufferDescription(UPPER_HISTOGRAM,this.m_title+" Up");
      this.SetBufferDescription(1,this.m_title+" Colors Up");
      this.SetBufferDescription(LOWER_HISTOGRAM,this.m_title+" Down");
      this.SetBufferDescription(3,this.m_title+" Colors Down");
      //--- Write offsets to buffers UPPER_HISTOGRAM, 1, LOWER_HISTOGRAM and 2
      this.SetBufferShift(UPPER_HISTOGRAM,teeth_shift);
      this.SetBufferShift(1,teeth_shift);
      this.SetBufferShift(LOWER_HISTOGRAM,lips_shift);
      this.SetBufferShift(3,lips_shift);
     }
  };
//+------------------------------------------------------------------+
//| Ichimoku Kinko Hyo indicator class                               |
//+------------------------------------------------------------------+
class CIndIchimoku : public CIndMSTF
  {
public:
//--- Constructor
   CIndIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                const int tenkan_sen,                    // period of Tenkan-sen
                const int kijun_sen,                     // period of Kijun-sen
                const int senkou_span_b                  // period of Senkou Span B
               ) : CIndMSTF(IND_ICHIMOKU,5,symbol,timeframe)
     {
      // Buffer indexes: 0 - TENKANSEN_LINE, 1 - KIJUNSEN_LINE, 2 - SENKOUSPANA_LINE, 3 - SENKOUSPANB_LINE, 4 - CHIKOUSPAN_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- period of Tenkan-sen
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(tenkan_sen<1 ? 9 : tenkan_sen);
         //--- period of Kijun-sen
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(kijun_sen<1 ? 26 : kijun_sen);
         //--- period of Senkou Span B
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(senkou_span_b<1 ? 52 : senkou_span_b);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),tenkan_sen,kijun_sen,senkou_span_b);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Ichimoku");
      this.SetDescription("Ichimoku Kinko Hyo");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Description of line buffers TENKANSEN_LINE, KIJUNSEN_LINE, SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE
      this.SetBufferDescription(TENKANSEN_LINE,::StringFormat("Tenkan-sen(%lu)",tenkan_sen));
      this.SetBufferDescription(KIJUNSEN_LINE,::StringFormat("Kijun-sen(%lu)",kijun_sen));
      this.SetBufferDescription(SENKOUSPANA_LINE,"Senkou Span A");
      this.SetBufferDescription(SENKOUSPANB_LINE,::StringFormat("Senkou Span B(%lu)",senkou_span_b));
      this.SetBufferDescription(CHIKOUSPAN_LINE,"Chikou Span");
      //--- Write shifts to buffers SENKOUSPANA_LINE, SENKOUSPANB_LINE and CHIKOUSPAN_LINE
      //this.SetBufferShift(SENKOUSPANA_LINE,kijun_sen);
      //this.SetBufferShift(SENKOUSPANB_LINE,kijun_sen);
      //this.SetBufferShift(CHIKOUSPAN_LINE,kijun_sen-senkou_span_b);
     }
  };
//+------------------------------------------------------------------+
//| Market Facilitation Index indicator class                        |
//+------------------------------------------------------------------+
class CIndBWMFI : public CIndMSTF
  {
public:
//--- Constructor
   CIndBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const ENUM_APPLIED_VOLUME applied_volume // volume type for calculation
            ) : CIndMSTF(IND_BWMFI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- volume type for calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("BW MFI");
      this.SetDescription("Market Facilitation Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_WILLIAMS;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Momentum indicator class                                         |
//+------------------------------------------------------------------+
class CIndMomentum : public CIndMSTF
  {
public:
//--- Constructor
   CIndMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                const int                 mom_period,    // averaging period
                const ENUM_APPLIED_PRICE  applied_price  // price type or handle
               ) : CIndMSTF(IND_MOMENTUM,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(mom_period<1 ? 14 : mom_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),mom_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Momentum");
      this.SetDescription("Momentum");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Money Flow Index indicator class                                 |
//+------------------------------------------------------------------+
class CIndMFI : public CIndMSTF
  {
public:
//--- Constructor
   CIndMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int                 ma_period,       // averaging period
           const ENUM_APPLIED_VOLUME applied_volume   // volume type for calculation
          ) : CIndMSTF(IND_MFI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- volume type for calculation
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("MFI");
      this.SetDescription("Money Flow Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Moving Average indicator class                                   |
//+------------------------------------------------------------------+
class CIndMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
          const int                 ma_period,     // averaging period
          const int                 ma_shift,      // horizontal shift of the indicator
          const ENUM_MA_METHOD      ma_method,     // smoothing type
          const ENUM_APPLIED_PRICE  applied_price  // price type or handle
         ) : CIndMSTF(IND_MA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 10 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("MA");
      this.SetDescription("Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Moving Average of Oscillator indicator class                     |
//+------------------------------------------------------------------+
class CIndOsMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                fast_ema_period, // fast MA period
            const int                slow_ema_period, // slow MA period
            const int                signal_period,   // difference averaging period
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
           ) : CIndMSTF(IND_OSMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- fast MA period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(fast_ema_period<1 ? 12 : fast_ema_period);
         //--- slow MA period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(slow_ema_period<1 ? 26 : slow_ema_period);
         //--- difference averaging period
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(signal_period<1 ? 9 : signal_period<2 ? 2 : signal_period);
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("OsMA");
      this.SetDescription("Moving Average of Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Moving Averages Convergence/Divergence indicator class           |
//+------------------------------------------------------------------+
class CIndMACD : public CIndMSTF
  {
public:
//--- Constructor
   CIndMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                fast_ema_period, // fast MA period
            const int                slow_ema_period, // slow MA period
            const int                signal_period,   // difference averaging period
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
           ) : CIndMSTF(IND_MACD,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - SIGNAL_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- fast MA period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(fast_ema_period<1 ? 12 : fast_ema_period);
         //--- slow MA period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(slow_ema_period<1 ? 26 : slow_ema_period);
         //--- difference averaging period
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(signal_period<1 ? 9 : signal_period);
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),fast_ema_period,slow_ema_period,signal_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("MACD");
      this.SetDescription("Moving Averages Convergence/Divergence");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=::Digits()+1;
      //--- Description of MAIN_LINE and SIGNAL_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(SIGNAL_LINE,"Signal");
     }
  };
//+------------------------------------------------------------------+
//| On Balance Volume indicator class                                |
//+------------------------------------------------------------------+
class CIndOBV : public CIndMSTF
  {
public:
//--- Constructor
   CIndOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const ENUM_APPLIED_VOLUME applied_volume   // volume type for calculation
          ) : CIndMSTF(IND_OBV,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- volume type for calculation
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("OBV");
      this.SetDescription("On Balance Volume");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Parabolic Stop and Reverse system indicator class                |
//+------------------------------------------------------------------+
class CIndSAR : public CIndMSTF
  {
public:
//--- Constructor
   CIndSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const double step,                      // price change step — acceleration factor
           const double maximum                    // maximum step
          ) : CIndMSTF(IND_SAR,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- price change step — acceleration factor
         this.m_param[0].type=TYPE_DOUBLE;
         this.m_param[0].double_value=step;
         //--- maximum step
         this.m_param[1].type=TYPE_DOUBLE;
         this.m_param[1].double_value=maximum;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%.2f,%.2f)",symbol_period,(current ? "" : ":"),step,maximum);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("SAR");
      this.SetDescription("Parabolic SAR");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Relative Strength Index indicator class                          |
//+------------------------------------------------------------------+
class CIndRSI : public CIndMSTF
  {
public:
//--- Constructor
   CIndRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int                ma_period,     // averaging period
           const ENUM_APPLIED_PRICE applied_price  // price type or handle
          ) : CIndMSTF(IND_RSI,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period<2 ? 2 : ma_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("RSI");
      this.SetDescription("Relative Strength Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Relative Vigor Index indicator class                             |
//+------------------------------------------------------------------+
class CIndRVI : public CIndMSTF
  {
public:
//--- Constructor
   CIndRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int ma_period                     // averaging period
          ) : CIndMSTF(IND_RVI,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - SIGNAL_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 10 : ma_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("RVI");
      this.SetDescription("Relative Vigor Index");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=3;
      //--- Description of MAIN_LINE and SIGNAL_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(SIGNAL_LINE,"Signal");
     }
  };
//+------------------------------------------------------------------+
//| Standard Deviation indicator class                               |
//+------------------------------------------------------------------+
class CIndStdDev : public CIndMSTF
  {
public:
//--- Constructor
   CIndStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const int                ma_period,        // averaging period
              const int                ma_shift,         // horizontal shift of the indicator
              const ENUM_MA_METHOD     ma_method,        // smoothing type
              const ENUM_APPLIED_PRICE applied_price     // price type or handle
             ) : CIndMSTF(IND_STDDEV,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 20 : ma_period<2 ? 2 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- smoothing type
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=ma_method;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("StdDev");
      this.SetDescription("Standard Deviation");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Stochastic Oscillator indicator class                            |
//+------------------------------------------------------------------+
class CIndStoch : public CIndMSTF
  {
public:
//--- Constructor
   CIndStoch(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int              Kperiod,          // K-period (number of bars for calculations)
             const int              Dperiod,          // D-period (primary smoothing period)
             const int              slowing,          // final smoothing
             const ENUM_MA_METHOD   ma_method,        // smoothing type
             const ENUM_STO_PRICE   price_field       // Stochastic calculation method
            ) : CIndMSTF(IND_STOCHASTIC,2,symbol,timeframe)
     {
      // Buffer indexes: 0 - MAIN_LINE, 1 - SIGNAL_LINE
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,5)==5)
        {
         ::ZeroMemory(this.m_param);
         //--- K period (number of bars for calculation)
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(Kperiod<1 ? 5 : Kperiod);
         //--- D period (primary smoothing period)
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(Dperiod<1 ? 3 : Dperiod);
         //--- final smoothing
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=(slowing<1 ? 3 : slowing);
         //--- smoothing type
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=ma_method;
         //--- stochastic calculation method
         this.m_param[4].type=TYPE_UINT;
         this.m_param[4].integer_value=price_field;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu,%lu)",symbol_period,(current ? "" : ":"),Kperiod,Dperiod,slowing);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Stoch");
      this.SetDescription("Stochastic Oscillator");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Description of MAIN_LINE and SIGNAL_LINE line buffers
      this.SetBufferDescription(MAIN_LINE,this.m_title);
      this.SetBufferDescription(SIGNAL_LINE,"Signal");
     }
  };
//+------------------------------------------------------------------+
//| Triple Exponential Moving Average indicator class                |
//+------------------------------------------------------------------+
class CIndTEMA : public CIndMSTF
  {
public:
//--- Constructor
   CIndTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                  ma_period,     // averaging period
            const int                  ma_shift,      // horizontal shift of the indicator
            const ENUM_APPLIED_PRICE   applied_price  // price type or handle
           ) : CIndMSTF(IND_TEMA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,3)==3)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period);
         //--- horizontal shift of the indicator
         this.m_param[1].type=TYPE_INT;
         this.m_param[1].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[2].type=TYPE_UINT;
         this.m_param[2].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("TEMA");
      this.SetDescription("Triple Exponential Moving Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Triple Exponential Moving Averages Oscillator indicator class    |
//+------------------------------------------------------------------+
class CIndTriX : public CIndMSTF
  {
public:
//--- Constructor
   CIndTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
            const int                ma_period,       // averaging period
            const ENUM_APPLIED_PRICE applied_price    // price type or handle
           ) : CIndMSTF(IND_TRIX,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,2)==2)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(ma_period<1 ? 14 : ma_period<2 ? 2 : ma_period);
         //--- price type or handle
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),ma_period);
      //--- Write description of parameters, indicator name, its description, title and category
      this.SetParameters(param);
      this.SetName("TRIX");
      this.SetDescription("Triple Exponential Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Larry Williams' Percent Range indicator class                    |
//+------------------------------------------------------------------+
class CIndWPR : public CIndMSTF
  {
public:
//--- Constructor
   CIndWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,
           const int calc_period                   // averaging period
          ) : CIndMSTF(IND_WPR,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- averaging period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(calc_period<1 ? 14 : calc_period);
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu)",symbol_period,(current ? "" : ":"),calc_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("%R");
      this.SetDescription("Williams' Percent Range");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_OSCILLATOR;
      this.m_digits=2;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Variable Index Dynamic Average indicator class                   |
//+------------------------------------------------------------------+
class CIndVIDyA : public CIndMSTF
  {
public:
//--- Constructor
   CIndVIDyA(const string symbol,const ENUM_TIMEFRAMES timeframe,
             const int                 cmo_period,    // the Chande Momentum period
             const int                 ema_period,    // period of the smoothing factor
             const int                 ma_shift,      // horizontal shift of the indicator
             const ENUM_APPLIED_PRICE  applied_price  // price type or handle
            ) : CIndMSTF(IND_VIDYA,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,4)==4)
        {
         ::ZeroMemory(this.m_param);
         //--- Chande Momentum period
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=(cmo_period<1 ? 9 : cmo_period);
         //--- smoothing factor period
         this.m_param[1].type=TYPE_UINT;
         this.m_param[1].integer_value=(ema_period<1 ? 12 : ema_period);
         //--- horizontal shift of the indicator
         this.m_param[2].type=TYPE_INT;
         this.m_param[2].integer_value=ma_shift;
         //--- price type or handle
         this.m_param[3].type=TYPE_UINT;
         this.m_param[3].integer_value=applied_price;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=StringFormat("(%s%s%lu,%lu)",symbol_period,(current ? "" : ":"),cmo_period,ema_period);
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("VIDYA");
      this.SetDescription("Variable Index Dynamic Average");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_TREND;
      this.m_digits=::Digits()+1;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
      //--- Write shift to buffer 0
      this.SetBufferShift(0,ma_shift);
     }
  };
//+------------------------------------------------------------------+
//| Volumes indicator class                                          |
//+------------------------------------------------------------------+
class CIndVolumes : public CIndMSTF
  {
public:
//--- Constructor
   CIndVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,
               const ENUM_APPLIED_VOLUME applied_volume  // volume type
              ) : CIndMSTF(IND_VOLUMES,1,symbol,timeframe)
     {
      //--- Set the size of the parameter array and fill it
      ::ResetLastError();
      if(::ArrayResize(this.m_param,1)==1)
        {
         ::ZeroMemory(this.m_param);
         //--- volume type
         this.m_param[0].type=TYPE_UINT;
         this.m_param[0].integer_value=applied_volume;
        }
      else
         ::PrintFormat("%s: ArrayResize failed. Error %ld",__FUNCTION__,::GetLastError());
      //--- Create description of parameters
      //--- If non-current chart symbol or period, their descriptions are added to parameters
      bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
      string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
      string param=(current ? "" : StringFormat("(%s)",symbol_period));
      //--- Write description of parameters, indicator name, its description, title, category and Digits
      this.SetParameters(param);
      this.SetName("Volumes");
      this.SetDescription("Volumes");
      this.m_title=this.Name()+this.Parameters();
      this.m_category=IND_CATEGORY_VOLUME;
      this.m_digits=0;
      //--- Write description of line buffers
      this.SetBufferDescription(0,this.m_title);
     }
  };
//+------------------------------------------------------------------+
//| Custom indicator class                                           |
//+------------------------------------------------------------------+
class CIndCustom : public CIndMSTF
  {
public:
//--- Constructor
   CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,
              const string path,                      // path to the indicator (for example, "Examples\\MACD.ex5")
              const string name,                      // name of the custom indicator
              const uint   buffers,                   // number of indicator buffers
              const MqlParam &param[]                 // array of custom indicator parameters
             ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe)
     {
      //--- If an empty array of parameters is passed, print this to log
      int total=(int)param.Size();
      if(total==0)
         ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__);
      //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name)
      ResetLastError();
      if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1)
        {
         //--- Reset data in the array and enter name (path to file and name of .ex5 file)
         ::ZeroMemory(this.m_param);
         //--- name of the custom indicator
         this.m_param[0].type=TYPE_STRING;
         this.m_param[0].string_value=path;
         //--- fill the array of indicator parameters
         for(int i=0;i<total;i++)
           {
            this.m_param[i+1].type=param[i].type;
            this.m_param[i+1].double_value=param[i].double_value;
            this.m_param[i+1].integer_value=param[i].integer_value;
            this.m_param[i+1].string_value=param[i].string_value;
           }
         //--- Create description of parameters
         //--- If non-current chart symbol or period, their descriptions are added to parameters
         bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period());
         string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription()));
         string param=(current ? "" : StringFormat("(%s)",symbol_period));
         //--- Write description of parameters, indicator name, its description, title and category
         this.SetParameters(param);
         this.SetName(name);
         this.SetDescription(name);
         this.m_title=this.Name()+this.Parameters();
         this.m_category=IND_CATEGORY_CUSTOM;
         //--- Write a description of the first line buffer
         this.SetBufferDescription(0,this.m_title);
        }
     }
  };

Esta lista contendrá todas las clases para crear todos los indicadores técnicos disponibles en el terminal de cliente + una clase para crear un indicador personalizado. Todas las clases son idénticas entre sí y los puntos principales estarán comentados en el código de las clases.

No hemos probado la creación y el funcionamiento de todos los indicadores presentados anteriormente. solo hemos probado los indicadores basados en medias móviles que dibujan una línea en el gráfico principal. En los siguientes artículos escribiremos las plantillas para crear versiones de símbolo/periodo múltiple de todo tipo de indicadores técnicos.

De hecho, ya está todo listo para crear indicadores múltiples. Pero iremos un poco más allá y crearemos una práctica herramienta para automatizar la creación y el uso de indicadores de símbolo/periodo múltiple. Esta será una clase de colección de indicadores que nos permitirá crear fácilmente cualquier indicador basado en indicadores estándar o personalizados, convirtiéndolos en indicadores de símbolo y periodo múltiple.


Clase de colección de indicadores

Una clase de colección de indicadores es en esencia una lista ordinaria de punteros a objetos. Simplemente le añadiremos a esto los métodos para crear cómodamente indicadores y añadir los recién creados a la colección. También podremos obtener fácilmente un puntero al indicador deseado y tomar de él los datos requeridos. Al añadir un nuevo indicador a la colección, comprobaremos primero la presencia de exactamente el mismo indicador en la colección. Si existe, se eliminará el objeto recién creado y se retornará un puntero al existente en la colección, para que dos objetos diferentes no se refieran a la misma parte de cálculo.

La estructura de la clase colección repetirá la estructura de las clases del indicador básico de símbolo/periodo múltiple. La única diferencia es que la mayoría de los métodos deberán encontrar primero el indicador deseado según su manejador, y luego tendrán que llamar al método deseado para establecer u obtener el valor o resultado de las acciones.

En el mismo fichero \MQL5\Include\IndMSTF\IndMSTF.mqh seguiremos escribiendo una nueva clase. Para que funcione, deberemos conectar un archivo de clase de array dinámico de punteros a los ejemplares de la clase CObject y sus descendientes CArrayObj:

//+------------------------------------------------------------------+
//| Indicator collection class                                       |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
class CMSTFIndicators
  {
private:

public:
  
  }

Luego escribiremos un objeto de lista y los métodos para trabajar con la clase en el cuerpo de la clase:

//+------------------------------------------------------------------+
//| Indicator collection class                                       |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
class CMSTFIndicators
  {
private:
   CArrayObj         m_list;
//--- Creates an indicator for the passed object
   bool              CreateIndicator(CIndMSTF *ind_obj);
//--- Adds the specified indicator to the collection
   int               AddNewIndicator(CIndMSTF *ind_obj,const string source);

public:
//--- Returns (1) indicator object by handle, (2) number of indicators in the collection
   CIndMSTF         *GetIndicatorObj(const int ind_handle,const string source) const;
   uint              IndicatorsTotal(void)                  const { return this.m_list.Total(); }

//--- Populates buffers of (1) the indicator by handle, (2) all indicators in the collection
   bool              Calculate(const int ind_handle);
   bool              Calculate(void);
//--- Sets the (1) specified, (2) default description of the indicator buffer line
   void              SetPlotLabel(const uint plot_index,const string descript);
   void              SetPlotLabelFromBuffer(const uint plot_index,const int ind_handle,const uint buffer_num);
//--- Sets the shift to the specified plotting buffer
   void              SetPlotShift(const uint plot_index,const int shift);
//--- (1) Sets (2) returns the initializing value of the given buffer specified by the indicator handle
   void              SetBufferInitValue(const int ind_handle,const uint buffer_num,const double value);
   double            BufferInitValue(const int ind_handle,const uint buffer_num) const;

//--- Returns indicator data by handle from the specified buffer at index (1) as is, (2) for the specified symbol/timeframe
   double            GetData(const int ind_handle,const uint buffer_num,const uint index);
   double            GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const uint index);

//--- (1) Copies data of the specified calculation part buffer of the indicator by handle into the indicator buffer, taking into account chart symbol/period,
//--- (2) returns the amount of data in the specified buffer of the indicator by handle
   bool              DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const int limit,double &buffer[]);
   uint              DataTotal(const int ind_handle,const uint buffer_num) const;

   //--- Returns (1) buffer description, (2) state of the line data of given buffer of indicator specified by handle on the specified bar
   //--- (3) indicator line state for the specific chart symbol/period, (4) indicator line state relation to the specified level,
   //--- (5)  state of relation of indicator line with specified level for certain chart symbol/period, (6) indicator category description
   string            BufferDescription(const int ind_handle,const uint buffer_num);
   ENUM_LINE_STATE   BufferLineState(const int ind_handle,const uint buffer_num,const int index);
   ENUM_LINE_STATE   BufferLineState(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const uint buffer_num,const int index);
   ENUM_LINE_STATE   BufferLineStateRelative(const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);
   ENUM_LINE_STATE   BufferLineStateRelative(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE);
   string            CategoryDescription(const int ind_handle);
   
//--- Sets (1) identifier, (2) Digits, (3) user description, (4) buffer description
   void              SetID(const int ind_handle,const int id);
   void              SetDigits(const int ind_handle,const int digits);
   void              SetDescription(const int ind_handle,const string descr);
   void              SetBufferDescription(const int ind_handle,const uint buffer_num,const string descr);

//--- Returns flag of whether the buffer is set as series, (2) historical data for symbol/period is synchronized
   bool              IsSeries(const int ind_handle,const uint buffer_num) const;
   bool              IsSynchronized(const int ind_handle) const;
   
//--- Returns (1) timeframe, (2) symbol, (3) name, (4) list of parameters, (5) handle, (6) Digits
//--- number of (7) buffers, (8) bars, (9) identifier, (10) description, (11) title, (12) category,
//--- (13) number of parameters, (14) program type, description of (15) category, (16) indicator buffer
   ENUM_TIMEFRAMES   Timeframe(const int ind_handle) const;
   string            Symbol(const int ind_handle) const;
   string            Name(const int ind_handle) const;
   string            Parameters(const int ind_handle) const;
   int               Digits(const int ind_handle) const;
   uint              BuffersTotal(const int ind_handle) const;
   uint              RatesTotal(const int ind_handle) const;
   int               ID(const int ind_handle) const;
   string            Description(const int ind_handle) const;
   string            Title(const int ind_handle) const;
   ENUM_IND_CATEGORY Category(const int ind_handle) const;
   uint              ParamsTotal(const int ind_handle) const;
//--- Returns (1) structure of parameters by index from array, (2) timeframe description
   MqlParam          GetMqlParam(const int ind_handle,const int index) const;
   string            TimeframeDescription(const int ind_handle)    const;
//--- Returns amount of calculated data
   int               Calculated(const int ind_handle) const;
   
//--- Virtual method returning the type of object (indicator)
      ENUM_INDICATOR    Type(const int ind_handle) const;
   
//--- Methods for adding indicators to the collection
   int               AddNewAC(const string symbol,const ENUM_TIMEFRAMES timeframe);
   int               AddNewAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14);
   int               AddNewADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14);
   int               AddNewAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int jaw_period=13,
                                                                                         const int jaw_shift=8,
                                                                                         const int teeth_period=8,
                                                                                         const int teeth_shift=5,
                                                                                         const int lips_period=5,
                                                                                         const int lips_shift=3,
                                                                                         const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                                                                         const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN);
   int               AddNewAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ama_period=9,
                                                                                   const int fast_ma_period=2,
                                                                                   const int slow_ma_period=30,
                                                                                   const int ama_shift=0,
                                                                                   const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewAO(const string symbol,const ENUM_TIMEFRAMES timeframe);
   int               AddNewATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14);
   int               AddNewBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13);
   int               AddNewBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13);
   int               AddNewBands(const string symbol,const ENUM_TIMEFRAMES timeframe,const int bands_period=20,
                                                                                     const int bands_shift=0,
                                                                                     const double deviation=2.0,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                   const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL);
   int               AddNewChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,const int fast_ma_period=3,
                                                                                       const int slow_ma_period=10,
                                                                                       const ENUM_MA_METHOD ma_method=MODE_EMA,
                                                                                       const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                    const int ma_shift=0,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14);
   int               AddNewEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                         const int ma_shift=0,
                                                                                         const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                         const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE,
                                                                                         const double deviation=0.1);
   int               AddNewForce(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13,
                                                                                     const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                     const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewFractals(const string symbol,const ENUM_TIMEFRAMES timeframe);
   int               AddNewFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                     const int ma_shift=0,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewGator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int jaw_period=13,
                                                                                     const int jaw_shift=8,
                                                                                     const int teeth_period=8,
                                                                                     const int teeth_shift=5,
                                                                                     const int lips_period=5,
                                                                                     const int lips_shift=3,
                                                                                     const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN);
   int               AddNewIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,const int tenkan_sen=9,
                                                                                        const int kijun_sen=26,
                                                                                        const int senkou_span_b=52);
   int               AddNewBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,const int mom_period=14,
                                                                                        const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                   const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=10,
                                                                                  const int ma_shift=0,
                                                                                  const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int fast_ema_period=12,
                                                                                    const int slow_ema_period=26,
                                                                                    const int signal_period=9,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,const int fast_ema_period=12,
                                                                                    const int slow_ema_period=26,
                                                                                    const int signal_period=9,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,const double step=0.02,
                                                                                   const double maximum=0.2);
   int               AddNewRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                   const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=10);
   int               AddNewStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=20,
                                                                                      const int ma_shift=0,
                                                                                      const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                      const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,const int Kperiod=5,
                                                                                          const int Dperiod=3,
                                                                                          const int slowing=3,
                                                                                          const ENUM_MA_METHOD ma_method=MODE_SMA,
                                                                                          const ENUM_STO_PRICE price_field=STO_LOWHIGH);
   int               AddNewTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                    const int ma_shif=0,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14,
                                                                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period=14);
   int               AddNewVIDyA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int cmo_period=9,
                                                                                     const int ema_period=12,
                                                                                     const int ma_shift=0,
                                                                                     const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);
   int               AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK);
   int               AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,       // path to the indicator (for example, "Examples\\MACD.ex5")
                                                                                      const string name,       // name of custom indicator (for example, "Custom MACD")
                                                                                      const uint   buffers,    // number of buffers
                                                                                      const MqlParam &param[]);// array of parameters
//--- Timer
   void OnTimer(void)
     {
      //--- In a loop through all indicators form the collection
      int total=this.m_list.Total();
      for(int i=0;i<total;i++)
        {
         //--- get a pointer to the next indicator object
         //--- and call its timer
         CIndMSTF *obj=this.m_list.At(i);
         if(obj!=NULL)
            obj.OnTimer();
        }
     }
//--- Constructor/destructor
                     CMSTFIndicators(void){ this.m_list.Clear(); }
                    ~CMSTFIndicators(void){;}
  };


Aplicación de los métodos para trabajar con los indicadores de la lista:

//+------------------------------------------------------------------+
//| Creates indicator calculation part for the passed object         |
//+------------------------------------------------------------------+
bool CMSTFIndicators::CreateIndicator(CIndMSTF *ind_obj)
  {
   //--- If the calculation part of the indicator could not be created
   if(!ind_obj.CreateIndicator())
     {
      //--- look for the index of the indicator object in the collection list
      //--- using the index, delete the indicator object from the collection list
      this.m_list.Sort();
      int index=this.m_list.Search(ind_obj);
      this.m_list.Delete(index);
      //--- Return false
      return false;
     }
//--- The calculation part has been successfully created - return true
   return true;
  }
//+------------------------------------------------------------------+
//| Returns indicator object by the calculation part handle          |
//+------------------------------------------------------------------+
CIndMSTF *CMSTFIndicators::GetIndicatorObj(const int ind_handle,const string source) const
  {
//--- If an invalid handle is passed to the method, report this and return NULL
   if(ind_handle==INVALID_HANDLE)
     {
      ::PrintFormat("%s: Error handle",source);
      .return NULL;
     }
//--- In a loop through all indicator objects in the collection list
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get a pointer to the next indicator object
      CIndMSTF *obj=this.m_list.At(i);
      if(obj==NULL)
         continue;
      //--- If the indicator handle is equal to that passed to the method -
      //--- return a pointer to the found indicator object
      if(obj.Handle()==ind_handle)
         return obj;
     }
//--- Nothing is found - return NULL
   .return NULL;
  }
//+------------------------------------------------------------------+
//| Populate buffers of the indicator at the handle                  |
//+------------------------------------------------------------------+
bool CMSTFIndicators::Calculate(const int ind_handle)
  {
   //--- Get a pointer to an indicator object using the handle
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
      return false;
   //--- Return the result of the Calculate method obtained from the handle of the indicator object
   return obj.Calculate();
  }
//+------------------------------------------------------------------+
//| Populate buffers of all indicators in the collection             |
//+------------------------------------------------------------------+
bool CMSTFIndicators::Calculate(void)
  {
   //--- Declare the variable for storing the result
   bool res=true;
//--- In a loop through all indicator objects in the collection list
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get a pointer to the next indicator object
      CIndMSTF *obj=this.m_list.At(i);
      if(obj==NULL)
         continue;
      //--- Add to the 'res' variable the result of calling the Calculate method of the next indicator object
      res &=obj.Calculate();
      //--- If the method worked with an error, inform that in the journal
      if(!res)
         ::PrintFormat("%s::%s: Error in indicator calculation: %s",__FUNCTION__,obj.Title(),TypeErrorcDescription(obj.TypeError()));
     }
//--- If the overall result is false, inform of that in the journal
   if(!res)
      ::PrintFormat("%s: Not all indicators have been calculated successfully. It is necessary to recalculate the buffers of all indicators",__FUNCTION__);
//--- Return the result of calling the Calculate methods of all indicators in the collection
   return res;
  }
//+------------------------------------------------------------------+
//| Returns data of the indicator at the handle                      |
//| from the specified buffers by index as is                        |
//+------------------------------------------------------------------+
double CMSTFIndicators::GetData(const int ind_handle,const uint buffer_num,const uint index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- Return data from the specified indicator buffer at the index passed to the method
   return obj.GetData(buffer_num,index);
  }
//+------------------------------------------------------------------+
//| Returns data of the indicator at the handle                      |
//| from the specified buffer at index for this symbol/timeframe     |
//+------------------------------------------------------------------+
double CMSTFIndicators::GetDataTo(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const uint index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- Return data from the specified indicator buffer at the index passed to the method
   return obj.GetDataTo(symbol_to,timeframe_to,buffer_num,index);
  }
//+------------------------------------------------------------------+
//| Fills the passed indicator buffer with data                      |
//+------------------------------------------------------------------+
bool CMSTFIndicators::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const int ind_handle,const uint buffer_num,const int limit,double &buffer[])
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return false;
     }
//--- Fill the buffer array passed to the method from the specified indicator buffer
   return obj.DataToBuffer(symbol_to,timeframe_to,buffer_num,limit,buffer);
  }
//+------------------------------------------------------------------+
//| Set the specified description for the buffer line                |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetPlotLabel(const uint plot_index,const string descript)
  {
   ::PlotIndexSetString(plot_index,PLOT_LABEL,descript);
  }
//+------------------------------------------------------------------+
//| Set default description for the buffer line                      |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetPlotLabelFromBuffer(const uint plot_index,const int ind_handle,const uint buffer_num)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set the description of the specified indicator buffer to the specified plotting buffer
   ::PlotIndexSetString(plot_index,PLOT_LABEL,obj.BufferDescription(buffer_num));
  }
//+------------------------------------------------------------------+
//| Set the shift for the specified plotting buffer                  |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetPlotShift(const uint plot_index,const int shift)
  {
   ::PlotIndexSetInteger(plot_index,PLOT_SHIFT,shift);
  }
//+------------------------------------------------------------------+
//| Return the description of the given buffer                       |
//| of the indicator specified by handle                             |
//+------------------------------------------------------------------+
string CMSTFIndicators::BufferDescription(const int ind_handle,const uint buffer_num)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If pointer to object received, return description of the specified buffer from it. Otherwise error text
   return(obj!=NULL ? obj.BufferDescription(buffer_num) : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Set the initializing value for the specified buffer              |
//| of the indicator specified by handle                             |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetBufferInitValue(const int ind_handle,const uint buffer_num,const double value)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set the specified initializing "empty" value for the specified buffer
   obj.SetBufferInitValue(buffer_num,value);
  }
//+------------------------------------------------------------------+
//| Return the initialization value of the specified buffer          |
//| of the indicator specified by handle                             |
//+------------------------------------------------------------------+
double CMSTFIndicators::BufferInitValue(const int ind_handle,const uint buffer_num) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the initializing "empty" value set for the specified buffer
   return obj.BufferInitValue(buffer_num);
  }
//+------------------------------------------------------------------+
//| Returns the line data state of the given buffer                  |
//| specified by indicator handle at the specified bar               |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineState(const int ind_handle,const uint buffer_num,const int index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Return the state of the line of the specified buffer at the specified index
   return obj.BufferLineState(buffer_num,index);
  }
//+------------------------------------------------------------------+
//| Returns the line data state of the given buffer                  |
//| specified by indicator handle at the symbol/timeframe bar        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineState(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const uint buffer_num,const int index)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Get the time of the passed to the method
   datetime array[1];
   if(::CopyTime(symbol,timeframe,index,1,array)!=1)
     {
      ::PrintFormat("%s::%s: Failed to get the time of the bar with index %ld. Error %lu",__FUNCTION__,obj.Title(),index,::GetLastError());
      return LINE_STATE_NONE;
     }
//--- Get the bar index in the indicator object buffer corresponding to the found time
   int bar=::iBarShift(obj.Symbol(),obj.Timeframe(),array[0]);
//--- If a bar is found, return the line state on the found bar, otherwise an undefined state
   return(bar!=WRONG_VALUE ? obj.BufferLineState(buffer_num,bar) : LINE_STATE_NONE);
  }
//+------------------------------------------------------------------+
//| Return the line data ratio for the given buffer                  |
//| specified by indicator handle at the specified bar               |
//| with specified valiues                                           |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineStateRelative(const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Return the ratio of the indicator line and the level in the specified buffer at the specified index
   return obj.BufferLineStateRelative(buffer_num,index,level0,level1);
  }
//+------------------------------------------------------------------+
//| Return the line data ratio for the given buffer                  |
//| specified by indicator handle at the specified bar               |
//| with the specified values on the specified chart symbol/period   |
//+------------------------------------------------------------------+
ENUM_LINE_STATE CMSTFIndicators::BufferLineStateRelative(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ind_handle,const int buffer_num,const int index,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return LINE_STATE_NONE;
     }
//--- Return the ratio of the indicator line and the level in the specified buffer at the specified index
   return obj.BufferLineStateRelative(symbol,timeframe,buffer_num,index,level0,level1);
  }
//+------------------------------------------------------------------+
//| Return category description                                      |
//+------------------------------------------------------------------+
string CMSTFIndicators::CategoryDescription(const int ind_handle)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the description of the category Otherwise error text
   return(obj!=NULL ? obj.CategoryDescription() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Set identifier                                                   |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetID(const int ind_handle,const int id)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set the identifier for the received object
   obj.SetID(id);
  }
//+------------------------------------------------------------------+
//| Set Digits of the indicator                                      |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetDigits(const int ind_handle,const int digits)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set Digits for the received object
   obj.SetDigits(digits);
  }
//+------------------------------------------------------------------+
//| Set a custom description                                         |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetDescription(const int ind_handle,const string descr)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set a description for the received object
   obj.SetDescription(descr);
  }
//+------------------------------------------------------------------+
//| Set a description of the specified buffer                        |
//+------------------------------------------------------------------+
void CMSTFIndicators::SetBufferDescription(const int ind_handle,const uint buffer_num,const string descr)
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return;
     }
//--- Set a description for the specified buffer of the received object
   obj.SetBufferDescription(buffer_num,descr);
  }
//+------------------------------------------------------------------+
//| Returns the timeseries flag of the given buffer                  |
//+------------------------------------------------------------------+
bool CMSTFIndicators::IsSeries(const int ind_handle,const uint buffer_num) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return false;
     }
//--- Return the timeseries flag of the specified buffer of the received object
   return obj.IsSeries(buffer_num);
  }
//+------------------------------------------------------------------+
//| Returns the synchronization flag for                             |
//| historical data for the symbol/period                            |
//+------------------------------------------------------------------+
bool CMSTFIndicators::IsSynchronized(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return false;
     }
//--- Return the synchronization flag of the received object
   return obj.IsSynchronized();
  }
//+------------------------------------------------------------------+
//| Return the timeframe of the specified indicator                  |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CMSTFIndicators::Timeframe(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the timeframe of the received object
   return obj.Timeframe();
  }
//+------------------------------------------------------------------+
//| Returns the symbol of the specified indicator                    |
//+------------------------------------------------------------------+
string CMSTFIndicators::Symbol(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the name of the symbol Otherwise error text
   return(obj!=NULL ? obj.Symbol() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the name of the specified indicator                       |
//+------------------------------------------------------------------+
string CMSTFIndicators::Name(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the name of the indicator Otherwise error text
   return(obj!=NULL ? obj.Name() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Returns a list of parameters of the specified indicator          |
//+------------------------------------------------------------------+
string CMSTFIndicators::Parameters(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return a list of indicator parameters Otherwise error text
   return(obj!=NULL ? obj.Parameters() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return Digits of the specified indicator                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::Digits(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return Digits of the received object
   return obj.Digits();
  }
//+------------------------------------------------------------------+
//| Return the number of buffers of the specified indicator          |
//+------------------------------------------------------------------+
uint CMSTFIndicators::BuffersTotal(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return 0;
     }
//--- Return the number of buffers of the received object
   return obj.BuffersTotal();
  }
//+------------------------------------------------------------------+
//| Return the number of timeseries bars for specified the indicator |
//+------------------------------------------------------------------+
uint CMSTFIndicators::RatesTotal(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return 0;
     }
//--- Return the number of bars in the timeseries of the received object
   return obj.RatesTotal();
  }
//+------------------------------------------------------------------+
//| Return the identifier of the specified indicator                 |
//+------------------------------------------------------------------+
int CMSTFIndicators::ID(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the identifier of the received object
   return obj.ID();
  }
//+------------------------------------------------------------------+
//| Return a description of the specified indicator                  |
//+------------------------------------------------------------------+
string CMSTFIndicators::Description(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the indicator description Otherwise error text
   return(obj!=NULL ? obj.Description() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the title of the specified indicator                      |
//+------------------------------------------------------------------+
string CMSTFIndicators::Title(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return the indicator title Otherwise error text
   return(obj!=NULL ? obj.Title() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the category of the specified indicator                   |
//+------------------------------------------------------------------+
ENUM_IND_CATEGORY CMSTFIndicators::Category(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return IND_CATEGORY_NONE;
     }
//--- Return the category of the received object
   return obj.Category();
  }
//+------------------------------------------------------------------+
//| Return the number of parameters of the specified indicator       |
//+------------------------------------------------------------------+
uint CMSTFIndicators::ParamsTotal(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return 0;
     }
//--- Return the number of parameters of the received object
   return obj.ParamsTotal();
  }
//+------------------------------------------------------------------+
//| Return the structure of parameters by index from the array       |
//| for the specified indicator                                      |
//+------------------------------------------------------------------+
MqlParam CMSTFIndicators::GetMqlParam(const int ind_handle,const int index) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      MqlParam null;
      ::ZeroMemory(null);
      return null;
     }
//--- Return the structure of parameters of the received object by index from the array of parameters
   return obj.GetMqlParam(index);
  }
//+------------------------------------------------------------------+
//| Return a timeframe description for the specified indicator       |
//+------------------------------------------------------------------+
string CMSTFIndicators::TimeframeDescription(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
//--- If the object is received, return a description of the indicator timeframe Otherwise error text
   return(obj!=NULL ? obj.Description() : ::StringFormat("%s: Failed to get indicator object",__FUNCTION__));
  }
//+------------------------------------------------------------------+
//| Return the amount of calculated data of the specified indicator  |
//+------------------------------------------------------------------+
int CMSTFIndicators::Calculated(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return WRONG_VALUE;
     }
//--- Return the amount of calculated data of the received object
   return obj.Calculated();
  }
//+------------------------------------------------------------------+
//| Return the type of the specified indicator                       |
//+------------------------------------------------------------------+
ENUM_INDICATOR CMSTFIndicators::Type(const int ind_handle) const
  {
//--- Get a pointer to the indicator object using the handle passed to the method
   CIndMSTF *obj=this.GetIndicatorObj(ind_handle,__FUNCTION__);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Failed to get indicator object",__FUNCTION__);
      return (ENUM_INDICATOR)WRONG_VALUE;
     }
//--- Return the indicator type of the received object
   return (ENUM_INDICATOR)obj.Type();
  }

La lógica de todos los métodos se comenta en el listado de métodos. Primero obtendremos un puntero al indicador requerido en la lista, luego estableceremos o retornaremos su propiedad, o realizaremos un cálculo y devolveremos su resultado. Todos los métodos llamados se han analizado anteriormente al discutir la clase básica del indicador de símbolo y periodo múltiple.

Implementación de los métodos para crear nuevos objetos de indicador:

//+------------------------------------------------------------------+
//| Add the specified indicator to the collection                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewIndicator(CIndMSTF *ind_obj,const string source)
  {
//--- Set the sorted list flag to the collection list
   this.m_list.Sort();
//--- Search the list for an index matching the indicator object passed to the method
   int index=this.m_list.Search(ind_obj);
//--- If such an indicator with the same parameters is already in the list,
   if(index>WRONG_VALUE)
     {
      //--- report this to journal and delete the new indicator object
      ::PrintFormat("%s: The %s indicator with such parameters %s is already in the collection",source,ind_obj.Name(),ind_obj.Parameters());
      delete ind_obj;
      //--- Get a pointer to an already existing indicator object in the list and return its handle
      ind_obj=this.m_list.At(index);
      return(ind_obj!=NULL ? ind_obj.Handle() : INVALID_HANDLE);
     }
//--- If such an indicator is not in the list, but it could not be placed in the list
   if(!this.m_list.Add(ind_obj))
     {
      //--- report the error in the journal, delete the indicator object and return INVALID_HANDLE
      ::PrintFormat("%s: Error. Failed to add %s indicator to collection",source,ind_obj.Name());
      delete ind_obj;
      return INVALID_HANDLE;
     }
//--- If indicator is placed in list, but creating a calculation part for it failed, return INVALID_HANDLE
//--- (if there is an error creating a calculation part, the indicator object is deleted in the CreateIndicator method)
   if(!this.CreateIndicator(ind_obj))
      return INVALID_HANDLE;
//--- Successful - inform about addition of a new indicator to collection and return its handle
   ::PrintFormat("%s: %s indicator (handle %ld) added to the collection",source,ind_obj.Title(),ind_obj.Handle());
   return ind_obj.Handle();
  }
//+------------------------------------------------------------------+
//| Add the Accelerator Oscillator indicator to the collection       |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAC(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAC *ind_obj=new CIndAC(symbol,timeframe);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create AC indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Accumulation/Distribution indicator to the collection     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAD *ind_obj=new CIndAD(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create A/D indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+--------------------------------------------------------------------+
//| Add Average Directional Movement Index to the collection           |
//+--------------------------------------------------------------------+
int CMSTFIndicators::AddNewADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndADX *ind_obj=new CIndADX(symbol,timeframe,adx_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create ADX indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add to the collection the indicator                              |
//| Average Directional Movement Index by Welles Wilder              |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndADXW *ind_obj=new CIndADXW(symbol,timeframe,adx_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create ADX Wilder indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Alligator indicator to the collection                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int jaw_period=13,
                                      const int jaw_shift=8,
                                      const int teeth_period=8,
                                      const int teeth_shift=5,
                                      const int lips_period=5,
                                      const int lips_shift=3,
                                      const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                      const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAlligator *ind_obj=new CIndAlligator(symbol,timeframe,jaw_period,jaw_shift,teeth_period,teeth_shift,lips_period,lips_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Alligator indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Adaptive Moving Average indicator to the collection      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ama_period=9,
                                const int fast_ma_period=2,
                                const int slow_ma_period=30,
                                const int ama_shift=0,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAMA *ind_obj=new CIndAMA(symbol,timeframe,ama_period,fast_ma_period,slow_ma_period,ama_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create AMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Awesome Oscillator indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewAO(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndAO *ind_obj=new CIndAO(symbol,timeframe);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create AO indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Average True Range indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndATR *ind_obj=new CIndATR(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create ATR indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Bears Power indicator to the collection                  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBears *ind_obj=new CIndBears(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Bears indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Bulls Power indicator to the collection                  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=13)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBulls *ind_obj=new CIndBulls(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Bulls indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Bollinger Bands® indicator to the collection             |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int bands_period=20,
                                  const int bands_shift=0,
                                  const double deviation=2.0,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBands *ind_obj=new CIndBands(symbol,timeframe,bands_period,bands_shift,deviation,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Bands indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Commodity Channel Index indicator to the collection      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=14,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCCI *ind_obj=new CIndCCI(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create CCI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Chaikin Oscillator indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const int fast_ma_period=3,
                                    const int slow_ma_period=10,
                                    const ENUM_MA_METHOD ma_method=MODE_EMA,
                                    const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCHO *ind_obj=new CIndCHO(symbol,timeframe,fast_ma_period,slow_ma_period,ma_method,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Chaikin indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+-------------------------------------------------------------------+
//| Add Double Exponential Moving Average to the collection           |
//+-------------------------------------------------------------------+
int CMSTFIndicators::AddNewDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ma_period=14,
                                const int ma_shift=0,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndDEMA *ind_obj=new CIndDEMA(symbol,timeframe,ma_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create DEMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the DeMarker indicator to the collection                     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndDeM *ind_obj=new CIndDeM(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create DeMarker indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Envelopes indicator to the collection                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int ma_period=14,
                                      const int ma_shift=0,
                                      const ENUM_MA_METHOD ma_method=MODE_SMA,
                                      const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE,
                                      const double deviation=0.1)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndEnvelopes *ind_obj=new CIndEnvelopes(symbol,timeframe,ma_method,ma_shift,ma_method,applied_price,deviation);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Envelopes indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Force Index indicator to the collection                  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int ma_period=13,
                                  const ENUM_MA_METHOD ma_method=MODE_SMA,
                                  const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndForce *ind_obj=new CIndForce(symbol,timeframe,ma_period,ma_method,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Force indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Fractals indicator to the collection                     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewFractals(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndFractals *ind_obj=new CIndFractals(symbol,timeframe);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Fractals indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Fractal Adaptive Moving Average indicator to collection  |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                 const int ma_period=14,
                                 const int ma_shift=0,
                                 const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndFrAMA *ind_obj=new CIndFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create FrAMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Gator indicator to the collection                        |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int jaw_period=13,
                                  const int jaw_shift=8,
                                  const int teeth_period=8,
                                  const int teeth_shift=5,
                                  const int lips_period=5,
                                  const int lips_shift=3,
                                  const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndGator *ind_obj=new CIndGator(symbol,timeframe,jaw_period,jaw_shift,teeth_period,teeth_shift,lips_period,lips_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Gator indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Ichimoku Kinko Hyo indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const int tenkan_sen=9,
                                    const int kijun_sen=26,
                                    const int senkou_span_b=52)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndIchimoku *ind_obj=new CIndIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Ichimoku indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Market Facilitation Index indicator to the collection    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndBWMFI *ind_obj=new CIndBWMFI(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create BW MFI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Momentum indicator to the collection                     |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    const int mom_period=14,
                                    const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMomentum *ind_obj=new CIndMomentum(symbol,timeframe,mom_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Momentum indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Money Flow Index indicator to the collection             |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=14,
                               const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMFI *ind_obj=new CIndMFI(symbol,timeframe,ma_period,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create MFI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Moving Average indicator to the collection               |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=10,
                               const int ma_shift=0,
                               const ENUM_MA_METHOD ma_method=MODE_SMA,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMA *ind_obj=new CIndMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create MA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Moving Average of Oscillator indicator to the collection |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int fast_ema_period=12,
                                const int slow_ema_period=26,
                                const int signal_period=9,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndOsMA *ind_obj=new CIndOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create OsMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add to the collection the indicator                              |
//| Moving Averages Convergence/Divergence                           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int fast_ema_period=12,
                                const int slow_ema_period=26,
                                const int signal_period=9,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndMACD *ind_obj=new CIndMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create MACD indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the On Balance Volume indicator to the collection            |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndOBV *ind_obj=new CIndOBV(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create OBV indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+-------------------------------------------------------------------+
//| Add Parabolic Stop and Reverse system to the collection           |
//+-------------------------------------------------------------------+
int CMSTFIndicators::AddNewSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const double step=0.02,
                               const double maximum=0.2)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndSAR *ind_obj=new CIndSAR(symbol,timeframe,step,maximum);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create SAR indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Relative Strength Index indicator to the collection      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                               const int ma_period=14,
                               const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndRSI *ind_obj=new CIndRSI(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create RSI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Relative Vigor Index indicator to the collection         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period=10)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndRVI *ind_obj=new CIndRVI(symbol,timeframe,ma_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create RVI indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Standard Deviation indicator to the collection           |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int ma_period=20,
                                  const int ma_shift=0,
                                  const ENUM_MA_METHOD ma_method=MODE_SMA,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndStdDev *ind_obj=new CIndStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create StdDev indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Stochastic Oscillator indicator to the collection        |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int Kperiod=5,
                                      const int Dperiod=3,
                                      const int slowing=3,
                                      const ENUM_MA_METHOD ma_method=MODE_SMA,
                                      const ENUM_STO_PRICE price_field=STO_LOWHIGH)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndStoch *ind_obj=new CIndStoch(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Stochastic indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+-------------------------------------------------------------------+
//| Add Triple Exponential Moving Average indicator to the collection |
//+-------------------------------------------------------------------+
int CMSTFIndicators::AddNewTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ma_period=14,
                                const int ma_shift=0,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndTEMA *ind_obj=new CIndTEMA(symbol,timeframe,ma_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create TEMA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
  
//+------------------------------------------------------------------+
//| Add to the collection the indicator                              |
//| Triple Exponential Moving Averages Oscillator                    |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                const int ma_period=14,
                                const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndTriX *ind_obj=new CIndTriX(symbol,timeframe,ma_period,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create TriX indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add Larry Williams' Percent Range to the collection              |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period=14)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndWPR *ind_obj=new CIndWPR(symbol,timeframe,calc_period);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create WPR indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add Variable Index Dynamic Average to the collection             |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewVIDyA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                  const int cmo_period=9,
                                  const int ema_period=12,
                                  const int ma_shift=0,
                                  const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndVIDyA *ind_obj=new CIndVIDyA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create VIDyA indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add the Volumes indicator to the collection                      |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK)
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndVolumes *ind_obj=new CIndVolumes(symbol,timeframe,applied_volume);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create Volumes indicator object",__FUNCTION__);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Add a custom indicator to the collection                         |
//+------------------------------------------------------------------+
int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const MqlParam &param[])
  {
//--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE
   CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,param);
   if(ind_obj==NULL)
     {
      ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name);
      return INVALID_HANDLE;
     }
//--- Return the result of adding the created indicator object to the collection list
   return this.AddNewIndicator(ind_obj,__FUNCTION__);
  }

Todos los códigos de la clase de colección de indicadores están listos. Su lógica se comentará en cada método, y su análisis no debería resultar difícil.


Simulación

Para probar el trabajo con clases de indicador múltiple y su colección, crearemos un indicador sencillo en el que organizaremos la selección de varias medias móviles. El propósito de las pruebas de hoy es comprobar el funcionamiento de las clases con indicadores que dibujan una línea desde un búfer en la ventana principal del gráfico. Para este fin resultarán adecuadas las medias móviles del conjunto de indicadores de tendencia del terminal de cliente. En los siguientes artículos implementaremos plantillas para crear de forma rápida cualquier indicador del conjunto de indicadores en el terminal y trabajar con ellos, además de trabajar con indicadores personalizados. Y poco a poco iremos ultimando las clases creadas hoy para que funcionen correctamente con cualquier tipo de indicadores.

El indicador de prueba mostrará las líneas de los indicadores múltiples creados en el gráfico, mientras que sus datos se mostrarán en el panel de información cuya creación ya describimos en el primer artículo del ciclo de trabajo con indicadores.

Dentro de un indicador crearemos dos indicadores idénticos. Uno se calculará a partir de los datos del gráfico actual y el otro se calculará partiendo de los datos del gráfico de los símbolos/periodos seleccionados en los ajustes. De este modo, siempre podremos ver la línea de la media móvil en el gráfico actual y la línea de la misma media móvil pero trazada en un periodo del gráfico diferente. El símbolo también podrá seleccionarse de otro gráfico, pero entonces las líneas no coincidirán con el nivel de precios.

Ahora crearemos un nuevo archivo de indicador con el nombre TestMSTFMovingAverages.mq5:




Marcaremos los manejadores OnTimer y OnChartEvent:


Y seleccionaremos los dos búferes que se dibujarán como líneas en la ventana principal del gráfico:


El nombre de los búferes pueden ser cualquiera: os renombraremos en el código. Luego pulsaremos «hecho» y obtendremos la plantilla del indicador:

//+------------------------------------------------------------------+
//|                                       TestMSTFMovingAverages.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
//--- plot MA1
#property indicator_label1  "MA1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot MA2
#property indicator_label2  "MA2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- indicator buffers
double         MA1Buffer[];
double         MA2Buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,MA1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,MA2Buffer,INDICATOR_DATA);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   
//--- return value of prev_calculated for the next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }

En el área global, añadiremos una enumeración para seleccionar el tipo de media móvil, conectaremos el archivo de clase del indicador de símbolo/periodo múltiple y el archivo de clase del panel, y declararemos las variables de entrada.
Después cambiaremos el nombre de los búferes
por un nombre más legible y declararemos variables indicadoras globales:

//+------------------------------------------------------------------+
//|                                       TestMSTFMovingAverages.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2
//--- enums
enum ENUM_USED_MA
  {
   USED_MA_AMA    =  IND_AMA,    // Adaptive Moving Average
   USED_MA_DEMA   =  IND_DEMA,   // Double Exponential Moving Average
   USED_MA_FRAMA  =  IND_FRAMA,  // Fractal Adaptive Moving Average
   USED_MA_MA     =  IND_MA,     // Moving Average
   USED_MA_TEMA   =  IND_TEMA,   // Triple Exponential Moving Average
   USED_MA_VIDYA  =  IND_VIDYA,  // Variable Index Dynamic Average
  };
//--- plot MA1
#property indicator_label1  "MA1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- plot MA2
#property indicator_label2  "MA2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//--- includes
#include <IndMSTF\IndMSTF.mqh>
#include <Dashboard\Dashboard.mqh>
//--- input parameters
input ENUM_USED_MA         InpIndicator   =  USED_MA_MA;       /* Used MA        */ // Type of moving average to use
input string               InpSymbol      =  NULL;             /* Symbol         */ // Moving average symbol
input ENUM_TIMEFRAMES      InpTimeframe   =  PERIOD_CURRENT;   /* Timeframe      */ // Moving average timeframe
input ENUM_APPLIED_PRICE   InpPrice       =  PRICE_CLOSE;      /* Applied Price  */ // Price used for MA calculation
input ENUM_MA_METHOD       InpMethod      =  MODE_SMA;         /* MA Method      */ // Moving Average calculation method
input int                  InpShift       =  0;                /* MA Shift       */ // Moving average shift
input bool                 InpAsSeries    =  true;             /* As Series flag */ // Timeseries flag of indicator buffer arrays

//--- indicator buffers
double         BufferMA1[];
double         BufferMA2[];
//--- global variables
int handle_ma1;
int handle_ma2;
CMSTFIndicators indicators;      // An instance of the indicator collection object
//--- variables for the panel
CDashboard *panel=NULL;          // Pointer to the panel object
int         mouse_bar_index;     // Index of the bar the data is taken from

Dentro del manejador OnInit() escribiremos la creación del temporizador, asignaremos los búferes a dibujar, crearemos los manejadores de los indicadores seleccionados y crearemos el panel informativo:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set a timer with an interval of 1 second
   EventSetTimer(1);
//--- Assign the BufferMA1 and BufferMA2 arrays to the plot buffers 0 and 1, respectively
   SetIndexBuffer(0,BufferMA1,INDICATOR_DATA);
   SetIndexBuffer(1,BufferMA2,INDICATOR_DATA);
//--- sets indicator shift
   //PlotIndexSetInteger(0,PLOT_SHIFT,InpShift);   // analog in line 116
   //PlotIndexSetInteger(1,PLOT_SHIFT,InpShift);   // analog in line 117
//--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference)
   ArraySetAsSeries(BufferMA1,InpAsSeries);
   ArraySetAsSeries(BufferMA2,InpAsSeries);
   
//--- For different indicators, the dashboard width will be individual (due to the number of parameters in the description)
   int width=0;
//--- According on the indicator selected in the settings, create two indicators of the same type
//--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings
   switch(InpIndicator)
     {
      case USED_MA_AMA     :
         handle_ma1=indicators.AddNewAMA(NULL,PERIOD_CURRENT,9,2,30,InpShift);
         handle_ma2=indicators.AddNewAMA(InpSymbol,InpTimeframe,9,2,30,InpShift);
         width=269;
        break;
      case USED_MA_DEMA    :
         handle_ma1=indicators.AddNewDEMA(NULL,PERIOD_CURRENT,14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewDEMA(InpSymbol,InpTimeframe,14,InpShift,InpPrice);
         width=255;
        break;
      case USED_MA_FRAMA   :
         handle_ma1=indicators.AddNewFrAMA(NULL,PERIOD_CURRENT,14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewFrAMA(InpSymbol,InpTimeframe,14,InpShift,InpPrice);
         width=259;
        break;
      case USED_MA_TEMA    :
         handle_ma1=indicators.AddNewTEMA(NULL,PERIOD_CURRENT,14,InpShift,InpPrice);
         handle_ma2=indicators.AddNewTEMA(InpSymbol,InpTimeframe,14,InpShift,InpPrice);
         width=253;
        break;
      case USED_MA_VIDYA   :
         handle_ma1=indicators.AddNewVIDyA(NULL,PERIOD_CURRENT,9,12,InpShift,InpPrice);
         handle_ma2=indicators.AddNewVIDyA(InpSymbol,InpTimeframe,9,12,InpShift,InpPrice);
         width=267;
        break;
      default:
         handle_ma1=indicators.AddNewMA(NULL,PERIOD_CURRENT,10,InpShift,InpMethod,InpPrice);
         handle_ma2=indicators.AddNewMA(InpSymbol,InpTimeframe,10,InpShift,InpMethod,InpPrice);
         width=231;
        break;
     }
//--- If failed to create indicator handles, return initialization error
   if(handle_ma1==INVALID_HANDLE || handle_ma2==INVALID_HANDLE)
      return INIT_FAILED;
//--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators
   indicators.SetPlotLabelFromBuffer(0,handle_ma1,0);
   indicators.SetPlotLabelFromBuffer(1,handle_ma2,0);
//--- Set "empty" values for calculation part buffers of the created indicators
   indicators.SetBufferInitValue(handle_ma1,0,EMPTY_VALUE);
   indicators.SetBufferInitValue(handle_ma2,0,EMPTY_VALUE);
//--- Set shifts for indicator lines
   indicators.SetPlotShift(0,InpShift);
   indicators.SetPlotShift(1,InpShift);
      
//--- Dashboard
//--- Create the panel
   panel=new CDashboard(1,20,20,width,264);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Set font parameters
   panel.SetFontParams("Calibri",9);
//--- Display the panel with the "Symbol, Timeframe description" header text
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));

//--- Create a table with ID 0 to display bar data in it
   panel.CreateNewTable(0);
//--- Draw a table with ID 0 on the panel background
   panel.DrawGrid(0,2,20,6,2,18,width/2-2);

//--- Create a table with ID 1 to display the data of indicator 1
   panel.CreateNewTable(1);
//--- Get the Y2 table coordinate with ID 0 and
//--- set the Y1 coordinate for the table with ID 1
   int y1=panel.TableY2(0)+22;
//--- Draw a table with ID 1 on the panel background
   panel.DrawGrid(1,2,y1,2,2,18,width/2-2);

//--- Create a table with ID 2 to display the data of indicator 2
   panel.CreateNewTable(2);
//--- Get the Y2 coordinate of the table with ID 1 and
//--- set the Y1 coordinate for the table with ID 2
   int y2=panel.TableY2(1)+3;
//--- Draw a table with ID 2 on the background of the dashboard
   panel.DrawGrid(2,2,y2,3,2,18,width/2-2);
   
//--- Initialize the variable with the index of the mouse cursor bar
   mouse_bar_index=0;
//--- Display the data of the current bar on the panel
   DrawData(mouse_bar_index,TimeCurrent());

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

Luego añadiremos el manejador OnDeinit():

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Delete the timer
   EventKillTimer();
//--- If the panel object exists, delete it
   if(panel!=NULL)
      delete panel;
//--- Delete all comments
   Comment("");
  }

En el manejador OnCalculate(), llamaremos al cálculo de todos los indicadores de símbolo/periodo múltiple. Si el cálculo falla, deberemos salir del manejador con un retorno de cero, para recalcular los indicadores de nuevo en el siguiente tick.
Tras realizar el cálculo correcto, los datos de las arrays de búferes de los objetos de indicador ya estarán presentes: ya podremos mostrarlos en el panel informativo. Después de enviar los datos al panel, enviaremos los datos de los búferes calculados a los búferes dibujados del indicador:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Number of bars for calculation
   int limit=rates_total-prev_calculated;
//--- If limit > 1, then this is the first calculation or change in the history
   if(limit>1)
     {
      //--- specify all the available history for calculation
      limit=rates_total-1;
      /*
      // If the indicator has any buffers that display other calculations (not multi-indicators),
      // initialize them here with the "empty" value set for these buffers
      */
     }
//--- Calculate all created multi-symbol multi-period indicators
   if(!indicators.Calculate())
      return 0;

//--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard
   DrawData(mouse_bar_index,time[mouse_bar_index]);

//--- From buffers of calculated indicators, output data to indicator buffers
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma1,0,limit,BufferMA1))
      return 0;
   if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_ma2,0,limit,BufferMA2))
      return 0;

//--- return value of prev_calculated for the next call
   return(rates_total);
  }

En el temporizador del indicador, llamaremos al temporizador del objeto de la colección de indicadores:

//+------------------------------------------------------------------+
//| Timer function                                                   | 
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- Call the indicator collection timer
   indicators.OnTimer();
  }

En el manejador de eventos del gráfico OnChartEvent(), llamaremos al manejador de eventos del objeto de panel y procesaremos el movimiento del cursor para determinar la barra sobre la que se encuentra:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Handling the panel
//--- Call the panel event handler
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- If the cursor moves or a click is made on the chart
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Declare the variables to record time and price coordinates in them
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- If the cursor coordinates are converted to date and time
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- write the bar index where the cursor is located to a global variable
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Display the bar data under the cursor on the panel 
         DrawData(mouse_bar_index,time);
        }
     }

//--- If we received a custom event, display the appropriate message in the journal
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Here we can implement handling a click on the close button on the panel
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

Función que envía los datos de los indicadores múltiples al panel:

//+------------------------------------------------------------------+
//| Display data from the specified timeseries index to the panel    |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Declare the variables to receive data in them
   MqlRates rates[1];

//--- Exit if unable to get the bar data by the specified index
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Set font parameters for bar and indicator data headers
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Set font parameters for bar and indicator data
   panel.SetFontParams(name,9);

//--- Display the data of the specified bar in table 0 on the panel
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Output the data of indicator 1 from the specified bar into table 1
   panel.DrawText(indicators.Title(handle_ma1), panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value1=indicators.GetData(handle_ma1,0,index);
   string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,indicators.Digits(handle_ma1)) : " ");
   panel.DrawText(value_str1,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110);
   
//--- Display a description of the indicator 1 line state
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state1=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma1,0,index);
   panel.DrawText(BufferLineStateDescription(state1),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110);
   
//--- Output the data of indicator 2 from the specified bar into table 2
   panel.DrawText(indicators.Title(handle_ma2), panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2);
   double value2=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,0,index);
   string value_str2=(value2!=EMPTY_VALUE ? DoubleToString(value2,indicators.Digits(handle_ma2)) : " ");
   panel.DrawText(value_str2,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110);
   
//--- Display a description of the indicator 2 line state
   panel.DrawText("Line state", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2);
   ENUM_LINE_STATE state2=indicators.BufferLineState(Symbol(),PERIOD_CURRENT,handle_ma2,0,index);
   panel.DrawText(BufferLineStateDescription(state2),panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110);
   
//--- Display description of relationship between indicator 1 line relative to indicator 2 line
   double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_ma2,0,index+1);
   ENUM_LINE_STATE stateR=indicators.BufferLineStateRelative(Symbol(),PERIOD_CURRENT,handle_ma1,0,index,value2,value21);
   string ma1=indicators.Name(handle_ma1);
   string ma2=indicators.Name(handle_ma2);
   string state_relative=
     (
      stateR==LINE_STATE_ABOVE      ? StringFormat("%s1 > %s2",ma1,ma2)   :
      stateR==LINE_STATE_BELOW      ? StringFormat("%s1 < %s2",ma1,ma2)           :
      stateR==LINE_STATE_CROSS_DOWN ? "Top-down crossing"   :
      stateR==LINE_STATE_CROSS_UP   ? "Bottom-up crossing"  :
      BufferLineStateDescription(stateR)
     );
   panel.DrawText(StringFormat("%s1 vs %s2",ma1,ma2), panel.CellX(2,2,0)+2, panel.CellY(2,2,0)+2);
   panel.DrawText(state_relative, panel.CellX(2,2,1)+2, panel.CellY(2,2,1)+2,clrNONE,110);
   
//--- Redraw the chart to immediately display all changes on the panel
   ChartRedraw(ChartID());
  }

Como podemos ver, para calcular los indicadores de símbolo/periodo múltiple, bastará con llamar al método Calculate() de la clase de colección de indicadores. Tras realizar el cálculo correcto, todos los datos estarán ya disponibles. Ya podremos recibirlos y procesarlos en los asesores. En los indicadores, tras el cálculo exitoso, los datos podrán mostrarse en el gráfico como líneas de búferes que se dibujan usando el método DataToBuffer() de la clase de colección de indicadores. Y eso es todo lo que necesitaremos para calcular y mostrar indicadores múltiples en un gráfico.

Después de compilar el indicador de prueba, lo ejecutaremos en un gráfico con el periodo M1, en los ajustes, seleccionaremos el símbolo actual y un periodo de cálculo del indicador M5. En este caso, se crearán los dos indicadores de media móvil seleccionados en los ajustes. Uno se calculará a partir de los datos del gráfico actual y el otro a partir de los datos del periodo de cinco minutos del gráfico. Cambiando el marco temporal del gráfico podremos ver cómo se dibujan las dos líneas en M1: una se corresponderá con la media móvil calculada en M1, mientras que la segunda se corresponderá con la media móvil calculada en M5. Si cambiamos el gráfico a M5, solo se creará un indicador, ya que el segundo será idéntico al primero y no se creará. Si cambiamos el gráfico a M15, un indicador se calculará para M15 y el segundo indicador se calculará para M5, y también se mostrará en el gráfico.


Como podemos ver, la funcionalidad declarada funciona cuando los indicadores con un búfer se muestran en la ventana principal del gráfico.

Todos los archivos de todas las clases y el indicador de prueba se pueden ver en los archivos adjuntos al artículo.


Conclusión

Hoy hemos implementado una funcionalidad que permite crear de forma rápida indicadores de símbolo/periodo múltiple y obtener sus datos en los indicadores con la representación de los indicadores calculados en la ventana del gráfico principal. En los próximos artículos implementaremos plantillas para crear otros indicadores estándar de símbolo y periodo múltiple, dibujando sus datos en la ventana principal del gráfico, y no solo con un búfer dibujado. Como resultado, dispondremos de una práctica herramienta para convertir rápidamente cualquier indicador en su propia versión de símbolo/periodo múltiple. Y, muy probablemente, las clases de indicadores múltiples se seguirán desarrollando al crear otros indicadores estándar y personalizados.


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

Archivos adjuntos |
Dashboard.mqh (217.85 KB)
IndMSTF.mqh (473.51 KB)
Permutación de las barras de precio en MQL5 Permutación de las barras de precio en MQL5
En este artículo, presentaremos un algoritmo de permutación de barras de precio y detallaremos cómo se pueden utilizar las pruebas de permutación para identificar los casos en los que se ha fabricado el rendimiento de la estrategia para engañar a los posibles compradores del asesor.
Añadimos un LLM personalizado a un robot comercial (Parte 1): Desplegando el equipo y el entorno Añadimos un LLM personalizado a un robot comercial (Parte 1): Desplegando el equipo y el entorno
Los modelos lingüísticos (LLM) son una parte importante de la inteligencia artificial que evoluciona rápidamente, por lo que debemos plantearnos cómo integrar unos LLM potentes en nuestro comercio algorítmico. A la mayoría de la gente le resulta difícil personalizar estos potentes modelos para adaptarlos a sus necesidades, implantarlos de forma local y luego aplicarlos al trading algorítmico. En esta serie de artículos abordaremos un enfoque paso a paso para lograr este objetivo.
Añadimos un LLM personalizado a un robot comercial (Parte 2): Ejemplo de despliegue del entorno Añadimos un LLM personalizado a un robot comercial (Parte 2): Ejemplo de despliegue del entorno
Los modelos lingüísticos (LLM) son una parte importante de la inteligencia artificial que evoluciona rápidamente, por lo que debemos plantearnos cómo integrar unos LLM potentes en nuestro comercio algorítmico. A la mayoría de la gente le resulta difícil adaptar estos modelos a sus necesidades, implantarlos de forma local y luego aplicarlos al trading algorítmico. En esta serie de artículos abordaremos un enfoque paso a paso para lograr este objetivo.
La estacionalidad en el mercado de divisas y oportunidades para aprovecharla La estacionalidad en el mercado de divisas y oportunidades para aprovecharla
Toda persona moderna está familiarizada con el concepto de estacionalidad, por ejemplo, todos estamos acostumbrados al aumento del precio de las verduras frescas en invierno o a la subida del precio del combustible durante las heladas severas, pero pocas personas saben que existen patrones similares en el mercado de divisas.