English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Cómo reducir el gasto de memoria en los indicadores auxiliares

Cómo reducir el gasto de memoria en los indicadores auxiliares

MetaTrader 5Indicadores | 19 marzo 2014, 09:26
1 993 0
ds2
ds2

1. En qué consiste el problema

Seguramente habrá tenido que usar o crear algún asesor o indicador que necesitara de otros indicadores auxiliares para su funcionamiento.

Por ejemplo, el famoso indicador MACD usa dos copias del indicador EMA (Exponential Moving Average), y calcula la diferencia entre sus valores:


Un indicador compuesto de este tipo equivale a en la práctica a varios simples. Por ejemplo, el mencionado MACD consume tres veces más memoria y tiempo del procesador que un EMA, dado que el terminal tiene que dedicar memoria a los buffers del indicador principal y para los buffers de todos sus indicadores auxiliares.

Además del MACD, existen otros indicadores más complejos en los que se implican, no sólo dos, sino más indicadores auxiliares.

Es más, el consumo crece acusadamente en caso de que:

  • el indicador use varios time frames (por ejemplo, si rastrea la coincidencia de ondas de varios time frames), en cada time frame debe crear copias por separado de los indicadores auxiliares;
  • el indicador sea multidivisa;
  • el trader, con ayuda de este indicador, comercie con varias parejas de divisas (conozco traders que comercian simultáneamente con más de dos decenas de parejas de divisas).

La concurrencia de estos factores es capaz de provocar una insuficiencia de la memoria operativa en la computadora (conozco casos reales, en los que por culpa de indicadores semejantes el terminal exigía gigabytes de memoria). En MetaTrader, la falta de memoria tiene el aspecto siguiente:


En esta situación, el terminal no es capaz de situar el indicador en el gráfico, o no lo calcula de manera correcta (si en el código del indicador no se contempla la gestión de la distribución de la memoria), o podría incluso cerrarse.

Afortunadamente, la computadora puede compensar una eventual falta de memoria operativa usando un mayor volumen de memoria virtual, es decir, guardando parte de la memoria en el disco duro. Todos los programas funcionarán, pero muy lentamente...


2. Indicador compuesto de simulación

Para continuar con la investigación de manera más concreta, en el marco propuesto por este artículo, vamos a crear algún indicador compuesto, más complicado que el MACD.

Un indicador que rastree el inicio de las tendencias de mercado (trend). Sumará las señales de 5 time frames, por ejemplo: H4, H1, M15, M5, M1. Esto permitirá captar la resonancia de las tendencias de mercado pequeñas y grandes que vayan surgiendo, hecho que aumentará la fiabilidad del pronóstico. Como fuentes de las señales en cierto time frame van a actuar los indicadores Ichimoku y Price_Channel, incluidos en los componentes de MetaTrader 5:

  • Si la línea Tenkan (roja) de Ichimoku está por encima de la línea Kijun (azul), la tendencia es ascendente; y si está por debajo, entonces la tendencia será descendente;


  • Si el precio está por encima de la línea media de Price_Channel, la tendencia está ascendiendo; si está por debajo, entonces está descendiendo.


En total, nuestro indicador usará 10 indicadores auxiliares: 5 time frames con 2 indicadores por cada uno de ellos. Lllamaremos a nuestro indicador Trender.

Este es su código fuente completo (se adjunta igualmente al artículo):

#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_minimum -1
#property indicator_maximum  1

#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  DarkTurquoise

// Búfer único del indicador
double ExtBuffer[];

// Time frames de los indicadores auxiliares
ENUM_TIMEFRAMES TF[5] = {PERIOD_H4, PERIOD_H1, PERIOD_M15, PERIOD_M5, PERIOD_M1};

// Handles de los indicadores auxiliares para todos los time frames
int h_Ichimoku[5], h_Channel[5];

//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0, ExtBuffer);
   ArraySetAsSeries(ExtBuffer, true);
   
   // Creamos los indicadores auxiliares
   for (int itf=0; itf<5; itf++)
     {
      h_Ichimoku[itf] = iCustom(Symbol(), TF[itf], 
                                "TestSlaveIndicators\\Ichimoku",
                                9, 26, 52
                               );
      h_Channel [itf] = iCustom(Symbol(), TF[itf],
                                "TestSlaveIndicators\\Price_Channel",
                                22
                               );
     }
  }
//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time, true);
  
   int limit = prev_calculated ? rates_total - prev_calculated : rates_total -1;

   for (int bar = limit; bar >= 0; bar--)
     {
      // Hora de la barra actual
      datetime Time  = time [bar];
      
      //--- Recopilamos las señales de todos los time frames
      double Signal = 0; // señal total
      double bufPrice[1], bufTenkan[1], bufKijun [1], bufMid[1], bufSignal[1];
      for (int itf=0; itf<5; itf++)
        {
         //=== Precio de la barra
         CopyClose(Symbol(), TF[itf], Time, 1, bufPrice);
         double Price = bufPrice[0];

         //=== Indicador Ichimoku         
         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== Indicador del canal
         CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         double Mid = bufMid[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;
        }
        
      ExtBuffer[bar] = Signal/10;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

Se debe usar este indicador en el gráfico con el time frame más pequeño de entre aquellos de los que se recopilan las señales, ya que sólo así podremos notar la aparición de todas las tendencias pequeñas. En nuestro caso, el time frame más pequeño es  M1. Este es el aspecto del indicador:


Ahora pasemos a lo más importante: vamos a calcular cuánta memoria consume nuestro indicador.

Echemos un vistazo al código fuente del indicador Ichimoku (puede ver el código completo en el anexo):

#property indicator_buffers 5

y Price_Channel (puede ver el código completo en el anexo):

#property indicator_buffers 3

En estas líneas podemos ver que estos indicadores crean 8 buffers. Lo multiplicamos por 5 time frames. Y añadimos un buffer del propio indicador Trender. ¡En total tenemos 41 búferes! Ya vemos los valores impresionantes que se esconden tras ciertos indicadores, tan sencillos a simple vista (si miramos al gráfico).

Con la configuración estándar del terminal, un buffer contiene aproximadamente 100000 valores del tipo double y ocupa 8 bytes. De esta forma, 41 buffers ocuparán unos 31 Mb de memoria, aproximadamente. Esto, en lo que respecta a los valores en sí, desconozco qué información adicional de servicio se almacena en los buffers.

"31 Mb no es tanto" - diréis. Pero cuando el trader comercia con una gran cantidad de parejas de divisas, semejantes volúmenes se convierten en un problema. Además de los indicadores, los propios gráficos consumen mucha memoria, ya que, a diferencia de los indicadores, cada barra tiene varios valores a la vez: OHLC, hora, volumen. ¿Cómo podemos hacer que quepa todo en una computadora?


3. Maneras de resolver el problema

Por supuesto, una de las soluciones es probar a instalar en la computadora una memoria operativa mayor. Si no puede adoptar esta solución por motivos técnicos, económicos o de otro tipo, o bien la memoria máxima que tiene instalada es insuficiente, deberá examinar esos indicadores que tanto están ocupando y lograr reducir su consumo.

Para llevar a cabo esta tarea vamos a recordar un poco...de geometría a nivel escolar. Supongamos que todos los buffers de nuestro indicador compuesto tienen la forma de un rectángulo simple:


Ls superficie de dicho rectángulo es la memoria que ocupa. Se puede reducir la superficie si disminuimos la altura o la anchura.

La anchura, en este caso, es la cantidad de barras sobre las que se construye el indicador. La altura es la cantidad de buffers de indicador.


4. Reducimos la cantidad de barras

4.1. La solución sencilla

No resulta necesario ser programador para ajustar la configuración de MetaTrader:



Reduciendo el valor del parámetro «Barras máximas en la ventana», reducirá igualmente el tamaño de los buffers de indicador en esas ventanas. Es un método muy efectivo y accesible para cualquiera (si el trader no tiene necesidad de estudiar en profundidad la historia durante el comercio).

4.2. ¿Existe alguna otra solución?

Los programadores de MQL5 saben que los buffers de indicador se muestran en el indicador como matrices dinámicas, sin tamaño establecido previamente. Aquí tenemos, por ejemplo, 5 buffers de Ichimoku:

double    ExtTenkanBuffer[];
double    ExtKijunBuffer[];
double    ExtSpanABuffer[];
double    ExtSpanBBuffer[];
double    ExtChinkouBuffer[];

La longitud de las matrices no se muestra, puesto que en cualquier caso, el tamaño de estas matrices será establecido por el terminal MetaTrader 5 en toda la longitud de la historia disponible.

Lo mismo sucede en la función OnCalculate:

int OnCalculate (const int rates_total,      // tamaño de la matriz price[]
               const int prev_calculated,  // número de barras procesadas en la llamada anterior
               const int begin,            // comienzo de los datos fiables
               const double& price[]       // matriz para el cálculo
   );

En esta función, al indicador se le asigna el buffer de precio. La memoria para él ya ha sido reservada por el terminal y el programador no puede influir en su tamaño.

Además, MQL5 permite usar el buffer de un indicador como buffer de precio para otro indicador (construir "un indicador en base a otro indicador"). Aquí el programador no puede establecer ninguna limitación de tamaño, lo único que hace es transmitir el handle del indicador.

De esta forma, en MQL5 no se contempla ningún mecanismo de limitación del tamaño de los buffers de indicador.


5. Disminuyendo la cantidad de buffers

El programador dispone aquí de mucha variedad donde elegir. He pensado varios métodos teóricos sencillos para disminuir la cantidad de buffers del indicador complejo. En todos los métodos, por supuesto, se reducen los buffers de los indicadores auxiliares, ya que se supone que en el indicador principal necesitamos todos los buffers.

Echemos un vistazo a estos métodos y comprobemos si funcionan en la práctica, así como los defectos y virtudes de cada uno.

5.1. Método "Need"

Si en el indicador auxiliar hay muchos buffers, puede resultar que no todos ellos sean necesarios para el indicador principal. Por ello, podemos optar por desconectar los buffers auxiliares, para que no ocupen memoria. Para lograrlo, debemos introducir ciertos cambios en el código fuente de dicho indicador auxiliar.

Llevaremos a cabo esta tarea con uno de nuestros indicadores auxiliares, Price_Channel. En él hay tres buffers, y Trender es calculado sólo por uno, así que tenemos cosas innecearias que podemos quitar.

El código completo de los indicadores Price_Channel (el indicador original), Price_Channel-Need (rehecho ya por completo) se adjunta al artículo, más adelante describiré sólo los cambios introducidos en él.

Lo primero es disminuir el contador de buffers de 3 a 1:

//#property indicator_buffers 3
  #property indicator_buffers 1
//#property indicator_plots   2
  #property indicator_plots   1

y quitamos las dos matrices de buffer sobrantes:

//--- indicator buffers
//double    ExtHighBuffer[];
//double    ExtLowBuffer[];
 double    ExtMiddBuffer[];

Ahora si intentamos compilar este indicador, el compilador nos mostrará todas las líneas donde hay referencias a esta matriz:


Este método nos permite encontrar rápidamente los lugares donde debemos introducir cambios. Además será muy útil en caso de que el código del indicador sea muy voluminoso.

En nuestro caso, el total de líneas de "undeclared identifier" asciende a 4. Vamos a corregirlas.

Como era de esperar, dos de ellas se encuentran en OnInit. Pero hemos tenido que eliminar, junto con ellas, la línea de ExtMiddBuffer, que nos hace falta, en su lugar añadiremos una análoga, pero con otro número de buffer. El indicador ya no puede tener un buffer con el número 2, sólo puede ser 0:

//   SetIndexBuffer(0,ExtHighBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtLowBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtMiddBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtMiddBuffer,INDICATOR_DATA);

Si planea usar posteriormente el indicador "cortado" en el modo de visualización, entonces deberá tener en cuenta que al cambiar de número el buffer, también se deberán cambiar los ajustes de representación del indicador. En nuestro caso sería:

//#property indicator_type1   DRAW_FILLING
  #property indicator_type1   DRAW_LINE

Si no necesita el modo de visualización, entonces puede ahorrarse el cambio de los ajustes de representación, esto no le conducirá a ningún error.

Sigamos trabajando con la lista "undeclared identifier". Los 2 últimos cambios (como es de esperar de nuevo) se encuentran en OnCalculate, donde se completan las matrices de los buffers. Dado que el ExtMiddBuffer que necesitamos requiere de los ExtHighBuffer y ExtLowBuffer remotos, hemos tenido que sustituirlos por las varianbles intermedias:

   //--- the main loop of calculations
   for(i=limit;i<rates_total;i++)
     {
//      ExtHighBuffer[i]=Highest(High,InpChannelPeriod,i);
        double      high=Highest(High,InpChannelPeriod,i);
//      ExtLowBuffer[i]=Lowest(Low,InpChannelPeriod,i);
        double      low=Lowest(Low,InpChannelPeriod,i);
//      ExtMiddBuffer[i]=(ExtHighBuffer[i]+ExtLowBuffer[i])/2.0;;
        ExtMiddBuffer[i]=(   high         +   low         )/2.0;;
     }

Como se puede ver, no hay nada de complicado en esta "operación quirúrgica". Hemos encontrado los lugares necesarios bastante rápido, y con sólo unos "movimientos de bisturí" hemos excluido dos buffers. A escala general, el ahorro en el indicador compuesto Trender es de 10 buffers (2*5 TF).

Podemos abrir Price_Channel y Price_Channel-Need uno debajo de otro para ver los buffers sobrantes que han desaparecido:

Para usar Price_Channel-Need en el indicador Trender, hay que cambiar en el código de Trender el nombre del indicador auxiliar, de "Price_Channel" a "Price_Channel-Need", así como el número del buffer que necesitamos, 0, en lugar de 2. El nuevo Trender-Need, ya listo, se adjunta al artículo.


5.2. Método "Aggregate"

Si el indicador principal lee información de más de un buffer del auxiliar y lleva a cabo alguna operación agregante con ellos (por ejemplo, una multiplicación o comparación), entonces no es necesario en absoluto ejecutar esta acción en el indicador principal. Se puede realizar dicha acción en el indicador auxiliar, y darle al indicador principal el resultado ya listo. Por ello ya deja de ser necesaria la presencia de varios buffers, uno solo puede sustituirlos.

En nuestro caso, podemos usar ese método con Ichimoku. Ya que Trender usa 2 buffers suyos (0 - Tenkan y 1 - Kijun):

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         double Tenkan = bufTenkan[0];
         CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         double Kijun  = bufKijun [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;

Si añadimos el buffer 0 o 1 de Ichimoku a un buffer de señal, entonces el fragmento de código de Trender representado más arriba será sustituido por el siguiente:

         CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufSignal);
         
         Signal += bufSignal[0];

El Trender-Aggregate al completo se adjunta al artículo.

Echemos un vistazo ahora a los cambios clave que hay que introducir en Ichimoku.

Este indicador, además, contiene varios buffers sin utilizar. Así que, además del método "Aggregate" utilizaremos el método "Need", descrito anteriormente. De esta forma, de los 5 buffers que contiene Ichimoku, sólo quedará uno, el que agrega los buffers que vayamos necesitando:

//#property indicator_buffers 5
  #property indicator_buffers 1
//#property indicator_plots   4
  #property indicator_plots   1

Le daremos un nuevo nombre a este único buffer:

//--- indicator buffers
//double    ExtTenkanBuffer[];
//double    ExtKijunBuffer[];
//double    ExtSpanABuffer[];
//double    ExtSpanBBuffer[];
//double    ExtChinkouBuffer[];
  double    ExtSignalBuffer[];

Este nuevo nombre también tiene un sentido práctico: permite eliminar del código del indicador todos los nombres de los buffers usados con anterioridad. Esto permitirá (usando la compilación, como en la descripción del método "Need") encontrar rápidamente todas las líneas que haya que cambiar.

Si tiene pensado visualizar el indicador en el gráfico, no olvide entonces introducir los cambios necesarios en los ajustes de representación. Asímismo, tenga en cuenta que, en nuestro caso, el buffer de agregado tiene un diapasón de valores diferente al de los dos buffer que consume. Ahora nos muestra, no un precio derivado, sino cuál de los dos buffers es mayor. Resulta más cómodo mostrar tales resultados en una ventana por separado, en la parte baja del gráfico:

//#property indicator_chart_window
  #property indicator_separate_window

Y así, introducimos los cambios en OnInit:

//--- indicator buffers mapping
//   SetIndexBuffer(0,ExtTenkanBuffer,INDICATOR_DATA);
//   SetIndexBuffer(1,ExtKijunBuffer,INDICATOR_DATA);
//   SetIndexBuffer(2,ExtSpanABuffer,INDICATOR_DATA);
//   SetIndexBuffer(3,ExtSpanBBuffer,INDICATOR_DATA);
//   SetIndexBuffer(4,ExtChinkouBuffer,INDICATOR_DATA);
     SetIndexBuffer(0,ExtSignalBuffer,INDICATOR_DATA);

Y las partes más interesantes se encuentran en OnCalculate. Preste atención: los tres buffers que no necesitamos los eliminamos, simplemente (dado que estamos usando el método "Need"), y los buffers ExtTenkanBuffer y ExtKijunBuffer, que nos son necesarios, los sustituimos por las variables temporales Tenkan y Kijun. Estas mismas variables son utilizadas al final del ciclo para calcular el buffer de agregado ExtSignalBuffer:

   for(int i=limit;i<rates_total;i++)
     {
//     ExtChinkouBuffer[i]=Close[i];
      //--- tenkan sen
      double high=Highest(High,InpTenkan,i);
      double low=Lowest(Low,InpTenkan,i);
//     ExtTenkanBuffer[i]=(high+low)/2.0;
       double  Tenkan    =(high+low)/2.0;
      //--- kijun sen
      high=Highest(High,InpKijun,i);
      low=Lowest(Low,InpKijun,i);
//     ExtKijunBuffer[i]=(high+low)/2.0;
       double  Kijun    =(high+low)/2.0;
      //--- senkou span a
//     ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
      //--- senkou span b
      high=Highest(High,InpSenkou,i);
      low=Lowest(Low,InpSenkou,i);
//     ExtSpanBBuffer[i]=(high+low)/2.0;

       //--- SIGNAL
       double Signal = 0;
       if (Tenkan > Kijun) Signal++;
       if (Tenkan < Kijun) Signal--;
       ExtSignalBuffer[i] = Signal;
     }

El total es de 4 buffers menos. Y si hubiésemos aplicado a Ichimoku sólo el método "Need", entonces habrían sido sólo 3 menos.

En total, dentro de Trender, el ahorro ha sido de 20 buffers (4*5 TF).

El código completo de Ichimoku-Aggregate se adjunta al artículo. Para tener una idea de qué aspecto tiene este indicador, comparándolo con el original, vamos a abrir ambos en el mismo gráfico. Como recordará, el indicador modificado se muestra ahora en la parte inferior del gráfico, en una ventana por separado:

5.3. Método "Include"

El método más radical para reducir la cantidad de buffers es deshacerse por completo de los indicadores auxiliares. Bien, en nuestro indicador compuesto tenemos sólo 1 buffer, que pertenece al indicador principal. Menos es imposible.

Podemos obtener un resultado así, si trasladamos el código de los indicadores auxiliares al indicador principal. A veces puede resultar un trabajo muy laborioso, pero el efecto esperado merece la pena. La principal dificultad aquí es la adaptación del código trasladado del indicador. Esto se debe a que dicho código no está pensado para que funcione dentro del código de otro indicador.

Los principales problemas que pueden aparecer en este caso son los siguientes:

  • Conflicto de nombres. Coincidencia en los nombres de variables, funciones, en especial funciones de sistema (OnCalculate, por ejemplo);
  • Ausencia de buffers. En ciertos indicadores esto puede convertirse en un obstáculo insalvable para la adaptación, si la lógica de los indicadores está estrechamente relacionada a nivel de almacenado/procesamiento de datos. La sustitución de buffers por matrices sencillas no es una solución en nuestro caso, dado que nuestro objetivo es la reducción del gasto de memoria. En nuestro caso es necesario precisamente renegar por completo del almacenado de alguna historia gigantesca en la memoria.

Vamos a mostrar algunas formas de resolver estos problemas de manera eficaz.

Tenemos que escribir cada indicador auxiliar como clase. Entonces, todas las variables y funciones tendrán (dentro de sus clases) nombres únicos, por lo que no entrarán en conflicto con otros indicadores.

Si hay que trasladar muchos indicadores, entonces lo más razonable sería estandarizar dichas clases, para evitar confundirse mientras se trabaja con ellas. Para ello, hay que crear una clase de indicador base, de modo que las clases de todos los indicadores auxiliares sean heredadas de ella.

Yo he escrito la siguiente clase:

class CIndicator
  {
protected:
   string symbol;             // pareja de divisas
   ENUM_TIMEFRAMES timeframe;  // time frame

   double Open[], High[], Low[], Close[]; // simulación de los buffers de precio
   int BufLen; // necesaria profundidad de llenado de los buffers de precio

public:
   //--- Análogos de las funciones estándar de indicadores
   void Create(string sym, ENUM_TIMEFRAMES tf) {symbol = sym; timeframe = tf;};
   void Init();
   void Calculate(datetime start_time); // start_time - dirección de la barra a calcular
  };

Ahora, sobre su base, comenzaremos a calcular la clase para el indicador Ichimoku. Los primero que haremos es escribir los parámetros de entrada de indicador exactamente con los nombres originales, en el aspecto de las propiedades. Para que no haya que cambiar nada después en el código del indicador:

class CIchimoku: public CIndicator
  {
private:
   // Simulación de los parámetros de entrada del indicador
   int InpTenkan;
   int InpKijun;
   int InpSenkou;

Guardamos los nombres de todos los buffers. Sí, ha oído bien, declaramos los 5 buffers de este indicador. Pero no serán reales. Constarán de sólo una barra cada uno:

public:   
   // Simulación de buffers de indicador
   double ExtTenkanBuffer [1];
   double ExtKijunBuffer  [1];
   double ExtSpanABuffer  [1];
   double ExtSpanBBuffer  [1];
   double ExtChinkouBuffer[1];   

¿Y para qué hemos hecho esto? Para tener que introducir después menos cambios en el código. Ahora lo verá. Redefinimos el método heredado CIchimoku.Calculate, completándolo con el código de la función OnCalculate, trasladado desde Ichimoku.

Preste atención al hecho de que, durante el traslado desde esta función se borró el ciclo según las barras de historia. Ahora allí sólo se calcula una barra con un tiempo establecido. El código principal de cálculos permanece inalterado. Este es el motivo por el que habíamos guardado tan cuidadosamente todos los nombres de los buffers y los parámetros de los indicadores.

Asímismo, preste atención a que al mismo principio del método Calculate los buffers de precio se llenan de valores. Allí se contienen exactamente los valores necesarios para calcular una barra.

   void Calculate(datetime start_time)
     {
      CopyHigh (symbol,timeframe,start_time,BufLen,High);
      CopyLow  (symbol,timeframe,start_time,BufLen,Low );
      CopyClose(symbol,timeframe,start_time,1     ,Close);

//    int limit;
      //---
//    if(prev_calculated==0) limit=0;
//    else                   limit=prev_calculated-1;
      //---
//    for(int i=limit;i<rates_total;i++)
      int i=0;
        {
         ExtChinkouBuffer[i]=Close[i];
         //--- tenkan sen
         double high=Highest(High,InpTenkan,i);
         double low=Lowest(Low,InpTenkan,i);
         ExtTenkanBuffer[i]=(high+low)/2.0;
         //--- kijun sen
         high=Highest(High,InpKijun,i);
         low=Lowest(Low,InpKijun,i);
         ExtKijunBuffer[i]=(high+low)/2.0;
         //--- senkou span a
         ExtSpanABuffer[i]=(ExtTenkanBuffer[i]+ExtKijunBuffer[i])/2.0;
         //--- senkou span b
         high=Highest(High,InpSenkou,i);
         low=Lowest(Low,InpSenkou,i);
         ExtSpanBBuffer[i]=(high+low)/2.0;
        }
      //--- done
//    return(rates_total);     
     };

Claro que podríamos no habernos preocupado demasiado de guardar el código original. Pero entonces, para trasladarlo, tendríamos que haber reescrito una gran parte, lo cual requeriría entender bastante bien su lógica de funcionamiento. En nuestro caso el indicador es sencillo, por lo que aclararse con él habría sido simple. ¿Pero se imagina lo que podría suceder con un indicador más complejo? Os mostraré un método que os ayudaría en tal caso.

Ahora completamos el método CIchimoku.Init, aquí todo es bastante sencillo:

   void Init(int Tenkan = 9, int Kijun = 26, int Senkou = 52)
     {
      InpTenkan = Tenkan; InpKijun = Kijun; InpSenkou = Senkou;
      BufLen = MathMax(MathMax(InpTenkan, InpKijun), InpSenkou);
     };

En el indicador Ichimoku hay dos funciones más que debemos trasladar a la clase CIchimoku: Highest y Lowest. Se encargan de buscar el valor máximo y mínimo en un fragmento establecido de los buffers de precio.

Los buffers de precio que tenemos ahora no son reales, dado que tienen un tamaño muy pequeño (ya se ha visto cómo llenarlos en el método Calculate, más arriba), por eso hay que cambiar un poco la lógica de funcionamiento de las funciones Highest y Lowest.

Aquí he seguido igualmente el principio de corregir lo mínimo posible, todos los cambios consisten en el añadido de una línea que, en realidad, cambia la numeración de las barras en el buffer del global (cuando la longitud del buffer constituye la historia entera) al local (dado que en nuestros buffers de precio ahora sólo tenemos los valores necesarios para el cálculo de una barra de indicador):

   double Highest(const double&array[],int range,int fromIndex)
     {
       fromIndex=MathMax(ArraySize(array)-1, 0);
      double res=0;
   //---
      res=array[fromIndex];
      for(int i=fromIndex;i>fromIndex-range && i>=0;i--)
        {
         if(res<array[i]) res=array[i];
        }
   //---
      return(res);
     }

El método Lowest se modifica de la siguiente forma.

En el indicador Price_Channel también se llevan a cabo cambios semejantes, sólo que se representará en forma de clase con el nombre CChannel. Puede ver las dos clases completas en Trender-Include, adjunto al artículo.

Ya he descrito los momentos básicos del traslado del código. Creo que para la mayoría de los indicadores, estos métodos serán suficientes.

Los indicadores con ajustes no estándar pueden representar una dificultad adicional. Por ejemplo, en el mismo Price_Channel existen líneas ordinarias:

   PlotIndexSetInteger(0,PLOT_SHIFT,1);
   PlotIndexSetInteger(1,PLOT_SHIFT,1);

Significan que el gráfico del indicador está desplazado en 1 barra. En nuestro caso esto provoca que, por ejemplo, las funciones CopyBuffer y CopyHigh usen dos barras diferentes, a pesar de que se hayan establecido en sus parámetros las mismas coordenadas de la barra (su hora).

En Trender-Include este problema está resuelto (en la clase CChannel se han añadido "unidades" en los lugares necesarios, a diferencia de la clase CIchimoku, donde tal problema era inexistente), así que si se encuentra con un indicador tan imprevisible, ya sabe dónde buscar pistas.

Así, ya hemos terminado con el traslado, y ahora ambos indicadores auxiliares se encuentran defenidos en dos clases dentro del indicador Trender-Include. Sólo nos queda cambiar la invocación de estos indicadores. En Trender teníamos las matrices de los handle, y en el Trender-Include son reemplazados con matrices de instancias:

// Handles de los indicadores auxiliares para todos los time frames
//int h_Ichimoku[5], h_Channel[5];
// Instancias de los indicadores auxiliares incorporados
CIchimoku o_Ichimoku[5]; CChannel o_Channel[5];

La creación de todos los indicadores auxiliares en OnInit tendrá ahora el aspectos siguiente:

   for (int itf=0; itf<5; itf++)
     {
      o_Ichimoku[itf].Create(Symbol(), TF[itf]);
      o_Ichimoku[itf].Init(9, 26, 52);
      o_Channel [itf].Create(Symbol(), TF[itf]);
      o_Channel [itf].Init(22);
     }

Y en OnCalculate en sustitución de CopyBuffer tendremos una invocación directa de las propiedades de las instancias:

         //=== Indicador Ishikomu
         o_Ichimoku[itf].Calculate(Time);

         //CopyBuffer(h_Ichimoku[itf], 0, Time, 1, bufTenkan);
         //double Tenkan = bufTenkan[0];
         double Tenkan = o_Ichimoku[itf].ExtTenkanBuffer[0];

         //CopyBuffer(h_Ichimoku[itf], 1, Time, 1, bufKijun );    
         //double Kijun  = bufKijun [0];
         double Kijun  = o_Ichimoku[itf].ExtKijunBuffer [0];
           
         if (Tenkan > Kijun) Signal++;
         if (Tenkan < Kijun) Signal--;
          
         //=== Indicador de canal
         o_Channel[itf].Calculate(Time);

         //CopyBuffer(h_Channel [itf], 2, Time, 1, bufMid);
         //double Mid = bufMid[0];
         double Mid = o_Channel[itf].ExtMiddBuffer[0];

         if (Price > Mid) Signal++;
         if (Price < Mid) Signal--;

Ya tenemos 40 buffers menos. Ha merecido la pena el esfuerzo.

Después de cada modificación del indicador Trender con los métodos "Need" y "Aggregate", anteriormente descritos, he probado el indicador obtenido en el modo visual.

Vamos a poner en marcha la prueba ahora mismo: abrimos en un gráfico el indicador original (Trender) y el modificado (Trender-Include).  Podemos llegar a la conclusión de que lo hemos modificado todo bien, dado que las líneas de ambos indicadores coinciden con exactitud una con otra:


5.4. ¿Podemos hacerlo uno a uno?

Ya hemos estudiado 3 métodos para reducir la cantidad de buffers de los indicadores auxiliares. Pero, ¿qué sucedería si cambiásemos la aproximación a la cuestión y redujéramos, no la cantidad general de buffers, sino la cantidad de buffers que se encuentran simultáneamente  en la memoria? Es decir, cargar los indicadores en la memoria uno a uno, no todos de golpe. Nos referimos a crear una especie de "rotonda": creamos un indicador auxiliar, leemos sus datos, lo borramos, creamos el siguiente, etcétera, pasando un time frame tras otro. El indicador con más buffers es Ichimoku, con 5 unidades. ¡Esto significa que, en teoría, en un momento dado, en la memoria podrían encontrarse no más de 5 buffers (más 1 adicional del indicador principal), por lo que el ahorro total sería de 35 buffers!

¿Es esto posible? En MQL5 existe incluso una función para eliminar indicadores - IndicatorRelease.

Sin embargo, no es tan sencillo como parece. MetaTrader 5 se preocupa porque los programas MQL5 funcionen a alta velocidad, por eso, toda serie temporal que sea invocada se guarda en el caché, por si fuera necesario para algún otro indicador, experto o script. Sólo en caso de que los datos no sean solicitados por mucho tiempo, estos serán descargados para liberar memoria. El tiempo de espera puede llegar a los 30 minutos.

Así que el hecho de crear y borrar indicadores de manera constante no nos permitirá obtener un gran ahorro de memoria instantáneamente. Sin embargo, al crear un nuevo indicador, el funcionamiento de la computadora se relentiza significativamente, dado que el indicador creado se basa la historia completa de los precios. Piense en lo razonable que es llevar a cabo esto en cada barra histórica del indicador principal...

Además, la idea del "indicador de rotonda", sin duda sería más interesante en un contexto de "lluvia de ideas". Si le viene a la cabeza alguna idea poco frecuente para optimizar la memoria de los indicaodres, escríbala en los comentarios del artículo, es posible que puedan ser estudiadas desde un punto de vista teórico o práctico en los siguientes artículos sobre el presente tema.


6. Midiendo el gasto real de memoria

Bien, en los capítulos anteriores hemos puesto en práctica 3 métodos funcionales para reducir la cantidad de buffers de los indicadores auxiliares. Comprobemos ahora en qué grado nos premite esto reducir el gasto real de la memoria.

Se puede medir el volumen de memoria ocupado por el terminal con el "Administrador de tareas" de Windows. En la pestaña "Procesos" se puede ver la cantidad de memoria virtual y operativa que ocupa el terminal. Por ejemplo:


Es posible realizar la medición por medio del siguiente algoritmo, que permite ver el gasto mínimo de memoria que lleva a cabo el terminal (que será bastante cercano al gasto de memoria por parte de los indicadores):

  1. Descargamos la historia profunda de precios del servidor MetaQuotes-Demo (basta con iniciar la simulación sobre un símbolo para que su historia se descargue de modo automático);
  2. Preparamos el terminal para la siguiente medida (abriendo los gráfico e indicadores necesarios) y lo reiniciamos para limpiar la memoria de información innecesaria;
  3. Esperamos a que el terminal, que ya se habrá reiniciado, finalice todos los cálculos de los indicadores. Eso se podrá comprobar por la carga cero del procesador;
  4. Minimizamos el terminal en la barra de tareas (con el botón estándar de Windows "Minimizar" en la esquina superior derecha del terminal), de manera que deje libre la memoria operativa que no esté usando en el momento presente para los cálculos (en la captura de pantalla tenemos precisamente un ejemplo del gasto de la memoria operativa en el estado de inactividad minimizada - se ve que puede ocupar bastante menos en comparación con la memoria virtual);
  5. En el "Administrador de tareas" calculamos la suma de los botones de "Memoria" (RAM) y "M. Virt." (memoria virtual). Así se llaman en Windows XP, en otras versiones del sistema operativo se pueden llamar de otra forma.

Parámetros de medición:

  • Para lograr una mayor precisión en la medición, usaremos, no sólo un gráfico de precios, sino todas las parejas disponibles en la cuenta de prueba de MetaQuotes, es decir, los 22 gráficos M1. Después se calculan los valores medios;
  • Ajuste "Máximo de barras en la ventana" (tenemos la descripción en el capítulo 4.1) estándar - 100000;
  • SO - Windows XP, 32 bit.

¿Qué cabe esperar de los resultados de las mediciones? Querría hacer dos observaciones:

  1. Aunque el indicador Trender usa 41 buffers, esto no significa que ocupe 41*100000 barras. La razón es que los buffers están repartidos entre 5 time frames, y los más grandes contienen menos barras que que los pequeños. Por ejemplo, en la historia de EURUSD el M1 contiene más de 4 millones de barras, y el de las horas (H1), propiamente, sólo unas 70000 (4000000/60). Por eso no conviene esperar que la reducción de la cantidad de buffers en Trender conlleve una disminución en el gasto de la memoria;
  2. La memoria se ve ocupada, no sólo por el indicador, si no también por las series de precios que usa. Trender usa cinco time frames. Esto significa que si reducimos el número de buffers en varias veces, el gasto total de memoria no se verá reducido en la misma cantidad. Por que en la memoria se van a usar igualmente 5 series de precios.

Al realizar la medición del gasto podrían observarse otros factores desconocidos, pero que influyen en el gasto de memoria. Para eso precisamente llevamos a cabo mediciones prácticas, para poder ver cuál será el ahorro real resultante de la optimización del indicador.

A continuación adjuntamos un recuadro con los resultados de todas las mediciones. En primer lugar se midió el gasto de memoria del terminal vacío. En la siguiente medición, restando esa cifra, logramos determinar el consumo de un gráfico. En las mediciones siguientes, restando el gasto del terminal y el gráfico, logramos descubrir cuánta memoria necesita cada indicador.

Objeto cuyo gasto medimos
Buffers de indicador
Time frames
Gasto de memoria
Terminal
0
0
38 Mb en el terminal
Gráfico
0
1
12 Mb en un gráfico vacío
Indicador Trender
41
5
46 Mb en un indicador
Indicador Trender-Need
31
5
42 Mb en un indicador
Indicador Trender-Aggregate 21
5
37 Mb en un indicador
Indicador Trender-Include 1
5
38 Mb en un indicador


Conclusiones de los resultados de las mediciones:

  • Al reducir la cantidad de buffers de indicador, disminuye el gasto de memoria por parte del indicador, pero no en una proporción igual a la cantidad de indicadores.
Los motivos ya han sido descritos en el capítulo de más arriba. Lo más probable es que si el indicador usara una cantidad menor de time frames, el beneficio en reducir la cantidad de buffers fuese más significativo.
  • El traslado del código de todos los indicadores auxiliares al indicador principal no siempre destaca como la opción más efectiva.

¿Por qué el método Include no ha resultado más efectivo que el método Aggregate? Para establecer las causas hay que recordar las diferencias básicas en el código de estos indicadores. En Aggregate las series de precios necesarias para los cálculos son suministradas por el terminal como matrices de entrada en OnCalculate, y en Include todos estos datos (para todos los time frames) son solicitados activamente para cada barra, a través de CopyHigh, CopyLow, CopyClose. Por lo visto esto produce un gasto adicional de memoria, relacionado con las particularidades del almacenamiento en la memoria caché de las series de precios al utilizar estas funciones.


Conclusión

Y así, a través de este artículo hemos conocido 3 métodos para reducir el gasto de memoria en los indicadores auxiliares, además de 1 método para ahorrar memoria mediante los ajustes adecuados del terminal.

Qué método usará usted en sus programas ya dependerá de lo accesibles y adecuados que se muestren estos en la situación que usted tenga entre manos. La cantidad de buffers y megabytes ahorrados también dependerá de los indicadores de que usted disponga: en algunos se podrá "recortar" más, mientras que en otros resultará no sobrar nada.

La memoria ahorrada le permitirá aumentar el número de parejas de divisas, de indicadores y estrategias usadas de manera simultánea en el terminal, incrementando así la fiabilidad de su portfolio comercial. Este cuidado tan sencillo de los recursos de su computadora puede transformarse en recursos materiales en su depósito.


Anexos

Al artículo se adjuntan los indicadores descritos en el mismo. Para que todo funcione correctamente hay que guardarlos en la carpeta "MQL5\Indicators\TestSlaveIndicators", dado que todas las versiones del indicador Trender (menos Trender-Include, naturalmente) buscan sus indicadores auxiliares en ella.


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

Archivos adjuntos |
ichimoku.mq5 (4.97 KB)
price_channel.mq5 (4.34 KB)
trender.mq5 (2.94 KB)
trender-need.mq5 (2.94 KB)
Cómo deshacerse del lastre de las DLL caseras Cómo deshacerse del lastre de las DLL caseras
Si a un programador de MQL5 no le basta con la funcional del lenguaje, entonces deberá recurrir a instrumentos adicionales. Para ello debrá usar otro lenguaje de programación y crear un DLL intermedio. En MQL5 existe un mecanismo de representación de diversos tipos de datos, con ayuda de estructuras y su transmisión a API, pero por desgracia, el MQL5 no responde a la cuestión de cómo extraer los datos del índice adoptado. En este artículo vamos a poner punto final a esta cuestión, mostrando mecanismos sencillos de intercambio de tipos complejos de datos y cómo trabajar con ellos.
Cómo copiar el trading desde MetaTrader 5 a MetaTrader 4 Cómo copiar el trading desde MetaTrader 5 a MetaTrader 4
¿Se puede tradear hoy en día en una cuenta real utilizando MetaTrader 5? ¿Cómo organizar este trading? Se aporta la base teórica de estas preguntas, así como los códigos con la ayuda de los cuales se realiza el copiado de las transacciones del terminal MetaTrader 5 a MetaTrader 4. Este artículo será útil tanto para los desarrolladores de los Asesores Expertos, como para los traders que operan en el mercado.
Depuración de programas en MQL5 Depuración de programas en MQL5
Este artículo va dirigido a los programadores que ya conocen el lenguaje, pero que aún no han asimilado suficiententemente bien el desarrollo de programas. El artículo nos descubrirá métodos prácticos para depurar programas, es el fruto de la experiencia combinada, no sólo mía, sino también de muchos de los programadores de cuya experiencia he aprendido.
Los miembros más activos de la comunidad MQL5 ¡han sido premiados con iPhones! Los miembros más activos de la comunidad MQL5 ¡han sido premiados con iPhones!
Después de que decidiéramos premiar a los participantes más destacados de MQL5.com, hemos seleccionado el criterio clave para determinar la contribución de cada participante al desarrollo de la Comunidad. Como resultado, tenemos los siguientes campeones que publicaron la mayor cantidad de artículos en la website - investeo (11 artículos) y victorg (10 artículos) y quien envió sus programas a la Base de Código - GODZILLA (340 programas), Integer (61 programas) y abolk (21 programas).