English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Implementación de Indicadores como Classes por Ejemplos de Zigzag y ATR

Implementación de Indicadores como Classes por Ejemplos de Zigzag y ATR

MetaTrader 5Indicadores | 21 marzo 2014, 13:17
1 359 0
Aleksandr Chugunov
Aleksandr Chugunov

¿Para qué lo necesitamos?

MetaQuotes Software Corp. revisó el concepto de trabajar con indicadores personalizados en la 5ª versión del terminal de cliente MetaTrader. Ahora se ejecutan mucho más rápido; solo hay un ejemplo de cada indicador con parámetros de entrada únicos, de modo que se calcula una vez independientemente de si usa sus copias en hasta tres gráficos de un símbolo, etc.

Pero la operación de un algoritmo permanece inalterable. Cuando perdemos la conexón a un servidor o una sincronización de historial significactiva, el valor prev_calculated (o IndicatorCounted() en MetaTrader 4) vuelve a cero, lo que lleva a un recálculo completo del indicador para el historial entero (los desarrolladores lo hicieron así a propósito para garantizar la corrección de los valores de los indicadores en cualquier circunstancia). Hay varias cosas que pueden afectar la velocidad de cálculos de los indicadores:

  • Período grande: rates_total;
  • Cálculos complejos que consumen muchos recursos;
  • Uso de varios símbolos y períodos;
  • Un ordenador personal poco potente;

Cuantas más cualidades se puedan aplicar a su situación, más acuciante será para usted el problema del recálculo del indicadorpara el historial entero. Además, la situación empeora con un mal canal para la transmisión de información.

Por supuesto, puede limitar la profundidad del cálculo del indicador usando un parámetro de entrada adicional, pero ya hay un matiz al usar indicadores iCustom. El número máximo de barras que se pueden usar en un gráfico o un indicador personalizado se configura en el alcance global para el terminal entero. La memoria se distribuye para cada buffer o indicador personalizado, y se limita solo por TERMINAL_MAXBARS.

No obstante, hay una adición significativa: si limita el número máximo de barras calculadas dentro del algoritmo del indicador (por ejemplo, usando un parámetro de entrada o directamente en el código), la memoria se distribuirá dinámicamente a la llegada de cada nueva barra (aumentará gradualmente al límite de TERMINAL_MAXBARS especificado (o un poco más, este algoritmo depende totalmente de los desarrolladores, lo pueden cambiar en las próximas versiones)).


Formas de Evitar el Recálculo del Indicador del Historial Entero

Por ahora, conozco las siguiente formas de resolver este problema:
  1. Pedir a MetaQuotes que revise este problema a nivel de plataforma
  2. Crear una clase separada para la implementación de un análogo de prev_calculated

Había otra variante como presuposición de que usted puede construir en el indicador un algoritmo de cálculo de prev_calculated, pero parece ser que MetaTrader 5, a diferencia de MetaTrader 4, "limpia" todos los buffers del indicador al poner prev_calculated a cero (es decir, pone a cero todo el array del indicador de forma forzada; no puede controlarlo, puesto que este comportamiento se implementa a nivel de plataforma).

Analicemos cada variante por separado.

  • La primera variante depende solo de los desarrolladores. Quizás la consideren después de la publicación de este artículo. Y quizás la implementación de un mecanismo funcional afectará de forma importante al rendimiento del block de cálculos de los indicadores personalizados (sin embargo, este mecanismo se puede implementar como opcional), y dejarán todo como está ahora.
  • Respecto a la segunda variante... La creación de una clase especial que sea responsable para la implementación de un análogo de prev_calculated. Lo podemos usar tanto en un indicador personalizado (solo para obtener los valores prev_calculated) y en un proveedor de datos para usar en Expert Advisors (o scripts) junto con una clase desarrollada por separado para el cálculo de un indicador personalizado necesario.


Ventajas y Desventajas de la Segunda Variante para Resolver el Problema

Ventajas:
  • volumen fijo de memoria requerida a través de la distribución sencilla de memoria para un array dinámico con la organización de acceso de anillo a los elementos de array;
  • sincronización y cálculo de un indicador al usar una clase separada para su cálculo bajo demanda (sin el uso de semáforos, flags, eventos, etc.);
  • al usar una llamada separada para el cálculo del indicador, el resultado del recálculo se devuelve en un formulario extendido (por ejemplo: no hubo cambios, solo cambié el último ray, se añadió un ray nuevo, etc.).
Desventajas:
  • necesidad de almacenar una copia propia del historial de precios, que se usa para el cálculo de valores de indicador;
  • necesidad de sincronización manual del historial con el historial del terminal usando operaciones de lógica de comparación de datos.


Creación de la clase CCustPrevCalculated para la Implementación de un Análogo de prev_calculated

La implementación de la clase en sí misma no contiene nada interesante para describir. El algoritmo considera tanto expandir el historial a amgos lados como su posible "corte" en el lado izquierdo. Asimismo, este algoritmo puede procesar la inserción de historial dentro de los datos calculados (es así para MetaTrader 4, en MetaTrader 5 aún no lo he comprobado). El código fuente de la clase se encuentra en la clase CustPrevCalculated.mqh.

Déjeme hablarse sobre las claves.


Crear un Acceso de Anillo para Elementos de Array

Para crear esta clase, vamos a usar un método poco convencional: acceso de anillo a los elementos del array para la distribución de una vez de la memoria para el array y para evitar procedimientos de copia de arrays excesivos. Consideremos el ejemplo de cinco elementos:


Acceso de anillo a los elementos del array


 
Inicialmente, trabajamos con el array cuya numeración empieza de 0. ¿Pero qué deberíamos hacer si necesitamos añadir el siguiente valor manteniendo el tamaño del array (añadir una barra nueva)? Hay dos formas:
  • copiar las celdas de memoria 2-5 a las celdas 1-4 respectivamente, así tendremos la celda de memoria 5 vacía;
  • cambiar el índice del array sin cambiar la información almacenada en él (direccionamiento de envoltura).

Para implementar la segunda variante necesitaremos una variable, que llamaremos DataStartInd. Guardará la posición del índice cero del array. Por motivos de conveniencia para futuros cálculos, su numeración se corresponderá con el índice usual de un array (es decir, empezará de cero). En la variable BarsLimit almacenaremos el número de elementos del array. Por tanto, la dirección del elemento del array para el índice virtual "I" se calculará usando la siguiente fórmula simple:

  • (DataStartInd+I) % BarsLimit – para numeración usual
  • (DataStartInd+DataBarsCount-1-I) % BarsLimit – para direccionamiento como en series cronológicas
La variable DataBarsCount guarda el número de celdas de memoria usadas realmente (solo podemos usar 3 de 5 celdas, por ejemplo).


Algoritmos de Sincronización de Historial

Para mí mismo seleccioné e implementé tres modos de trabajo con el algoritmo de sincronización de una copia del historial (historial local) con el historial del terminal de cliente:
  • CPCHSM_NotSynch – sincronización del historial local no se lleva a cabo para barras ya formadas (asumiendo el riesgo y la responsabilidad). En realidad, este modo se puede usar libremente para un indicador en el que las desviaciones insignificantes de valores de precio no pueden afectar de manera importante la preción de los cálculos (MA, ADX, etc.). Pero este modo puede ser fatal para ZigZag, por ejemplo, donde un exceso de un pico sobre otro es significativo.
  • CPCHSM_Normal – el historial local se sincronica en cada nueva barra por el algoritmo descrito abajo.
  • CPCHSM_Paranoid – el historial local se sincronica en cada nueva llamada de la función de sincronización de datis descrita abajo.

El mecanismo de sincronización mismo se basa en otro parámetro configurado por un programador: HSMinute (guardado como HistorySynchSecond). Suponemos que un Dealer Center solo puede corregir los últimos minutos HSMinute del historial. Si no se encontraron diferencias durante la sincronización de ese período, el historial se considera idéntico, y se detiene la comparación. Si se encuentra una diferencia, el historial entero se revisa y corrige.

Además, el algoritmo comprueba solo precios/diferenciales/volúmenes de la estructura MqlRates especificados durante la inicialización. Por ejemplo, para dibujar ZigZag solo necesitamos precios High (alto) y Low (bajo).


Uso Práctico de la Clase CCustPrevCalculated

Para inicializar la clase CCustPrevCalculated debemos llamar a la función InitData(), que devolverá 'true' en caso de tener éxito:
CCustPrevCalculated CustPrevCalculated;
CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
Para sincronizar el historial necesitamos llamar a la función PrepareData():
CPCPrepareDataResultCode resData;
resData = CustPrevCalculated.PrepareData();

Variantes de valores que puede devolver la función PrepareData():

enum CPCPrepareDataResultCode
  {
   CPCPDRC_NoData,                     // Returned when there is no data for calculation (not prepared by the server)
   CPCPDRC_FullInitialization,         // Full initialization of the array has been performed
   CPCPDRC_Synch,                      // Synchronization with adding new bars has been performed
   CPCPDRC_SynchOnlyLastBar,           // Synchronization of only the last bar has been performed (possible cutting of the history)
   CPCPDRC_NoRecountNotRequired        // Recalculation has not been performed, since the data was not changed
  };


Funciones de la clase CCustPrevCalculated para Acceso a Datos

Nota: para acelerar los cálculos, se excluyen las comprobaciones de desbordamiento del array. Para ser más precisos, los valores erróneos se devolverán si el índice es incorrecto.

Nombre
Propósito
 uint GetDataBarsCount()
 Devuelve el número de barras disponibles
 uint GetDataBarsCalculated()
 Devuelve el número de barras que no han cambiado
 uint GetDataStartInd()
 Devuelve el índice para accesos de envoltura (para indicadores personalizados)
 bool GetDataBarsCuttingLeft()
 Devuleve el resultado de cortar las barras desde la izquierda
 double GetDataOpen(int shift, bool AsSeries)
 Devuelve 'Open' (apertura) para la barra de cambio
 double GetDataHigh(int shift, bool AsSeries)
 Devuelve 'High' (alto) para la barra de cambio
 double GetDataLow(int shift, bool AsSeries)
 Devuelve 'Low' (bajo) para la barra de cambio
 double GetDataClose(int shift, bool AsSeries)
 Devuelve 'Close' (cierre) para la barra de cambio
 datetime GetDataTime(int shift, bool AsSeries)
 Devuelve 'Time' (tiempo) para la barra de cambio
 long GetDataTick_volume(int shift, bool AsSeries)
 Devuelve 'Tick_volume' (volumen de ticks) para la barra de cambio
 long GetDataReal_volume(int shift, bool AsSeries)
 Devuelve 'Real_volume' (volumen real) para la barra de cambio
 int GetDataSpread(int shift, bool AsSeries)
 Devuelve 'Spread' (diferencial) para la barra de cambio


Examples of Further Optimization of the Class CCustPrevCalculated

  • Rechazo desde MqlRates al cambiar a varios arrays (determinados por un propósito determinado) - disminuye los requisitos de memoria, pero aumenta la carga en el número de llamadas de copias de array).
  • División de cada función de acceso a dos funciones independientes para el uso definitivo de cierto tipo de indexación de array (rechazo del parámetro «bool AsSeries»). La ventaja está solo en la condición lógica «if (AsSeries)».


Crear CCustZigZagPPC para el Cálculo del Indicador Personalizado ZigZag en Base a los Datos de la Clase CCustPrevCalculated

Este algoritmo se basa en el indicador personalizado Professional ZigZag. El código fuente de la clase está en el archivo ZigZags.mqh; además, la biblioteca OutsideBar.mqh se usa para trabajar con barras externas.

Creemos una estructura separada para la descripción de una barra de nuestro indicador:

struct ZZBar
  {
   double UP, DN;                      // Buffers of the ZigZag indicator
   OrderFormationBarHighLow OB;       // Buffer for caching of an external bar
  };

Asimismo, determinemos el resultado de la devolución de los cálculos de la clase:

enum CPCZZResultCode
  {
   CPCZZRC_NotInitialized,             // Class is no initialized
   CPCZZRC_NoData,                     // Faield to receive data (including the external bar)
   CPCZZRC_NotChanged,                 // No changes of ZZ rays
   CPCZZRC_Changed                     // ZZ rays changed
  };

Para inicializar la clase CCustZigZagPPC debemos llamar la función Init() una vez; si tiene éxito, devolverá 'true':

CCustZigZagPPC ZZ1;
ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);

Para los cálculos del indicador debemos comenzar la actualización de los datos basados en los datos anteriormente calculados de la clase CCustPrevCalculated:

CPCPrepareDataResultCode resZZ1;
resZZ1 = ZZ1.PrepareData(resData);

Y después llamaremos al procedimiento Calculate():

if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
   ZZ1.Calculate();

El ejemplo completo de uso de una clase CCustPrevCalculated junto con varias clases CCustZigZagPPC se da en el archivo ScriptSample_CustZigZagPPC.mq5.


Función de Acceso a DAtos de la Clase CCustZigZagPPC

Nombre
Propósito
 uint GetBarsCount()
 Devuelve el número de barras disponibles
 uint GetBarsCalculated()  Devuelve el número de barras calculadas
 double GetUP(uint shift, bool AsSeries)
 Devuelve el valor del pico de ZigZag para una barra
 double GetDN(uint shift, bool AsSeries)
 Devuelve el valor low de ZigZag para una barra
 OrderFormationBarHighLow GetOB(uint shift, bool AsSeries)  Devuelve el valor 'Outside' (fuera) para una barra


Revisión Visual y de Programa

Para la revisión visual, adjuntemos el indicador original a un gráfico, y sobre él adjuntemos el indicador de prueba especialmente escrito Indicator_CustZigZag.mq5 con parámetros de entrada idénticos (pero debería seleccionar colores diferentes para ver ambos indicadores). Aquí puede ver el resultado de su funcionamiento:

Rojo - original, azul - nuestro indicador propio, calculado en las últimas 100 barras.

Podemos compararlos igualmente en un Expert Advisor. ¿Habrá alguna diferencia? Los resultados obtenidos de iCustom("AlexSTAL_ZigZagProf") y la clase CCustZigZagPPC se comparan en cada tick en el Expert Advisor de prueba Expert_CustZigZagPPC_test.mq5. La información sobre el cálculo se muestra en el diario (puede que no hayan cálculos en las primeras barras por falta de historial para el algoritmo):

(EURUSD,M1)                1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 
(EURUSD,M1) Divergence on the bar: 7 

Consideremos este Expert Advisor con más detalle. Determine las variables globales para su funcionamiento:

#include <ZigZags.mqh>

CCustPrevCalculated CustPrevCalculated;
CCustZigZagPPC ZZ1;
int HandleZZ;

Initialize the variables:

int OnInit()
  {
   // Creating new class and initializing it
   CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);
   
   // Initializing the class ZZ
   ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
   
   // Receiving handle for the custom indicator
   HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true);
   Print("ZZ_handle = ", HandleZZ, "  error = ", GetLastError());

   return(0);
  }
Procesar ticks en el Expert Advisor:
void OnTick()
  {
   // Calculation of data
   CPCPrepareDataResultCode resData, resZZ1;
   resData = CustPrevCalculated.PrepareData();
   
   // Start recalculation for each indicator! PrepareData obligatory!
   resZZ1 = ZZ1.PrepareData(resData);
   
   // Расчет данных ZZ1
   if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) )
      return;

   // Получим результаты расчета
   ZZ1.Calculate();

Ahora tenemos barras ZZ1.GetBarsCalculated() calculadas por el CCustZigZagPPC. Añadamos el código de datos comparados de iCustom("AlexSTAL_ZigZagProf") y la clase CCustZigZagPPC:

   int tmpBars = (int)ZZ1.GetBarsCalculated();
   double zzUP[], zzDN[];
   CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP);
   CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN);
   
   // Perform comparison
   string tmpSt1 = "", tmpSt2 = "";
   for (int i = (tmpBars-1); i >= 0; i--)
     {
      double tmpUP = ZZ1.GetUP(i, false);
      double tmpDN = ZZ1.GetDN(i, false);
      if (tmpUP != zzUP[i])
         Print("Divergence on the bar: ", i);
      if (tmpDN != zzDN[i])
         Print("Divergence on the bar: ", i);
      if (tmpUP != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; ";
      if (tmpDN != EMPTY_VALUE)
         tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; ";

      if (zzUP[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; ";
      if (zzDN[i] != EMPTY_VALUE)
         tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; ";
     }
  Print("Tick processed: ", tmpSt1);
  Print("                              ", tmpSt2);
  }

Aquí está el sencillo uso práctico de la clase CCustZigZagPPC en un Expert Advisor o script. Las funciones de acceso directo GetUP(), GetDN(), GetOB() en lugar de CopyBuffer().


Mover Nuestro Indicador a una Clase Separada (por el ejemplo de iATR)

En la base del archivo ZigZags.mqh, yo hice la plantilla MyIndicator.mqh para un desarrollo rápido de indicadores personalizados de acuerdo con los principios descritos arriba.

Plan general:

1. Fase Preparatoria

  • Copie MyIndicator.mqh como archivo con otro nombre (en mi ejemplo es ATRsample.mqh) y abra el último en MetaEditor 5.
  • Sustituya el texto "MyInd" por el nombre de su indicador (en mi ejemplo es "ATR").

2. Elija parámetros externos que se tomarán del indicador inicial (original) a la clase, declárelos e inicialícelos.

En mi ejemplo, el indicador ATR tiene un parámetro externo:
input int InpAtrPeriod=14;  // ATR period
  • añada este parámetro a nuestra clase y a la función de inicialización de la clase:
class CCustATR
  {
protected:
   ...
   uchar iAtrPeriod;
   ...
public:
   ...
   bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
  • cambie el encabezamiento del cuerpo de la función Init e inicialice el parámetro variable con el valor de entrada:
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod)
{
      ...
      BarsLimit = Limit;
      iAtrPeriod = AtrPeriod;
      ...

3. Determine el número requerido de buffers en el indicador inicial, y declárelos en nuestra clase. Asimismo, declare las funciones de devolución de los buffers INDICATOR_DATA.

  • Cambie la estructura
struct ATRBar
  {
   double Val;                          // Indicator buffers
  };

a nuestra estructura propia:

struct ATRBar
  {
   double ATR;
   double TR;
  };
  • Determine los valores de cero:
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData)
{
   ...
   for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].ATR = EMPTY_VALUE;
      Buf[PInd(i, false)].TR = EMPTY_VALUE;
     }
   ...
  • Cambie y añada la función de valores de devolución de los buffers INDICATOR_DATA:

cambie (si solo hay un buffer, puede saltarse el cambio)

clase CCustATR
  {
   ...
   double GetVal(uint shift, bool AsSeries);                      // returns the Val value of the buffer for a bar
   ...

a

class CCustATR
  {
   ...
   double GetATR(uint shift, bool AsSeries);                      // Возвращает значение буфера ATR для бара
   ...

y cambie el código de la función correspondiente:

double CCustATR::GetATR(uint shift, bool AsSeries)
{
   if ( shift > (DataBarsCount-1) )
      return(EMPTY_VALUE);
   return(Buf[PInd(shift, AsSeries)].ATR);
}
Nota: en lugar de varias funciones de valores de devolución de buffer, puede usar solo uno que tiene un parámetro adicional - número o nombre del buffer.


4. Copìe la lógia de la función OnCalculate() del indicador inicial a la función correspondiente de la clase

  • Revisiones primarias
CPCATRResultCode CCustATR::Calculate()
{
   ...
   // Check if there are enough bars for the calculation
   if (DataBarsCount <= iAtrPeriod)
      return(CPCATRRC_NoData);
   ...
  • Cálculos: en el primer tick y el número de barras para cálculos en los siguientes ticks:
   if ( DataBarsCalculated != 0 )
      BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1;
   else
     {
      Buf[PInd(0, false)].TR = 0.0;
      Buf[PInd(0, false)].ATR = 0.0;
      //--- filling out the array of True Range values for each period
      for (uint i = 1; i < DataBarsCount; i++)
         Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                                  MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      //--- first AtrPeriod values of the indicator are not calculated
      double firstValue = 0.0;
      for (uint i = 1; i <= iAtrPeriod; i++)
        {
         Buf[PInd(i, false)].ATR = 0;
         firstValue += Buf[PInd(i, false)].TR;
        }
      //--- calculating the first value of the indicator
      firstValue /= iAtrPeriod;
      Buf[PInd(iAtrPeriod, false)].ATR = firstValue;
      
      BarsForRecalculation = DataBarsCount - iAtrPeriod - 2;
     }
  • El cálculo en cada tick mismo:
   for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++)
     {
      Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - 
                               MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false));
      Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod;
      ...

Eso es todo. Hemos creado nuestra clase. Para la revisión visual, puede crear un indicador de prueba (en mi ejemplo es Indicator_ATRsample.mq5):



Se me ocurrió una idea al corregir el artículo: cuando usa la clase CCustPrevCalculated junto con solo un indicador personalizado, puede integrar la creación, inicialización y sincronización de esta clase en el indicador personalizado (en mi ejemplo, eran CCustZigZagPPC y CCustATR). Al llamar a la función de inicialización de indicadores personalizados para este propósito, debe usar el señalizador cero en el objeto:

   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);

Ahora, la estructura general

#include <CustPrevCalculated.mqh>
#include <ATRsample.mqh>
CCustPrevCalculated CustPrevCalculated;
CCustATR ATR;

int OnInit()
  {
   CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30);
   ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData();
   CPCPrepareDataResultCode resATR = ATR.PrepareData(resData);
   if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) )
      ATR.Calculate();
  }

se simplificará a:

#include <ATRsample.mqh>
CCustATR ATR;

int OnInit()
  {
   ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
  }

int OnCalculate(...)
  {
   ATR.Calculate();
  }
Puede encontrar un ejemplo práctico en el archivo Indicator_ATRsample2.mq5.

Influencia de la Tecnología Descrita sobre Rendimiento en el Probador de Estrategias

Para la revisión, creé un Expert Advisor de prueba (TestSpeed_IndPrevCalculated.mq5) que recibe el valor del indicador de la barra cero en cada tick según una de tres variantes:

enum eTestVariant
  {
   BuiltIn,    // Built-in indicator iATR
   Custom,     // Custom indicator iCustom("ATR")
   IndClass    // Calculation in the class
  };

Este Expert Advisor se ejecutó 10 veces en 1 agente con los siguientes parámetros de optimización:

  • Símbolo: EURUSD
  • Período: entire history [1993..2001]
  • Modo de trading: cada tick
  • Parámetro externo: FalseParameter [0..9]

Medí el tiempo de optimización al usar cada una de las tres variantes del indicador. El resultado de la revisión se muestra como un histograma lineal.

El tiempo de optimización para tres tipos de implementación del indicador ATR

    El código fuente del Expert Advisor usado para medir el tiempo de optimización:

    //+------------------------------------------------------------------+
    //|                                  TestSpeed_IndPrevCalculated.mq5 |
    //|                                         Copyright 2011, AlexSTAL |
    //|                                           http://www.alexstal.ru |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2011, AlexSTAL"
    #property link      "http://www.alexstal.ru"
    #property version   "1.00"
    //--- connect the include file with the CustATR class
    #include <ATRsample.mqh>
    //--- set the selection of the parameter as an enumeration
    enum eTestVariant
      {
       BuiltIn,    // Built-in indicator iATR
       Custom,     // Custom indicator iCustom("ATR")
       IndClass    // Calculation withing the class
      };
    //--- input variables
    input eTestVariant TestVariant;
    input int          FalseParameter = 0;
    //--- period of the ATR indicator
    const uchar        InpAtrPeriod = 14;
    //--- handle of the built-in or custom indicator
    int                Handle;
    //--- indicator based on the class 
    CCustATR           *ATR;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       //---
       switch(TestVariant)
         {
          case BuiltIn:
             Handle = iATR(_Symbol, _Period, InpAtrPeriod);
             break;
          case Custom:
             Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod);
             break;
          case IndClass:
             ATR = new CCustATR;
             ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod);
             break;
         };
       //---
       return(0);
      }
    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
       switch(TestVariant)
         {
          case IndClass:
             delete ATR;
             break;
         };
      }
    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       double tmpValue[1];
       switch(TestVariant)
         {
          case BuiltIn:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case Custom:
             CopyBuffer(Handle, 0, 0, 1, tmpValue);
             break;
          case IndClass:
             ATR.Calculate();
             tmpValue[0] = ATR.GetATR(0, true);
             break;
         };
      }
    //+------------------------------------------------------------------+

    Como podemos observar, esta tecnología no disminuye el rendimiento en el Probador de Estrategias de forma significativa en compraración con el uso de un indicador personalizado ordinario.


    Notas para el Uso Práctico de esta Tecnología

    • al simular un Expert Advisor en el Probador de Estrategias, el valor prev_calculated no se puede poner a cero en un indicador personalizado, y por eso se desactiva la sincronización del historial en este modo;
    • el cálculo del indicador se realiza solo en las últimas barras 'n' que se configuraron durante la inicialización inicial de las clases;
    • el cálculo implica una vinculación estricta a un determinado símbolo y período de la clase inicializada. Para llevar a cabo cálculos en otros símbolos o períodos, deberá crear nuevas instancias de las clases.


    Conclusión

    En cada situación, un programador debería considerar todas las ventajas y desventajas de diferentes variantes de implementación de la tarea. La implementación sugerida en este artículo es justamente una forma con sus propias ventajas y desventajas.

    P.S. ¡Los errores son humanos! Si encuentra errores, por favor, no dude en informarme.

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

    Random Walk y el Indicador de Tendencias Random Walk y el Indicador de Tendencias
    Random Walk (RW) es muy parecido a los datos del mercado real, pero tiene algunos detalles significativos. En este artículo veremos las propiedades de Random Walk, que simularemos usando el juego de cara o cruz. Para estudiar las propiedades de los datos se desarrolló el indicador de tendencias.
    Gráficos y diagramas en HTML Gráficos y diagramas en HTML
    Hoy día es difícil encontrar un ordenador que no tenga instalado un navegador de internet. Los navegadores han ido evolucionado y mejorando durante mucho tiempo. Este artículo tratará la forma más sencilla y segura de crear gráficos y diagramas basándonos en la información obtenida del Terminal de Cliente MetaTrader 5 para mostrarlos en el navegador.
    Exponer código C# a MQL5 usando exportaciones no gestionadas Exponer código C# a MQL5 usando exportaciones no gestionadas
    En este artículo presento diferentes métodos de interacción entre código MQL5 y código gestionado C#. También facilito varios ejemplos sobre cómo ordenar estructuras MQL5 en contraposición a C#, y cómo invocar funciones DLL exportadas en scripts MQL5. Creo que los ejemplos que proporciono podrán servir como base para estudios futuros sobre escritura de DLLs en código gestionado. Este artículo también abre puertas para MetaTrader para usar varias bibliotecas que ya están implementadas en C#.
    MQL5 Cloud Network: ¿Aún sigue calculando? MQL5 Cloud Network: ¿Aún sigue calculando?
    Ya ha pasado casi un año desde el lanzamiento de la red de cálculos en la nube MQL5 Cloud Network. Este acontecimiento, representa una revolución que marca una nueva era en el comercio algorítmico, ya que ahora cualquier trader, con sólo cliquear un par de veces puede tener a su disposición cientos y miles de núcleos de cálculo para optimizar sus estrategia comercial.