Promediación de series de precio para cálculos intermedios sin usar buffers adicionales

Nikolay Kositsin | 21 enero, 2014


Introducción

En mi artículo "Principios del cálculo económico de indicadores" realicé pruebas razonablemente convincentes que demuestran el hecho de que no todas las llamadas de un indicador técnico o personalizado en un código representan la forma más óptima de realizar cálculos intermedios en un indicador.

La velocidad final de ejecución puede que parezca mucho más baja si la comparamos con la que tendríamos si hubiéramos situado el código para los cálculos intermedios en nuestro indicador.

Este tipo de enfoque a la hora de escribir código sería muy atractivo si fuera lo suficientemente simple. De hecho, se complica seriamente el código al describir buffers adicionales usados para almacenar resultados intermedios de cálculo.

A pesar de la variedad de cálculos intermedios, los más necesarios son los distintos algoritmos de promediación. En la mayoría de los casos podemos usar funciones simples y universales que simplifican en gran medida la tarea de escribir dicho código. En este artículo describiremos el proceso de creación de estas funciones y el trabajo con ellas.


1. Idea general de funciones de promediación que trabajan con una barra

El enfoque clásico de la promediación sobre una barra actual consiste en un buffer de indicador intermedio que llenaremos con la información requerida y a continuación seleccionaremos un rango de valores previos igual al periodo de promediación, calculando finalmente el valor promedio.

El procedimiento para procesar esta selección es el siguiente:

SmoothVar(bar) = Function(Var(bar - (n-1)), Var(bar - (n-2)), ......... Var(bar)) 

donde:

Dicho enfoque sobre la promediación lleva, en nuestro caso, a la aparición de dos ciclos de cálculo. En el primer ciclo los datos se calculan y se ponen en un buffer intermedio. En el segundo ciclo, la promediación usando otro ciclo de búsqueda adicional de celdas del buffer del indicador se realiza sobre la base de la fórmula sugerida anteriormente. Este cálculo será mucho más sencillo si acumulamos la selección de los datos intermedios en la propia función. En este caso la función de promediación será la siguiente:

SmoothVar(bar) = Function(Var(bar), bar)

Se escribe un nuevo valor Var(bar) para la selección de los valores dentro de la función en una barra actual, y los valores de Var(bar-n) que se hacen innecesarios se borran de la selección. Con este enfoque, el código de promediación parece muy trivial y no requiere buffers de indicador adicionales. Dentro de la función, la matriz almacena la cantidad exacta de datos necesarios para el cálculo de una barra, no de todo el historial de datos.

En este caso, hay solo un ciclo de cálculo de datos. Debe recordarse que para llamar a esta función de promediación en una barra actual, ¡debemos llamarla en todas las barras anteriores primero!


2. Promediación clásica como ejemplo de la implementación de una función que trabaja con una barra

Tales funciones de promediación deben contener variables que no deben perder sus valores entre las llamadas de esas funciones. Además, la promediación de tipo único con diferentes parámetros puede usarse en un código para varias ocasiones. Por tanto, para evitar el conflicto de usar recursos de memoria compartidos debemos implementar dichas funciones como clases, y eso es lo que hemos hecho.

Los algoritmos de la promediación clásica se describen en la clase CMoving_Average:

class CMoving_Average : public CMovSeriesTools
  {
public:  
   double    MASeries  (uint begin,               // Index of start of reliable bars
                       uint prev_calculated,      // Amount of history in bars at a previous tick
                       uint rates_total,          // Amount of history in bars at a current tick
                       int Length,                // Period of averaging
                       ENUM_MA_METHOD MA_Method,  // Method of averaging (MODE_SMA, MODE_EMA, MODE_SMMA, MODE_LWMA)
                       double series,             // Value of price series calculated for the bar with the 'bar' number
                       uint bar,                  // Bar index
                       bool set                   // Direction of indexing of arrays.
                      );

   double    SMASeries (uint begin,          // Index of start of reliable bars
                       uint prev_calculated, // Amount of history in bars at a previous tick
                       uint rates_total,     // Amount of history in bars at a current tick
                       int Length,           // Period of averaging
                       double series,        // Value of price series calculated for the bar with the 'bar' number
                       uint bar,             // Bar index
                       bool set              // Direction of indexing of arrays.
                      );
                        
   double    EMASeries (uint begin,          // Index of start of reliable bars
                       uint prev_calculated, // Amount of history in bars at a previous tick
                       uint rates_total,     // Amount of history in bars at a current tick
                       double Length,        // Period of averaging
                       double series,        // Value of price series calculated for the bar with the 'bar' number
                       uint bar,             // Bar index
                       bool set              // Direction of indexing of arrays.
                      );
                        
   double    SMMASeries(uint begin,            // Index of start of reliable bars
                         uint prev_calculated, // Amount of history in bars at a previous tick
                         uint rates_total,     // Amount of history in bars at a current tick
                         int Length,           // Period of averaging
                         double series,        // Value of price series calculated for the bar with the 'bar' number
                         uint bar,             // Bar index
                         bool set              // Direction of indexing of arrays.
                      );

   double    LWMASeries(uint begin,            // Index of start of reliable bars
                         uint prev_calculated, // Amount of history in bars at a previous tick
                         uint rates_total,     // Amount of history in bars at a current tick
                         int Length,           // Period of averaging
                         double series,        // Value of price series calculated the bar with the 'bar' number
                         uint bar,             // Bar index
                         bool set              // Direction of indexing of arrays.
                        );

protected:
   double            m_SeriesArray[];
   int               m_Size_, m_count, m_weight;
   double            m_Moving, m_MOVING, m_Pr;
   double            m_sum, m_SUM, m_lsum, m_LSUM;
  };

Esta clase deriva de la clase básica CMovSeriesTools que contiene funciones-métodos protegidos adicionales y una comprobación de la exactitud del periodo de las medias móviles.

La clase básica contiene un código universal adicional que se usa en todas las clases que recomiendo, y no sirve de nada copiarlo varias veces en las clases derivadas. En las tareas donde se usa la promediación, los miembros de la clase protegidos no se usan de forma explícita, luego vamos dejar de hablar de ellos por ahora.

La clase CMoving_Average consta de cinco funciones de promediación de tipo único cuyos nombres hablan por sí mismos y no necesitan ser descritas en detalle.

La primera función, MASeries(), es una colección de otras cuatro funciones que permiten seleccionar un algoritmo de promediación usando el parámetro MA_Method. El código de los algoritmos de promediación se optimiza para un máximo rendimiento, y esta es la razón por la que los principales parámetros de las funciones (longitud, series, barra) se complementan con parámetros adicionales begin, prev_calculated, rates_total y set, cuya finalidad es absolutamente idéntica a la de las variables del indicador con los mismos nombres.

El parámetro 'set' establece el flag de indexación de los elementos de las series de precio 'series' en las funciones de promediación que son iguales a las matrices de las variables.

Debemos tener en cuenta que todas las funciones de promediación de esta clase tienen el parámetro Length fijo y ¡no puede cambiarse cuando el código del programa está siendo ejecutado! La función EMASeries() de la clase CMoving_Average tiene su parámetro ¡de tipo doble!

Ahora que nos hemos familiarizado con la clase CMoving_Average podemos empezar a usarla en los indicadores. Para hacer esto, usamos la directiva #include que añade los contenidos del archivo MASeries_Cls.mqh en el alcance global del código del indicador que hemos desarrollado:

#include <MASeries_Cls.mqh> 

A continuación debemos determinar el número necesario de procedimientos de promediación en el código del indicador y luego en la parte de OnCalculate() (antes de los operadores del bucle y de los corchetes curvos) declaramos las variables estáticas de la clase CMoving_Average según el número requerido de procedimientos de promediación. Debe haber una variable separada de la clase y una celda separada en la matriz de la clase para cada procedimiento de promediación.

//---- declaration of variables of the class CMoving_Average из файла MASeries_Cls.mqh
static CMoving_Average MA1, MA2, MA3, MA4;

Las variables de la clase OnCalculate() en la función se declaran como estáticas, ya que sus valores deben mantenerse entre las llamadas de esta función. Ahora podemos empezar a trabajar con la promediación. Como ejemplo, voy a mostrar cuatro procedimientos consecutivos de promediación de series de precio: SMA/EMA/SMMA/LWMA (el indicador MAx4.mq5):

//---- The main cycle of indicator calculation 
for(bar = first; bar < rates_total; bar++) 
 {
  //----+ Four calls of the function MASeries.  
  ma1_ = MA1.MASeries(start1, prev_calculated, rates_total, Length1, MODE_SMA,  price[bar], bar, false);
  ma2_ = MA2.MASeries(start2, prev_calculated, rates_total, Length2, MODE_EMA,  ma1_,       bar, false);
  ma3_ = MA3.MASeries(start3, prev_calculated, rates_total, Length3, MODE_SMMA, ma2_,       bar, false);
  ma4_ = MA4.MASeries(start4, prev_calculated, rates_total, Length4, MODE_LWMA, ma3_,       bar, false);
  //----       
  MAx4[bar] = ma4_ + dPriceShift;
 }

El resultado de cada promediación previa (excluyendo la última) se usa en el siguiente algoritmo de promediación y el resultado final se pasa al buffer del indicador.

Creo que la parte más importante de este código es la cuidadosa inicialización preliminar de las variables de los índices que muestra el inicio de las barras fiables. En esta situación el código es el siguiente:

//---- Initialization of variables of start of reliable information
start1 = 0 + begin;
start2 = Length1 + begin;
start3 = Length1 + begin; // the previous EMA averaging doesn't change the start of reliable information
start4 = Length1 + Length3 + begin;

Observe que en esta situación, el algoritmo LWMA de promediación es el último y no afecta a nada. Pero si no fuera el último el cambio del inicio de la información fiable sería igual a Length4+1, ¡y no Length4!

Quiero añadir que si no queda claro a partir del código anterior cuál es el número de inicios de información fiable, debemos tomar un número mayor e incrementarlo de forma experimental si es necesario.


3. Comparar el indicador creado usando clases con sus análogas que usen indicadores técnicos y personalizados

Sería muy interesante comparar el funcionamiento del indicador creado MAx4.mq5 con su análogo idéntico (iMAx4.mq5) que usa el indicador técnico iMA().

Bueno, al realizar la prueba será razonable probar otro indicador (MAx3x1.mq5) similar a MAx4.mq5, pero habiendo realizado la primera promediación usando la llamada del indicador y las otras tres usando la clase CMoving_Average. Y al tiempo que el conjunto estándar de indicadores del terminal de cliente incluye el indicador de media móvil personalizado, he realizado otro indicador análogo con la finalidad de realizar pruebas (cMAx4.mq5).

Para el siguiente análisis me preparé para poner a prueba los asesores expertos: MAx4_Test.mq5, iMAx4_Test.mq5, MAx3x1_Test.mq5 y cMAx4_Test.mq5 respectivamente. Las condiciones para la realización de estas pruebas se describieron en detalle en el artículo "Principios del cálculo económico de indicadores". En este artículo no voy a describir los detalles de la prueba, pero voy a mostrar los resultados finales de la ejecución de los cuatro asesores expertos en el probador de estrategia para los últimos 12 meses sobre EURUSD Н4 con el modelado de cada tick y el valor de los parámetros de entrada del periodo de todos los asesores expertos igual a 500.

Fig.1 El resultado de la prueba de los indicadores 

Los peores resultados de nuestras pruebas se dan en el indicador que llama a los indicadores personalizados. Por tanto, esta forma de escribir el código solo es recomendable ¡para  perezosos! Por supuesto, otro "líder" que viene al final pero que se basa en las llamadas de los indicadores técnicos obtiene resultados mucho mejores, aunque sin embargo aún se alejan del ideal.

El líder real de las pruebas es ¡el indicador desarrollado usando clases!

El híbrido que usa clases e indicadores técnicos se sitúa en segundo lugar, pero esto no siempre ocurre así. Si el tiempo de prueba de un indicador es importante es mejor comprobar dichas variantes de forma individual para cada situación.


Visión general de las clases de promediación implementadas

№  Algoritmo: Nombre de clase Nombre de archivo Cambio de inicio de la barras fiables
después de aplicar un algoritmo
Posibilidad de cambio
dinámico de la longitud del parámetro
   1  Promediación clásica  CMoving_Average  MASeries_Cls.mqh Length/0/Length/Length + 1 (SMA/EMA/SMMA/LWMA) no
   2  Desviación estándar   CStdDeviation  StdDevSeries_Cls.mqh Longitud no
   3  Ajuste JMA  CJJMA  JJMASeries_Cls.mqh 30 si
   4  Ajuste T3  CT3  T3Series_Cls.mqh 0 si
   5  Ajuste ultralineal  CJurX  JurXSeries_Cls.mqh 0 si
   6  Ajuste de Tushar Chande   CCMO  CMOSeries_Cls.mqh Longitud + 1 no
   7  Ajuste de Kaufman  CAMA  AMASeries_Cls.mqh Longitud + 3 no
   8  Promediación parabólica  CParMA  ParMASeries_Cls.mqh Longitud no
   9  Velocidad de cambio  CMomentum  MomSeries_Cls.mqh                                             Longitud + 1                               no
  10  Velocidad de cambio normalizada  CnMomentum  nMomSeries_Cls.mqh                                             Longitud + 1                               no
  11  Ratio de cambio  CROC  ROCSeries_Cls.mqh                                             Longitud + 1                               no

La clase descrita anteriormente CMoving_Average incluye cinco algoritmos de promediación.

La clase CCMO contiene algoritmos de promediación y un oscilador.

Las demás clases incluyen algoritmos únicos de promediación. La filosofía para el uso de cualquiera de las clases recomendadas es absolutamente la misma que la utilizada en el procedimiento de uso de la clase CMoving_Average descrito anteriormente. El código de todos los algoritmos de promediación (excepto de la parabólica) se optimiza para la máxima velocidad de ejecución. El código de la promediación parabólica no se optimizó debido a la complejidad de este proceso. Los últimos tres algoritmos no representan una promediación. Los he añadido debido a su alta popularidad y compatibilidad con los trabajos de los analistas técnicos más famosos.

Para una comprensión más sencilla de la información, es mejor representar los algoritmos de promediación en archivos .mqh separados, y para un uso práctico lo mejor es tenerlos en un único archivo.

Para usarlas en los indicadores, todas las clases recomendadas se encuentran en el archivo SmoothAlgorithms.mqh. Además, el archivo se complementa con las funciones de iPriceSeries.mqh. Solo la función PriceSeries() se usa en los ejemplos de este artículo:

double PriceSeries
 (
  uint applied_price,  // Price constant
  uint   bar,          // Index of shift for a specified number of periods back or forward
                       // relatively to a current bar.
  const double& Open [],
  const double& Low  [],
  const double& High [],
  const double& Close[]
 )

Esta función tiene por finalidad trabajar con indicadores basados en el uso del segundo tipo de llamada de la función OnCalculate().

La idea principal al crear esta función es ampliar el conjunto de series de tiempo de la enumeración ENUM_APPLIED_PRICE con variantes personalizadas. La función devuelve el valor de las series de tiempo del precio por su número, que varía de 1 a 11.


4. Ejemplos prácticos de la implementación del código de un programa usando las clases de promediación

Por si es suficiente con mostrar otro ejemplo del uso de otras clases, para estar seguros de que todos se realizan de la misma forma que la promediación cuádruple. Voy a mostrar una variante de la implementación de la función OnCalculate() en un caso análogo al del indicador CCI usando las clases CJJMA y CJurX (JCCX.mq5).

int OnCalculate(const int rates_total,     // amount of history in bars on a current tick
              const int prev_calculated,   // amount of history in bars on a previous tick
              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[]
             )
  {
//----+   
   //---- Checking whether there are enough bars for calculation
   if (rates_total < 0) return(0);
    
   //---- Declaration of variables  with floating point  
   double price_, jma, up_cci, dn_cci, up_jccx, dn_jccx, jccx;
   //----+ Declaration of integer variables
   int first, bar;
   
   //---- calculation of start number 'first' for the cycle of recalculation of bars
   if (prev_calculated == 0) // checking for the first start of calculation of indicator
        first = 0;           // starting number for calculation of all bars
   else first = prev_calculated - 1; // starting number for calculation of new bars
   
   //---- declaration of variables of the class CJurX from the file SmoothAlgorithms.mqh
   static CJurX Jur1, Jur2;
   //---- declaration of variables of the class CJMA from the file SmoothAlgorithms.mqh
   static CJJMA JMA;
   
   //---- Main cycle of calculation of the indicator
   for(bar = first; bar < rates_total; bar++)
    {
     //----+ Calling the PriceSeries function to get the input price price_
     price_ = PriceSeries(IPC, bar, open, low, high, close); 

     //----+ One call of the JJMASeries function to get JMA
     jma = JMA.JJMASeries(0, prev_calculated, rates_total, 0, JMAPhase, JMALength, price_, bar, false); 

     //----+ Determine the deviation of price from the value of JMA
     up_cci = price_ - jma;         
     dn_cci = MathAbs(up_cci);

     //----+ Two calls of the JurXSeries function.  
     up_jccx = Jur1.JurXSeries(30, prev_calculated, rates_total, 0, JurXLength, up_cci, bar, false);
     dn_jccx = Jur2.JurXSeries(30, prev_calculated, rates_total, 0, JurXLength, dn_cci, bar, false); 

     //---- Preventing zero divide in empty values
     if (dn_jccx == 0) jccx = EMPTY_VALUE;
     else
      {
       jccx = up_jccx / dn_jccx;
       
       //---- Limitation of the indicator from the top and from the bottom
       if (jccx > +1)jccx = +1;
       if (jccx < -1)jccx = -1;
      }

     //---- Loading the obtained value to the indicator buffer
     JCCX[bar] = jccx;
    }
//----+     
   return(rates_total);
  }

Fig.2 El indicador JCCX 

Pero esta vez he añadido las clases apropiadas de otro archivo en el alcance global en el código del indicador.

#include <SmoothAlgorithms.mqh>

Ahora quiero que preste atención a otro asunto. La cuestión es que hay un gran número de indicadores que pueden representarse como funciones de una barra y con los que resulta realmente cómodo trabajar usando clases.

Por ejemplo, sería interesante dibujar el canal de Bollinger basándonos en la media móvil Vidya de Tushar Chande. En este caso, se usan dos clases: CCMO y CStdDeviation. Usando la primera clase obtenemos el valor de la media móvil VIDYA, y usando la segunda calculamos el valor de la desviación estándar de las series de precio para la media móvil.

Despues de esto, usamos esta desviación para el cálculo del límite superior e inferior del canal:

//+------------------------------------------------------------------+
// Description of the classes CStdDeviation{}; and CCMO{};           | 
//+------------------------------------------------------------------+ 
#include <SmoothAlgorithms.mqh> 
//+==================================================================+
//|  The algorithm of getting the Bollinger channel from             |
//|  the moving average VIDYA                                        |
//+==================================================================+
class CVidyaBands
{
public:
  double VidyaBandsSeries(uint begin,               // number of the start of reliable calculation of bars
                         uint prev_calculated,      // amount of history on a previous tick in bars
                         uint rates_total,          // amount of history on a current tick in bars
                         int CMO_period,            // the period of averaging of the oscillator CMO
                         double EMA_period,         // the period of averaging of EMA
                         ENUM_MA_METHOD MA_Method,  // the type of averaging
                         int BBLength,              // the period of averaging of the Bollinger channel
                         double deviation,          // Deviation
                         double series,             // Value of the price series calculated for a bar with the number 'bar'
                         uint bar,                  // Bar index
                         bool set,                  // Direction of indexing of arrays
                         double& DnMovSeries,       // Value of the lower border of the channel for a current bar
                         double& MovSeries,         // Value of the middle line of the channel for a current bar
                         double& UpMovSeries        // Value of the upper border of the channel for a current bar 
                        ) 
   {
//----+
    //----+ Calculation of the middle line    
    MovSeries = m_VIDYA.VIDYASeries(begin, prev_calculated, rates_total, 
                                    CMO_period, EMA_period, series, bar, set); 
    //----+ Calculation of the Bollinger channel
    double StdDev = m_STD.StdDevSeries(begin+CMO_period+1, prev_calculated, rates_total, 
                                      BBLength, deviation, series, MovSeries, bar, set);
    DnMovSeries = MovSeries - StdDev;
    UpMovSeries = MovSeries + StdDev;
//----+
    return(StdDev); 
   }

  protected:
    //---- declaration of variables of the classes CCMO and CStdDeviation
    CCMO           m_VIDYA;
    CStdDeviation  m_STD;
};

Y así tenemos ¡una simple y pequeña clase!

Los últimos tres parámetros de entrada de la función VidyaBandsSeries() pasan los valores necesarios del canal a través de un enlace.

Me gustaría decir que en este caso no podemos declarar las variables de las clases dentro de la función VidyaBandsSeries() y hacerlas estáticas, ya que las variables estáticas en las clases tienen un significado muy distinto. Por ello, esta declaración debe realizarse en el alcance global de la clase:

protegido: 
  //---- declaration of variables of the classes CCMO and CStdDeviation 
  CCMO           m_VIDYA; 
  CStdDeviation  m_STD;

En un canal de Bollinger normal, el periodo de la promediación de la media móvil y el periodo de promediación del propio canal son siempre iguales.

En esta clase he separado estos parámetros para darle más libertad (EMA_period y BBLength). El propio indicador (VidyaBBands.mq5) realizado en base a esta clase es tan simple al usar la clase CVidyaBands que no necesitamos analizar su código en este artículo.

Este tipo de funciones de indicador deben colocarse en un archivo mqh separado. He situado estas funciones en el archivo IndicatorsAlgorithms.mqh.

Fig.3 El indicador VidyaBBands 


5. Comparación del rendimiento de un indicador que utiliza clases con uno que no las utiliza 

En primer lugar, quiero encontrar cómo el uso de clases, al escribir código de un indicador, disminuye su rendimiento.

Para esta finalidad el código del indicador JJMA.mq5 se escribió sin usar las clases (JMA.mq5), y luego fue probado en las mismas condiciones que durante la prueba anterior. Los resultados finales de las pruebas no muestran grandes diferencias:

Fig. 4 Resultados de las pruebas de los indicadores JMA y JJMA 

Por supuesto, hay algunos cambios adicionales para usar las clases, pero no son relevantes comparados con las ventajas que proporcionan. 


6. Ventajas del uso de las clases de promediación

Una ventaja del uso de estos algoritmos, que es realmente convincente, es que la sustitución de las llamadas de los indicadores técnicos y personalizados generan un gran incremento del rendimiento del código descrito anteriormente.

Otra ventaja práctica de tales clases es la conveniencia de su uso. Por ejemplo, todo lo que se describe en el popular libro de William Blau "Momentum, Direction and Divergence" parece ser la base de una prueba real para este tipo de enfoque de la creación de indicadores. El código de los indicadores sale con una compresión máxima, comprensible y a menudo con un único ciclo de recálculo de las barras.

Puede fácilmente desarrollar cualquier indicador -uno clásico o uno técnico, usando métodos alternativos de promediación. Una amplia variedad de algoritmos de promediación proporcionan muchas posibilidades para la creación de sistemas de trading no tradicionales, a menudo con una detección más temprana de tendencias y un menor número de inicios falsos.


7. Algunas recomendaciones sobre el uso de los algoritmos de promediación en un código de indicador específico

Un rápido vistazo a cualquier indicador desarrollado usando los diferentes algoritmos de promediación que aquí se describen es suficiente para comprender lo distintos que son estos algoritmos.

 

 Fig. 5 Las medias móviles que usan diferentes algoritmos de promediación.

Por tanto, sería razonable suponer que no todos los algoritmos recomendados son igualmente buenos en cada situación. Aunque puede resultar difícil determinar una límite estricto para el uso de uno u otro algoritmo, es posible proporcionar algunas recomendaciones generales sobre su uso.

Por ejemplo, los algoritmos de Tushar Chande y Kaufman están pensados para determinar las situaciones de tendencia y no son adecuados para ajustes adicionales con objeto de realizar filtrados de ruidos. Por tanto, es mejor usar las series de precio no procesadas o bien los valores del indicador sin promediarlos con estos algoritmos. Este es el resultado del procesamiento de los valores del indicador Momentum usando el algoritmo de Kaufman (el indicador 4c_Momentum_AMA.mq5)

Fig.6 Resultado de procesar los valores del indicador Momentum usando el algoritmo de Kaufman 

Creo que el algoritmo de la promediación clásica no necesita ninguna recomendación especial. Su aplicación es muy amplia. En cualquier situación en la que estos algoritmos se usen, podemos usar con éxito los cuatro algoritmos restantes (JMA, T3, ultralineal y parabólico). Este es un ejemplo del indicador MACD donde EMA y SMA se han sustituido con la promediación de JMA (el indicador JMACD.mq5):

 Fig.7 El gráfico de MACD usando la promediación de JMA

Y este es el resultado del ajuste del indicador calculado en lugar de cambiar su algoritmo de promediación para una mejor calidad en la determinación de la tendencia actual (el indicador JMomentum.mq5):

Fig.8 Resultado del ajuste de JMA del indicador Momentum

No es una sorpresa que el comportamiento del mercado cambie constantemente. Por tanto, sería ingenuo pensar que podemos encontrar un único algoritmo ideal para una parte concreta del mercado financiero ¡ahora y para siempre! ¡ay de mí! ¡Nada en esta vida dura para siempre! No obstante, por mi parte por ejemplo, en este mercado de eternos cambios a menudo uso los indicadores de tendencias rápidas y medias como JFATL.mq5 y J2JMA.mq5. Estoy muy satisfecho con las predicciones.

Otra cosa que quiero añadir. Los algoritmos de promediación se pueden reutilizar. Se pueden obtener buenos resultados al aplicar promediaciones repetidas a valores ya promediados. De hecho, en este artículo empecé analizando el proceso de dibujo de un indicador a partir de ellas (el indicador MAx4.mq5).


8. La idea general de componer el código de los algoritmos de promediación

Y ahora, al final del artículo, me gustaría que prestara atención al mecanismo de las funciones de promediación.

En primer lugar, la mayoría de los algoritmos de promediación incluyen matrices dinámicas de variables del tipo m_SeriesArray[] para almacenar los valores de las series de los parámetros de entrada.

Tan pronto como aparece información para el cálculo, debemos asignar memoria para dicha matriz una sola vez. Esto se hace usando la función SeriesArrayResize() de la clase CMovSeriesTools.

//----+ Changing the size of the array of variables
if(bar==begin && !SeriesArrayResize(__FUNCTION__, Length, m_SeriesArray, m_Size_))
   return(EMPTY_VALUE);

A continuación debemos escribir en cada barra el valor actual de las series de precio "series" en el valor más antiguo de la matriz y memorizar el número de su posición en la variable m_count. Esto se hace usando la función Recount_ArrayZeroPos() de la clase CMovSeriesTools.

//----+ transposition and initialization of cells of the array m_SeriesArray 
Recount_ArrayZeroPos(m_count, Length_, prev_calculated, series, bar, m_SeriesArray);

Y ahora, si necesitamos encontrar un elemento con un cambio relativo respecto al elemento actual debemos usar la función Recount_ArrayNumber() de la clase CMovSeriesTools:

for(iii=1; iii<=Length; iii++, rrr--)
   {
    kkk = Recount_ArrayNumber(m_count, Length_, iii);
    m_sum += m_SeriesArray[kkk] * rrr;
    m_lsum += m_SeriesArray[kkk];
    m_weight += iii;
   }

Normalmente, en estas situaciones, el elemento más nuevo se escribe en la posición cero y los demás (excepto el más antiguo) se sobreescriben de forma preliminar en las siguientes posiciones por turnos. Sin embargo, no se están guardando los recursos del ordenador y ¡cuanto más complejo es el enfoque descrito anteriormente, más racional es!

Además de los algoritmos de promediación, los cuerpos de estas funciones contienen las llamadas de las funciones usadas para la determinación de las posiciones de las barras relativas al inicio del cálculo de las barras:

//----+ Checking the start of reliable calculation of bars
if(BarCheck1(begin, bar, set)) return(EMPTY_VALUE);

el momento en el que hay suficiente información para comenzar con la inicialización de las variables:

//----+ Initialization of zero
if(BarCheck2(begin, bar, set, Length))

y situaciones en las que la última barra se cierra:

//----+ Saving values of the variables 
if(BarCheck4(rates_total, bar, set))

o no se cierra:

//----+ Restoring values of the variables
if(BarCheck5(rates_total, bar, set))

La primera comprobación determina una situación en la que no hay suficientes barras para que funcione la función de promediación, y devuelve un resultado vacío. Una vez que se ha pasado la segunda comprobación y hay suficientes datos para el primer cálculo, se realiza la inicialización de las variables una vez. Son necesarios dos últimas comprobaciones para el procesado múltiple de los valores en la barra abierta actual. Ya lo he descrito en mi artículo dedicado a la optimización de un código de programa.

Y ahora unas palabras sobre la optimización del código del programa de dichas funciones para un rendimiento máximo. Por ejemplo, el algoritmo SMA implica promediar los valores seleccionados de un periodo de una serie de precios en cada barra. Estos valores están literalmente sumados y divididos por el periodo en cada barra.

Pero la diferencia entre la suma de las barras previas y la suma de las barras actuales es la primera que se suma al valor de la serie de precios con un cambio en un periodo con relación al periodo actual, y el segundo con el valor actual. Por tanto, sería mucho más racional calcular dicha suma solo una vez durante la inicialización de las funciones, y a continuación en cada barra añadir solo los nuevos valores de la serie de precios a esta suma y restar de esta los valores más antiguos. Esto es lo que se hace exactamente en dicha función.


Conclusión

Las implementaciones de los algoritmos de promediación sugeridas en el artículo usando las clases son las versiones simple, de tipo simple y universal, por lo que no tendrá problemas estudiándolas.

Los archivos adjuntos al artículo contienen muchos ejemplos para facilitar la comprensión de este enfoque sobre la escritura del código de indicadores. En el artículo Include__en.zip todas las clases se distribuyen en archivos. El archivo Include_en.zip contiene solo dos archivos que son suficientes para compilar todos los indicadores del archivo Indicators.zip. Los asesores expertos para las pruebas se encuentran en el archivo Experts.zip.